From e27ae481589d67c5cf53585d8a26ee5590fed28e Mon Sep 17 00:00:00 2001
From: Sebastien Merle <s.merle@gmail.com>
Date: Fri, 30 Sep 2022 09:42:44 -0700
Subject: [PATCH] Add support for PCE initiated LSPs

---
 .../epce/src/epce_pcep_server_handler.erl     |  5 ++
 src/te/apps/epce/src/epce_server.erl          | 83 +++++++++++++++++--
 src/te/rebar.lock                             |  4 +-
 tutorial/2-6-netgen-topology.yml.template     | 15 +---
 tutorial/2-6-te-demo-start-testbed.sh         |  2 -
 tutorial/2-6-te-demo.md                       | 17 ++++
 6 files changed, 100 insertions(+), 26 deletions(-)

diff --git a/src/te/apps/epce/src/epce_pcep_server_handler.erl b/src/te/apps/epce/src/epce_pcep_server_handler.erl
index 210395885..df786bfc6 100644
--- a/src/te/apps/epce/src/epce_pcep_server_handler.erl
+++ b/src/te/apps/epce/src/epce_pcep_server_handler.erl
@@ -17,6 +17,7 @@
 -export([init/1]).
 -export([opened/4]).
 -export([flow_added/2]).
+-export([flow_initiated/2]).
 -export([ready/1]).
 -export([request_route/2]).
 -export([flow_delegated/2]).
@@ -49,6 +50,10 @@ flow_added(Flow, State) ->
         ok -> {ok, State}
     end.
 
+flow_initiated(Flow, State) ->
+    ok = epce_server:flow_initiated(Flow),
+    {ok, State}.
+
 ready(State) ->
     {ok, State}.
 
diff --git a/src/te/apps/epce/src/epce_server.erl b/src/te/apps/epce/src/epce_server.erl
index 11a1625ef..507255385 100644
--- a/src/te/apps/epce/src/epce_server.erl
+++ b/src/te/apps/epce/src/epce_server.erl
@@ -15,10 +15,12 @@
 -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]).
 
@@ -32,6 +34,11 @@
 -export([terminate/2]).
 
 
+%%% MACROS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-define(LARGE_TIMEOUT, 20000).
+
+
 %%% RECORDS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
 -record(sess, {
@@ -60,6 +67,10 @@ get_flows() ->
 update_flow(FlowId, LabelStack) ->
     gen_server:call(?MODULE, {update_flow, FlowId, LabelStack}).
 
+initiate_flow(Name, FromAddr, ToAddr, BindingLabel) ->
+    gen_server:call(?MODULE, {initiate_flow, Name, FromAddr, ToAddr,
+                              BindingLabel}, ?LARGE_TIMEOUT).
+
 
 %%% HANDLER FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
@@ -69,6 +80,9 @@ 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}).
 
@@ -92,11 +106,26 @@ handle_call({update_flow, FlowId, Labels}, From,
                 error -> {reply, {error, session_not_found}, State};
                 {ok, #sess{pid = Pid}} ->
                     #{source := S, destination := D, constraints := C} = R,
-                    ReqRoute = route_from_labels(S, D, C, Labels),
+                    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, FromAddr, ToAddr, Binding}, From,
+            #state{sessions = SessMap} = State) ->
+    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;
 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]),
@@ -114,6 +143,11 @@ 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)]),
@@ -122,7 +156,7 @@ handle_call({request_route, RouteReq}, _From, State) ->
         {error, _Reason} = Error ->
             {reply, Error, State};
         {ok, Labels} ->
