diff --git a/proto/context.proto b/proto/context.proto index f5dec30796a8426f512947d369b8db5f5889471a..74132e7377c0097c1febb0024b969e843f6370a5 100644 --- a/proto/context.proto +++ b/proto/context.proto @@ -239,6 +239,7 @@ enum ServiceTypeEnum { SERVICETYPE_L3NM = 1; SERVICETYPE_L2NM = 2; SERVICETYPE_TAPI_CONNECTIVITY_SERVICE = 3; + SERVICETYPE_TE = 4; } enum ServiceStatusEnum { diff --git a/src/common/DeviceTypes.py b/src/common/DeviceTypes.py index c5ea4c54fef7b739a4ad33dd3759c3bdef124038..1cda1fa21a8a1ba996f5a7797059ebe0ace19318 100644 --- a/src/common/DeviceTypes.py +++ b/src/common/DeviceTypes.py @@ -18,6 +18,7 @@ class DeviceTypeEnum(Enum): EMULATED_DATACENTER = 'emu-datacenter' EMULATED_OPEN_LINE_SYSTEM = 'emu-open-line-system' EMULATED_PACKET_ROUTER = 'emu-packet-router' + EMULATED_PACKET_SWITCH = 'emu-packet-switch' DATACENTER = 'datacenter' MICROVAWE_RADIO_SYSTEM = 'microwave-radio-system' OPTICAL_ROADM = 'optical-roadm' diff --git a/src/context/service/database/ServiceModel.py b/src/context/service/database/ServiceModel.py index 8b32d1cc9eeec248d1097f972df93dbd2c0882fa..2bddc09a5c090d0a153417134ff4802a599001ee 100644 --- a/src/context/service/database/ServiceModel.py +++ b/src/context/service/database/ServiceModel.py @@ -34,6 +34,7 @@ class ORM_ServiceTypeEnum(Enum): L3NM = ServiceTypeEnum.SERVICETYPE_L3NM L2NM = ServiceTypeEnum.SERVICETYPE_L2NM TAPI_CONNECTIVITY_SERVICE = ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE + TE = ServiceTypeEnum.SERVICETYPE_TE grpc_to_enum__service_type = functools.partial( grpc_to_enum, ServiceTypeEnum, ORM_ServiceTypeEnum) diff --git a/src/device/service/DeviceServiceServicerImpl.py b/src/device/service/DeviceServiceServicerImpl.py index 9ffd028a67a34cfcce7a737a5817128126941759..69784738bcad29fa002f44b47a6d20e1b434802e 100644 --- a/src/device/service/DeviceServiceServicerImpl.py +++ b/src/device/service/DeviceServiceServicerImpl.py @@ -157,6 +157,16 @@ class DeviceServiceServicerImpl(DeviceServiceServicer): config_rule = (ORM_ConfigActionEnum.SET, resource_key, json.dumps(resource_value, sort_keys=True)) running_config_rules.append(config_rule) + mpls_label = connection_config_rules.pop('mpls_label', None) + if mpls_label != None: + mpls_label_config_rule = (ORM_ConfigActionEnum.SET, "mpls_label", mpls_label) + running_config_rules.append(mpls_label_config_rule) + + pcc_address = connection_config_rules.pop('pcc_address', None) + if pcc_address != None: + pcc_address_config_rule = (ORM_ConfigActionEnum.SET, "pcc_address", pcc_address) + running_config_rules.append(pcc_address_config_rule) + #for running_config_rule in running_config_rules: # LOGGER.info('[AddDevice] running_config_rule: {:s}'.format(str(running_config_rule))) update_config(self.database, device_uuid, 'running', running_config_rules) diff --git a/src/service/service/database/ServiceModel.py b/src/service/service/database/ServiceModel.py index cf756af60a8178a9ae2fda2a5fa5ddeebc73912c..fd8cfc71ed8783509db7a93424f4e4d65fc15663 100644 --- a/src/service/service/database/ServiceModel.py +++ b/src/service/service/database/ServiceModel.py @@ -34,6 +34,7 @@ class ORM_ServiceTypeEnum(Enum): L3NM = ServiceTypeEnum.SERVICETYPE_L3NM L2NM = ServiceTypeEnum.SERVICETYPE_L2NM TAPI_CONNECTIVITY_SERVICE = ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE + TE = ServiceTypeEnum.SERVICETYPE_TE grpc_to_enum__service_type = functools.partial( grpc_to_enum, ServiceTypeEnum, ORM_ServiceTypeEnum) diff --git a/src/te/apps/epce/src/epce_server.erl b/src/te/apps/epce/src/epce_server.erl index bd3a13f4429590268346d4569a95eb6a09fd3a06..11a1625efa0e961658a98d01223481f7512fdbf7 100644 --- a/src/te/apps/epce/src/epce_server.erl +++ b/src/te/apps/epce/src/epce_server.erl @@ -181,12 +181,16 @@ terminate(_Reason, _State) -> %%% INTERNAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -compute_path({1, 1, 1, 1}, {6, 6, 6, 6}) -> - {ok, [16020, 16040, 16060]}; -compute_path({6, 6, 6, 6}, {1, 1, 1, 1}) -> - {ok, [16040, 16020, 16010]}; -compute_path(_Src, _Dst) -> - {error, nopath}. +compute_path(From, To) -> + case epce_ted:compute_path(pcc_address, From, To) of + {ok, Devices} -> + Labels = tl([L || #{mpls_label := L} <- Devices, L =/= undefined]), + logger:debug("Route from ~p to ~p: ~p", [From, To, Labels]), + {ok, Labels}; + {error, Reason} -> + logger:warning("Failed to find a route from ~p to ~p", [From, To]), + {error, Reason} + end. route_from_labels(Source, Destination, Constraints, Labels) -> #{ diff --git a/src/te/apps/epce/src/epce_sup.erl b/src/te/apps/epce/src/epce_sup.erl index 21a6a56b2ff39b05b2bc440f1089fb18024fe8ef..f04e9697e0dec0759dbdb512e8b37dcda5546408 100644 --- a/src/te/apps/epce/src/epce_sup.erl +++ b/src/te/apps/epce/src/epce_sup.erl @@ -12,14 +12,14 @@ %%% MACROS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --define(SERVER, ?MODULE). --define(PCE_SERVER, epce_server). +-define(TED_WORKER, epce_ted). +-define(PCE_WORKER, epce_server). %%% BEHAVIOUR SUPERVISOR FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% start_link() -> - supervisor:start_link({local, ?SERVER}, ?MODULE, []). + supervisor:start_link({local, ?MODULE}, ?MODULE, []). init([]) -> SupFlags = #{ @@ -27,11 +27,17 @@ init([]) -> intensity => 0, period => 1 }, + TEDSpec = #{ + id => ?TED_WORKER, + start => {?TED_WORKER, start_link, []}, + restart => permanent, + shutdown => brutal_kill + }, ServerSpec = #{ - id => ?PCE_SERVER, - start => {?PCE_SERVER, start_link, []}, + id => ?PCE_WORKER, + start => {?PCE_WORKER, start_link, []}, restart => permanent, shutdown => brutal_kill }, - {ok, {SupFlags, [ServerSpec]}}. + {ok, {SupFlags, [TEDSpec, ServerSpec]}}. diff --git a/src/te/apps/epce/src/epce_ted.erl b/src/te/apps/epce/src/epce_ted.erl new file mode 100644 index 0000000000000000000000000000000000000000..aaf5a4e9a09183e51686457613f89a1804f5276c --- /dev/null +++ b/src/te/apps/epce/src/epce_ted.erl @@ -0,0 +1,203 @@ +-module(epce_ted). + +-behaviour(gen_server). + + +%%% INCLUDES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-include_lib("kernel/include/logger.hrl"). + + +%%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% API Functions +-export([start_link/0]). +-export([device_added/2]). +-export([device_updated/2]). +-export([device_deleted/1]). +-export([link_added/2]). +-export([link_updated/2]). +-export([link_deleted/1]). +-export([compute_path/3]). + +-export([get_graph/0]). + +% Behaviour gen_server functions +-export([init/1]). +-export([handle_call/3]). +-export([handle_cast/2]). +-export([handle_continue/2]). +-export([handle_info/2]). +-export([code_change/3]). +-export([terminate/2]). + + +%%% RECORDS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-record(state, { + graph :: diagraph:graph(), + pcc_address_to_id = #{} :: map() +}). + + +%%% API FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +device_added(Id, Device) -> + gen_server:call(?MODULE, {device_added, Id, Device}). + +device_updated(Id, Device) -> + gen_server:call(?MODULE, {device_updated, Id, Device}). + +device_deleted(Id) -> + gen_server:call(?MODULE, {device_deleted, Id}). + +link_added(Id, Link) -> + gen_server:call(?MODULE, {link_added, Id, Link}). + +link_updated(Id, Link) -> + gen_server:call(?MODULE, {link_updated, Id, Link}). + +link_deleted(Id) -> + gen_server:call(?MODULE, {link_deleted, Id}). + +compute_path(Index, From, To) + when Index =:= id; Index =:= pcc_address -> + gen_server:call(?MODULE, {compute_path, Index, From, To}); +compute_path(Index, _From, _To) -> + {error, {invalid_index, Index}}. + + +get_graph() -> + gen_server:call(?MODULE, get_graph). + + +%%% BEHAVIOUR gen_server FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init([]) -> + ?LOG_INFO("Starting TED process...", []), + % {ok, #state{graph = digraph:new([private, cyclic])}}. + {ok, #state{graph = digraph:new([protected, cyclic])}}. + +handle_call({device_added, Id, Device}, _From, State) -> + ?LOG_DEBUG("Adding TED device ~p: ~p", [Id, Device]), + {reply, ok, do_update_device(State, Id, Device)}; +handle_call({device_updated, Id, Device}, _From, State) -> + ?LOG_DEBUG("Updating TED device ~p: ~p", [Id, Device]), + {reply, ok, do_update_device(State, Id, Device)}; +handle_call({device_deleted, Id}, _From, State) -> + ?LOG_DEBUG("Deleting TED device ~p", [Id]), + {reply, ok, do_delete_device(State, Id)}; +handle_call({link_added, Id, Link}, _From, State) -> + ?LOG_DEBUG("Adding TED link ~p: ~p", [Id, Link]), + {reply, ok, do_update_link(State, Id, Link)}; +handle_call({link_updated, Id, Link}, _From, State) -> + ?LOG_DEBUG("Updating TED link ~p: ~p", [Id, Link]), + {reply, ok, do_update_link(State, Id, Link)}; +handle_call({link_deleted, Id}, _From, State) -> + ?LOG_DEBUG("Deleting TED link ~p", [Id]), + {reply, ok, do_delete_link(State, Id)}; +handle_call({compute_path, Index, From, To}, _From, #state{graph = G} = State) -> + case as_ids(State, Index, [From, To]) of + {ok, [FromId, ToId]} -> + {reply, do_compute_path(G, FromId, ToId), State}; + {error, Reason} -> + {reply, {error, Reason}, State} + end; +handle_call(get_graph, _From, #state{graph = G} = State) -> + {reply, G, State}; +handle_call(Request, _From, State) -> + logger:warning("Unexpected call to TED process ~w", [Request]), + {reply, {error, unexpected_call}, State}. + +handle_cast(Request, State) -> + logger:warning("Unexpected cast to TED process ~w", [Request]), + {noreply, State}. + +handle_continue(_Continue, State) -> + {noreply, State}. + +handle_info(Info, State) -> + logger:warning("Unexpected message to TED process ~w", [Info]), + {noreply, State}. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +terminate(_Reason, _State) -> + ?LOG_INFO("Terminating TED process...", []), + ok. + + +%%% INTERNAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +as_ids(_State, id, Keys) -> + {ok, Keys}; +as_ids(State, IndexType, Keys) -> + as_ids(State, IndexType, Keys, []). + +as_ids(_State, _IndexType, [], Acc) -> + {ok, lists:reverse(Acc)}; +as_ids(#state{pcc_address_to_id = Index} = State, pcc_address, [Key | Rest], Acc) -> + case maps:find(Key, Index) of + error -> {error, {unknown_key, Key}}; + {ok, Id} -> as_ids(State, pcc_address, Rest, [Id | Acc]) + end. + +do_update_device(#state{graph = G} = State, Id, NewDevice) -> + State2 = case digraph:vertex(G, Id) of + false -> State; + {Id, OldDevice} -> index_remove_device(State, OldDevice) + end, + digraph:add_vertex(G, Id, NewDevice), + index_add_device(State2, NewDevice). + +do_delete_device(#state{graph = G} = State, Id) -> + case digraph:vertex(G, Id) of + false -> State; + {Id, OldDevice} -> + digraph:del_vertex(G, Id), + index_remove_device(State, OldDevice) + end. + +index_remove_device(#state{pcc_address_to_id = Index} = State, + #{pcc_address := OldAddress}) -> + Index2 = maps:remove(OldAddress, Index), + State#state{pcc_address_to_id = Index2}. + +index_add_device(State, #{pcc_address := undefined}) -> + State; +index_add_device(#state{pcc_address_to_id = Index} = State, + #{id := Id, pcc_address := NewAddress}) -> + Index2 = Index#{NewAddress => Id}, + State#state{pcc_address_to_id = Index2}. + +do_update_link(#state{graph = G} = State, Id, Link) -> + #{endpoints := [EP1, EP2]} = Link, + #{device := D1} = EP1, + #{device := D2} = EP2, + digraph:add_edge(G, {Id, a}, D1, D2, Link), + digraph:add_edge(G, {Id, b}, D2, D1, Link), + State. + +do_delete_link(#state{graph = G} = State, Id) -> + digraph:del_edge(G, {Id, a}), + digraph:del_edge(G, {Id, b}), + State. + +do_compute_path(G, FromId, ToId) -> + case digraph:get_short_path(G, FromId, ToId) of + false -> {error, not_found}; + Ids -> {ok, retrieve_devices(G, Ids, [])} + end. + +retrieve_devices(_G, [], Acc) -> + lists:reverse(Acc); +retrieve_devices(G, [Id | Rest], Acc) -> + case digraph:vertex(G, Id) of + false -> {error, invalid_path}; + {Id, Device} -> + retrieve_devices(G, Rest, [Device | Acc]) + end. diff --git a/src/te/apps/tfte/src/tfte_app.erl b/src/te/apps/tfte/src/tfte_app.erl index 159197fdfa95043bb72beef2007e0c766f983b15..96724904dd7a044442c5568b13d64a2e2a72df44 100644 --- a/src/te/apps/tfte/src/tfte_app.erl +++ b/src/te/apps/tfte/src/tfte_app.erl @@ -8,18 +8,18 @@ -behaviour(application). -%--- Includes ------------------------------------------------------------------ +%%% INCLUDES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -include_lib("kernel/include/logger.hrl"). -%--- Exports ------------------------------------------------------------------- +%%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Behaviour application callback functions -export([start/2, stop/1]). -%--- Behaviour application Callback Functions ---------------------------------- +%%% BEHAVIOUR applicaation CALLBACK FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% start(_StartType, _StartArgs) -> case tfte_sup:start_link() of @@ -34,7 +34,7 @@ stop(_State) -> ok. -%--- Internal Functions -------------------------------------------------------- +%%% INTERNAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% add_services() -> case application:get_env(tfte, services) of @@ -71,7 +71,6 @@ resolve_endpoints(Name, [{Transport, HostSpec, PortSpec, SslOpts} | Rest], Acc) resolve_host_spec(_Name, Hostname) when is_list(Hostname) -> Hostname; resolve_host_spec(Name, {env, Key}) when is_list(Key) -> - ?LOG_DEBUG("????? HOST ~s ~s -> ~p", [Name, Key, os:getenv(Key)]), try os:getenv(Key) of false -> throw({Name, service_hostname_not_found, Key}); Hostname -> Hostname @@ -82,7 +81,6 @@ resolve_host_spec(Name, {env, Key}) when is_list(Key) -> resolve_port_spec(_Name, Port) when is_integer(Port) -> Port; resolve_port_spec(Name, {env, Key}) when is_list(Key) -> - ?LOG_DEBUG("????? PORT ~s ~s -> ~p", [Name, Key, os:getenv(Key)]), try os:getenv(Key) of false -> throw({Name, service_port_not_found, Key}); PortStr -> diff --git a/src/te/apps/tfte/src/tfte_context.erl b/src/te/apps/tfte/src/tfte_context.erl new file mode 100644 index 0000000000000000000000000000000000000000..ee0fafc07ed46fe85c4c884da9dac061298c0ab0 --- /dev/null +++ b/src/te/apps/tfte/src/tfte_context.erl @@ -0,0 +1,162 @@ +-module(tfte_context). + +-behaviour(gen_statem). + + +%%% INCLUDES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-include_lib("kernel/include/logger.hrl"). + + +%%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% API functions +-export([start_link/0]). + +% Behaviour gen_statem functions +-export([init/1]). +-export([callback_mode/0]). +-export([handle_event/4]). +-export([terminate/3]). +-export([code_change/4]). + + +%%% Records %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-record(data, { + uuid :: map(), + sub :: term() | undefined, + obj :: map() | undefined +}). + + +%%% MACROS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-define(SUBSCRIBE_RETRY_TIMEOUT, 1000). +-define(RETRIEVE_RETRY_TIMEOUT, 10000). + + +%%% API FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +start_link() -> + gen_statem:start_link(?MODULE, [], []). + + +%%% BEHAVIOUR gen_statem FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init([]) -> + {ok, Name} = application:get_env(tfte, context), + ?LOG_INFO("Starting context ~s service handler...", [Name]), + UUID = #{context_uuid => #{uuid => Name}}, + {ok, subscribe, #data{uuid = UUID}}. + +callback_mode() -> [handle_event_function, state_enter]. + +%-- SUBSCRIBE STATE ------------------------------------------------------------ +handle_event(enter, _, subscribe, #data{sub = undefined}) -> + {keep_state_and_data, [{state_timeout, 0, do_suscribe}]}; +handle_event(enter, _, subscribe, Data) -> + % We already have a context subscription + {next_state, ready, Data}; +handle_event(state_timeout, do_suscribe, subscribe, Data) -> + ?LOG_DEBUG("Subscribing to context events...", []), + case do_subscribe() of + {ok, Sub} -> + ?LOG_INFO("Subscribed to context events", []), + Data2 = Data#data{sub = Sub}, + {next_state, retrieve, Data2}; + {error, Reason} -> + ?LOG_ERROR("Failed to subscribe to context service events: ~p", [Reason]), + {keep_state_and_data, [{state_timeout, ?SUBSCRIBE_RETRY_TIMEOUT, do_suscribe}]} + end; +%-- RETRIEVE STATE ------------------------------------------------------------- +handle_event(enter, _, retrieve, _Data) -> + {keep_state_and_data, [{state_timeout, 0, do_retrieve}]}; +handle_event(state_timeout, do_retrieve, retrieve, #data{uuid = UUID} = Data) -> + ?LOG_DEBUG("Retrieving context ~p...", [UUID]), + case get_object(UUID) of + error -> + {keep_state_and_data, [{state_timeout, ?RETRIEVE_RETRY_TIMEOUT, do_retrieve}]}; + {ok, Context} -> + ?LOG_DEBUG("Got context: ~p", [Context]), + tfte_server:context_ready(Context), + {next_state, ready, Data#data{obj = Context}} + end; +handle_event(info, {headers, Id, Value}, retrieve, + #data{sub = #{stream_id := Id}}) -> + %TODO: Handle HTTP errors ??? + ?LOG_DEBUG("Received context stream header: ~p", [Value]), + keep_state_and_data; +handle_event(info, {data, Id, Value}, retrieve, + #data{sub = #{stream_id := Id}}) -> + ?LOG_DEBUG("Received context event, retrying context: ~p", [Value]), + {keep_state_and_data, [{state_timeout, 0, do_retrieve}]}; +handle_event(info, {'DOWN', Ref, process, Pid, Reason}, retrieve, + #data{sub = #{stream_id := Id, monitor_ref := Ref, stream_pid := Pid}} = Data) -> + ?LOG_DEBUG("Context subscription is down: ~p", [Reason]), + Data2 = Data#data{sub = undefined}, + Info = receive + {trailers, Id, {Status, Message, Metadata}} -> + {Reason, Status, Message, Metadata} + after 0 -> + Reason + end, + ?LOG_ERROR("Context subscription error: ~p", [Info]), + {next_state, subscribe, Data2}; +%-- READY STATE ---------------------------------------------------------------- +handle_event(enter, _, ready, _Data) -> + keep_state_and_data; +handle_event(info, {headers, Id, Value}, ready, + #data{sub = #{stream_id := Id}}) -> + %TODO: Handle HTTP errors ??? + ?LOG_DEBUG("Received context stream header: ~p", [Value]), + keep_state_and_data; +handle_event(info, {data, Id, #{context_id := UUID, event := Event}}, ready, + #data{uuid = UUID, sub = #{stream_id := Id}}) -> + ?LOG_DEBUG("Received context event: ~p", [Event]), + tfte_server:context_event(Event), + keep_state_and_data; +handle_event(info, {'DOWN', Ref, process, Pid, Reason}, ready, + #data{sub = #{stream_id := Id, monitor_ref := Ref, stream_pid := Pid}} = Data) -> + ?LOG_DEBUG("Context subscription is down: ~p", [Reason]), + Data2 = Data#data{sub = undefined}, + Info = receive + {trailers, Id, {Status, Message, Metadata}} -> + {Reason, Status, Message, Metadata} + after 0 -> + Reason + end, + ?LOG_ERROR("Context subscription error: ~p", [Info]), + {next_state, subscribe, Data2}; +%-- ANY STATE ------------------------------------------------------------------ +handle_event(info, Msg, StateName, _Data) -> + ?LOG_WARNING("Unexpected context message in state ~w: ~p", [StateName, Msg]), + keep_state_and_data. + +terminate(Reason, _State, _Data) -> + ?LOG_INFO("Context service handler terminated: ~p", [Reason]), + ok. + +code_change(_OldVsn, OldState, OldData, _Extra) -> + {ok, OldState, OldData}. + + +%%% INTERNAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +grpc_opts() -> + #{channel => context}. + +do_subscribe() -> + context_context_service_client:get_context_events(#{}, grpc_opts()). + +get_object(UUID) -> + case context_context_service_client:get_context(UUID, grpc_opts()) of + {error, Reason} -> + ?LOG_ERROR("Local error while retrieving the context object: ~p", [Reason]), + error; + {error, Reason, _Headers} -> + ?LOG_ERROR("Remote error while retrieving the context object: ~p", [Reason]), + error; + {ok, Result, _Headers} -> + {ok, Result} + end. \ No newline at end of file diff --git a/src/te/apps/tfte/src/tfte_server.erl b/src/te/apps/tfte/src/tfte_server.erl new file mode 100644 index 0000000000000000000000000000000000000000..29dddf3d1dab6b08810746c5f5a2ef2636c5ad99 --- /dev/null +++ b/src/te/apps/tfte/src/tfte_server.erl @@ -0,0 +1,100 @@ +-module(tfte_server). + +-behaviour(gen_statem). + + +%%% INCLUDES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-include_lib("kernel/include/logger.hrl"). + + +%%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% API functions +-export([start_link/0]). +-export([context_ready/1]). +-export([context_event/1]). +-export([topology_ready/1]). +-export([topology_event/1]). + +% Behaviour gen_statem functions +-export([init/1]). +-export([callback_mode/0]). +-export([handle_event/4]). +-export([terminate/3]). +-export([code_change/4]). + + +%%% Records %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-record(data, { +}). + + +%%% MACROS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +%%% API FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +start_link() -> + gen_statem:start_link(?MODULE, [], []). + +context_ready(Context) -> + gen_statem:cast(?MODULE, {context_ready, Context}). + +context_event(Event) -> + gen_statem:cast(?MODULE, {context_event, Event}). + +topology_ready(Topology) -> + gen_statem:cast(?MODULE, {topology_ready, Topology}). + +topology_event(Event) -> + gen_statem:cast(?MODULE, {topology_event, Event}). + + +%%% BEHAVIOUR gen_statem FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init([]) -> + ?LOG_INFO("Starting server...", []), + {ok, wait_context, #data{}}. + +callback_mode() -> [handle_event_function, state_enter]. + +%-- WAIT_CONTEXT STATE --------------------------------------------------------- +handle_event(enter, _, wait_context, _Data) -> + keep_state_and_data; +handle_event(cast, {context_ready, _Context}, wait_contex, Data) -> + ?LOG_DEBUG("Teraflow context initialized: ~p", [_Context]), + tfte_topology:context_updated(), + {next_state, ready, Data}; +%-- READY STATE ---------------------------------------------------------------- +handle_event(enter, _, ready, _Data) -> + keep_state_and_data; +handle_event(cast, {context_ready, _Context}, ready, _Data) -> + ?LOG_DEBUG("Teraflow context updated: ~p", [_Context]), + tfte_topology:context_updated(), + keep_state_and_data; +handle_event(cast, {context_event, _Event}, ready, _Data) -> + ?LOG_DEBUG("Teraflow context event: ~p", [_Event]), + keep_state_and_data; +handle_event(cast, {topology_ready, _Topology}, ready, _Data) -> + ?LOG_DEBUG("Teraflow topology updated: ~p", [_Topology]), + keep_state_and_data; +handle_event(cast, {topology_event, _Event}, ready, _Data) -> + ?LOG_DEBUG("Teraflow topology event: ~p", [_Event]), + keep_state_and_data; +%-- ANY STATE ------------------------------------------------------------------ +handle_event(EventType, EventContent, State, Data) -> + ?LOG_WARNING(Data, "Unexpected ~w event in state ~w: ~w", + [EventType, State, EventContent]), + keep_state_and_data. + +terminate(Reason, _State, _Data) -> + ?LOG_INFO("Server terminated: ~p", [Reason]), + ok. + +code_change(_OldVsn, OldState, OldData, _Extra) -> + {ok, OldState, OldData}. + + +%%% INTERNAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/src/te/apps/tfte/src/tfte_service_sup.erl b/src/te/apps/tfte/src/tfte_service_sup.erl new file mode 100644 index 0000000000000000000000000000000000000000..2223589e2350d51b8aa32004bf45e7b24b76b939 --- /dev/null +++ b/src/te/apps/tfte/src/tfte_service_sup.erl @@ -0,0 +1,50 @@ +%%%----------------------------------------------------------------------------- +%% @doc tfte service supervisor. +%% @end +%%%----------------------------------------------------------------------------- + +-module(tfte_service_sup). + +-behaviour(supervisor). + + +%%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% API Functions +-export([start_link/0]). + +% Behaviour supervisor callback functions +-export([init/1]). + + +%%% MACROS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-define(SERVER, ?MODULE). + + +%%% API FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + + +%%% BEHAVIOUR supervisor CALLBACK FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init([]) -> + SupFlags = #{strategy => one_for_one, + intensity => 0, + period => 1}, + ContextSpec = #{ + id => tfte_context, + start => {tfte_context, start_link, []}, + restart => permanent, + shutdown => brutal_kill + }, + TopologySpec = #{ + id => tfte_topology, + start => {tfte_topology, start_link, []}, + restart => permanent, + shutdown => brutal_kill + }, + ChildSpecs = [ContextSpec, TopologySpec], + {ok, {SupFlags, ChildSpecs}}. diff --git a/src/te/apps/tfte/src/tfte_sup.erl b/src/te/apps/tfte/src/tfte_sup.erl index 2944889cb2f302992cc3d72164730e375665d783..57c95483ea74e20df048990b92d3950ab48a216e 100644 --- a/src/te/apps/tfte/src/tfte_sup.erl +++ b/src/te/apps/tfte/src/tfte_sup.erl @@ -8,7 +8,7 @@ -behaviour(supervisor). -%--- Exports ------------------------------------------------------------------- +%%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % API Functions -export([start_link/0]). @@ -17,22 +17,36 @@ -export([init/1]). -%--- Macros -------------------------------------------------------------------- +%%% MACROS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -define(SERVER, ?MODULE). +-define(ROOT_SERVER, tfte_server). -%--- API Functions ------------------------------------------------------------- +%%% API FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% start_link() -> supervisor:start_link({local, ?SERVER}, ?MODULE, []). -%--- Behaviour supervisor Callback Functions ----------------------------------- +%%% BEHAVIOUR supervisor CALLBACK FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% init([]) -> SupFlags = #{strategy => one_for_all, intensity => 0, period => 1}, - ChildSpecs = [], + ServiceSupSpec = #{ + id => service_sup, + start => {tfte_service_sup, start_link, []}, + restart => permanent, + type => supervisor, + shutdown => brutal_kill + }, + ServerSpec = #{ + id => ?ROOT_SERVER, + start => {?ROOT_SERVER, start_link, []}, + restart => permanent, + shutdown => brutal_kill + }, + ChildSpecs = [ServerSpec, ServiceSupSpec], {ok, {SupFlags, ChildSpecs}}. diff --git a/src/te/apps/tfte/src/tfte_te_service.erl b/src/te/apps/tfte/src/tfte_te_service.erl index 7c2a7225b497ef5ba5b56ba7aac419e4f5bacbe6..1cadd7aad0b7f512445fae62fbcdb380d92bf544 100644 --- a/src/te/apps/tfte/src/tfte_te_service.erl +++ b/src/te/apps/tfte/src/tfte_te_service.erl @@ -3,12 +3,12 @@ -behaviour(te_te_service_bhvr). -%--- Includes ------------------------------------------------------------------ +%%% INCLUDES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -include_lib("grpcbox/include/grpcbox.hrl"). -%--- Exports ------------------------------------------------------------------- +%%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Behaviour te_te_service_bhvr callback functions -export([request_lsp/2]). @@ -16,7 +16,7 @@ -export([delete_lsp/2]). -%--- Behaviour te_te_service_bhvr Callback Functions --------------------------- +%%% BEHAVIOUR te_te_service_bhvr CALLBACK FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%% request_lsp(_Ctx, _Service) -> {error, {?GRPC_STATUS_UNIMPLEMENTED, <<"Not yet implemented">>}, diff --git a/src/te/apps/tfte/src/tfte_topology.erl b/src/te/apps/tfte/src/tfte_topology.erl new file mode 100644 index 0000000000000000000000000000000000000000..d2c6fc0d9cdb2ceb097bc21dff5211308ed7bd9e --- /dev/null +++ b/src/te/apps/tfte/src/tfte_topology.erl @@ -0,0 +1,394 @@ +-module(tfte_topology). + +-behaviour(gen_statem). + + +%%% INCLUDES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-include_lib("kernel/include/logger.hrl"). + + +%%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% API functions +-export([start_link/0]). +-export([context_updated/0]). + +% Behaviour gen_statem functions +-export([init/1]). +-export([callback_mode/0]). +-export([handle_event/4]). +-export([terminate/3]). +-export([code_change/4]). + + +%%% Records %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-record(data, { + uuid :: map(), + sub :: term() | undefined, + obj :: map() | undefined, + devices = #{} :: map(), + links = #{} :: map() +}). + + +%%% MACROS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-define(SUBSCRIBE_RETRY_TIMEOUT, 1000). +-define(RETRIEVE_RETRY_TIMEOUT, 10000). + + +%%% API FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +start_link() -> + gen_statem:start_link(?MODULE, [], []). + +context_updated() -> + gen_statem:cast(?MODULE, context_updated). + + +%%% BEHAVIOUR gen_statem FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init([]) -> + {ok, ContextName} = application:get_env(tfte, context), + {ok, TopoName} = application:get_env(tfte, topology), + ContextUUID = #{context_uuid => #{uuid => ContextName}}, + TopoUUID = #{context_id => ContextUUID, + topology_uuid => #{uuid => TopoName}}, + ?LOG_INFO("Starting topology ~s service handler...", [TopoName]), + {ok, retrieve, #data{uuid = TopoUUID}}. + +callback_mode() -> [handle_event_function, state_enter]. + +%-- RETRIEVE STATE ------------------------------------------------------------- +handle_event(enter, _, retrieve, _Data) -> + {keep_state_and_data, [{state_timeout, 0, do_retrieve}]}; +handle_event(state_timeout, do_retrieve, retrieve, #data{uuid = UUID} = Data) -> + ?LOG_DEBUG("Retrieving topology ~p...", [UUID]), + case get_object(UUID) of + error -> + {keep_state_and_data, [{state_timeout, ?RETRIEVE_RETRY_TIMEOUT, do_retrieve}]}; + {ok, Topology} -> + ?LOG_DEBUG("Got topology: ~p", [Topology]), + {next_state, subscribe, Data#data{obj = Topology}} + end; +handle_event(cast, context_updated, retrieve, _Data) -> + {keep_state_and_data, [{state_timeout, 0, do_retrieve}]}; +%-- SUBSCRIBE STATE ------------------------------------------------------------ +handle_event(enter, _, subscribe, #data{sub = undefined}) -> + {keep_state_and_data, [{state_timeout, 0, do_suscribe}]}; +handle_event(enter, _, subscribe, Data) -> + % We already have a topology subscription + {next_state, ready, Data}; +handle_event(state_timeout, do_suscribe, subscribe, #data{uuid = UUID} = Data) -> + ?LOG_DEBUG("Subscribing to topology events...", []), + case do_subscribe(UUID) of + {ok, Sub} -> + ?LOG_INFO("Subscribed to topology events", []), + Data2 = #data{obj = Obj} = Data#data{sub = Sub}, + #{device_ids := DeviceIds, link_ids := LinkIds} = Obj, + case update_topology(Data2, DeviceIds, LinkIds) of + {ok, Data3} -> + tfte_server:topology_ready(Obj), + {next_state, ready, Data3}; + {error, Reason} -> + ?LOG_ERROR("Failed to load topology: ~p", [Reason]), + statem_rollback_to_retrieve(Data2) + end; + {error, Reason} -> + ?LOG_ERROR("Failed to subscribe to topology service events: ~p", [Reason]), + {next_state, retrieve, [{state_timeout, ?SUBSCRIBE_RETRY_TIMEOUT, do_retrieve}]} + end; +%-- READY STATE ---------------------------------------------------------------- +handle_event(enter, _, ready, _Data) -> + keep_state_and_data; +handle_event(info, {headers, Id, Value}, ready, + #data{sub = #{stream_id := Id}}) -> + %TODO: Handle HTTP errors ??? + ?LOG_DEBUG("Received topology stream header: ~p", [Value]), + keep_state_and_data; +handle_event(info, {data, Id, #{event := Event}}, ready, + #data{sub = #{stream_id := Id}} = Data) -> + ?LOG_DEBUG("Received topology event: ~p", [Event]), + handle_topology_event(Data, Event); +handle_event(info, {'DOWN', Ref, process, Pid, Reason}, ready, + #data{sub = #{stream_id := Id, monitor_ref := Ref, stream_pid := Pid}} = Data) -> + ?LOG_DEBUG("Topology subscription is down: ~p", [Reason]), + Data2 = Data#data{sub = undefined}, + Info = receive + {trailers, Id, {Status, Message, Metadata}} -> + {Reason, Status, Message, Metadata} + after 0 -> + Reason + end, + ?LOG_ERROR("Topology subscription error: ~p", [Info]), + {next_state, retrieve, Data2}; +handle_event(cast, context_updated, ready, _Data) -> + keep_state_and_data; +%-- ANY STATE ------------------------------------------------------------------ +handle_event(info, Msg, StateName, _Data) -> + ?LOG_WARNING("Unexpected topology message in state ~w: ~p", [StateName, Msg]), + keep_state_and_data. + +terminate(Reason, _State, _Data) -> + ?LOG_INFO("Topology service handler terminated: ~p", [Reason]), + ok. + +code_change(_OldVsn, OldState, OldData, _Extra) -> + {ok, OldState, OldData}. + + +%%% INTERNAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +statem_rollback_to_retrieve(#data{sub = undefined} = Data) -> + {next_state, retrieve, Data, [{state_timeout, ?RETRIEVE_RETRY_TIMEOUT, do_retrieve}]}; +statem_rollback_to_retrieve(#data{sub = Sub} = Data) -> + grpcbox_client:close_send_and_recv(Sub), + Data2 = Data#data{sub = undefined}, + {next_state, retrieve, Data2, [{state_timeout, ?RETRIEVE_RETRY_TIMEOUT, do_retrieve}]}. + +handle_topology_event(#data{uuid = UUID} = Data, + #{event_type := 'EVENTTYPE_UPDATE'} = Event) -> + case get_object(UUID) of + error -> + statem_rollback_to_retrieve(Data); + {ok, #{device_ids := DeviceIds, link_ids := LinkIds} = Topology} -> + ?LOG_DEBUG("Got new topology: ~p", [Topology]), + Data2 = Data#data{obj = Topology}, + case update_topology(Data2, DeviceIds, LinkIds) of + {ok, Data3} -> + tfte_server:topology_event(Event), + {keep_state, Data3}; + {error, Reason} -> + ?LOG_ERROR("Failed to update topology: ~p", [Reason]), + statem_rollback_to_retrieve(Data2) + end + end; +handle_topology_event(_Data, Event) -> + tfte_server:topology_event(Event), + keep_state_and_data. + +update_topology(Data, DeviceIds, LinkIds) -> + try + {Data2, Events} = update_devices(Data, DeviceIds, []), + {Data3, Events2} = update_links(Data2, LinkIds, Events), + post_topology_events(lists:reverse(Events2)), + {ok, Data3} + catch + throw:Reason -> + {error, Reason} + end. + +post_topology_events(Events) -> + lists:foreach(fun post_topology_event/1, Events). + +post_topology_event({device_added, Id, Device}) -> + epce_ted:device_added(Id, Device); +post_topology_event({device_updated, Id, Device}) -> + epce_ted:device_updated(Id, Device); +post_topology_event({device_deleted, Id}) -> + epce_ted:device_deleted(Id); +post_topology_event({link_added, Id, Link}) -> + epce_ted:link_added(Id, Link); +post_topology_event({link_updated, Id, Link}) -> + epce_ted:link_updated(Id, Link); +post_topology_event({link_deleted, Id}) -> + epce_ted:link_deleted(Id). + +update_devices(#data{devices = OldDevices} = Data, DeviceIds, Events) -> + {NewDevices, Events2} = update_devices(OldDevices, #{}, DeviceIds, Events), + {Data#data{devices = NewDevices}, Events2}. + +update_devices(OldDevices, NewDevices, [], Events) -> + Events2 = [{device_deleted, post_process_device_id(I)} + || I <- maps:keys(OldDevices)] ++ Events, + {NewDevices, Events2}; +update_devices(OldDevices, NewDevices, [Id | Rest], Events) -> + case get_device(Id) of + error -> throw({device_retrieval_error, Id}); + {ok, Device} -> + Id2 = post_process_device_id(Id), + Device2 = post_process_device(Device), + NewDevices2 = NewDevices#{Id => Device}, + case maps:take(Id, OldDevices) of + error -> + % New device + Events2 = [{device_added, Id2, Device2} | Events], + update_devices(OldDevices, NewDevices2, Rest, Events2); + {Device, OldDevices2} -> + % Device did not change + update_devices(OldDevices2, NewDevices2, Rest, Events); + {_OldDevice, OldDevices2} -> + % Device changed + Events2 = [{device_updated, Id2, Device2} | Events], + update_devices(OldDevices2, NewDevices2, Rest, Events2) + end + end. + +update_links(#data{links = OldLinks} = Data, LinksIds, Events) -> + {NewLinks, Events2} = update_links(OldLinks, #{}, LinksIds, Events), + {Data#data{links = NewLinks}, Events2}. + +update_links(OldLinks, NewLinks, [], Events) -> + Events2 = [{link_deleted, post_process_link_id(I)} + || I <- maps:keys(OldLinks)] ++ Events, + {NewLinks, Events2}; +update_links(OldLinks, NewLinks, [Id | Rest], Events) -> + case get_link(Id) of + error -> throw({link_retrieval_error, Id}); + {ok, Link} -> + Id2 = post_process_link_id(Id), + Link2 = post_process_link(Link), + NewLinks2 = NewLinks#{Id => Link}, + case maps:take(Id, OldLinks) of + error -> + % New Link + Events2 = [{link_added, Id2, Link2} | Events], + update_links(OldLinks, NewLinks2, Rest, Events2); + {Link, OldLinks2} -> + % Link did not change + update_links(OldLinks2, NewLinks2, Rest, Events); + {_OldLink, OldLinks2} -> + % Link changed + Events2 = [{link_updated, Id2, Link2} | Events], + update_links(OldLinks2, NewLinks2, Rest, Events2) + end + end. + +post_process_device_id(#{device_uuid := #{uuid := Name}}) -> + Name. + +post_process_device(Device) -> + #{id => device_id(Device), + type => device_type(Device), + pcc_address => device_pcc_address(Device), + mpls_label => device_mpls_label(Device), + status => device_status(Device), + endpoints => device_endpoints(Device)}. + +device_id(#{device_id := Id}) -> + post_process_device_id(Id). + +device_type(#{device_type := Type}) -> + Type. + +device_status(#{device_operational_status := 'DEVICEOPERATIONALSTATUS_UNDEFINED'}) -> + undefined; +device_status(#{device_operational_status := 'DEVICEOPERATIONALSTATUS_DISABLED'}) -> + disabled; +device_status(#{device_operational_status := 'DEVICEOPERATIONALSTATUS_ENABLED'}) -> + enabled. + +device_mpls_label(Device) -> + case device_config_value(<<"mpls_label">>, Device) of + undefined -> undefined; + LabelBin -> + try binary_to_integer(LabelBin) + catch error:badarg -> undefined + end + end. + +device_pcc_address(Device) -> + case device_config_value(<<"pcc_address">>, Device) of + undefined -> undefined; + AddressBin -> + case inet_parse:address(binary_to_list(AddressBin)) of + {ok, Address} -> Address; + {error,einval} -> undefined + end + end. + +device_config_value(Key, #{device_config := #{config_rules := Rules}}) -> + device_config_value(Key, Rules); +device_config_value(_Key, []) -> + undefined; +device_config_value(Key, [#{action := 'CONFIGACTION_SET', + config_rule := {custom, Rule}} | Rest]) -> + case Rule of + #{resource_key := Key, resource_value := Value} -> Value; + _ -> device_config_value(Key, Rest) + end; +device_config_value(Key, [_Rule | Rest]) -> + device_config_value(Key, Rest). + + +device_endpoints(Device) -> + device_endpoints(Device, []). + +device_endpoints(#{device_endpoints := Endpoints}, Acc) -> + device_endpoints(Endpoints, Acc); +device_endpoints([], Acc) -> + lists:reverse(Acc); +device_endpoints([#{endpoint_id := #{endpoint_uuid := #{uuid := Name}}} | Rest], Acc) -> + device_endpoints(Rest, [Name | Acc]). + +post_process_link_id(#{link_uuid := #{uuid := Name}}) -> + Name. + +post_process_link(Link) -> + #{id => link_id(Link), + endpoints => link_endpoints(Link)}. + +link_id(#{link_id := Id}) -> + post_process_link_id(Id). + +link_endpoints(Link) -> + link_endpoints(Link, []). + +link_endpoints(#{link_endpoint_ids := Endpoints}, Acc) -> + link_endpoints(Endpoints, Acc); +link_endpoints([], Acc) -> + lists:reverse(Acc); +link_endpoints([#{device_id := #{device_uuid := #{uuid := DevName}}, + endpoint_uuid := #{uuid := EndpointName}} | Rest], Acc) -> + Endpoint = #{ + device => DevName, + endpoint => EndpointName + }, + link_endpoints(Rest, [Endpoint | Acc]). + + +%-- GRPC UNTILITY FUNCTION ----------------------------------------------------- + +grpc_opts() -> + #{channel => context}. + +do_subscribe(UUID) -> + context_context_service_client:get_topology_events(UUID, grpc_opts()). + +get_object(UUID) -> + case context_context_service_client:get_topology(UUID, grpc_opts()) of + {error, Reason} -> + ?LOG_ERROR("Local error while retrieving the topology object: ~p", [Reason]), + error; + {error, Reason, _Headers} -> + ?LOG_ERROR("Remote error while retrieving the topology object: ~p", [Reason]), + error; + {ok, Result, _Headers} -> + {ok, Result} + end. + +get_device(UUID) -> + case context_context_service_client:get_device(UUID, grpc_opts()) of + {error, Reason} -> + ?LOG_ERROR("Local error while retrieving a device object: ~p", [Reason]), + error; + {error, Reason, _Headers} -> + ?LOG_ERROR("Remote error while retrieving a device object: ~p", [Reason]), + error; + {ok, Result, _Headers} -> + {ok, Result} + end. + +get_link(UUID) -> + case context_context_service_client:get_link(UUID, grpc_opts()) of + {error, Reason} -> + ?LOG_ERROR("Local error while retrieving a link object: ~p", [Reason]), + error; + {error, Reason, _Headers} -> + ?LOG_ERROR("Remote error while retrieving a link object: ~p", [Reason]), + error; + {ok, Result, _Headers} -> + {ok, Result} + end. diff --git a/src/te/config/sys.config.src b/src/te/config/sys.config.src index 93bfbafb1cf9ee5d457fc41dc2e97389deb731f0..f3c18564177e11bcf21f4fab4d45e905f806060c 100644 --- a/src/te/config/sys.config.src +++ b/src/te/config/sys.config.src @@ -1,6 +1,8 @@ [ {tfte, [ + {context, <<"admin">>}, + {topology, <<"tetestbed">>}, {services, [ {te, [ {http, {env, "TESERVICE_SERVICE_HOST"}, {env, "TESERVICE_SERVICE_PORT_GRPC"}, []} @@ -72,7 +74,7 @@ {logger_level, ${ERLANG_LOGGER_LEVEL}}, {logger, [ {handler, default, logger_std_h, #{ - level => info, + level => ${ERLANG_LOGGER_LEVEL}, filter_default => log, config => #{type => standard_io}, formatter => {logger_formatter, #{ diff --git a/tutorial/2-6-te-demo-start-testbed.sh b/tutorial/2-6-te-demo-start-testbed.sh index dea41a71a4371384b32d91f74e2ec90af171d30f..3266d1141a246fd7bfd4dd3efd155128a792114f 100755 --- a/tutorial/2-6-te-demo-start-testbed.sh +++ b/tutorial/2-6-te-demo-start-testbed.sh @@ -11,26 +11,30 @@ if [[ ! -f "${NEGENDIR}/exe/netgen" ]]; then exit 1 fi -export PCE_NETNS="$1" -export PCE_IP="$2" -RT1_INT_IP="$3" -RT1_EXT_IP="$4" -RT6_INT_IP="$5" -RT6_EXT_IP="$6" - - -if [[ -z $PCE_NETNS || -z $PCE_IP || -z RT1_INT_IP || -z RT1_EXT_IP || -z RT6_INT_IP || -z RT6_EXT_IP ]]; then - echo "USAGE: $0 PCE_NETNS PCE_IP RT1_INT_IP RT1_EXT_IP RT6_INT_IP RT6_EXT_IP" - echo " e.g: $0 cni-588a2d06-e64f-907b-d51b-bed0307007c9 10.1.103.133 10 11 12 13" - exit 1 +PCE_IP=$( kubectl --namespace tfs get $(kubectl --namespace tfs get pods --selector=app=teservice -o name) --template '{{.status.podIP}}' ) +echo "Teraflow PCE IP address: $PCE_IP" +NAMESPACES=$( ip netns list | cut -d' ' -f1 ) +PCE_NETNS="" +for n in $NAMESPACES; do + if sudo ip -n $n addr list | grep $PCE_IP > /dev/null; then + echo "Teraflow TE service namespace: $n" + PCE_NETNS=$n + break + fi +done +if [[ -z $PCE_NETNS ]]; then + echo "Teraflow network namespace for TE service not found" + exit1 fi IFS=. read PCE_IP1 PCE_IP2 PCE_IP3 PCE_IP4 <<< "$PCE_IP" -export RT1_PCE_INT_IF_IP="$PCE_IP1.$PCE_IP2.$PCE_IP3.$RT1_INT_IP" -export RT1_PCE_EXT_IF_IP="$PCE_IP1.$PCE_IP2.$PCE_IP3.$RT1_EXT_IP" -export RT6_PCE_INT_IF_IP="$PCE_IP1.$PCE_IP2.$PCE_IP3.$RT6_INT_IP" -export RT6_PCE_EXT_IF_IP="$PCE_IP1.$PCE_IP2.$PCE_IP3.$RT6_EXT_IP" +export PCE_IP +export PCE_NETNS +export RT1_PCE_INT_IF_IP="$PCE_IP1.$PCE_IP2.$PCE_IP3.10" +export RT1_PCE_EXT_IF_IP="$PCE_IP1.$PCE_IP2.$PCE_IP3.11" +export RT6_PCE_INT_IF_IP="$PCE_IP1.$PCE_IP2.$PCE_IP3.12" +export RT6_PCE_EXT_IF_IP="$PCE_IP1.$PCE_IP2.$PCE_IP3.13" cp "${ROOTDIR}/2-6-netgen-config.yml" "${RUNDIR}/config.yml" cat "${ROOTDIR}/2-6-netgen-topology.yml.template" | envsubst > "${RUNDIR}/topology.yml" diff --git a/tutorial/2-6-te-demo.md b/tutorial/2-6-te-demo.md index 6a99007b4c5d481cc7692ea963c138ff51234c0d..774a38babe9f0fea53296cbbd83467c55f303e59 100644 --- a/tutorial/2-6-te-demo.md +++ b/tutorial/2-6-te-demo.md @@ -78,20 +78,14 @@ ### Run the Test-Bed -To start the testbed, we need to figure out the IP of the TE service: - $ kubectl --namespace tfs get $(kubectl --namespace tfs get pods --selector=app=teservice -o name) --template '{{.status.podIP}}' - -And select the network namespace that defines it by looking into each of them until you find the correct one: - $ ip netns list - $ sudo ip -n XXX addr list - -When we have the IP and namespace we can start the testbed. +First load the [teraflow configuration file](./2-6-teraflow-topology.json) using the webui. +The first time the configuration is loaded may partialy fail because it tries to load the topology before the devices and links, but a second load should work. In first console: $ cd ~/testbed - $ ../tfs-ctrl/tutorial/2-6-te-demo-start-testbed.sh NETWORK_NAMESPACE TE_SERVICE_IP 10 11 12 13 + $ ../tfs-ctrl/tutorial/2-6-te-demo-start-testbed.sh Then in second console: $ sudo -i # cd /tmp/negen - # ./tmux.sh \ No newline at end of file + # ./tmux.sh diff --git a/tutorial/2-6-teraflow-topology.json b/tutorial/2-6-teraflow-topology.json new file mode 100644 index 0000000000000000000000000000000000000000..8b5188866c4e2d2b5dff26b6b3bf26c7eb56b2ec --- /dev/null +++ b/tutorial/2-6-teraflow-topology.json @@ -0,0 +1,210 @@ +{ + "contexts": [ + { + "context_id": {"context_uuid": {"uuid": "admin"}}, + "topology_ids": [], + "service_ids": [] + } + ], + "topologies": [ + { + "topology_id": {"topology_uuid": {"uuid": "tetestbed"}, "context_id": {"context_uuid": {"uuid": "admin"}}}, + "device_ids": [ + {"device_uuid": {"uuid": "SW1"}}, + {"device_uuid": {"uuid": "RT1"}}, + {"device_uuid": {"uuid": "RT2"}}, + {"device_uuid": {"uuid": "RT3"}}, + {"device_uuid": {"uuid": "RT4"}}, + {"device_uuid": {"uuid": "RT5"}}, + {"device_uuid": {"uuid": "RT6"}} + ], + "link_ids": [ + {"link_uuid": {"uuid": "RT1/SW1"}}, + {"link_uuid": {"uuid": "RT2/SW1"}}, + {"link_uuid": {"uuid": "RT3/SW1"}}, + {"link_uuid": {"uuid": "RT2/RT4/1"}}, + {"link_uuid": {"uuid": "RT2/RT4/2"}}, + {"link_uuid": {"uuid": "RT3/RT5/1"}}, + {"link_uuid": {"uuid": "RT3/RT5/2"}}, + {"link_uuid": {"uuid": "RT4/RT5"}}, + {"link_uuid": {"uuid": "RT4/RT6"}}, + {"link_uuid": {"uuid": "RT5/RT6"}} + ] + } + ], + "devices": [ + { + "device_id": {"device_uuid": {"uuid": "SW1"}}, + "device_type": "emu-packet-switch", + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": + "{\"endpoints\": [{\"sample_types\": [], \"type\": \"copper\", \"uuid\": \"df8bb169-2013-4b82-9455-69777f7a01d6\"}, {\"sample_types\": [], \"type\": \"copper\", \"uuid\": \"061119c1-2aa4-48e9-be64-3ddf465fc80a\"}, {\"sample_types\": [], \"type\": \"copper\", \"uuid\": \"495ea3f8-e67f-46a0-84bd-a230a4b7067d\"}]}"}} + ]}, + "device_operational_status": 1, + "device_drivers": [0], + "device_endpoints": [] + }, + { + "device_id": {"device_uuid": {"uuid": "RT1"}}, + "device_type": "emu-packet-router", + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/mpls_label", "resource_value": "16010"}}, + {"action": 1, "custom": {"resource_key": "_connect/pcc_address", "resource_value": "1.1.1.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "1.1.1.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": + "{\"endpoints\": [{\"sample_types\": [], \"type\": \"copper\", \"uuid\": \"eth-src\"}, {\"sample_types\": [], \"type\": \"copper\", \"uuid\": \"eth-sw1\"}]}"}} + ]}, + "device_operational_status": 1, + "device_drivers": [0], + "device_endpoints": [] + }, + { + "device_id": {"device_uuid": {"uuid": "RT2"}}, + "device_type": "emu-packet-router", + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/mpls_label", "resource_value": "16020"}}, + {"action": 1, "custom": {"resource_key": "_connect/pcc_address", "resource_value": "2.2.2.2"}}, + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "2.2.2.2"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": + "{\"endpoints\": [{\"sample_types\": [], \"type\": \"copper\", \"uuid\": \"eth-sw1\"}, {\"sample_types\": [], \"type\": \"copper\", \"uuid\": \"eth-rt4-1\"}, {\"sample_types\": [], \"type\": \"copper\", \"uuid\": \"eth-rt4-2\"}]}"}} + ]}, + "device_operational_status": 1, + "device_drivers": [0], + "device_endpoints": [] + }, + { + "device_id": {"device_uuid": {"uuid": "RT3"}}, + "device_type": "emu-packet-router", + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/mpls_label", "resource_value": "16030"}}, + {"action": 1, "custom": {"resource_key": "_connect/pcc_address", "resource_value": "3.3.3.3"}}, + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "3.3.3.3"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": + "{\"endpoints\": [{\"sample_types\": [], \"type\": \"copper\", \"uuid\": \"eth-sw1\"}, {\"sample_types\": [], \"type\": \"copper\", \"uuid\": \"eth-rt5-1\"}, {\"sample_types\": [], \"type\": \"copper\", \"uuid\": \"eth-rt5-2\"}]}"}} + ]}, + "device_operational_status": 1, + "device_drivers": [0], + "device_endpoints": [] + }, + { + "device_id": {"device_uuid": {"uuid": "RT4"}}, + "device_type": "emu-packet-router", + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/mpls_label", "resource_value": "16040"}}, + {"action": 1, "custom": {"resource_key": "_connect/pcc_address", "resource_value": "4.4.4.4"}}, + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "4.4.4.4"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": + "{\"endpoints\": [{\"sample_types\": [], \"type\": \"copper\", \"uuid\": \"eth-rt2-1\"}, {\"sample_types\": [], \"type\": \"copper\", \"uuid\": \"eth-rt2-2\"}, {\"sample_types\": [], \"type\": \"copper\", \"uuid\": \"eth-rt5\"}, {\"sample_types\": [], \"type\": \"copper\", \"uuid\": \"eth-rt6\"}]}"}} + ]}, + "device_operational_status": 1, + "device_drivers": [0], + "device_endpoints": [] + }, + { + "device_id": {"device_uuid": {"uuid": "RT5"}}, + "device_type": "emu-packet-router", + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/mpls_label", "resource_value": "16050"}}, + {"action": 1, "custom": {"resource_key": "_connect/pcc_address", "resource_value": "5.5.5.5"}}, + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "5.5.5.5"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": + "{\"endpoints\": [{\"sample_types\": [], \"type\": \"copper\", \"uuid\": \"eth-rt3-1\"}, {\"sample_types\": [], \"type\": \"copper\", \"uuid\": \"eth-rt3-2\"}, {\"sample_types\": [], \"type\": \"copper\", \"uuid\": \"eth-rt4\"}, {\"sample_types\": [], \"type\": \"copper\", \"uuid\": \"eth-rt6\"}]}"}} + ]}, + "device_operational_status": 1, + "device_drivers": [0], + "device_endpoints": [] + }, + { + "device_id": {"device_uuid": {"uuid": "RT6"}}, + "device_type": "emu-packet-router", + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/mpls_label", "resource_value": "16060"}}, + {"action": 1, "custom": {"resource_key": "_connect/pcc_address", "resource_value": "6.6.6.6"}}, + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "6.6.6.6"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": + "{\"endpoints\": [{\"sample_types\": [], \"type\": \"copper\", \"uuid\": \"eth-rt4\"}, {\"sample_types\": [], \"type\": \"copper\", \"uuid\": \"eth-rt5\"}, {\"sample_types\": [], \"type\": \"copper\", \"uuid\": \"eth-dst\"}]}"}} + ]}, + "device_operational_status": 1, + "device_drivers": [0], + "device_endpoints": [] + } + ], + "links": [ + { + "link_id": {"link_uuid": {"uuid": "RT1/SW1"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "RT1"}}, "endpoint_uuid": {"uuid": "eth-sw1"}}, + {"device_id": {"device_uuid": {"uuid": "SW1"}}, "endpoint_uuid": {"uuid": "df8bb169-2013-4b82-9455-69777f7a01d6"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "RT2/SW1"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "RT2"}}, "endpoint_uuid": {"uuid": "eth-sw1"}}, + {"device_id": {"device_uuid": {"uuid": "SW1"}}, "endpoint_uuid": {"uuid": "061119c1-2aa4-48e9-be64-3ddf465fc80a"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "RT3/SW1"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "RT3"}}, "endpoint_uuid": {"uuid": "eth-sw1"}}, + {"device_id": {"device_uuid": {"uuid": "SW1"}}, "endpoint_uuid": {"uuid": "495ea3f8-e67f-46a0-84bd-a230a4b7067d"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "RT2/RT4/1"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "RT2"}}, "endpoint_uuid": {"uuid": "eth-rt4-1"}}, + {"device_id": {"device_uuid": {"uuid": "RT4"}}, "endpoint_uuid": {"uuid": "eth-rt2-1"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "RT2/RT4/2"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "RT2"}}, "endpoint_uuid": {"uuid": "eth-rt4-2"}}, + {"device_id": {"device_uuid": {"uuid": "RT4"}}, "endpoint_uuid": {"uuid": "eth-rt2-2"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "RT3/RT5/1"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "RT3"}}, "endpoint_uuid": {"uuid": "eth-rt5-1"}}, + {"device_id": {"device_uuid": {"uuid": "RT5"}}, "endpoint_uuid": {"uuid": "eth-rt3-1"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "RT3/RT5/2"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "RT3"}}, "endpoint_uuid": {"uuid": "eth-rt5-2"}}, + {"device_id": {"device_uuid": {"uuid": "RT5"}}, "endpoint_uuid": {"uuid": "eth-rt3-2"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "RT4/RT5"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "RT4"}}, "endpoint_uuid": {"uuid": "eth-rt5"}}, + {"device_id": {"device_uuid": {"uuid": "RT5"}}, "endpoint_uuid": {"uuid": "eth-rt4"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "RT4/RT6"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "RT4"}}, "endpoint_uuid": {"uuid": "eth-rt6"}}, + {"device_id": {"device_uuid": {"uuid": "RT6"}}, "endpoint_uuid": {"uuid": "eth-rt4"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "RT5/RT6"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "RT5"}}, "endpoint_uuid": {"uuid": "eth-rt6"}}, + {"device_id": {"device_uuid": {"uuid": "RT6"}}, "endpoint_uuid": {"uuid": "eth-rt5"}} + ] + } + ] +}