diff --git a/.gitignore b/.gitignore
index 0a116f850780386a9fe1010b22164f4c7dbf8228..a9144d6699af12319a67e8bad5cec982f3ae6a8c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -168,5 +168,8 @@ delete_local_deployment.sh
 local_docker_deployment.sh
 local_k8s_deployment.sh
 
+# asdf configuration
+.tool-versions
+
 # Other logs
 **/logs/*.log.*
diff --git a/manifests/teservice.yaml b/manifests/teservice.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..15f1619df08069f00db883f0b918c17837c707d1
--- /dev/null
+++ b/manifests/teservice.yaml
@@ -0,0 +1,81 @@
+# 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.
+
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: teservice
+spec:
+  selector:
+    matchLabels:
+      app: teservice
+  template:
+    metadata:
+      annotations:
+        config.linkerd.io/skip-inbound-ports: "4189"
+      labels:
+        app: teservice
+    spec:
+      terminationGracePeriodSeconds: 5
+      shareProcessNamespace: true
+      containers:
+      - name: server
+        image: labs.etsi.org:5050/tfs/controller/te:latest
+        imagePullPolicy: Always
+        ports:
+        - containerPort: 10030
+        env:
+        - name: ERLANG_LOGGER_LEVEL
+          value: "debug"
+        - name: ERLANG_COOKIE
+          value: "tfte-unsafe-cookie"
+        - name: ERLANG_NODE_NAME
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.name
+        - name: ERLANG_NODE_IP
+          valueFrom:
+            fieldRef:
+              fieldPath: status.podIP
+        readinessProbe:
+          exec:
+            command: ["/tfte/bin/tfte", "status"]
+        livenessProbe:
+          exec:
+            command: ["/tfte/bin/tfte", "status"]
+        resources:
+          requests:
+            cpu: 250m
+            memory: 512Mi
+          limits:
+            cpu: 700m
+            memory: 1024Mi
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: teservice
+spec:
+  type: ClusterIP
+  selector:
+    app: teservice
+  ports:
+  - name: grpc
+    protocol: TCP
+    port: 10030
+    targetPort: 10030
+  - name: pcep
+    protocol: TCP
+    port: 4189
+    targetPort: 4189
diff --git a/my_deploy.sh b/my_deploy.sh
index 7e8abb1282655dd300d48457f655ac932cbb6e68..888fc98903eb665729d7e0843cf9e9fc8b60741d 100755
--- a/my_deploy.sh
+++ b/my_deploy.sh
@@ -34,6 +34,9 @@ export TFS_COMPONENTS="context device pathcomp service slice compute webui load_
 # 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"
 
diff --git a/proto/.gitignore b/proto/.gitignore
index d1dea37b3a85abaa18b5bd65d3ec0e1d3c6fe9b6..4d6f9cbd73b283a2520ac926f56294a2aa060478 100644
--- a/proto/.gitignore
+++ b/proto/.gitignore
@@ -3,5 +3,8 @@ src/*/*
 # used to prevent breaking symbolic links from source code folders
 !src/*/.gitignore
 !src/python/__init__.py
+!src/erlang/rebar.config
+!src/erlang/rebar.lock
+!src/erlang/src/tfpb.app.src
 
 uml/generated
diff --git a/proto/context.proto b/proto/context.proto
index 9f779d8db310a98ea05682e619b1357c27c76904..55a80470d40463742cc3e034ca9e933f4ff6c3f0 100644
--- a/proto/context.proto
+++ b/proto/context.proto
@@ -273,6 +273,7 @@ enum ServiceTypeEnum {
   SERVICETYPE_L3NM = 1;
   SERVICETYPE_L2NM = 2;
   SERVICETYPE_TAPI_CONNECTIVITY_SERVICE = 3;
+  SERVICETYPE_TE = 4;
 }
 
 enum ServiceStatusEnum {
diff --git a/proto/generate_code_erlang.sh b/proto/generate_code_erlang.sh
new file mode 100755
index 0000000000000000000000000000000000000000..80fb977e44536482888d2963995fd811e95417fd
--- /dev/null
+++ b/proto/generate_code_erlang.sh
@@ -0,0 +1,74 @@
+#!/bin/bash -eu
+# 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.
+
+set -e
+
+FORCE=0
+DEFAULT_ACTION="generate"
+
+usage() {
+    echo "Usage: $0 [-f] [clean|generate]" 1>&2
+    echo "Options:"
+    echo "  -f: Force regeneration of all protocol buffers"
+    exit 1;
+}
+
+while getopts "fc" o; do
+    case "${o}" in
+        f)
+            FORCE=1
+            ;;
+        *)
+            usage
+            ;;
+    esac
+done
+shift $((OPTIND-1))
+
+ACTION=${1:-$DEFAULT_ACTION}
+cd $(dirname $0)
+ROOT=$(pwd)
+ERLANG_PROTO_DIR="$ROOT/src/erlang"
+BUILD_CHECK="$ERLANG_PROTO_DIR/.generated"
+
+tfpb_clean() {
+    rm -f "$BUILD_CHECK"
+    rm -rf "$ERLANG_PROTO_DIR/src/"*.erl
+    rm -rf "$ERLANG_PROTO_DIR/src/erlang/_build"
+}
+
+tfpb_generate() {
+    if [[ -f "$BUILD_CHECK" && $FORCE != 1 ]]; then
+        echo "Protocol buffer code for Erlang already generated, use -f to force"
+        exit 0
+    fi
+
+    tfpb_clean
+    mkdir -p "$ERLANG_PROTO_DIR"
+    cd "$ERLANG_PROTO_DIR"
+    rebar3 compile
+    rebar3 grpc gen
+    rebar3 compile
+    touch "$BUILD_CHECK"
+
+    echo "Protocol buffer code for Erlang generated"
+}
+
+case "$ACTION" in
+    clean) tfpb_clean;;
+    generate) tfpb_generate;;
+    *) usage;;
+esac
+
diff --git a/proto/l3_centralizedattackdetector.proto b/proto/l3_centralizedattackdetector.proto
index 56273cb628b5d4b8517c2640de6a88b0e57dab3d..142d3f48aabde6b538866a162205506d38020d37 100644
--- a/proto/l3_centralizedattackdetector.proto
+++ b/proto/l3_centralizedattackdetector.proto
@@ -18,13 +18,13 @@ import "context.proto";
 
 service L3Centralizedattackdetector {
   // Analyze single input to the ML model in the CAD component
-  rpc AnalyzeConnectionStatistics (L3CentralizedattackdetectorMetrics) returns (Empty) {}
+  rpc AnalyzeConnectionStatistics (L3CentralizedattackdetectorMetrics) returns (StatusMessage) {}
 
   // Analyze a batch of inputs to the ML model in the CAD component
-  rpc AnalyzeBatchConnectionStatistics (L3CentralizedattackdetectorBatchInput) returns (Empty) {}
+  rpc AnalyzeBatchConnectionStatistics (L3CentralizedattackdetectorBatchInput) returns (StatusMessage) {}
 
   // Get the list of features used by the ML model in the CAD component
-  rpc GetFeaturesIds (Empty) returns (AutoFeatures) {}
+  rpc GetFeaturesIds (context.Empty) returns (AutoFeatures) {}
 
   // Sets the list of attack IPs in order to be used to compute the prediction accuracy of the ML model in the CAD component in case of testing the ML model
   rpc SetAttackIPs (AttackIPs) returns (Empty) {}
@@ -66,7 +66,7 @@ message L3CentralizedattackdetectorBatchInput {
 	repeated L3CentralizedattackdetectorMetrics metrics = 1;
 }
 
-message Empty {
+message StatusMessage {
 	string message = 1;
 }
 
diff --git a/proto/src/erlang/.gitignore b/proto/src/erlang/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..b583d287f2c200fe88ad47c7e8d9cfcf15b0135c
--- /dev/null
+++ b/proto/src/erlang/.gitignore
@@ -0,0 +1,5 @@
+*
+!rebar.config
+!rebar.lock
+!src/
+!src/tfpb.app.src
diff --git a/proto/src/erlang/rebar.config b/proto/src/erlang/rebar.config
new file mode 100644
index 0000000000000000000000000000000000000000..55e139eabf9bf5e0bdcb66af889d78bdc8aa9c11
--- /dev/null
+++ b/proto/src/erlang/rebar.config
@@ -0,0 +1,21 @@
+% 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]}.
+
+{grpc, [{protos, "../.."},
+        {gpb_opts, [{i, "../.."}, {strbin, true}, {descriptor, true}, {module_name_suffix, "_pb"}]}]}.
+
+{plugins, [grpcbox_plugin]}.
diff --git a/proto/src/erlang/rebar.lock b/proto/src/erlang/rebar.lock
new file mode 100644
index 0000000000000000000000000000000000000000..d353eaf344ffe261be7200c1cb3a1ad76bf80703
--- /dev/null
+++ b/proto/src/erlang/rebar.lock
@@ -0,0 +1,23 @@
+{"1.2.0",
+[{<<"acceptor_pool">>,{pkg,<<"acceptor_pool">>,<<"1.0.0">>},1},
+ {<<"chatterbox">>,{pkg,<<"ts_chatterbox">>,<<"0.12.0">>},1},
+ {<<"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}]}.
+[
+{pkg_hash,[
+ {<<"acceptor_pool">>, <<"43C20D2ACAE35F0C2BCD64F9D2BDE267E459F0F3FD23DAB26485BF518C281B21">>},
+ {<<"chatterbox">>, <<"4E54F199E15C0320B85372A24E35554A2CCFC4342E0B7CD8DAED9A04F9B8EF4A">>},
+ {<<"ctx">>, <<"8FF88B70E6400C4DF90142E7F130625B82086077A45364A78D208ED3ED53C7FE">>},
+ {<<"gproc">>, <<"CEA02C578589C61E5341FCE149EA36CCEF236CC2ECAC8691FBA408E7EA77EC2F">>},
+ {<<"grpcbox">>, <<"97C7126296A091602D372EBF5860A04F7BC795B45B33A984CAD2B8E362774FD8">>},
+ {<<"hpack">>, <<"17670F83FF984AE6CD74B1C456EDDE906D27FF013740EE4D9EFAA4F1BF999633">>}]},
+{pkg_hash_ext,[
+ {<<"acceptor_pool">>, <<"0CBCD83FDC8B9AD2EEE2067EF8B91A14858A5883CB7CD800E6FCD5803E158788">>},
+ {<<"chatterbox">>, <<"6478C161BC60244F41CD5847CC3ACCD26D997883E9F7FACD36FF24533B2FA579">>},
+ {<<"ctx">>, <<"A14ED2D1B67723DBEBBE423B28D7615EB0BDCBA6FF28F2D1F1B0A7E1D4AA5FC2">>},
+ {<<"gproc">>, <<"580ADAFA56463B75263EF5A5DF4C86AF321F68694E7786CB057FD805D1E2A7DE">>},
+ {<<"grpcbox">>, <<"161ABE9E17E7D1982EFA6488ADEAA13C3E847A07984A6E6B224E553368918647">>},
+ {<<"hpack">>, <<"06F580167C4B8B8A6429040DF36CC93BBA6D571FAEAEC1B28816523379CBB23A">>}]}
+].
diff --git a/proto/src/erlang/src/tfpb.app.src b/proto/src/erlang/src/tfpb.app.src
new file mode 100644
index 0000000000000000000000000000000000000000..097bdc5979ece9d9d02322c16bd97816460afc00
--- /dev/null
+++ b/proto/src/erlang/src/tfpb.app.src
@@ -0,0 +1,24 @@
+%% 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, tfpb,
+ [{description, "Teraflow Erlang Protocol Buffers"},
+  {vsn, "0.1.0"},
+  {registered, []},
+  {applications, [kernel, stdlib, grpcbox]},
+  {env, []},
+  {modules, []},
+  {licenses, ["Apache 2.0"]},
+  {links, []}
+ ]}.
diff --git a/src/common/Constants.py b/src/common/Constants.py
index ed1c1475ad3c69cfb9bd650f0d99f33c6cf0f2bc..423f2558b71b189b9e771e5af94968d28f8777c0 100644
--- a/src/common/Constants.py
+++ b/src/common/Constants.py
@@ -56,6 +56,7 @@ class ServiceNameEnum(Enum):
     OPTICALATTACKDETECTOR  = 'opticalattackdetector'
     OPTICALATTACKMITIGATOR = 'opticalattackmitigator'
     CACHING                = 'caching'
+    TE                     = 'te'
 
     # Used for test and debugging only
     DLT_GATEWAY    = 'dltgateway'
@@ -80,6 +81,7 @@ DEFAULT_SERVICE_GRPC_PORTS = {
     ServiceNameEnum.OPTICALATTACKMANAGER   .value : 10005,
     ServiceNameEnum.INTERDOMAIN            .value : 10010,
     ServiceNameEnum.PATHCOMP               .value : 10020,
+    ServiceNameEnum.TE                     .value : 10030,
 
     # Used for test and debugging only
     ServiceNameEnum.DLT_GATEWAY   .value : 50051,
diff --git a/src/context/service/database/models/enums/ServiceType.py b/src/context/service/database/models/enums/ServiceType.py
index 668133abff416dd21f693ff41d7b3c6431f5e148..3937eaa114429ce9d004933a5d5baf1ae6137513 100644
--- a/src/context/service/database/models/enums/ServiceType.py
+++ b/src/context/service/database/models/enums/ServiceType.py
@@ -21,6 +21,7 @@ class ORM_ServiceTypeEnum(enum.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/service/client/TEServiceClient.py b/src/service/client/TEServiceClient.py
new file mode 100644
index 0000000000000000000000000000000000000000..19ca95bceb285c03df635859728ecf15640d8438
--- /dev/null
+++ b/src/service/client/TEServiceClient.py
@@ -0,0 +1,67 @@
+# 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.
+
+import grpc, logging
+from common.Constants import ServiceNameEnum
+from common.Settings import get_service_host, get_service_port_grpc
+from common.proto.context_pb2 import Empty, Service, ServiceId, ServiceStatus
+from common.proto.te_pb2_grpc import TEServiceStub
+from common.tools.client.RetryDecorator import retry, delay_exponential
+from common.tools.grpc.Tools import grpc_message_to_json_string
+
+LOGGER = logging.getLogger(__name__)
+MAX_RETRIES = 15
+DELAY_FUNCTION = delay_exponential(initial=0.01, increment=2.0, maximum=5.0)
+RETRY_DECORATOR = retry(max_retries=MAX_RETRIES, delay_function=DELAY_FUNCTION, prepare_method_name='connect')
+
+class TEServiceClient:
+    def __init__(self, host=None, port=None):
+        if not host: host = get_service_host(ServiceNameEnum.TE)
+        if not port: port = get_service_port_grpc(ServiceNameEnum.TE)
+        self.endpoint = '{:s}:{:s}'.format(str(host), str(port))
+        LOGGER.debug('Creating channel to {:s}...'.format(str(self.endpoint)))
+        self.channel = None
+        self.stub = None
+        self.connect()
+        LOGGER.debug('Channel created')
+
+    def connect(self):
+        self.channel = grpc.insecure_channel(self.endpoint)
+        self.stub = TEServiceStub(self.channel)
+
+    def close(self):
+        if self.channel is not None: self.channel.close()
+        self.channel = None
+        self.stub = None
+
+    @RETRY_DECORATOR
+    def RequestLSP(self, request : Service) -> ServiceStatus:
+        LOGGER.debug('RequestLSP request: {:s}'.format(grpc_message_to_json_string(request)))
+        response = self.stub.RequestLSP(request)
+        LOGGER.debug('RequestLSP result: {:s}'.format(grpc_message_to_json_string(response)))
+        return response
+
+    @RETRY_DECORATOR
+    def UpdateLSP(self, request : ServiceId) -> ServiceStatus:
+        LOGGER.debug('UpdateLSP request: {:s}'.format(grpc_message_to_json_string(request)))
+        response = self.stub.UpdateLSP(request)
+        LOGGER.debug('UpdateLSP result: {:s}'.format(grpc_message_to_json_string(response)))
+        return response
+
+    @RETRY_DECORATOR
+    def DeleteLSP(self, request : ServiceId) -> Empty:
+        LOGGER.debug('DeleteLSP request: {:s}'.format(grpc_message_to_json_string(request)))
+        response = self.stub.DeleteLSP(request)
+        LOGGER.debug('DeleteLSP result: {:s}'.format(grpc_message_to_json_string(response)))
+        return response
diff --git a/src/service/service/ServiceServiceServicerImpl.py b/src/service/service/ServiceServiceServicerImpl.py
index f50eee6f61e0b0664e0f883feaf6b807e037b0c5..55d0b593b96d8e07bd8a12d76d7deafe2e42ff8f 100644
--- a/src/service/service/ServiceServiceServicerImpl.py
+++ b/src/service/service/ServiceServiceServicerImpl.py
@@ -27,6 +27,7 @@ from common.tools.grpc.Tools import grpc_message_to_json, grpc_message_to_json_s
 from context.client.ContextClient import ContextClient
 from pathcomp.frontend.client.PathCompClient import PathCompClient
 from service.service.tools.ConnectionToString import connection_to_string
+from service.client.TEServiceClient import TEServiceClient
 from .service_handler_api.ServiceHandlerFactory import ServiceHandlerFactory
 from .task_scheduler.TaskScheduler import TasksScheduler
 from .tools.GeodesicDistance import gps_distance
@@ -117,6 +118,28 @@ class ServiceServiceServicerImpl(ServiceServiceServicer):
             service.service_type = request.service_type                                     # pylint: disable=no-member
         service.service_status.service_status = ServiceStatusEnum.SERVICESTATUS_PLANNED     # pylint: disable=no-member
 
+        if service.service_type == ServiceTypeEnum.SERVICETYPE_TE:
+            # TE service:
+            context_client.SetService(request)
+
+            te_service_client = TEServiceClient()
+            service_status = te_service_client.RequestLSP(service)
+
+            if service_status.service_status == ServiceStatusEnum.SERVICESTATUS_ACTIVE:
+                _service : Optional[Service] = get_service_by_id(
+                    context_client, request.service_id, rw_copy=True,
+                    include_config_rules=False, include_constraints=False, include_endpoint_ids=False)
+                _service.service_status.service_status = ServiceStatusEnum.SERVICESTATUS_ACTIVE
+                service_id = context_client.SetService(_service)
+                return service_id
+            else:
+                MSG = 'RequestLSP for Service({:s}) returned ServiceStatus({:s})'
+                context_uuid = request.service_id.context_id.context_uuid.uuid
+                service_uuid = request.service_id.service_uuid.uuid
+                service_key = '{:s}/{:s}'.format(context_uuid, service_uuid)
+                str_service_status = ServiceStatusEnum.Name(service_status.service_status)
+                raise Exception(MSG.format(service_key, str_service_status))
+
         del service.service_endpoint_ids[:] # pylint: disable=no-member
         for endpoint_id in request.service_endpoint_ids:
             service.service_endpoint_ids.add().CopyFrom(endpoint_id)    # pylint: disable=no-member
@@ -214,6 +237,14 @@ class ServiceServiceServicerImpl(ServiceServiceServicer):
         service.service_status.service_status = ServiceStatusEnum.SERVICESTATUS_PENDING_REMOVAL
         context_client.SetService(service)
 
+        if service.service_type == ServiceTypeEnum.SERVICETYPE_TE:
+            # TE service
+            te_service_client = TEServiceClient()
+            te_service_client.DeleteLSP(request)
+            context_client.RemoveService(request)
+            return Empty()
+
+        # Normal service
         # Feed TaskScheduler with this service and the sub-services and sub-connections related to this service.
         # TaskScheduler identifies inter-dependencies among them and produces a schedule of tasks (an ordered list of
         # tasks to be executed) to implement the requested delete operation.
diff --git a/src/service/service/service_handler_api/FilterFields.py b/src/service/service/service_handler_api/FilterFields.py
index f86412a8c736fda4d2fff2b485453cc3bf1ce0b1..1b22c5c42e908e9b9455358edd2abf54442628f5 100644
--- a/src/service/service/service_handler_api/FilterFields.py
+++ b/src/service/service/service_handler_api/FilterFields.py
@@ -23,7 +23,8 @@ SERVICE_TYPE_VALUES = {
     ServiceTypeEnum.SERVICETYPE_UNKNOWN,
     ServiceTypeEnum.SERVICETYPE_L3NM,
     ServiceTypeEnum.SERVICETYPE_L2NM,
-    ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE
+    ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE,
+    ServiceTypeEnum.SERVICETYPE_TE,
 }
 
 DEVICE_DRIVER_VALUES = {
diff --git a/src/te/.dockerignore b/src/te/.dockerignore
new file mode 100644
index 0000000000000000000000000000000000000000..558d2f3ece10e09c6a5ecd710c21e3f727bdd25c
--- /dev/null
+++ b/src/te/.dockerignore
@@ -0,0 +1,4 @@
+Dockerfile
+_build
+README.md
+.tool-versions
diff --git a/src/te/.gitignore b/src/te/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..eeb7cfc4aefeeb2f4fa86e208119ebc360c3f7af
--- /dev/null
+++ b/src/te/.gitignore
@@ -0,0 +1,21 @@
+.tool-versions
+.rebar3
+_*
+.eunit
+*.o
+*.beam
+*.plt
+*.swp
+*.swo
+.erlang.cookie
+ebin
+log
+erl_crash.dump
+.rebar
+logs
+_build
+.idea
+*.iml
+rebar3.crashdump
+*~
+config/dev.config
diff --git a/src/te/Dockerfile b/src/te/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..aaca9fe87ad330c2d43406d5b5df2a585617d937
--- /dev/null
+++ b/src/te/Dockerfile
@@ -0,0 +1,59 @@
+# 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.
+
+# Multi-stage Docker image build
+
+# Build stage 0
+FROM erlang:24.3-alpine
+
+RUN apk add --no-cache bash git
+
+RUN mkdir /var/teraflow
+WORKDIR /var/teraflow
+
+COPY proto proto
+RUN bash -c proto/generate_code_erlang.sh
+RUN mkdir src
+COPY src/te src/te
+WORKDIR src/te
+RUN rebar3 as prod release
+
+# Build stage 1
+FROM alpine
+
+# Install some libs
+RUN apk add --no-cache libgcc libstdc++ && \
+    apk add --no-cache openssl && \
+    apk add --no-cache libcrypto1.1 && \
+    apk add --no-cache ncurses-libs
+
+# Install the released application
+COPY --from=0 /var/teraflow/src/te/_build/prod/rel/tfte /tfte
+
+# Expose relevant ports
+EXPOSE 10030
+EXPOSE 4189
+
+ARG ERLANG_LOGGER_LEVEL_DEFAULT=debug
+ARG ERLANG_COOKIE_DEFAULT=tfte-unsafe-cookie
+ARG ERLANG_NODE_IP_DEFAULT=127.0.0.1
+ARG ERLANG_NODE_NAME_DEFAULT=tfte
+
+ENV ERLANG_LOGGER_LEVEL=$ERLANG_LOGGER_LEVEL_DEFAULT
+ENV ERLANG_COOKIE=$ERLANG_COOKIE_DEFAULT
+ENV ERLANG_NODE_IP=$ERLANG_NODE_IP_DEFAULT
+ENV ERLANG_NODE_NAME=$ERLANG_NODE_NAME_DEFAULT
+
+ENTRYPOINT ["/tfte/bin/tfte"]
+CMD ["foreground"]
diff --git a/src/te/README.md b/src/te/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..5da478e61ca7f88182e757c80ea7b2916de9998d
--- /dev/null
+++ b/src/te/README.md
@@ -0,0 +1,81 @@
+TeraFlow Traffic Engineering Service
+====================================
+
+This service is mean as an example of a Teraflow Service made in Erlang.
+
+The Traffic Engineering service is tested on Ubuntu 20.04. Follow the instructions below to build, test, and run this service on your local environment.
+
+
+## Build
+
+First the TeraFlow protocol buffer code must have been generated:
+
+    $ ../../proto/generate_code_erlang.sh
+
+Then the TE service can be built:
+
+    $ rebar3 compile
+
+
+## Execute Unit Tests
+
+    $ rebar3 eunit
+
+
+## Run Service Console
+
+First you need to crete a configuration file if not already done, and customize it if required:
+
+	$ cp config/dev.config.template config/dev.config
+
+Then you  can start the service in console mode:
+
+    $ rebar3 shell
+
+
+## Docker
+
+### Build Image
+
+The docker image must be built from the root of the Teraflow project:
+
+    $ docker build -t te:dev -f src/te/Dockerfile .
+
+
+### Run a shell from inside the container
+
+    $ docker run -ti --rm --entrypoint sh te:dev
+
+
+### Run Docker Container
+
+    $ docker run -d --name te --init te:dev
+
+
+### Open a Console to a Docker Container's Service
+
+    $ docker exec -it te /tfte/bin/tfte remote_console
+
+
+### Show Logs
+
+    $ docker logs te
+
+
+## Kubernetes
+
+### Open a Console
+
+    $ kubectl --namespace tfs exec -ti $(kubectl --namespace tfs get pods --selector=app=teservice -o name) -c server -- /tfte/bin/tfte remote_console
+
+
+### Show Logs
+
+    $ kubectl --namespace tfs logs $(kubectl --namespace tfs get pods --selector=app=teservice -o name) -c server
+
+
+## Teraflow
+
+To build and deploy the TE service as part of Teraflow, the following line must be added or uncomented in your `my_deploy.sh`:
+
+    export TFS_COMPONENTS="${TFS_COMPONENTS} te"
diff --git a/src/te/apps/epce/src/epce.app.src b/src/te/apps/epce/src/epce.app.src
new file mode 100644
index 0000000000000000000000000000000000000000..13324fd2fd68567623543b16affdd5b4a0241ad4
--- /dev/null
+++ b/src/te/apps/epce/src/epce.app.src
@@ -0,0 +1,30 @@
+%% 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, epce,
+ [{description, "An Erlang PCE"},
+  {vsn, "0.1.0"},
+  {registered, []},
+  {mod, {epce_app, []}},
+  {applications,
+   [kernel,
+    stdlib,
+    pcep_server
+   ]},
+  {env,[]},
+  {modules, []},
+
+  {licenses, ["Apache 2.0"]},
+  {links, []}
+ ]}.
diff --git a/src/te/apps/epce/src/epce_app.erl b/src/te/apps/epce/src/epce_app.erl
new file mode 100644
index 0000000000000000000000000000000000000000..19f574f38eb93080beca9b3cc7c47a78994fce57
--- /dev/null
+++ b/src/te/apps/epce/src/epce_app.erl
@@ -0,0 +1,34 @@
+%%%-----------------------------------------------------------------------------
+%% 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_app).
+
+-behaviour(application).
+
+
+%%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% Behaviour application functions
+-export([start/2, stop/1]).
+
+
+%%% BEHAVIOUR application FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+start(_StartType, _StartArgs) ->
+    epce_sup:start_link().
+
+stop(_State) ->
+    ok.
diff --git a/src/te/apps/epce/src/epce_pcep_server_handler.erl b/src/te/apps/epce/src/epce_pcep_server_handler.erl
new file mode 100644
index 0000000000000000000000000000000000000000..dea88b5d0b253573b956aabcf240ffed0caf15be
--- /dev/null
+++ b/src/te/apps/epce/src/epce_pcep_server_handler.erl
@@ -0,0 +1,90 @@
+%%%-----------------------------------------------------------------------------
+%% 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_pcep_server_handler).
+
+-behaviour(gen_pcep_handler).
+
+
+%%% INCLUDES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-include_lib("kernel/include/logger.hrl").
+-include_lib("pcep_codec/include/pcep_codec_te.hrl").
+
+
+%%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% API functions
+
+% Behaviour gen_pcep_handler functions
+-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]).
+-export([flow_status_changed/3]).
+-export([terminate/2]).
+
+
+%%% RECORDS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-record(state, {}).
+
+
+%%% API FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+%%% BEHAVIOUR gen_pcep_handler FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+init([]) ->
+    {ok, #{}, #state{}}.
+
+opened(Id, Caps, Sess, State) ->
+    case epce_server:session_opened(Id, Caps, Sess) of
+        ok -> {ok, State};
+        {error, Reason} -> {error, Reason}
+    end.
+
+flow_added(Flow, State) ->
+    case epce_server:flow_added(Flow) of
+        {error, _Reason} = Error -> Error;
+        ok -> {ok, State}
+    end.
+
+flow_initiated(Flow, State) ->
+    ok = epce_server:flow_initiated(Flow),
+    {ok, State}.
+
+ready(State) ->
+    {ok, State}.
+
+request_route(RouteReq, State) ->
+    case epce_server:request_route(RouteReq) of
+        {error, _Reason} = Error -> Error;
+        {ok, Route} -> {ok, Route, State}
+    end.
+
+flow_delegated(_Flow, State) ->
+    {ok, State}.
+
+flow_status_changed(FlowId, NewStatus, State) ->
+    epce_server:flow_status_changed(FlowId, NewStatus),
+    {ok, State}.
+
+terminate(_Reason, _State) ->
+    ok.
diff --git a/src/te/apps/epce/src/epce_server.erl b/src/te/apps/epce/src/epce_server.erl
new file mode 100644
index 0000000000000000000000000000000000000000..d1d86b5766a3f8d8fe31b873dddbf7c8076ca0f5
--- /dev/null
+++ b/src/te/apps/epce/src/epce_server.erl
@@ -0,0 +1,346 @@
+%%%-----------------------------------------------------------------------------
+%% 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.
diff --git a/src/te/apps/epce/src/epce_sup.erl b/src/te/apps/epce/src/epce_sup.erl
new file mode 100644
index 0000000000000000000000000000000000000000..79c17c9a869a5bdba078e225a7187c0ebcdbcd92
--- /dev/null
+++ b/src/te/apps/epce/src/epce_sup.erl
@@ -0,0 +1,59 @@
+%%%-----------------------------------------------------------------------------
+%% 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]}}.
+
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..8313bec1ca6b3b75e374dfef7b8fd23d29690d68
--- /dev/null
+++ b/src/te/apps/epce/src/epce_ted.erl
@@ -0,0 +1,239 @@
+%%%-----------------------------------------------------------------------------
+%% 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.
diff --git a/src/te/apps/tfte/src/tfte.app.src b/src/te/apps/tfte/src/tfte.app.src
new file mode 100644
index 0000000000000000000000000000000000000000..abebf116975d5a820d2da462c319f67fdbf93118
--- /dev/null
+++ b/src/te/apps/tfte/src/tfte.app.src
@@ -0,0 +1,32 @@
+%% 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, []}
+ ]}.
diff --git a/src/te/apps/tfte/src/tfte_app.erl b/src/te/apps/tfte/src/tfte_app.erl
new file mode 100644
index 0000000000000000000000000000000000000000..a629a1b139d4d2a2965ff724676f23b6734ddd11
--- /dev/null
+++ b/src/te/apps/tfte/src/tfte_app.erl
@@ -0,0 +1,110 @@
+%%%-----------------------------------------------------------------------------
+%% 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.
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..453852f34dd5210b8c7d407d967a3da36f635349
--- /dev/null
+++ b/src/te/apps/tfte/src/tfte_context.erl
@@ -0,0 +1,187 @@
+%%%-----------------------------------------------------------------------------
+%% 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
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..002bda810e901debddd72a3cfa7ec50bab8e3f97
--- /dev/null
+++ b/src/te/apps/tfte/src/tfte_server.erl
@@ -0,0 +1,189 @@
+%%%-----------------------------------------------------------------------------
+%% 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
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..6ec5d09b069ca856f6298fdc8effa2bb01cf115a
--- /dev/null
+++ b/src/te/apps/tfte/src/tfte_service_sup.erl
@@ -0,0 +1,64 @@
+%%%-----------------------------------------------------------------------------
+%% 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}}.
diff --git a/src/te/apps/tfte/src/tfte_sup.erl b/src/te/apps/tfte/src/tfte_sup.erl
new file mode 100644
index 0000000000000000000000000000000000000000..4630511d6fec909b002acd57bec61790e54f7e94
--- /dev/null
+++ b/src/te/apps/tfte/src/tfte_sup.erl
@@ -0,0 +1,66 @@
+%%%-----------------------------------------------------------------------------
+%% 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}}.
diff --git a/src/te/apps/tfte/src/tfte_te_service.erl b/src/te/apps/tfte/src/tfte_te_service.erl
new file mode 100644
index 0000000000000000000000000000000000000000..b9911ee37a5719c838baca7f86dd326b810f9bc8
--- /dev/null
+++ b/src/te/apps/tfte/src/tfte_te_service.erl
@@ -0,0 +1,66 @@
+%%%-----------------------------------------------------------------------------
+%% 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.
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..39897caa800ebed291d34d91b4a6b154c90f0f2e
--- /dev/null
+++ b/src/te/apps/tfte/src/tfte_topology.erl
@@ -0,0 +1,405 @@
+%%%-----------------------------------------------------------------------------
+%% 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.
diff --git a/src/te/apps/tfte/src/tfte_util.erl b/src/te/apps/tfte/src/tfte_util.erl
new file mode 100644
index 0000000000000000000000000000000000000000..fb058c7cf6b247c13bd95f2f0c5696eec30da8bf
--- /dev/null
+++ b/src/te/apps/tfte/src/tfte_util.erl
@@ -0,0 +1,43 @@
+%%%-----------------------------------------------------------------------------
+%% 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).
diff --git a/src/te/config/dev.config.template b/src/te/config/dev.config.template
new file mode 100644
index 0000000000000000000000000000000000000000..6e21dc0d2d021447cf5eb84869d97b7a3662f58c
--- /dev/null
+++ b/src/te/config/dev.config.template
@@ -0,0 +1,78 @@
+% 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
diff --git a/src/te/config/sys.config.src b/src/te/config/sys.config.src
new file mode 100644
index 0000000000000000000000000000000000000000..edcd4384a3236df42b1e530c8b3a92b96c80e09e
--- /dev/null
+++ b/src/te/config/sys.config.src
@@ -0,0 +1,101 @@
+% 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
diff --git a/src/te/config/vm.args.src b/src/te/config/vm.args.src
new file mode 100644
index 0000000000000000000000000000000000000000..899705ce169a8302bdd201751342db58b5c85421
--- /dev/null
+++ b/src/te/config/vm.args.src
@@ -0,0 +1,4 @@
++C multi_time_warp
++sbwt none
+-name ${ERLANG_NODE_NAME}@${ERLANG_NODE_IP}
+-setcookie ${ERLANG_COOKIE}
diff --git a/src/te/rebar.config b/src/te/rebar.config
new file mode 100644
index 0000000000000000000000000000000000000000..01f7a899ee6fb69970bd38d6b75c47450887f313
--- /dev/null
+++ b/src/te/rebar.config
@@ -0,0 +1,54 @@
+% 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}
+        ]}
+    ]}
+]}.
diff --git a/src/te/rebar.lock b/src/te/rebar.lock
new file mode 100644
index 0000000000000000000000000000000000000000..c435b045661e26b84495269c3ba79406990d09fb
--- /dev/null
+++ b/src/te/rebar.lock
@@ -0,0 +1,41 @@
+{"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">>}]}
+].
diff --git a/src/te/tests/deploy_specs.sh b/src/te/tests/deploy_specs.sh
new file mode 100644
index 0000000000000000000000000000000000000000..818fb2b0d69ae63b197a83683e68aed96e50d6e2
--- /dev/null
+++ b/src/te/tests/deploy_specs.sh
@@ -0,0 +1,147 @@
+# 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"
diff --git a/src/te/tests/netgen-config.yml b/src/te/tests/netgen-config.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d037088ce6cafdf95110ce5eadd31e8fb7a302ed
--- /dev/null
+++ b/src/te/tests/netgen-config.yml
@@ -0,0 +1,115 @@
+# 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:
diff --git a/src/te/tests/netgen-topology.yml.template b/src/te/tests/netgen-topology.yml.template
new file mode 100644
index 0000000000000000000000000000000000000000..fd21c436324f03e59e37b7cb5cd829245782e133
--- /dev/null
+++ b/src/te/tests/netgen-topology.yml.template
@@ -0,0 +1,548 @@
+# 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.
+
+#
+#                          +---------+
+#                          |         |
+#                          |   SRC   |
+#                          | 9.9.9.1 |
+#                          |         |
+#                          +---------+
+#                               |eth-rt1 (.1)
+#                               |
+#                               |10.0.10.0/24
+#                               |
+#                               |eth-src (.2)
+#                          +---------+                      .
+#                          |         |                      .
+#                          |   RT1   |eth-rt1-pce (???)     .
+#                          | 1.1.1.1 +----------------------------------+
+#                          |  16010  |                      .       ??? |
+#                          +---------+                      .           |
+#                               |eth-sw1                    .           |
+#                               |                           .           |
+#                               |                           .           |
+#                               |                           .           |
+#          +---------+          |          +---------+      .           |
+#          |         |          |          |         |      .           |
+#          |   RT2   |eth-sw1   |   eth-sw1|   RT3   |      .           |
+#          | 2.2.2.2 +----------+----------+ 3.3.3.3 |      .           |
+#          |  16020  |     10.0.1.0/24     |  16030  |      .           |
+#          +---------+                     +---------+      .eth-pce-rt1|(???)
+#     eth-rt4-1|  |eth-rt4-2          eth-rt5-1|  |eth-rt5-2.      +----+----+
+#              |  |                            |  |         .      |         |
+#   10.0.2.0/24|  |10.0.3.0/24      10.0.4.0/24|  |10.0.5.0/24     |   PCE   |
+#              |  |                            |  |         .      |   ????  |
+#     eth-rt2-1|  |eth-rt2-2          eth-rt3-1|  |eth-rt3-2.      |         |
+#          +---------+                     +---------+      .      +----+----+
+#          |         |                     |         |      .eth-pce-rt6|(???)
+#          |   RT4   |     10.0.6.0/24     |   RT5   |      .           |
+#          | 4.4.4.4 +---------------------+ 5.5.5.5 |      .           |
+#          |  16040  |eth-rt5       eth-rt4|  16050  |      .           |
+#          +---------+                     +---------+      .           |
+#        eth-rt6|                                |eth-rt6   .           |
+#               |                                |          .           |
+#    10.0.7.0/24|                                |10.0.8.0/24           |
+#               |          +---------+           |          .           |
+#               +----------|         |-----------+          .           |
+#                   eth-rt4|   RT6   |eth-rt5               .           |
+#                          | 6.6.6.6 |eth-rt6-pce (????)    .      ???? |
+#                          |  16060  +----------------------------------+
+#                          +---------+                      .
+#                               |eth-dst (.1)               .
+#                               |                           .
+#                               |10.0.11.0/24
+#                               |
+#                               |eth-rt6 (.2)
+#                          +---------+
+#                          |         |
+#                          |   DST   |
+#                          | 9.9.9.2 |
+#                          |         |
+#                          +---------+
+#
+
+---
+
+routers:
+
+  src:
+    links:
+      lo:
+        ipv4: 9.9.9.1/32
+        ipv6: 2001:db8:1066::1/128
+        mpls: yes
+      eth-rt1:
+        peer: [rt1, eth-src]
+        ipv4: 10.0.10.1/24
+        mpls: yes
+    frr:
+      zebra:
+        run: yes
+        config:
+    shell: |
+      ip route add 9.9.9.2/32 encap mpls 1111 via inet 10.0.10.2 src 9.9.9.1
+
+  rt1:
+    links:
+      lo:
+        ipv4: 1.1.1.1/32
+        mpls: yes
+      eth-sw1:
+        peer: [sw1, sw1-rt1]
+        ipv4: 10.0.1.1/24
+        mpls: yes
+      eth-src:
+        peer: [src, eth-rt1]
+        ipv4: 10.0.10.2/24
+        mpls: yes
+    frr:
+      zebra:
+        run: yes
+        config:
+      pathd:
+        args: "-M pathd_pcep"
+        config: |
+          debug pathd pcep basic
+          segment-routing
+           traffic-eng
+            pcep
+             pce-config CONFIG
+              source-address ip 1.1.1.1
+             pce PCE
+              pce-initiated
+              address ip ${PCE_IP}
+              config CONFIG
+             pcc
+              peer PCE
+          !
+      isisd:
+        run: yes
+        config: |
+          interface lo
+           ip router isis 1
+           ipv6 router isis 1
+           isis passive
+          !
+          interface eth-sw1
+           ip router isis 1
+           ipv6 router isis 1
+           isis hello-multiplier 3
+          !
+          router isis 1
+           net 49.0000.0000.0000.0001.00
+           is-type level-1
+           redistribute ipv4 static level-1
+           redistribute ipv4 connected level-1
+           topology ipv6-unicast
+           segment-routing on
+           segment-routing global-block 16000 23999
+           segment-routing node-msd 8
+           segment-routing prefix 1.1.1.1/32 index 10 explicit-null
+           segment-routing prefix 2001:db8:1000::1/128 index 11 explicit-null
+          !
+    shell: |
+      ip route add 9.9.9.1/32 dev eth-src
+      ip link add eth-rt1-pce type veth peer name eth-pce-rt1
+      ip addr add ${RT1_PCE_INT_IF_IP}/24 dev eth-rt1-pce
+      ip link set eth-pce-rt1 netns ${PCE_NETNS}
+      ip -n ${PCE_NETNS} addr add ${RT1_PCE_EXT_IF_IP}/24 dev eth-pce-rt1
+      ip link set eth-rt1-pce up
+      ip -n ${PCE_NETNS} link set eth-pce-rt1 up
+      ip route add ${RT1_PCE_EXT_IF_IP}/24 via ${RT1_PCE_INT_IF_IP} dev eth-rt1-pce src 1.1.1.1
+      ip -n ${PCE_NETNS} route add ${RT1_PCE_INT_IF_IP}/32 via ${RT1_PCE_EXT_IF_IP} dev eth-pce-rt1
+      ip -n ${PCE_NETNS} route add 1.1.1.1/32 via ${RT1_PCE_EXT_IF_IP} dev eth-pce-rt1
+
+  rt2:
+    links:
+      lo:
+        ipv4: 2.2.2.2/32
+        ipv6: 2001:db8:1000::2/128
+        mpls: yes
+      eth-sw1:
+        peer: [sw1, sw1-rt2]
+        ipv4: 10.0.1.2/24
+        mpls: yes
+      eth-rt4-1:
+        peer: [rt4, eth-rt2-1]
+        ipv4: 10.0.2.2/24
+        mpls: yes
+      eth-rt4-2:
+        peer: [rt4, eth-rt2-2]
+        ipv4: 10.0.3.2/24
+        mpls: yes
+    frr:
+      zebra:
+        run: yes
+        config:
+      isisd:
+        run: yes
+        config: |
+          interface lo
+           ip router isis 1
+           ipv6 router isis 1
+           isis passive
+          !
+          interface eth-sw1
+           ip router isis 1
+           ipv6 router isis 1
+           isis hello-multiplier 3
+          !
+          interface eth-rt4-1
+           ip router isis 1
+           ipv6 router isis 1
+           isis network point-to-point
+           isis hello-multiplier 3
+          !
+          interface eth-rt4-2
+           ip router isis 1
+           ipv6 router isis 1
+           isis network point-to-point
+           isis hello-multiplier 3
+          !
+          router isis 1
+           net 49.0000.0000.0000.0002.00
+           is-type level-1
+           topology ipv6-unicast
+           segment-routing on
+           segment-routing global-block 16000 23999
+           segment-routing node-msd 8
+           segment-routing prefix 2.2.2.2/32 index 20 no-php-flag
+           segment-routing prefix 2001:db8:1000::2/128 index 21 no-php-flag
+          !
+
+  rt3:
+    links:
+      lo:
+        ipv4: 3.3.3.3/32
+        ipv6: 2001:db8:1000::3/128
+        mpls: yes
+      eth-sw1:
+        peer: [sw1, sw1-rt3]
+        ipv4: 10.0.1.3/24
+        mpls: yes
+      eth-rt5-1:
+        peer: [rt5, eth-rt3-1]
+        ipv4: 10.0.4.3/24
+        mpls: yes
+      eth-rt5-2:
+        peer: [rt5, eth-rt3-2]
+        ipv4: 10.0.5.3/24
+        mpls: yes
+    frr:
+      zebra:
+        run: yes
+        config:
+      isisd:
+        run: yes
+        config: |
+          interface lo
+           ip router isis 1
+           ipv6 router isis 1
+           isis passive
+          !
+          interface eth-sw1
+           ip router isis 1
+           ipv6 router isis 1
+           isis hello-multiplier 3
+          !
+          interface eth-rt5-1
+           ip router isis 1
+           ipv6 router isis 1
+           isis network point-to-point
+           isis hello-multiplier 3
+          !
+          interface eth-rt5-2
+           ip router isis 1
+           ipv6 router isis 1
+           isis network point-to-point
+           isis hello-multiplier 3
+          !
+          router isis 1
+           net 49.0000.0000.0000.0003.00
+           is-type level-1
+           topology ipv6-unicast
+           segment-routing on
+           segment-routing global-block 16000 23999
+           segment-routing node-msd 8
+           segment-routing prefix 3.3.3.3/32 index 30 no-php-flag
+           segment-routing prefix 2001:db8:1000::3/128 index 31 no-php-flag
+          !
+
+  rt4:
+    links:
+      lo:
+        ipv4: 4.4.4.4/32
+        ipv6: 2001:db8:1000::4/128
+        mpls: yes
+      eth-rt2-1:
+        peer: [rt2, eth-rt4-1]
+        ipv4: 10.0.2.4/24
+        mpls: yes
+      eth-rt2-2:
+        peer: [rt2, eth-rt4-2]
+        ipv4: 10.0.3.4/24
+        mpls: yes
+      eth-rt5:
+        peer: [rt5, eth-rt4]
+        ipv4: 10.0.6.4/24
+        mpls: yes
+      eth-rt6:
+        peer: [rt6, eth-rt4]
+        ipv4: 10.0.7.4/24
+        mpls: yes
+    frr:
+      zebra:
+        run: yes
+        config:
+      isisd:
+        run: yes
+        config: |
+          interface lo
+           ip router isis 1
+           ipv6 router isis 1
+           isis passive
+          !
+          interface eth-rt2-1
+           ip router isis 1
+           ipv6 router isis 1
+           isis network point-to-point
+           isis hello-multiplier 3
+          !
+          interface eth-rt2-2
+           ip router isis 1
+           ipv6 router isis 1
+           isis network point-to-point
+           isis hello-multiplier 3
+          !
+          interface eth-rt5
+           ip router isis 1
+           ipv6 router isis 1
+           isis network point-to-point
+           isis hello-multiplier 3
+          !
+          interface eth-rt6
+           ip router isis 1
+           ipv6 router isis 1
+           isis network point-to-point
+           isis hello-multiplier 3
+          !
+          router isis 1
+           net 49.0000.0000.0000.0004.00
+           is-type level-1
+           topology ipv6-unicast
+           segment-routing on
+           segment-routing global-block 16000 23999
+           segment-routing node-msd 8
+           segment-routing prefix 4.4.4.4/32 index 40 no-php-flag
+           segment-routing prefix 2001:db8:1000::4/128 index 41 no-php-flag
+          !
+
+  rt5:
+    links:
+      lo:
+        ipv4: 5.5.5.5/32
+        ipv6: 2001:db8:1000::5/128
+        mpls: yes
+      eth-rt3-1:
+        peer: [rt3, eth-rt5-1]
+        ipv4: 10.0.4.5/24
+        mpls: yes
+      eth-rt3-2:
+        peer: [rt3, eth-rt5-2]
+        ipv4: 10.0.5.5/24
+        mpls: yes
+      eth-rt4:
+        peer: [rt4, eth-rt5]
+        ipv4: 10.0.6.5/24
+        mpls: yes
+      eth-rt6:
+        peer: [rt6, eth-rt5]
+        ipv4: 10.0.8.5/24
+        mpls: yes
+    frr:
+      zebra:
+        run: yes
+        config:
+      isisd:
+        run: yes
+        config: |
+          interface lo
+           ip router isis 1
+           ipv6 router isis 1
+           isis passive
+          !
+          interface eth-rt3-1
+           ip router isis 1
+           ipv6 router isis 1
+           isis network point-to-point
+           isis hello-multiplier 3
+          !
+          interface eth-rt3-2
+           ip router isis 1
+           ipv6 router isis 1
+           isis network point-to-point
+           isis hello-multiplier 3
+          !
+          interface eth-rt4
+           ip router isis 1
+           ipv6 router isis 1
+           isis network point-to-point
+           isis hello-multiplier 3
+          !
+          interface eth-rt6
+           ip router isis 1
+           ipv6 router isis 1
+           isis network point-to-point
+           isis hello-multiplier 3
+          !
+          router isis 1
+           net 49.0000.0000.0000.0005.00
+           is-type level-1
+           topology ipv6-unicast
+           segment-routing on
+           segment-routing global-block 16000 23999
+           segment-routing node-msd 8
+           segment-routing prefix 5.5.5.5/32 index 50 no-php-flag
+           segment-routing prefix 2001:db8:1000::5/128 index 51 no-php-flag
+          !
+
+  rt6:
+    links:
+      lo:
+        ipv4: 6.6.6.6/32
+        ipv6: 2001:db8:1000::6/128
+        mpls: yes
+      eth-rt4:
+        peer: [rt4, eth-rt6]
+        ipv4: 10.0.7.6/24
+        mpls: yes
+      eth-rt5:
+        peer: [rt5, eth-rt6]
+        ipv4: 10.0.8.6/24
+        mpls: yes
+      eth-dst:
+        peer: [dst, eth-rt6]
+        ipv4: 10.0.11.1/24
+        mpls: yes
+    frr:
+      zebra:
+        run: yes
+        config:
+      pathd:
+        args: "-M pathd_pcep"
+        config: |
+          debug pathd pcep
+          segment-routing
+           traffic-eng
+            pcep
+             pce-config CONFIG
+              source-address ip 6.6.6.6
+             pce PCE
+              pce-initiated
+              address ip ${PCE_IP}
+              config CONFIG
+             pcc
+              peer PCE
+          !
+      isisd:
+        run: yes
+        config: |
+          interface lo
+           ip router isis 1
+           ipv6 router isis 1
+           isis passive
+          !
+          interface eth-rt4
+           ip router isis 1
+           ipv6 router isis 1
+           isis network point-to-point
+           isis hello-multiplier 3
+          !
+          interface eth-rt5
+           ip router isis 1
+           ipv6 router isis 1
+           isis network point-to-point
+           isis hello-multiplier 3
+          !
+          router isis 1
+           net 49.0000.0000.0000.0006.00
+           is-type level-1
+           redistribute ipv4 static level-1
+           redistribute ipv4 connected level-1
+           topology ipv6-unicast
+           segment-routing on
+           segment-routing global-block 16000 23999
+           segment-routing node-msd 8
+           segment-routing prefix 6.6.6.6/32 index 60 explicit-null
+           segment-routing prefix 2001:db8:1000::6/128 index 61 explicit-null
+          !
+    shell: |
+      ip route add 9.9.9.2/32 dev eth-dst
+      ip link add eth-rt6-pce type veth peer name eth-pce-rt6
+      ip addr add ${RT6_PCE_INT_IF_IP}/24 dev eth-rt6-pce
+      ip link set eth-pce-rt6 netns ${PCE_NETNS}
+      ip -n ${PCE_NETNS} addr add ${RT6_PCE_EXT_IF_IP}/24 dev eth-pce-rt6
+      ip link set eth-rt6-pce up
+      ip -n ${PCE_NETNS} link set eth-pce-rt6 up
+      ip route add ${RT6_PCE_EXT_IF_IP}/24 via ${RT6_PCE_INT_IF_IP} dev eth-rt6-pce src 6.6.6.6
+      ip -n ${PCE_NETNS} route add ${RT6_PCE_INT_IF_IP}/32 via ${RT6_PCE_EXT_IF_IP} dev eth-pce-rt6
+      ip -n ${PCE_NETNS} route add 6.6.6.6/32 via ${RT6_PCE_EXT_IF_IP} dev eth-pce-rt6
+
+  dst:
+    links:
+      lo:
+        ipv4: 9.9.9.2/32
+        ipv6: 2001:db8:1066::2/128
+        mpls: yes
+      eth-rt6:
+        peer: [rt6, eth-dst]
+        ipv4: 10.0.11.2/24
+        mpls: yes
+    frr:
+      zebra:
+        run: yes
+        config:
+    shell: |
+      ip route add 9.9.9.1/32 encap mpls 6666 via inet 10.0.11.1
+
+switches:
+  sw1:
+    links:
+      sw1-rt1:
+        peer: [rt1, rt1-sw1]
+      sw1-rt2:
+        peer: [rt2, rt2-sw1]
+      sw1-rt3:
+        peer: [rt3, rt3-sw1]
+
+frr:
+  #perf: yes
+  #valgrind: yes
+  base-configs:
+    all: |
+      hostname %(node)
+      password 1
+      log file %(logdir)/%(node)-%(daemon).log
+      log commands
+    zebra: |
+      debug zebra kernel
+      debug zebra packet
+      debug zebra mpls
+    isisd: |
+      debug isis events
+      debug isis route-events
+      debug isis spf-events
+      debug isis sr-events
+      debug isis lsp-gen
diff --git a/src/te/tests/service-descriptors.json b/src/te/tests/service-descriptors.json
new file mode 100644
index 0000000000000000000000000000000000000000..15023ac9da8ff443bad6274af9de8246db524358
--- /dev/null
+++ b/src/te/tests/service-descriptors.json
@@ -0,0 +1,24 @@
+{
+    "services": [
+        {
+            "service_id": {
+                "context_id": {"context_uuid": {"uuid": "admin"}},
+                "service_uuid": {"uuid": "2c025055-bf6c-4250-8560-cf62f2d29e72"}
+            },
+            "service_type": 4, "service_status": {"service_status": 1},
+            "service_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid":"RT1"}}, "endpoint_uuid": {"uuid":"eth-src"}},
+                {"device_id": {"device_uuid": {"uuid":"RT6"}}, "endpoint_uuid": {"uuid":"eth-dst"}}
+            ],
+            "service_constraints": [],
+            "service_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "/lsp-fw", "resource_value": {
+                    "symbolic_name": "foo", "binding_label": 1111
+                }}},
+                {"action": 1, "custom": {"resource_key": "/lsp-bw", "resource_value": {
+                    "symbolic_name": "bar", "binding_label": 6666
+                }}}
+            ]}
+        }
+    ]
+}
diff --git a/src/te/tests/start-testbed.sh b/src/te/tests/start-testbed.sh
new file mode 100755
index 0000000000000000000000000000000000000000..07a30e091252f753ff3c89c65378c6bbcde8bae3
--- /dev/null
+++ b/src/te/tests/start-testbed.sh
@@ -0,0 +1,58 @@
+#!/bin/bash
+# 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.
+
+set -e
+
+ROOTDIR="$( cd $( dirname $0 ); pwd )"
+RUNDIR="$( pwd )"
+NETGENDIR="${RUNDIR}/netgen"
+
+if [[ ! -f "${NETGENDIR}/exe/netgen" ]]; then
+    echo "Failed to find Netgen binary at ${NETGENDIR}/exe/netgen"
+    exit 1
+fi
+
+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 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}/netgen-config.yml" "${RUNDIR}/config.yml"
+cat "${ROOTDIR}/netgen-topology.yml.template" | envsubst > "${RUNDIR}/topology.yml"
+
+sudo -i bash -c "\
+    cd ${RUNDIR}/netgen;\
+    sysctl -w net.ipv4.conf.all.rp_filter=0;\
+    PATH=/usr/lib/frr:\$PATH ./exe/netgen ../topology.yml -c ../config.yml"
diff --git a/src/te/tests/test_te_service.py b/src/te/tests/test_te_service.py
new file mode 100644
index 0000000000000000000000000000000000000000..6237049d4ed7c69f2a6f12efc1ce0365fca7133b
--- /dev/null
+++ b/src/te/tests/test_te_service.py
@@ -0,0 +1,108 @@
+# 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.
+
+# Simple script to test GRPC calls to the TE service.
+# First get the TE service IP using:
+# > kubectl -n tfs get services
+# Change it in this script and run with:
+# > PYTHONPATH=./src python test_te_service.py
+
+import json, sys
+from common.proto.context_pb2 import ConfigActionEnum, Service, ServiceStatusEnum, ServiceTypeEnum
+from common.tools.grpc.Tools import grpc_message_to_json_string
+from service.client.TEServiceClient import TEServiceClient
+
+#  {"name": "", 
+#     "service_config": {
+#        "config_rules": [
+#           {
+#             "action": "CONFIGACTION_SET",
+#              "custom": {
+#                 "resource_key": "/lsp-fw",
+#                 "resource_value": "{\n\"binding_label\": 1111,\n\"symbolic_name\": \"foo\"\n}"}},
+#           {
+#              "action": "CONFIGACTION_SET",
+#              "custom": {
+#                 "resource_key": "/lsp-bw",
+#                 "resource_value": "{\n\"binding_label\": 6666,\n\"symbolic_name\": \"bar\"\n}"}}]},
+#        "service_constraints": [],
+#        "service_endpoint_ids": [
+#           {"device_id": {"device_uuid": {"uuid": "RT1"}}, "endpoint_uuid": {"uuid": "eth-src"}},
+#           {"device_id": {"device_uuid": {"uuid": "RT6"}}, "endpoint_uuid": {"uuid": "eth-dst"}}],
+#        "service_id": {"context_id": {"context_uuid": {"uuid": "admin"}},
+#        "service_uuid": {"uuid": "2c025055-bf6c-4250-8560-cf62f2d29e72"}},
+#        "service_status": {"service_status": "SERVICESTATUS_PLANNED"},
+#        "service_type": "SERVICETYPE_TE"}
+
+service = Service()
+service.service_id.context_id.context_uuid.uuid = 'admin'
+service.service_id.service_uuid.uuid = 'test-te-service'
+
+service.service_type = ServiceTypeEnum.SERVICETYPE_TE
+service.service_status.service_status = ServiceStatusEnum.SERVICESTATUS_PLANNED
+
+# SRC Endpoint:
+src_endpoint_id = service.service_endpoint_ids.add()
+src_endpoint_id.device_id.device_uuid.uuid = 'RT1'
+src_endpoint_id.endpoint_uuid.uuid = 'eth-src'
+
+# DST Endpoint:
+dst_endpoint_id = service.service_endpoint_ids.add()
+dst_endpoint_id.device_id.device_uuid.uuid = 'RT6'
+dst_endpoint_id.endpoint_uuid.uuid = 'eth-dst'
+
+# # Capacity SLA
+# sla_capacity = service.service_constraints.add()
+# sla_capacity.sla_capacity.capacity_gbps = 10.0
+
+# # Latency SLA
+# sla_latency = service.service_constraints.add()
+# sla_latency.sla_latency.e2e_latency_ms = 20.0
+
+# Example config rules:
+config_rule_1 = service.service_config.config_rules.add()
+config_rule_1.action = ConfigActionEnum.CONFIGACTION_SET
+config_rule_1.custom.resource_key = '/lsp-fw'
+config_rule_1.custom.resource_value = json.dumps({
+    'binding_label': 1111, 'symbolic_name': "foo"
+})
+
+config_rule_2 = service.service_config.config_rules.add()
+config_rule_2.action = ConfigActionEnum.CONFIGACTION_SET
+config_rule_2.custom.resource_key = '/lsp-bw'
+config_rule_2.custom.resource_value = json.dumps({
+    'binding_label': 6666, 'symbolic_name': "bar"
+})
+
+def main():
+    # Connect:
+    te_service_client = TEServiceClient(host='XXX.XXX.XXX.XXX', port=10030)
+
+    # RequestLSP
+    print('request:', grpc_message_to_json_string(service))
+    service_status = te_service_client.RequestLSP(service)
+    print('response:', grpc_message_to_json_string(service_status))
+
+    # DeleteLSP
+    #print('request:', grpc_message_to_json_string(service))
+    #service_status = te_service_client.DeleteLSP(service)
+    #print('response:', grpc_message_to_json_string(service_status))
+
+    # Close:
+    te_service_client.close()
+
+    return 0
+
+if __name__ == '__main__':
+    sys.exit(main())
diff --git a/src/te/tests/topology-descriptors.json b/src/te/tests/topology-descriptors.json
new file mode 100644
index 0000000000000000000000000000000000000000..a34d8ce09b3367bce0943ef3de8565baec776842
--- /dev/null
+++ b/src/te/tests/topology-descriptors.json
@@ -0,0 +1,195 @@
+{
+    "contexts": [
+        {
+            "context_id": {"context_uuid": {"uuid": "admin"}},
+            "topology_ids": [],
+            "service_ids": []
+        }
+    ],
+    "topologies": [
+        {
+            "topology_id": {"topology_uuid": {"uuid": "admin"}, "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_operational_status": 2, "device_drivers": [0], "device_endpoints": [],
+            "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_id": {"device_uuid": {"uuid": "RT1"}}, "device_type": "emu-packet-router",
+            "device_operational_status": 2, "device_drivers": [0], "device_endpoints": [],
+            "device_config": {"config_rules": [
+                {"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"}
+                ]}}},
+                {"action": 1, "custom": {"resource_key": "/te_data", "resource_value": {"mpls_label": 16010, "pcc_address": "1.1.1.1"}}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "RT2"}}, "device_type": "emu-packet-router",
+            "device_operational_status": 2, "device_drivers": [0], "device_endpoints": [],
+            "device_config": {"config_rules": [
+                {"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"}
+                ]}}},
+                {"action": 1, "custom": {"resource_key": "/te_data", "resource_value": {"mpls_label": 16020, "pcc_address": "2.2.2.2"}}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "RT3"}}, "device_type": "emu-packet-router",
+            "device_operational_status": 2, "device_drivers": [0], "device_endpoints": [],
+            "device_config": {"config_rules": [
+                {"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"}
+                ]}}},
+                {"action": 1, "custom": {"resource_key": "/te_data", "resource_value": {"mpls_label": 16030, "pcc_address": "3.3.3.3"}}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "RT4"}}, "device_type": "emu-packet-router",
+            "device_operational_status": 2, "device_drivers": [0], "device_endpoints": [],
+            "device_config": {"config_rules": [
+                {"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"}
+                ]}}},
+                {"action": 1, "custom": {"resource_key": "/te_data", "resource_value": {"mpls_label": 16040, "pcc_address": "4.4.4.4"}}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "RT5"}}, "device_type": "emu-packet-router",
+            "device_operational_status": 2, "device_drivers": [0], "device_endpoints": [],
+            "device_config": {"config_rules": [
+                {"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"}
+                ]}}},
+                {"action": 1, "custom": {"resource_key": "/te_data", "resource_value": {"mpls_label": 16050, "pcc_address": "5.5.5.5"}}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "RT6"}}, "device_type": "emu-packet-router",
+            "device_operational_status": 2, "device_drivers": [0], "device_endpoints": [],
+            "device_config": {"config_rules": [
+                {"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"}
+                ]}}},
+                {"action": 1, "custom": {"resource_key": "/te_data", "resource_value": {"mpls_label": 16060, "pcc_address": "6.6.6.6"}}}
+            ]}
+        }
+    ],
+    "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"}}
+            ]
+        }
+    ]
+}
diff --git a/src/te/tutorial/1-6-setup-erlang-environmnet.md b/src/te/tutorial/1-6-setup-erlang-environmnet.md
new file mode 100644
index 0000000000000000000000000000000000000000..2d1519a6f3dc0e4bb686bfa107c0ca5b16eb6cd2
--- /dev/null
+++ b/src/te/tutorial/1-6-setup-erlang-environmnet.md
@@ -0,0 +1,56 @@
+# 1.5. Setup Erlang Environment
+
+First we need to install Erlang. There is multiple way, for development we will
+be using *ASDF*, a tool that allows the installation of multiple version of Erlang
+at the same time, and switch from one version to the other at will.
+
+
+## 1.5.1. Setup Erlang using asdf
+
+First, install any missing dependencies:
+
+    sudo apt install curl git autoconf libncurses-dev build-essential m4 libssl-dev 
+
+Download *ASDF* tool to the local account:
+
+    git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.10.2
+
+Make *ASDF* activate on login by adding these lines at the end of the `~/.bashrc` file:
+
+    . $HOME/.asdf/asdf.sh
+    . $HOME/.asdf/completions/asdf.bash
+
+Logout and log back in to activate *ASDF*.
+
+*ASDF* supports multiple tools by installing there corresponding plugins.
+Install *ASDF* plugin for Erlang:
+
+    asdf plugin add erlang https://github.com/asdf-vm/asdf-erlang.git
+
+Install a version of Erlang:
+
+    asdf install erlang 24.3.4.2
+
+Activate Erlang locally for TFS controller. This will create a local file
+called `.tool-versions` defining which version of the tools to use when
+running under the current directory:
+
+    cd tfs-ctrl/
+    asdf local erlang 24.3.4.2
+
+Erlang projects uses a build tool called rebar3. It is used to manager project
+dependenecies, compile a project and generate project releases.
+Install rebar3 localy from source:
+
+    cd ~
+    git clone https://github.com/erlang/rebar3.git
+    cd rebar3
+    asdf local erlang 24.3.4.2
+    ./bootstrap
+    ./rebar3 local install
+
+Update `~/.bashrc` to use rebar3 by adding this line at the end:
+
+    export PATH=$HOME/.cache/rebar3/bin:$PATH
+
+Logout and log back in.
diff --git a/src/te/tutorial/2-6-te-demo.md b/src/te/tutorial/2-6-te-demo.md
new file mode 100644
index 0000000000000000000000000000000000000000..c53a60c51cf20717e51fb514c5b02d6594ce7904
--- /dev/null
+++ b/src/te/tutorial/2-6-te-demo.md
@@ -0,0 +1,130 @@
+# 2.6. Traffic Engineering Demo
+
+## Setup Test-Bed
+
+### Setup libyang
+
+    $ sudo apt update
+    $ sudo apt-get install cmake libpcre2-dev git make build-essential
+    $ mkdir -p ~/testbed
+    $ cd ~/testbed
+    $ git clone git@github.com:CESNET/libyang.git
+    $ cd libyang
+    $ git checkout v2.0.0
+    $ mkdir build; cd build
+    $ cmake -D CMAKE_INSTALL_PREFIX:PATH=/usr -D CMAKE_BUILD_TYPE:String="Release" ..
+    $ make
+    $ sudo make install
+
+
+### Setup Free Range Routing
+
+    $ sudo apt update
+    $ sudo apt-get install git autoconf automake libtool make libreadline-dev texinfo pkg-config libpam0g-dev libjson-c-dev bison flex libc-ares-dev python3-dev python3-sphinx install-info build-essential libsnmp-dev perl libcap-dev python2 libelf-dev libunwind-dev protobuf-c-compiler libprotobuf-c-dev libsystemd-dev
+    $ mkdir -p ~/testbed
+    $ cd ~/testbed
+    $ git clone git@github.com:opensourcerouting/frr.git
+    $ cd frr
+    $ curl https://bootstrap.pypa.io/pip/2.7/get-pip.py --output get-pip.py
+    $ sudo python2 ./get-pip.py
+    $ export CFLAGS="-I /usr/local/include -g -O2"
+    $ sudo rm -rf /usr/lib/frr
+    $ sudo rm -rf /var/run/frr
+    $ sudo mkdir -p /etc/frr
+    $ sudo mkdir -p /var/run/frr
+    $ sudo chown -R root:root /etc/frr
+    $ ./bootstrap.sh
+    $ ./configure \
+        --prefix=/usr \
+        --includedir=\${prefix}/include \
+        --enable-exampledir=\${prefix}/share/doc/frr/examples \
+        --bindir=\${prefix}/bin \
+        --sbindir=\${prefix}/lib/frr \
+        --libdir=\${prefix}/lib/frr \
+        --libexecdir=\${prefix}/lib/frr \
+        --localstatedir=/var/run/frr \
+        --sysconfdir=/etc/frr \
+        --with-moduledir=\${prefix}/lib/frr/modules \
+        --enable-configfile-mask=0640 \
+        --enable-logfile-mask=0640 \
+        --enable-snmp=agentx \
+        --enable-multipath=64 \
+        --enable-user=root \
+        --enable-group=root \
+        --enable-vty-group=root \
+        --enable-vtysh \
+        --with-pkg-git-version \
+        --with-pkg-extra-version=-MyOwnFRRVersion \
+        --enable-systemd=yes \
+        --enable-config-rollbacks \
+        --enable-pathd \
+        --enable-pcep
+    $ make
+    $ sudo make install
+
+
+### Setup NetGen
+
+    $ sudo apt update
+    $ sudo apt-get install git ruby ruby-dev tmux gettext-base
+    $ mkdir -p ~/testbed
+    $ cd ~/testbed
+    $ git clone git@github.com:sylane/netgen.git
+    $ cd netgen
+    $ git checkout teraflow
+    $ sudo gem install bundler:1.15
+    $ bundle _1.15_ install
+
+
+### Run the Test-Bed
+
+First load the [teraflow configuration file](~/tfs-ctrl/src/te/tests/topology-descriptors.json) using the webui.
+
+In first console:
+    $ cd ~/testbed
+    $ ../tfs-ctrl/src/te/tests/start-testbed.sh
+
+Then in second console:
+    $ sudo -i
+    # cd /tmp/negen
+    # ./tmux.sh
+
+Be sure that both PCC connected to the TE service before going further.
+This can be done by looking at the TE service log:
+
+    $ kubectl --namespace tfs logs $(kubectl --namespace tfs get pods --selector=app=teservice -o name) -c server
+
+### 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) -c server -- /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).
+
+Another option is to use the router names:
+
+    1> {ok, Flow1to6} = epce_server:initiate_flow(<<"foo">>, <<"RT1">>, <<"RT6">>, 1111).
+    2> {ok, Flow6to1} = epce_server:initiate_flow(<<"bar">>, <<"RT6">>, <<"RT1">>, 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]).
+
+### Setup a flow using the GRPC test script
+
+This does the same as the the setup from the Erlang console, but through GRPC.
+After deploying Teraflow (with te), get the te service ip using:
+
+    $ kubectl -n tfs get services
+
+Replace the IP in the python script `src/te/tests/test_te_service.py`.
+Be sure the topology as been loaded, and netgen started as described previously,
+and run the following command from the root of the teraflow controller:
+
+    $ PYTHONPATH=./src python src/te/tests/test_te_service.py