-            Route = route_from_labels(S, D, C, Labels),
+            Route = routereq_from_labels(S, D, C, Labels),
             {reply, {ok, Route}, State}
     end;
 handle_call({flow_status_changed, FlowId, NewStatus}, _From,
@@ -131,12 +165,13 @@ handle_call({flow_status_changed, FlowId, NewStatus}, _From,
     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 request ~w", [Request]),
+handle_call(Request, From, State) ->
+    logger:warning("Unexpected call from ~w: ~p", [From, Request]),
     {reply, {error, unexpected_call}, State}.
 
 
-handle_cast(_Request, State) ->
+handle_cast(Request, State) ->
+    logger:warning("Unexpected cast: ~p", [Request]),
     {noreply, State}.
 
 handle_continue(_Continue, State) ->
@@ -153,7 +188,17 @@ handle_info({flow_updated, FlowId, NewRoute, From},
             {noreply, State#state{flows = Flows2}}
     end;
 handle_info({flow_update_error, FlowId, Reason, From}, State) ->
-    logger:error("Flow ~w updated error: ~w", [FlowId, Reason]),
+    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},
@@ -169,7 +214,8 @@ handle_info({'DOWN', MonRef, process, Pid, _Reason},
         _X ->
             {noreply, State}
     end;
-handle_info(_Info, State) ->
+handle_info(Info, State) ->
+    logger:warning("Unexpected message: ~p", [Info]),
     {noreply, State}.
 
 code_change(_OldVsn, State, _Extra) ->
@@ -189,10 +235,10 @@ compute_path(From, To) ->
             {ok, Labels};
         {error, Reason} ->
             logger:warning("Failed to find a route from ~p to ~p", [From, To]),
-            {error, Reason}
+            {error, route_not_found}
     end.
 
-route_from_labels(Source, Destination, Constraints, Labels) ->
+routereq_from_labels(Source, Destination, Constraints, Labels) ->
     #{
         source => Source,
         destination => Destination,
@@ -207,6 +253,13 @@ route_from_labels(Source, Destination, Constraints, 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].
 
@@ -216,6 +269,9 @@ route_to_labels(#{steps := Steps}) ->
 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() ->
@@ -238,5 +294,14 @@ 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.
diff --git a/src/te/rebar.lock b/src/te/rebar.lock
index 6eb067ecd..ef9747fe4 100644
--- a/src/te/rebar.lock
+++ b/src/te/rebar.lock
@@ -11,11 +11,11 @@
  {<<"hpack">>,{pkg,<<"hpack_erl">>,<<"0.2.3">>},2},
  {<<"pcep_codec">>,
   {git,"git@github.com:stritzinger/pcep_codec.git",
-       {ref,"3d1623fdf0c62d3daf400ac65aaf985f8bd40835"}},
+       {ref,"84dcc430b2aa427984c100cc19dd35d946b22ff9"}},
   1},
  {<<"pcep_server">>,
   {git,"git@github.com:stritzinger/pcep_server.git",
-       {ref,"2cf692e7e5fa2e9ac0fd54e5aa64ffb17f4f1b4a"}},
+       {ref,"3910bf5546879cb91a3483008b6aed11753deaa6"}},
   0},
  {<<"ranch">>,{pkg,<<"ranch">>,<<"2.0.0">>},1}]}.
 [
diff --git a/tutorial/2-6-netgen-topology.yml.template b/tutorial/2-6-netgen-topology.yml.template
index 286f40005..d16e34841 100644
--- a/tutorial/2-6-netgen-topology.yml.template
+++ b/tutorial/2-6-netgen-topology.yml.template
@@ -103,17 +103,11 @@ routers:
           debug pathd pcep basic
           segment-routing
            traffic-eng
-            mpls-te on
-            policy color 1 endpoint 6.6.6.6
-             name DEFAULT
-             binding-sid 1111
-             candidate-path preference 100 name RUNTIME dynamic
-             !
-            !
             pcep
              pce-config CONFIG
               source-address ip 1.1.1.1
              pce PCE
+              pce-initiated
               address ip ${PCE_IP}
               config CONFIG
              pcc
@@ -438,16 +432,11 @@ routers:
           debug pathd pcep
           segment-routing
            traffic-eng
-            policy color 1 endpoint 1.1.1.1
-             name DEFAULT
-             binding-sid 6666
-             candidate-path preference 200 name RUNTIME dynamic
-             !
-            !
             pcep
              pce-config CONFIG
               source-address ip 6.6.6.6
              pce PCE
+              pce-initiated
               address ip ${PCE_IP}
               config CONFIG
              pcc
diff --git a/tutorial/2-6-te-demo-start-testbed.sh b/tutorial/2-6-te-demo-start-testbed.sh
index 3266d1141..5ba48479c 100755
--- a/tutorial/2-6-te-demo-start-testbed.sh
+++ b/tutorial/2-6-te-demo-start-testbed.sh
@@ -41,7 +41,5 @@ cat "${ROOTDIR}/2-6-netgen-topology.yml.template" | envsubst > "${RUNDIR}/topolo
 
 sudo -i bash -c "\
     cd ${RUNDIR}/netgen;\
-    sysctl -w net.ipv6.conf.all.disable_ipv6=1;\
-    sysctl -w net.ipv6.conf.default.disable_ipv6=1;\
     sysctl -w net.ipv4.conf.all.rp_filter=0;\
     PATH=/usr/lib/frr:\$PATH ./exe/netgen ../topology.yml -c ../config.yml"
diff --git a/tutorial/2-6-te-demo.md b/tutorial/2-6-te-demo.md
index 774a38bab..da822e3a3 100644
--- a/tutorial/2-6-te-demo.md
+++ b/tutorial/2-6-te-demo.md
@@ -89,3 +89,20 @@ Then in second console:
     $ sudo -i
     # cd /tmp/negen
     # ./tmux.sh
+
+### Setup a flow from the Erlang console
+
+We will setup two unidirectional flow between router 1 and 6.
+We will use the binding label 1111 for the flow from router 1 to router 6, and the binding label 6666 for the flow from router 6 to router 1.
+
+    $ kubectl --namespace tfs exec -ti $(kubectl --namespace tfs get pods --selector=app=teservice -o name) -- /tfte/bin/tfte remote_console
+    1> {ok, Flow1to6} = epce_server:initiate_flow(<<"foo">>, {1, 1, 1, 1}, {6, 6, 6, 6}, 1111).
+    2> {ok, Flow6to1} = epce_server:initiate_flow(<<"bar">>, {6, 6, 6, 6}, {1, 1, 1, 1}, 6666).
+
+Now if we go to the tmux session src (Ctrl-B 0) we can ping dst:
+
+    $ ping 9.9.9.2
+
+From the Erlang console we can update the initiated flows to change the path the packets are flowing through:
+
+    3> epce_server:update_flow(Flow6to1, [16050, 16030, 16010]).
-- 
GitLab