Skip to content
Snippets Groups Projects
Commit 77a78c72 authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

Merge branch 'develop' of https://labs.etsi.org/rep/tfs/controller into fix/tid-openconfig-acls

parents 623bf640 280a4771
No related branches found
No related tags found
2 merge requests!142Release TeraFlowSDN 2.1,!139Fixes on OpenConfig and ACLs
Showing
with 2894 additions and 0 deletions
%%%-----------------------------------------------------------------------------
%% Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%%-----------------------------------------------------------------------------
-module(epce_server).
-behaviour(gen_server).
%%% INCLUDES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-include_lib("kernel/include/logger.hrl").
-include_lib("pcep_server/include/pcep_server.hrl").
%%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% API Functions
-export([start_link/0]).
-export([get_flows/0]).
-export([update_flow/2]).
-export([initiate_flow/4]).
% Handler Functions
-export([session_opened/3]).
-export([flow_added/1]).
-export([flow_initiated/1]).
-export([request_route/1]).
-export([flow_status_changed/2]).
% 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]).
%%% MACROS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-define(LARGE_TIMEOUT, infinity).
%%% RECORDS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-record(sess, {
id,
caps,
monref,
pid
}).
-record(state, {
bouncer,
sessions = #{},
sess_pids = #{},
flows = #{}
}).
%%% API FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
get_flows() ->
gen_server:call(?MODULE, get_flows).
update_flow(FlowId, LabelStack) ->
gen_server:call(?MODULE, {update_flow, FlowId, LabelStack}).
initiate_flow(Name, From, To, BindingLabel) ->
gen_server:call(?MODULE, {initiate_flow, Name, From, To,
BindingLabel}, ?LARGE_TIMEOUT).
%%% HANDLER FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
session_opened(Id, Caps, Pid) ->
gen_server:call(?MODULE, {session_opened, Id, Caps, Pid}).
flow_added(Flow) ->
gen_server:call(?MODULE, {flow_added, Flow}).
flow_initiated(Flow) ->
gen_server:call(?MODULE, {flow_initiated, Flow}).
request_route(RouteReq) ->
gen_server:call(?MODULE, {request_route, RouteReq}).
flow_status_changed(FlowId, NewStatus) ->
gen_server:call(?MODULE, {flow_status_changed, FlowId, NewStatus}).
%%% BEHAVIOUR gen_server FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
init([]) ->
{ok, bouncer_start(#state{})}.
handle_call(get_flows, _From, #state{flows = Flows} = State) ->
{reply, {ok, Flows}, State};
handle_call({update_flow, FlowId, Labels}, From,
#state{flows = Flows, sessions = SessMap} = State) ->
case maps:find(FlowId, Flows) of
error -> {reply, {error, flow_not_found}, State};
{ok, #{owner := Owner, route := #{} = R}} ->
case maps:find(Owner, SessMap) of
error -> {reply, {error, session_not_found}, State};
{ok, #sess{pid = Pid}} ->
#{source := S, destination := D, constraints := C} = R,
ReqRoute = routereq_from_labels(S, D, C, Labels),
session_update_flow(State, Pid, FlowId, ReqRoute, From),
{noreply, State}
end
end;
handle_call({initiate_flow, Name, FromKey, ToKey, Binding}, From,
#state{sessions = SessMap} = State) ->
case {pcc_address(FromKey), pcc_address(ToKey)} of
{{error, Reason}, _} ->
{reply, {error, Reason}, State};
{_, {error, Reason}} ->
{reply, {error, Reason}, State};
{{ok, FromAddr}, {ok, ToAddr}} ->
case maps:find(FromAddr, SessMap) of
error -> {reply, {error, session_not_found}, State};
{ok, #sess{pid = Pid}} ->
case compute_path(FromAddr, ToAddr) of
{error, Reason} ->
{reply, {error, Reason}, State};
{ok, Labels} ->
InitRoute = routeinit_from_labels(Name, FromAddr,
ToAddr, [], Binding, Labels),
session_initiate_flow(State, Pid, InitRoute, From),
{noreply, State}
end
end
end;
handle_call({session_opened, Id, Caps, Pid}, _From,
#state{sessions = SessMap, sess_pids = SessPids} = State) ->
logger:debug("Session with capabilities ~w open to ~w", [Caps, Id]),
case maps:find(Id, SessMap) of
{ok, _} -> {reply, {error, already_opened}, State};
error ->
MonRef = erlang:monitor(process, Pid),
SessRec = #sess{id = Id, caps = Caps, monref = MonRef, pid = Pid},
{reply, ok, State#state{
sessions = SessMap#{Id => SessRec},
sess_pids = SessPids#{Pid => SessRec}
}}
end;
handle_call({flow_added, #{id := Id, route := Route} = Flow},
_From, #state{flows = Flows} = State) ->
logger:debug("Flow ~w with route ~w added", [Id, route_to_labels(Route)]),
{reply, ok, State#state{flows = Flows#{Id => Flow}}};
handle_call({flow_initiated, #{id := Id, route := Route} = Flow},
_From, #state{flows = Flows} = State) ->
logger:debug("Flow ~w with route ~p initiated",
[Id, route_to_labels(Route)]),
{reply, ok, State#state{flows = Flows#{Id => Flow}}};
handle_call({request_route, RouteReq}, _From, State) ->
logger:info("Route from ~w to ~w requested",
[maps:get(source, RouteReq), maps:get(destination, RouteReq)]),
#{source := S, destination := D, constraints := C} = RouteReq,
case compute_path(S, D) of
{error, _Reason} = Error ->
{reply, Error, State};
{ok, Labels} ->
Route = routereq_from_labels(S, D, C, Labels),
{reply, {ok, Route}, State}
end;
handle_call({flow_status_changed, FlowId, NewStatus}, _From,
#state{flows = Flows} = State) ->
logger:info("Flow ~w status changed to ~w", [FlowId, NewStatus]),
Flow = maps:get(FlowId, Flows),
{reply, ok, State#state{
flows = maps:put(FlowId, Flow#{status := NewStatus}, Flows)}};
handle_call(Request, From, State) ->
logger:warning("Unexpected call from ~w: ~p", [From, Request]),
{reply, {error, unexpected_call}, State}.
handle_cast(Request, State) ->
logger:warning("Unexpected cast: ~p", [Request]),
{noreply, State}.
handle_continue(_Continue, State) ->
{noreply, State}.
handle_info({flow_updated, FlowId, NewRoute, From},
#state{flows = Flows} = State) ->
logger:info("Flow ~w updated to ~w", [FlowId, route_to_labels(NewRoute)]),
case maps:find(FlowId, Flows) of
error -> {noreply, State};
{ok, Flow} ->
Flows2 = Flows#{FlowId => Flow#{route => NewRoute}},
gen_server:reply(From, ok),
{noreply, State#state{flows = Flows2}}
end;
handle_info({flow_update_error, FlowId, Reason, From}, State) ->
logger:error("Flow ~w updated error: ~p", [FlowId, Reason]),
gen_server:reply(From, {error, Reason}),
{noreply, State};
handle_info({flow_initiated, #{id := FlowId, route := Route} = Flow, From},
#state{flows = Flows} = State) ->
logger:info("Flow ~w initiated to ~p",
[FlowId, route_to_labels(Route)]),
gen_server:reply(From, {ok, FlowId}),
{noreply, State#state{flows = Flows#{FlowId => Flow}}};
handle_info({flow_init_error, Reason, From}, State) ->
logger:error("Flow initialisation error: ~p", [Reason]),
gen_server:reply(From, {error, Reason}),
{noreply, State};
handle_info({'DOWN', MonRef, process, Pid, _Reason},
#state{sessions = SessMap, sess_pids = PidMap} = State) ->
case maps:take(Pid, PidMap) of
{#sess{id = Id, monref = MonRef}, PidMap2} ->
SessMap2 = maps:remove(Id, SessMap),
%TODO: Do something about the flows from this session ?
{noreply, State#state{
sessions = SessMap2,
sess_pids = PidMap2
}};
_X ->
{noreply, State}
end;
handle_info(Info, State) ->
logger:warning("Unexpected message: ~p", [Info]),
{noreply, State}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
terminate(_Reason, _State) ->
ok.
%%% INTERNAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
ted_index(Id) when is_binary(Id) -> id;
ted_index({_, _, _, _}) -> pcc_address.
pcc_address(Key) ->
case epce_ted:lookup(ted_index(Key), Key) of
{error, Reason} ->
logger:warning("Failed to find a PCC address for router ~p: ~p",
[Key, Reason]),
{error, router_not_found};
{ok, #{pcc_address := Addr}} ->
{ok, Addr}
end.
compute_path(From, To) when is_binary(From), is_binary(To) ->
compute_path_result(From, To, epce_ted:compute_path(id, From, To));
compute_path({_, _, _, _} = From, {_, _, _, _} = To) ->
compute_path_result(From, To, epce_ted:compute_path(pcc_address, From, To)).
compute_path_result(From, To, {error, Reason}) ->
logger:warning("Failed to find a route from ~p to ~p: ~p",
[From, To, Reason]),
{error, route_not_found};
compute_path_result(From, To, {ok, Devices}) ->
Labels = tl([L || #{mpls_label := L} <- Devices, L =/= undefined]),
logger:debug("Route from ~p to ~p: ~p", [From, To, Labels]),
{ok, Labels}.
routereq_from_labels(Source, Destination, Constraints, Labels) ->
#{
source => Source,
destination => Destination,
constraints => Constraints,
steps => [
#{
is_loose => false,
nai_type => absent,
sid => #mpls_stack_entry{label = L}
}
|| L <- Labels
]
}.
routeinit_from_labels(Name, Source, Destination, Constraints, Binding, Labels) ->
Route = routereq_from_labels(Source, Destination, Constraints, Labels),
Route#{
name => Name,
binding_label => Binding
}.
route_to_labels(#{steps := Steps}) ->
[Sid#mpls_stack_entry.label || #{sid := Sid} <- Steps].
%-- Session Interface Functions ------------------------------------------------
session_update_flow(#state{bouncer = Pid}, SessPid, FlowId, Route, Args) ->
Pid ! {update_flow, SessPid, FlowId, Route, Args}.
session_initiate_flow(#state{bouncer = Pid}, SessPid, Route, Args) ->
Pid ! {initiate_flow, SessPid, Route, Args}.
bouncer_start(#state{bouncer = undefined} = State) ->
Self = self(),
Pid = erlang:spawn_link(fun() ->
bouncer_bootstrap(Self)
end),
receive bouncer_ready -> ok end,
State#state{bouncer = Pid}.
bouncer_bootstrap(Parent) ->
Parent ! bouncer_ready,
bouncer_loop(Parent).
bouncer_loop(Parent) ->
receive
{update_flow, SessPid, FlowId, ReqRoute, Args} ->
case pcep_server_session:update_flow(SessPid, FlowId, ReqRoute) of
{ok, NewRoute} ->
Parent ! {flow_updated, FlowId, NewRoute, Args},
bouncer_loop(Parent);
{error, Reason} ->
Parent ! {flow_update_error, FlowId, Reason, Args},
bouncer_loop(Parent)
end;
{initiate_flow, SessPid, InitRoute, Args} ->
case pcep_server_session:initiate_flow(SessPid, InitRoute) of
{ok, Flow} ->
Parent ! {flow_initiated, Flow, Args},
bouncer_loop(Parent);
{error, Reason} ->
Parent ! {flow_init_error, Reason, Args},
bouncer_loop(Parent)
end
end.
%%%-----------------------------------------------------------------------------
%% Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%%-----------------------------------------------------------------------------
-module(epce_sup).
-behaviour(supervisor).
%%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Behaviour supervisor functions
-export([start_link/0]).
-export([init/1]).
%%% MACROS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-define(TED_WORKER, epce_ted).
-define(PCE_WORKER, epce_server).
%%% BEHAVIOUR SUPERVISOR FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
SupFlags = #{
strategy => one_for_all,
intensity => 0,
period => 1
},
TEDSpec = #{
id => ?TED_WORKER,
start => {?TED_WORKER, start_link, []},
restart => permanent,
shutdown => brutal_kill
},
ServerSpec = #{
id => ?PCE_WORKER,
start => {?PCE_WORKER, start_link, []},
restart => permanent,
shutdown => brutal_kill
},
{ok, {SupFlags, [TEDSpec, ServerSpec]}}.
%%%-----------------------------------------------------------------------------
%% Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%%-----------------------------------------------------------------------------
-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([lookup/2]).
-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}}.
lookup(Index, Key)
when Index =:= id; Index =:= pcc_address ->
gen_server:call(?MODULE, {lookup, Index, Key});
lookup(Index, _Key) ->
{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({lookup, Index, Key}, _From, #state{graph = G} = State) ->
case as_ids(State, Index, [Key]) of
{ok, [Id]} ->
{reply, do_lookup(G, Id), 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.
do_lookup(G, Id) ->
case digraph:vertex(G, Id) of
{_, Info} -> {ok, Info};
false -> {error, not_found}
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.
%% Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
{application, tfte,
[{description, "Teraflow Traffic Engineering Service"},
{vsn, "0.1.0"},
{registered, []},
{mod, {tfte_app, []}},
{applications,
[kernel,
stdlib,
tfpb,
jsx,
epce
]},
{env,[]},
{modules, []},
{licenses, ["Apache 2.0"]},
{links, []}
]}.
%%%-----------------------------------------------------------------------------
%% Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% @doc tfte public API
%% @end
%%%-----------------------------------------------------------------------------
-module(tfte_app).
-behaviour(application).
%%% INCLUDES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-include_lib("kernel/include/logger.hrl").
%%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Behaviour application callback functions
-export([start/2, stop/1]).
%%% BEHAVIOUR applicaation CALLBACK FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
start(_StartType, _StartArgs) ->
case tfte_sup:start_link() of
{ok, Pid} ->
add_services(),
{ok, Pid};
Other ->
Other
end.
stop(_State) ->
ok.
%%% INTERNAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
add_services() ->
case application:get_env(tfte, services) of
{ok, Services} -> add_services(Services);
_ -> ok
end.
add_services([]) -> ok;
add_services([{Name, EndpointsSpecs, GrpcOpts} | Rest]) ->
try resolve_endpoints(Name, EndpointsSpecs, []) of
Endpoints ->
case grpcbox_channel_sup:start_child(Name, Endpoints, GrpcOpts) of
{ok, _Pid} ->
?LOG_INFO("GRPC channel to ~s service started", [Name]),
ok;
{error, Reason} ->
?LOG_WARNING("GRPC channel to ~s service failed to start: ~p",
[Name, Reason]),
ok
end
catch
throw:{Name, Reason, Extra} ->
?LOG_WARNING("Failed to resolve ~s service configuration: ~s ~p",
[Name, Reason, Extra])
end,
add_services(Rest).
resolve_endpoints(_Name, [], Acc) ->
lists:reverse(Acc);
resolve_endpoints(Name, [{Transport, HostSpec, PortSpec, SslOpts} | Rest], Acc) ->
Acc2 = [{Transport, resolve_host_spec(Name, HostSpec),
resolve_port_spec(Name, PortSpec), SslOpts} | Acc],
resolve_endpoints(Name, Rest, Acc2).
resolve_host_spec(_Name, Hostname) when is_list(Hostname) -> Hostname;
resolve_host_spec(Name, {env, Key}) when is_list(Key) ->
try os:getenv(Key) of
false -> throw({Name, service_hostname_not_found, Key});
Hostname -> Hostname
catch
_:Reason ->
throw({Name, service_hostname_error, Reason})
end.
resolve_port_spec(_Name, Port) when is_integer(Port) -> Port;
resolve_port_spec(Name, {env, Key}) when is_list(Key) ->
try os:getenv(Key) of
false -> throw({Name, service_port_not_found, Key});
PortStr ->
try list_to_integer(PortStr) of
Port -> Port
catch
_:Reason ->
throw({Name, service_port_error, Reason})
end
catch
_:Reason ->
throw({Name, service_port_error, Reason})
end.
%%%-----------------------------------------------------------------------------
%% Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%%-----------------------------------------------------------------------------
-module(tfte_context).
-behaviour(gen_statem).
%%% INCLUDES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-include_lib("kernel/include/logger.hrl").
%%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% API functions
-export([start_link/0]).
-export([is_ready/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({local, ?MODULE}, ?MODULE, [], []).
is_ready() ->
case whereis(?MODULE) of
undefined -> false;
_ -> gen_statem:call(?MODULE, is_ready)
end.
%%% 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({call, _From}, is_ready, State, _Data) ->
{keep_state_and_data, [{reply, State =:= ready}]};
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
%%%-----------------------------------------------------------------------------
%% Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%%-----------------------------------------------------------------------------
-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]).
-export([request_lsp/1]).
-export([delete_lsp/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, {
services = #{}
}).
%%% MACROS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% API FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
start_link() ->
gen_statem:start_link({local, ?MODULE}, ?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}).
request_lsp(ServiceMap) ->
gen_statem:call(?MODULE, {request_lsp, ServiceMap}).
delete_lsp(ServiceId) ->
gen_statem:call(?MODULE, {delete_lsp, ServiceId}).
%%% BEHAVIOUR gen_statem FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
init([]) ->
?LOG_INFO("Starting server...", []),
case tfte_context:is_ready() of
false -> {ok, wait_context, #data{}};
true -> {ok, ready, #data{}}
end.
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_context, 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;
handle_event({call, From}, {request_lsp, ServiceMap}, ready, Data) ->
?LOG_DEBUG("Teraflow service ~s requested its LSPs",
[format_service_id(maps:get(service_id, ServiceMap, undefined))]),
{Result, Data2} = do_request_lsp(Data, ServiceMap),
{keep_state, Data2, [{reply, From, Result}]};
handle_event({call, From}, {delete_lsp, ServiceId}, ready, Data) ->
?LOG_DEBUG("Teraflow service ~s delete its LSPs",
[format_service_id(ServiceId)]),
{Result, Data2} = do_delete_lsp(Data, ServiceId),
{keep_state, Data2, [{reply, From, Result}]};
%-- ANY STATE ------------------------------------------------------------------
handle_event(EventType, EventContent, State, _Data) ->
?LOG_WARNING("Unexpected tfte_server ~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 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
format_service_id(undefined) -> <<"undefined">>;
format_service_id(#{context_id := #{context_uuid := #{uuid := ContextName}},
service_uuid := #{uuid := ServiceUUID}}) ->
iolist_to_binary(io_lib:format("~s:~s", [ContextName, ServiceUUID])).
do_request_lsp(#data{services = Services} = Data,
#{service_type := 'SERVICETYPE_TE'} = ServiceMap) ->
try
#{service_config := Config,
service_endpoint_ids := Endpoints,
service_id := ServiceId} = ServiceMap,
#{<<"binding_label">> := BindingLabel1, <<"symbolic_name">> := SymbolicName1}
= tfte_util:custom_config(Config, <<"/lsp-fw">>),
#{<<"binding_label">> := BindingLabel2, <<"symbolic_name">> := SymbolicName2}
= tfte_util:custom_config(Config, <<"/lsp-bw">>),
[#{device_id := #{device_uuid := #{uuid := Id1}}},
#{device_id := #{device_uuid := #{uuid := Id2}}}] = Endpoints,
case epce_server:initiate_flow(SymbolicName1, Id1, Id2, BindingLabel1) of
{error, Reason} ->
?LOG_ERROR("Error while setting up service ~s forward LSP: ~p",
[format_service_id(ServiceId), Reason]),
{{error, Reason}, Data};
{ok, ForwardFlow} ->
case epce_server:initiate_flow(SymbolicName2, Id2, Id1, BindingLabel2) of
{error, Reason} ->
?LOG_ERROR("Error while setting up service ~s backward LSP: ~p",
[format_service_id(ServiceId), Reason]),
%TODO: Cleanup forward flow ?
{{error, Reason}, Data};
{ok, BackwardFlow} ->
ServiceData = {ServiceMap, ForwardFlow, BackwardFlow},
Services2 = Services#{ServiceId => ServiceData},
Data2 = Data#data{services = Services2},
{{ok, 'SERVICESTATUS_ACTIVE'}, Data2}
end
end
catch T:E:S ->
?LOG_ERROR("Error while requesintg LSP: ~p:~p", [T, E]),
?LOG_ERROR("Stacktrace: ~p", [S]),
{{error, internal_error}, Data}
end;
do_request_lsp(Data, ServiceMap) ->
?LOG_ERROR("Invalid arguments to RequestLSP call: ~p", [ServiceMap]),
{{error, badarg}, Data}.
do_delete_lsp(Data, ServiceId) ->
?LOG_INFO("LSP DELETION REQUESTED ~p", [ServiceId]),
{{error, not_implemented}, Data}.
\ No newline at end of file
%%%-----------------------------------------------------------------------------
%% Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% @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}}.
%%%-----------------------------------------------------------------------------
%% Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% @doc tfte top level supervisor.
%% @end
%%%-----------------------------------------------------------------------------
-module(tfte_sup).
-behaviour(supervisor).
%%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% API Functions
-export([start_link/0]).
% Behaviour supervisor callback functions
-export([init/1]).
%%% MACROS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-define(SERVER, ?MODULE).
-define(ROOT_SERVER, tfte_server).
%%% API FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
start_link() ->
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
%%% BEHAVIOUR supervisor CALLBACK FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
init([]) ->
SupFlags = #{strategy => one_for_all,
intensity => 0,
period => 1},
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}}.
%%%-----------------------------------------------------------------------------
%% Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%%-----------------------------------------------------------------------------
-module(tfte_te_service).
-behaviour(te_te_service_bhvr).
%%% INCLUDES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-include_lib("grpcbox/include/grpcbox.hrl").
-include_lib("kernel/include/logger.hrl").
%%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Behaviour te_te_service_bhvr callback functions
-export([request_lsp/2]).
-export([update_lsp/2]).
-export([delete_lsp/2]).
%%% BEHAVIOUR te_te_service_bhvr CALLBACK FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%
request_lsp(Ctx, Service) ->
?LOG_INFO("Requesting LSP: ~p", [Service]),
try tfte_server:request_lsp(Service) of
{ok, Status} ->
{ok, #{service_status => Status}, Ctx};
{error, Reason} ->
?LOG_INFO("Error while requesting LSP: ~p", [Reason]),
{ok, #{service_status => 'SERVICESTATUS_UNDEFINED'}, Ctx}
catch E:R:S ->
?LOG_ERROR("Error while requesting LSP: ~p:~p ~p", [E, R, S]),
{ok, #{service_status => 'SERVICESTATUS_UNDEFINED'}, Ctx}
end.
update_lsp(_Ctx, _ServiceId) ->
{error, {?GRPC_STATUS_UNIMPLEMENTED, <<"Not yet implemented">>},
#{headers => #{}, trailers => #{}}}.
delete_lsp(Ctx, ServiceId) ->
?LOG_ERROR("Deleting LSP: ~p", [ServiceId]),
try tfte_server:delete_lsp(ServiceId) of
{ok, Status} ->
{ok, Status, Ctx};
{error, Reason} ->
?LOG_INFO("Error while deleting LSP: ~p", [Reason]),
{ok, #{service_status => 'SERVICESTATUS_UNDEFINED'}, Ctx}
catch E:R:S ->
?LOG_ERROR("Error while deleting LSP: ~p:~p ~p", [E, R, S]),
{ok, #{service_status => 'SERVICESTATUS_UNDEFINED'}, Ctx}
end.
%%%-----------------------------------------------------------------------------
%% Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%%-----------------------------------------------------------------------------
-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(),
names = #{} :: map()
}).
%%% MACROS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-define(SUBSCRIBE_RETRY_TIMEOUT, 1000).
-define(RETRIEVE_RETRY_TIMEOUT, 10000).
%%% API FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
start_link() ->
gen_statem:start_link({local, ?MODULE}, ?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, #{device_ids := Devices, link_ids := Links } = Topology} ->
case {length(Devices), length(Links)} of
{D, L} when D =:= 0; L =:= 0 ->
?LOG_WARNING("Got topology, but there is missing devices or links", []),
{keep_state_and_data, [{state_timeout, 1000, do_retrieve}]};
_ ->
?LOG_DEBUG("Got topology: ~p", [Topology]),
{next_state, subscribe, Data#data{obj = Topology}}
end
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) ->
update_devices(Data, OldDevices, #{}, DeviceIds, Events).
update_devices(Data, OldDevices, NewDevices, [], Events) ->
#data{names = Names} = Data,
Events2 = [{device_deleted, maps:get(I, Names, undefined)}
|| I <- maps:keys(OldDevices)] ++ Events,
{Data#data{devices = NewDevices}, Events2};
update_devices(Data, OldDevices, NewDevices, [GivenId | Rest], Events) ->
case get_device(GivenId) of
error -> throw({device_retrieval_error, GivenId});
{ok, Device} ->
Device2 = #{id := Id, real_id := RealId} = post_process_device(Device),
#data{names = Names} = Data,
Data2 = Data#data{names = Names#{RealId => Id}},
NewDevices2 = NewDevices#{Id => Device},
case maps:take(Id, OldDevices) of
error ->
% New device
Events2 = [{device_added, Id, Device2} | Events],
update_devices(Data2, OldDevices, NewDevices2, Rest, Events2);
{Device, OldDevices2} ->
% Device did not change
update_devices(Data2, OldDevices2, NewDevices2, Rest, Events);
{_OldDevice, OldDevices2} ->
% Device changed
Events2 = [{device_updated, Id, Device2} | Events],
update_devices(Data2, OldDevices2, NewDevices2, Rest, Events2)
end
end.
update_links(#data{links = OldLinks} = Data, LinksIds, Events) ->
update_links(Data, OldLinks, #{}, LinksIds, Events).
update_links(Data, OldLinks, NewLinks, [], Events) ->
Events2 = [{link_deleted, post_process_link_id(I)}
|| I <- maps:keys(OldLinks)] ++ Events,
{Data#data{links = NewLinks}, Events2};
update_links(Data, 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(Data, Link),
NewLinks2 = NewLinks#{Id => Link},
case maps:take(Id, OldLinks) of
error ->
% New Link
Events2 = [{link_added, Id2, Link2} | Events],
update_links(Data, OldLinks, NewLinks2, Rest, Events2);
{Link, OldLinks2} ->
% Link did not change
update_links(Data, OldLinks2, NewLinks2, Rest, Events);
{_OldLink, OldLinks2} ->
% Link changed
Events2 = [{link_updated, Id2, Link2} | Events],
update_links(Data, OldLinks2, NewLinks2, Rest, Events2)
end
end.
post_process_device(#{device_id := Id, name := Name} = Device) ->
#{id => Name,
real_id => Id,
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_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) ->
try device_config_value(<<"/te_data">>, Device) of
Map when is_map(Map) -> maps:get(<<"mpls_label">>, Map, undefined);
_ -> undefined
catch error:badarg -> undefined
end.
device_pcc_address(Device) ->
try device_config_value(<<"/te_data">>, Device) of
Map when is_map(Map) ->
case maps:get(<<"pcc_address">>, Map, undefined) of
AddressBin ->
case inet_parse:address(binary_to_list(AddressBin)) of
{ok, Address} -> Address;
{error,einval} -> undefined
end
end;
_ -> undefined
catch
error:badarg -> undefined
end.
device_config_value(Key, #{device_config := Config}) ->
tfte_util:custom_config(Config, Key).
device_endpoints(Device) ->
device_endpoints(Device, []).
device_endpoints(#{device_endpoints := Endpoints}, Acc) ->
device_endpoints(Endpoints, Acc);
device_endpoints([], Acc) ->
lists:reverse(Acc);
device_endpoints([#{name := Name} | Rest], Acc) ->
device_endpoints(Rest, [Name | Acc]).
post_process_link_id(#{link_uuid := #{uuid := Name}}) ->
Name.
post_process_link(Data, Link) ->
#{id => link_id(Link),
endpoints => link_endpoints(Data, Link)}.
link_id(#{link_id := Id}) ->
post_process_link_id(Id).
link_endpoints(Data, Link) ->
link_endpoints(Data, Link, []).
link_endpoints(Data, #{link_endpoint_ids := Endpoints}, Acc) ->
link_endpoints(Data, Endpoints, Acc);
link_endpoints(_Data, [], Acc) ->
lists:reverse(Acc);
link_endpoints(Data, [#{device_id := RealId,
endpoint_uuid := #{uuid := EndpointName}} | Rest], Acc) ->
#data{names = Names} = Data,
Endpoint = #{
device => maps:get(RealId, Names, undefined),
endpoint => EndpointName
},
link_endpoints(Data, 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.
%%%-----------------------------------------------------------------------------
%% Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%%-----------------------------------------------------------------------------
-module(tfte_util).
%%% INCLUDES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-include_lib("kernel/include/logger.hrl").
%%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% API functions
-export([custom_config/2]).
%%% API FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
custom_config(#{config_rules := Rules}, Key) ->
custom_config(Rules, Key);
custom_config([], _Key) ->
undefined;
custom_config([#{action := 'CONFIGACTION_SET',
config_rule := {custom, Rule}} | Rest], Key) ->
case Rule of
#{resource_key := Key, resource_value := Value} -> jsx:decode(Value);
_ -> custom_config(Rest, Key)
end;
custom_config([_Rule | Rest], Key) ->
custom_config(Rest, Key).
% Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
%
% Licensed under the Apache License, Version 2.0 (the "License");
% you may not use this file except in compliance with the License.
% You may obtain a copy of the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS,
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
% See the License for the specific language governing permissions and
% limitations under the License.
[
{tfte, [
{services, [
{te, [{http, "localhost", 10030, []}], #{}}
]}
]},
{pcep_server, [
{handler, {epce_pcep_server_handler, []}}
]},
{epce, [
]},
{grpcbox, [
{servers, [#{
grpc_opts => #{
service_protos => [te_pb, grpcbox_health_pb, grpcbox_reflection_pb],
%client_cert_dir => "",
services => #{
'te.TEService' => tfte_te_service,
'grpc.health.v1.Health' => grpcbox_health_service,
'grpc.reflection.v1alpha.ServerReflection' => grpcbox_reflection_service
}
},
transport_opts => #{
ssl => false
%keyfile => "",
%certfile => "",
%cacertfile => ""
},
listen_opts => #{
port => 10030,
ip => {0,0,0,0}
},
pool_opts => #{
size => 10
},
server_opts => #{
header_table_size => 4096,
enable_push => 1,
max_concurrent_streams => unlimited,
initial_window_size => 65535,
max_frame_size => 16384,
max_header_list_size => unlimited
}
}]}
]},
{kernel, [
{logger_level, debug},
{logger, [
{handler, default, logger_std_h, #{
level => debug,
filter_default => log,
config => #{type => standard_io},
formatter => {logger_formatter, #{
legacy_header => false,
single_line => true
}}
}}
]}
]}
].
\ No newline at end of file
% Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
%
% Licensed under the Apache License, Version 2.0 (the "License");
% you may not use this file except in compliance with the License.
% You may obtain a copy of the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS,
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
% See the License for the specific language governing permissions and
% limitations under the License.
[
{tfte, [
{context, <<"admin">>},
{topology, <<"admin">>},
{services, [
{te, [
{http, {env, "TESERVICE_SERVICE_HOST"}, {env, "TESERVICE_SERVICE_PORT_GRPC"}, []}
], #{}},
{service, [
{http, {env, "SERVICESERVICE_SERVICE_HOST"}, {env, "SERVICESERVICE_SERVICE_PORT_GRPC"}, []}
], #{}},
{monitoring, [
{http, {env, "MONITORINGSERVICE_SERVICE_HOST"}, {env, "MONITORINGSERVICE_SERVICE_PORT_GRPC"}, []}
], #{}},
{compute, [
{http, {env, "COMPUTESERVICE_SERVICE_HOST"}, {env, "COMPUTESERVICE_SERVICE_PORT_GRPC"}, []}
], #{}},
{device, [
{http, {env, "DEVICESERVICE_SERVICE_HOST"}, {env, "DEVICESERVICE_SERVICE_PORT_GRPC"}, []}
], #{}},
{context, [
{http, {env, "CONTEXTSERVICE_SERVICE_HOST"}, {env, "CONTEXTSERVICE_SERVICE_PORT_GRPC"}, []}
], #{}},
{automation, [
{http, {env, "AUTOMATIONSERVICE_SERVICE_HOST"}, {env, "AUTOMATIONSERVICE_SERVICE_PORT_GRPC"}, []}
], #{}}
]}
]},
{pcep_server, [
{handler, {epce_pcep_server_handler, []}}
]},
{epce, [
]},
{grpcbox, [
{servers, [#{
grpc_opts => #{
service_protos => [te_pb, grpcbox_health_pb, grpcbox_reflection_pb],
%client_cert_dir => "",
services => #{
'te.TEService' => tfte_te_service,
'grpc.health.v1.Health' => grpcbox_health_service,
'grpc.reflection.v1alpha.ServerReflection' => grpcbox_reflection_service
}
},
transport_opts => #{
ssl => false
%keyfile => "",
%certfile => "",
%cacertfile => ""
},
listen_opts => #{
port => 10030,
ip => {0,0,0,0}
},
pool_opts => #{
size => 10
},
server_opts => #{
header_table_size => 4096,
enable_push => 1,
max_concurrent_streams => unlimited,
initial_window_size => 65535,
max_frame_size => 16384,
max_header_list_size => unlimited
}
}]}
]},
{kernel, [
{logger_level, ${ERLANG_LOGGER_LEVEL}},
{logger, [
{handler, default, logger_std_h, #{
level => ${ERLANG_LOGGER_LEVEL},
filter_default => log,
config => #{type => standard_io},
formatter => {logger_formatter, #{
legacy_header => false,
single_line => true
}}
}}
]}
]}
].
\ No newline at end of file
+C multi_time_warp
+sbwt none
-name ${ERLANG_NODE_NAME}@${ERLANG_NODE_IP}
-setcookie ${ERLANG_COOKIE}
% Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
%
% Licensed under the Apache License, Version 2.0 (the "License");
% you may not use this file except in compliance with the License.
% You may obtain a copy of the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS,
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
% See the License for the specific language governing permissions and
% limitations under the License.
{erl_opts, [debug_info]}.
{deps, [
grpcbox,
jsx,
{pcep_server, {git, "https://github.com/stritzinger/pcep_server.git", {branch, "master"}}}
]}.
{shell, [
{config, "config/dev.config"},
{apps, [epce, tfte, tfpb, grpcbox]}
]}.
{project_app_dirs, ["apps/*", "../../proto/src/erlang"]}.
{relx, [
{release, {tfte, "1.0.0"}, [
runtime_tools,
epce,
grpcbox,
jsx,
tfpb,
tfte
]},
{vm_args_src, "config/vm.args.src"},
{sys_config_src, "config/sys.config.src"},
{dev_mode, true},
{include_erts, false},
{extended_start_script, true}
]}.
{profiles, [
{prod, [
{relx, [
{dev_mode, false},
{include_erts, true},
{include_src, false}
]}
]}
]}.
{"1.2.0",
[{<<"acceptor_pool">>,{pkg,<<"acceptor_pool">>,<<"1.0.0">>},1},
{<<"chatterbox">>,{pkg,<<"ts_chatterbox">>,<<"0.12.0">>},1},
{<<"codec_sequencer">>,
{git,"https://github.com/stritzinger/codec_sequencer.git",
{ref,"fc8760894f7962ef1497bf6ce4247eb75db9d5ca"}},
2},
{<<"ctx">>,{pkg,<<"ctx">>,<<"0.6.0">>},1},
{<<"gproc">>,{pkg,<<"gproc">>,<<"0.8.0">>},1},
{<<"grpcbox">>,{pkg,<<"grpcbox">>,<<"0.15.0">>},0},
{<<"hpack">>,{pkg,<<"hpack_erl">>,<<"0.2.3">>},2},
{<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},0},
{<<"pcep_codec">>,
{git,"https://github.com/stritzinger/pcep_codec.git",
{ref,"ca5eb0822d9971ec4bcfb427a49b2e516081a126"}},
1},
{<<"pcep_server">>,
{git,"https://github.com/stritzinger/pcep_server.git",
{ref,"ea751fa807f4c1f5635f781431fe384610166b0a"}},
0},
{<<"ranch">>,{pkg,<<"ranch">>,<<"2.0.0">>},1}]}.
[
{pkg_hash,[
{<<"acceptor_pool">>, <<"43C20D2ACAE35F0C2BCD64F9D2BDE267E459F0F3FD23DAB26485BF518C281B21">>},
{<<"chatterbox">>, <<"4E54F199E15C0320B85372A24E35554A2CCFC4342E0B7CD8DAED9A04F9B8EF4A">>},
{<<"ctx">>, <<"8FF88B70E6400C4DF90142E7F130625B82086077A45364A78D208ED3ED53C7FE">>},
{<<"gproc">>, <<"CEA02C578589C61E5341FCE149EA36CCEF236CC2ECAC8691FBA408E7EA77EC2F">>},
{<<"grpcbox">>, <<"97C7126296A091602D372EBF5860A04F7BC795B45B33A984CAD2B8E362774FD8">>},
{<<"hpack">>, <<"17670F83FF984AE6CD74B1C456EDDE906D27FF013740EE4D9EFAA4F1BF999633">>},
{<<"jsx">>, <<"D12516BAA0BB23A59BB35DCCAF02A1BD08243FCBB9EFE24F2D9D056CCFF71268">>},
{<<"ranch">>, <<"FBF3D79661C071543256F9051CAF19D65DAA6DF1CF6824D8F37A49B19A66F703">>}]},
{pkg_hash_ext,[
{<<"acceptor_pool">>, <<"0CBCD83FDC8B9AD2EEE2067EF8B91A14858A5883CB7CD800E6FCD5803E158788">>},
{<<"chatterbox">>, <<"6478C161BC60244F41CD5847CC3ACCD26D997883E9F7FACD36FF24533B2FA579">>},
{<<"ctx">>, <<"A14ED2D1B67723DBEBBE423B28D7615EB0BDCBA6FF28F2D1F1B0A7E1D4AA5FC2">>},
{<<"gproc">>, <<"580ADAFA56463B75263EF5A5DF4C86AF321F68694E7786CB057FD805D1E2A7DE">>},
{<<"grpcbox">>, <<"161ABE9E17E7D1982EFA6488ADEAA13C3E847A07984A6E6B224E553368918647">>},
{<<"hpack">>, <<"06F580167C4B8B8A6429040DF36CC93BBA6D571FAEAEC1B28816523379CBB23A">>},
{<<"jsx">>, <<"0C5CC8FDC11B53CC25CF65AC6705AD39E54ECC56D1C22E4ADB8F5A53FB9427F3">>},
{<<"ranch">>, <<"C20A4840C7D6623C19812D3A7C828B2F1BD153EF0F124CB69C54FE51D8A42AE0">>}]}
].
# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ----- TeraFlowSDN ------------------------------------------------------------
# Set the URL of the internal MicroK8s Docker registry where the images will be uploaded to.
export TFS_REGISTRY_IMAGES="http://localhost:32000/tfs/"
# Set the list of components, separated by spaces, you want to build images for, and deploy.
#export TFS_COMPONENTS="context device pathcomp service slice compute webui load_generator"
export TFS_COMPONENTS="context device pathcomp service slice webui"
# Uncomment to activate Monitoring
#export TFS_COMPONENTS="${TFS_COMPONENTS} monitoring"
# Uncomment to activate Automation and Policy Manager
#export TFS_COMPONENTS="${TFS_COMPONENTS} automation policy"
# Uncomment to activate Optical CyberSecurity
#export TFS_COMPONENTS="${TFS_COMPONENTS} dbscanserving opticalattackmitigator opticalattackdetector opticalattackmanager"
# Uncomment to activate L3 CyberSecurity
#export TFS_COMPONENTS="${TFS_COMPONENTS} l3_attackmitigator l3_centralizedattackdetector"
# Uncomment to activate TE
export TFS_COMPONENTS="${TFS_COMPONENTS} te"
# Set the tag you want to use for your images.
export TFS_IMAGE_TAG="dev"
# Set the name of the Kubernetes namespace to deploy TFS to.
export TFS_K8S_NAMESPACE="tfs"
# Set additional manifest files to be applied after the deployment
export TFS_EXTRA_MANIFESTS="manifests/nginx_ingress_http.yaml"
# Uncomment to monitor performance of components
#export TFS_EXTRA_MANIFESTS="${TFS_EXTRA_MANIFESTS} manifests/servicemonitors.yaml"
# Uncomment when deploying Optical CyberSecurity
#export TFS_EXTRA_MANIFESTS="${TFS_EXTRA_MANIFESTS} manifests/cachingservice.yaml"
# Set the new Grafana admin password
export TFS_GRAFANA_PASSWORD="admin123+"
# Disable skip-build flag to rebuild the Docker images.
export TFS_SKIP_BUILD=""
# ----- CockroachDB ------------------------------------------------------------
# Set the namespace where CockroackDB will be deployed.
export CRDB_NAMESPACE="crdb"
# Set the external port CockroackDB Postgre SQL interface will be exposed to.
export CRDB_EXT_PORT_SQL="26257"
# Set the external port CockroackDB HTTP Mgmt GUI interface will be exposed to.
export CRDB_EXT_PORT_HTTP="8081"
# Set the database username to be used by Context.
export CRDB_USERNAME="tfs"
# Set the database user's password to be used by Context.
export CRDB_PASSWORD="tfs123"
# Set the database name to be used by Context.
export CRDB_DATABASE="tfs"
# Set CockroachDB installation mode to 'single'. This option is convenient for development and testing.
# See ./deploy/all.sh or ./deploy/crdb.sh for additional details
export CRDB_DEPLOY_MODE="single"
# Disable flag for dropping database, if it exists.
export CRDB_DROP_DATABASE_IF_EXISTS="YES"
# Disable flag for re-deploying CockroachDB from scratch.
export CRDB_REDEPLOY=""
# ----- NATS -------------------------------------------------------------------
# Set the namespace where NATS will be deployed.
export NATS_NAMESPACE="nats"
# Set the external port NATS Client interface will be exposed to.
export NATS_EXT_PORT_CLIENT="4222"
# Set the external port NATS HTTP Mgmt GUI interface will be exposed to.
export NATS_EXT_PORT_HTTP="8222"
# Disable flag for re-deploying NATS from scratch.
export NATS_REDEPLOY=""
# ----- QuestDB ----------------------------------------------------------------
# Set the namespace where QuestDB will be deployed.
export QDB_NAMESPACE="qdb"
# Set the external port QuestDB Postgre SQL interface will be exposed to.
export QDB_EXT_PORT_SQL="8812"
# Set the external port QuestDB Influx Line Protocol interface will be exposed to.
export QDB_EXT_PORT_ILP="9009"
# Set the external port QuestDB HTTP Mgmt GUI interface will be exposed to.
export QDB_EXT_PORT_HTTP="9000"
# Set the database username to be used for QuestDB.
export QDB_USERNAME="admin"
# Set the database user's password to be used for QuestDB.
export QDB_PASSWORD="quest"
# Set the table name to be used by Monitoring for KPIs.
export QDB_TABLE_MONITORING_KPIS="tfs_monitoring_kpis"
# Set the table name to be used by Slice for plotting groups.
export QDB_TABLE_SLICE_GROUPS="tfs_slice_groups"
# Disable flag for dropping tables if they exist.
export QDB_DROP_TABLES_IF_EXIST="YES"
# Disable flag for re-deploying QuestDB from scratch.
export QDB_REDEPLOY=""
# ----- K8s Observability ------------------------------------------------------
# Set the external port Prometheus Mgmt HTTP GUI interface will be exposed to.
export PROM_EXT_PORT_HTTP="9090"
# Set the external port Grafana HTTP Dashboards will be exposed to.
export GRAF_EXT_PORT_HTTP="3000"
# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Directory used for Netgen's operation.
# Default: "/tmp/netgen"
# netgen_runstatedir:
# Clean exit.
# Default: 'false'
# clean_exit:
# Valgrind parameters.
# Default: "--tool=memcheck"
# valgrind_params: "--tool=memcheck --leak-check=full --trace-children=yes"
# valgrind_params: "--tool=memcheck --leak-check=full"
# valgrind_params: "--tool=memcheck --leak-check=full --show-leak-kinds=all"
# valgrind_params: "--tool=callgrind --dump-instr=yes --collect-jumps=yes"
# Perf directory
# Default: [netgen_runstatedir]/perf
# perf_dir:
# Plugins configuration.
plugins:
frr:
# FRR's sysconfdir (--sysconfdir).
# Default: "/etc/frr"
# sysconfdir:
# FRR's localstatedir (--localstatedir).
# Default: "/var/run/frr"
# localstatedir:
# FRR's user (--enable-user).
# Default: "frr"
# user:
user: "root"
# FRR's group (--enable-group).
# Default: "frr"
# group:
group: "root"
# Directory to store FRR logs.
# Default: [netgen_runstatedir]/frrlogs
# logdir:
tcpdump:
# Directory to store tcpdump captures.
# Default: [netgen_runstatedir]/pcaps
# pcap_dir:
# Filter on which nodes tcpdump should run.
# Default: []
# whitelist:
# Filter on which nodes tcpdump should not run.
# Default: []
# blacklist:
tmux:
# Path of tmux script used to open a shell on all routers.
# Default: [netgen_runstatedir]/tmux.sh
# file:
# Panels per node.
# Default: 1
# panels-per-node:
bird:
# BIRD's sysconfdir (--sysconfdir).
# Default: "/etc/bird"
# sysconfdir:
# BIRD's localstatedir (--localstatedir).
# Default: "/var/run/bird"
# localstatedir:
# BIRD's user (--enable-user).
# Default: "bird"
# user:
# BIRD's group (--enable-group).
# Default: "bird"
# group:
# Directory to store BIRD logs.
# Default: [netgen_runstatedir]/birdlogs
# logdir:
bgpsimple:
# Path to bgp_simple script
# Default: "bgp_simple.pl"
# path:
iou:
# IOU working directory.
# Default: [netgen_runstatedir]/iou
# dir:
dynamips:
# dynamips working directory.
# Default: [netgen_runstatedir]/dynamips
# dir:
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment