diff --git a/ofc23 b/ofc23
new file mode 120000
index 0000000000000000000000000000000000000000..a1135d4c59a81997350864319a6c267eaaf9ed93
--- /dev/null
+++ b/ofc23
@@ -0,0 +1 @@
+src/tests/ofc23/
\ No newline at end of file
diff --git a/proto/context.proto b/proto/context.proto
index 49d16229cdac5de84f25cfaa7d196d25184f46f0..2dfbb7805eb444ee94e27bb00ca05d9a1c83b8ec 100644
--- a/proto/context.proto
+++ b/proto/context.proto
@@ -191,6 +191,7 @@ enum DeviceDriverEnum {
   DEVICEDRIVER_IETF_NETWORK_TOPOLOGY = 4;
   DEVICEDRIVER_ONF_TR_352 = 5;
   DEVICEDRIVER_XR = 6;
+  DEVICEDRIVER_IETF_L2VPN = 7;
 }
 
 enum DeviceOperationalStatusEnum {
diff --git a/scripts/show_logs_compute.sh b/scripts/show_logs_compute.sh
index fc992eb43e5872b4522db6f5c8ce39207f12d559..f0c24b63aa7b7e5c6678659c34dee34e8ce5b49e 100755
--- a/scripts/show_logs_compute.sh
+++ b/scripts/show_logs_compute.sh
@@ -24,4 +24,4 @@ export TFS_K8S_NAMESPACE=${TFS_K8S_NAMESPACE:-"tfs"}
 # Automated steps start here
 ########################################################################################################################
 
-kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/computeservice
+kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/computeservice -c server
diff --git a/scripts/show_logs_device.sh b/scripts/show_logs_device.sh
index 6a77c38152716f1e6fbf320671dda25d974431c8..e643f563a6b8ba250985b013cecc9340c53c9411 100755
--- a/scripts/show_logs_device.sh
+++ b/scripts/show_logs_device.sh
@@ -24,4 +24,4 @@ export TFS_K8S_NAMESPACE=${TFS_K8S_NAMESPACE:-"tfs"}
 # Automated steps start here
 ########################################################################################################################
 
-kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/deviceservice
+kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/deviceservice -c server
diff --git a/scripts/show_logs_load_generator.sh b/scripts/show_logs_load_generator.sh
index d0f2527d74840d48a10e0ec7ba018f513eea2c52..51438f181f5492a1c9c9bc8dd0b5a76f6db1046c 100755
--- a/scripts/show_logs_load_generator.sh
+++ b/scripts/show_logs_load_generator.sh
@@ -24,4 +24,4 @@ export TFS_K8S_NAMESPACE=${TFS_K8S_NAMESPACE:-"tfs"}
 # Automated steps start here
 ########################################################################################################################
 
-kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/load-generatorservice
+kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/load-generatorservice -c server
diff --git a/scripts/show_logs_monitoring.sh b/scripts/show_logs_monitoring.sh
index 1a152a32216545f53607880c3908266f4ac41e95..61b0b5cc024f89daeffc0745c2689d85500f4115 100755
--- a/scripts/show_logs_monitoring.sh
+++ b/scripts/show_logs_monitoring.sh
@@ -24,4 +24,4 @@ export TFS_K8S_NAMESPACE=${TFS_K8S_NAMESPACE:-"tfs"}
 # Automated steps start here
 ########################################################################################################################
 
-kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/monitoringservice server
+kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/monitoringservice -c server
diff --git a/scripts/show_logs_service.sh b/scripts/show_logs_service.sh
index 7ca1c1c2f4286a5fc46f7d36197d376472b447ed..cc75e19c64935f99c7919f9371717b91b0e6b3cb 100755
--- a/scripts/show_logs_service.sh
+++ b/scripts/show_logs_service.sh
@@ -24,4 +24,4 @@ export TFS_K8S_NAMESPACE=${TFS_K8S_NAMESPACE:-"tfs"}
 # Automated steps start here
 ########################################################################################################################
 
-kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/serviceservice
+kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/serviceservice -c server
diff --git a/scripts/show_logs_slice.sh b/scripts/show_logs_slice.sh
index c71bc92eaa4a8d411372fc0ad4194881a5a2a9c8..7fa8091cce081ff7cef152f465bea8e426b40124 100755
--- a/scripts/show_logs_slice.sh
+++ b/scripts/show_logs_slice.sh
@@ -24,4 +24,4 @@ export TFS_K8S_NAMESPACE=${TFS_K8S_NAMESPACE:-"tfs"}
 # Automated steps start here
 ########################################################################################################################
 
-kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/sliceservice
+kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/sliceservice -c server
diff --git a/src/automation/src/main/java/eu/teraflow/automation/Serializer.java b/src/automation/src/main/java/eu/teraflow/automation/Serializer.java
index 08691b5266b8172a2bd0449df870033bd2664dd0..b0729aa55b25da030f9722330e22a0976a3d007f 100644
--- a/src/automation/src/main/java/eu/teraflow/automation/Serializer.java
+++ b/src/automation/src/main/java/eu/teraflow/automation/Serializer.java
@@ -853,6 +853,8 @@ public class Serializer {
                 return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352;
             case XR:
                 return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_XR;
+            case IETF_L2VPN:
+                return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN;
             case UNDEFINED:
             default:
                 return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED;
@@ -874,6 +876,8 @@ public class Serializer {
                 return DeviceDriverEnum.ONF_TR_352;
             case DEVICEDRIVER_XR:
                 return DeviceDriverEnum.XR;
+            case DEVICEDRIVER_IETF_L2VPN:
+                return DeviceDriverEnum.IETF_L2VPN;
             case DEVICEDRIVER_UNDEFINED:
             case UNRECOGNIZED:
             default:
diff --git a/src/automation/src/main/java/eu/teraflow/automation/context/model/DeviceDriverEnum.java b/src/automation/src/main/java/eu/teraflow/automation/context/model/DeviceDriverEnum.java
index a364bb0e3cb821d20061d574428984acacb0cc46..3a26937e79d0df2cfead305a10ccadf3c54eae89 100644
--- a/src/automation/src/main/java/eu/teraflow/automation/context/model/DeviceDriverEnum.java
+++ b/src/automation/src/main/java/eu/teraflow/automation/context/model/DeviceDriverEnum.java
@@ -23,5 +23,6 @@ public enum DeviceDriverEnum {
     P4,
     IETF_NETWORK_TOPOLOGY,
     ONF_TR_352,
-    XR
+    XR,
+    IETF_L2VPN
 }
diff --git a/src/automation/src/test/java/eu/teraflow/automation/SerializerTest.java b/src/automation/src/test/java/eu/teraflow/automation/SerializerTest.java
index 494e608a105897fe005a18b7041b34fe95b40f8b..0931054c682dede502fb9f22bf911439e52c2140 100644
--- a/src/automation/src/test/java/eu/teraflow/automation/SerializerTest.java
+++ b/src/automation/src/test/java/eu/teraflow/automation/SerializerTest.java
@@ -1215,6 +1215,8 @@ class SerializerTest {
                         DeviceDriverEnum.ONF_TR_352,
                         ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352),
                 Arguments.of(DeviceDriverEnum.XR, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_XR),
+                Arguments.of(
+                        DeviceDriverEnum.IETF_L2VPN, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN),
                 Arguments.of(
                         DeviceDriverEnum.UNDEFINED, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED));
     }
diff --git a/src/automation/target/generated-sources/grpc/context/ContextOuterClass.java b/src/automation/target/generated-sources/grpc/context/ContextOuterClass.java
index 060e81a556893b1ca3c60928569983506bff3672..b1bccdeccf564b0d8d7bd2a8606f614b00ede972 100644
--- a/src/automation/target/generated-sources/grpc/context/ContextOuterClass.java
+++ b/src/automation/target/generated-sources/grpc/context/ContextOuterClass.java
@@ -177,6 +177,10 @@ public final class ContextOuterClass {
      * <code>DEVICEDRIVER_XR = 6;</code>
      */
     DEVICEDRIVER_XR(6),
+    /**
+     * <code>DEVICEDRIVER_IETF_L2VPN = 7;</code>
+     */
+    DEVICEDRIVER_IETF_L2VPN(7),
     UNRECOGNIZED(-1),
     ;
 
@@ -212,6 +216,10 @@ public final class ContextOuterClass {
      * <code>DEVICEDRIVER_XR = 6;</code>
      */
     public static final int DEVICEDRIVER_XR_VALUE = 6;
+    /**
+     * <code>DEVICEDRIVER_IETF_L2VPN = 7;</code>
+     */
+    public static final int DEVICEDRIVER_IETF_L2VPN_VALUE = 7;
 
 
     public final int getNumber() {
@@ -245,6 +253,7 @@ public final class ContextOuterClass {
         case 4: return DEVICEDRIVER_IETF_NETWORK_TOPOLOGY;
         case 5: return DEVICEDRIVER_ONF_TR_352;
         case 6: return DEVICEDRIVER_XR;
+        case 7: return DEVICEDRIVER_IETF_L2VPN;
         default: return null;
       }
     }
diff --git a/src/common/DeviceTypes.py b/src/common/DeviceTypes.py
index 99255defdb6b5ee155607536a2e13d23b97b2d3a..bb8948585f163aeb84ee758b8581bc6509d29799 100644
--- a/src/common/DeviceTypes.py
+++ b/src/common/DeviceTypes.py
@@ -25,9 +25,12 @@ class DeviceTypeEnum(Enum):
     EMULATED_OPEN_LINE_SYSTEM       = 'emu-open-line-system'
     EMULATED_OPTICAL_ROADM          = 'emu-optical-roadm'
     EMULATED_OPTICAL_TRANSPONDER    = 'emu-optical-transponder'
+    EMULATED_OPTICAL_SPLITTER       = 'emu-optical-splitter'        # passive component required for XR Constellation
     EMULATED_P4_SWITCH              = 'emu-p4-switch'
+    EMULATED_PACKET_RADIO_ROUTER    = 'emu-packet-radio-router'
     EMULATED_PACKET_ROUTER          = 'emu-packet-router'
     EMULATED_PACKET_SWITCH          = 'emu-packet-switch'
+    EMULATED_XR_CONSTELLATION       = 'emu-xr-constellation'
 
     # Real device types
     DATACENTER                      = 'datacenter'
@@ -36,6 +39,10 @@ class DeviceTypeEnum(Enum):
     OPTICAL_ROADM                   = 'optical-roadm'
     OPTICAL_TRANSPONDER             = 'optical-transponder'
     P4_SWITCH                       = 'p4-switch'
+    PACKET_RADIO_ROUTER             = 'packet-radio-router'
     PACKET_ROUTER                   = 'packet-router'
     PACKET_SWITCH                   = 'packet-switch'
-    XR_CONSTELLATION                = 'xr-constellation'
\ No newline at end of file
+    XR_CONSTELLATION                = 'xr-constellation'
+
+    # ETSI TeraFlowSDN controller
+    TERAFLOWSDN_CONTROLLER          = 'teraflowsdn'
diff --git a/src/common/tools/object_factory/Device.py b/src/common/tools/object_factory/Device.py
index 0cc4555d455bf28ac2143a5d58b87e084a8360c7..66c87b14dd866d44b5d48addf93d172aea962f8e 100644
--- a/src/common/tools/object_factory/Device.py
+++ b/src/common/tools/object_factory/Device.py
@@ -43,6 +43,9 @@ DEVICE_MICROWAVE_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY]
 DEVICE_P4_TYPE      = DeviceTypeEnum.P4_SWITCH.value
 DEVICE_P4_DRIVERS   = [DeviceDriverEnum.DEVICEDRIVER_P4]
 
+DEVICE_TFS_TYPE    = DeviceTypeEnum.TERAFLOWSDN_CONTROLLER.value
+DEVICE_TFS_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN]
+
 def json_device_id(device_uuid : str):
     return {'device_uuid': {'uuid': device_uuid}}
 
@@ -120,6 +123,13 @@ def json_device_p4_disabled(
     return json_device(
         device_uuid, DEVICE_P4_TYPE, DEVICE_DISABLED, endpoints=endpoints, config_rules=config_rules, drivers=drivers)
 
+def json_device_tfs_disabled(
+        device_uuid : str, endpoints : List[Dict] = [], config_rules : List[Dict] = [],
+        drivers : List[Dict] = DEVICE_TFS_DRIVERS
+    ):
+    return json_device(
+        device_uuid, DEVICE_TFS_TYPE, DEVICE_DISABLED, endpoints=endpoints, config_rules=config_rules, drivers=drivers)
+
 def json_device_connect_rules(address : str, port : int, settings : Dict = {}):
     return [
         json_config_rule_set('_connect/address',  address),
diff --git a/src/common/type_checkers/Assertions.py b/src/common/type_checkers/Assertions.py
index c0442d8770c682ac1eea032980b58e7028be90c4..ba82e535ec958104bd14abf625eb6cd38c2a08ee 100644
--- a/src/common/type_checkers/Assertions.py
+++ b/src/common/type_checkers/Assertions.py
@@ -33,6 +33,7 @@ def validate_device_driver_enum(message):
         'DEVICEDRIVER_IETF_NETWORK_TOPOLOGY',
         'DEVICEDRIVER_ONF_TR_352',
         'DEVICEDRIVER_XR',
+        'DEVICEDRIVER_IETF_L2VPN',
     ]
 
 def validate_device_operational_status_enum(message):
diff --git a/src/compute/service/rest_server/nbi_plugins/ietf_l2vpn/Constants.py b/src/compute/service/rest_server/nbi_plugins/ietf_l2vpn/Constants.py
index f95b532af4ba01968d17bc3958e1cffbf84a5e7f..ed25dbab3cd6b07ef73d64c5d37ad64e85353c02 100644
--- a/src/compute/service/rest_server/nbi_plugins/ietf_l2vpn/Constants.py
+++ b/src/compute/service/rest_server/nbi_plugins/ietf_l2vpn/Constants.py
@@ -74,4 +74,18 @@ BEARER_MAPPINGS = {
     'R3:1/3': ('R3', '1/3', '5.3.1.3', None, 0, None, None, None, None),
     'R4:1/2': ('R4', '1/2', '5.4.1.2', None, 0, None, None, None, None),
     'R4:1/3': ('R4', '1/3', '5.4.1.3', None, 0, None, None, None, None),
+
+    # OFC'23
+    'PE1:1/1': ('PE1', '1/1', '10.1.1.1', None, 0, None, None, None, None),
+    'PE1:1/2': ('PE1', '1/2', '10.1.1.2', None, 0, None, None, None, None),
+    'PE2:1/1': ('PE2', '1/1', '10.2.1.1', None, 0, None, None, None, None),
+    'PE2:1/2': ('PE2', '1/2', '10.2.1.2', None, 0, None, None, None, None),
+    'PE3:1/1': ('PE3', '1/1', '10.3.1.1', None, 0, None, None, None, None),
+    'PE3:1/2': ('PE3', '1/2', '10.3.1.2', None, 0, None, None, None, None),
+    'PE4:1/1': ('PE4', '1/1', '10.4.1.1', None, 0, None, None, None, None),
+    'PE4:1/2': ('PE4', '1/2', '10.4.1.2', None, 0, None, None, None, None),
+
+    'R149:eth-1/0/22': ('R149', 'eth-1/0/22', '5.5.5.5', None, 0, None, None, '5.5.5.1', '100'),
+    'R155:eth-1/0/22': ('R155', 'eth-1/0/22', '5.5.5.1', None, 0, None, None, '5.5.5.5', '100'),
+    'R199:eth-1/0/21': ('R199', 'eth-1/0/21', '5.5.5.6', None, 0, None, None, '5.5.5.5', '100'),
 }
diff --git a/src/context/service/database/models/enums/DeviceDriver.py b/src/context/service/database/models/enums/DeviceDriver.py
index 6997e7dfbff6bc1d4b6452a28f11cdac9aae412f..a612803e235de2c6d2d8c91052416a675a3a3085 100644
--- a/src/context/service/database/models/enums/DeviceDriver.py
+++ b/src/context/service/database/models/enums/DeviceDriver.py
@@ -24,6 +24,7 @@ class ORM_DeviceDriverEnum(enum.Enum):
     IETF_NETWORK_TOPOLOGY = DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY
     ONF_TR_352            = DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352
     XR                    = DeviceDriverEnum.DEVICEDRIVER_XR
+    IETF_L2VPN            = DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN
 
 grpc_to_enum__device_driver = functools.partial(
     grpc_to_enum, DeviceDriverEnum, ORM_DeviceDriverEnum)
diff --git a/src/device/service/DeviceServiceServicerImpl.py b/src/device/service/DeviceServiceServicerImpl.py
index be40e64ecd25a5c46c23d5ec0a73a2484b65691d..2b08b6c7e03cfd50557f25f99ffea3032dbb811e 100644
--- a/src/device/service/DeviceServiceServicerImpl.py
+++ b/src/device/service/DeviceServiceServicerImpl.py
@@ -12,11 +12,12 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from typing import Dict
 import grpc, logging
 from common.method_wrappers.Decorator import MetricsPool, safe_and_metered_rpc_method
 from common.method_wrappers.ServiceExceptions import NotFoundException, OperationFailedException
 from common.proto.context_pb2 import (
-    Device, DeviceConfig, DeviceDriverEnum, DeviceId, DeviceOperationalStatusEnum, Empty)
+    Device, DeviceConfig, DeviceDriverEnum, DeviceId, DeviceOperationalStatusEnum, Empty, Link)
 from common.proto.device_pb2 import MonitoringSettings
 from common.proto.device_pb2_grpc import DeviceServiceServicer
 from common.tools.context_queries.Device import get_device
@@ -28,8 +29,8 @@ from .monitoring.MonitoringLoops import MonitoringLoops
 from .ErrorMessages import ERROR_MISSING_DRIVER, ERROR_MISSING_KPI
 from .Tools import (
     check_connect_rules, check_no_endpoints, compute_rules_to_add_delete, configure_rules, deconfigure_rules,
-    populate_config_rules, populate_endpoint_monitoring_resources, populate_endpoints, populate_initial_config_rules,
-    subscribe_kpi, unsubscribe_kpi, update_endpoints)
+    get_device_controller_uuid, populate_config_rules, populate_endpoint_monitoring_resources, populate_endpoints,
+    populate_initial_config_rules, subscribe_kpi, unsubscribe_kpi, update_endpoints)
 
 LOGGER = logging.getLogger(__name__)
 
@@ -73,9 +74,16 @@ class DeviceServiceServicerImpl(DeviceServiceServicer):
 
             errors = []
 
+            # Sub-devices and sub-links are exposed by intermediate controllers or represent mgmt links.
+            # They are used to assist in path computation algorithms, and/or to identify dependencies
+            # (which controller is in charge of which sub-device).
+            new_sub_devices : Dict[str, Device] = dict()
+            new_sub_links : Dict[str, Link] = dict()
+
             if len(device.device_endpoints) == 0:
                 # created from request, populate endpoints using driver
-                errors.extend(populate_endpoints(device, driver, self.monitoring_loops))
+                errors.extend(populate_endpoints(
+                    device, driver, self.monitoring_loops, new_sub_devices, new_sub_links))
 
             if len(device.device_config.config_rules) == len(connection_config_rules):
                 # created from request, populate config rules using driver
@@ -87,12 +95,20 @@ class DeviceServiceServicerImpl(DeviceServiceServicer):
                 for error in errors: LOGGER.error(error)
                 raise OperationFailedException('AddDevice', extra_details=errors)
 
+            device.device_operational_status = DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_DISABLED
             device_id = context_client.SetDevice(device)
 
+            for sub_device in new_sub_devices.values():
+                context_client.SetDevice(sub_device)
+
+            for sub_links in new_sub_links.values():
+                context_client.SetLink(sub_links)
+
             # Update endpoint monitoring resources with UUIDs
             device_with_uuids = context_client.GetDevice(device_id)
             populate_endpoint_monitoring_resources(device_with_uuids, self.monitoring_loops)
 
+            context_client.close()
             return device_id
         finally:
             self.mutex_queues.signal_done(device_uuid)
@@ -109,6 +125,13 @@ class DeviceServiceServicerImpl(DeviceServiceServicer):
             if device is None:
                 raise NotFoundException('Device', device_uuid, extra_details='loading in ConfigureDevice')
 
+            device_controller_uuid = get_device_controller_uuid(device)
+            if device_controller_uuid is not None:
+                device = get_device(context_client, device_controller_uuid, rw_copy=True)
+                if device is None:
+                    raise NotFoundException(
+                        'Device', device_controller_uuid, extra_details='loading in ConfigureDevice')
+
             driver : _Driver = get_driver(self.driver_instance_cache, device)
             if driver is None:
                 msg = ERROR_MISSING_DRIVER.format(device_uuid=str(device_uuid))
diff --git a/src/device/service/ErrorMessages.py b/src/device/service/ErrorMessages.py
index 1fbea721fdc52bdf759581c0525b30b1206ae844..bb7702e4e629bad43df4870d923f0a1829378e2e 100644
--- a/src/device/service/ErrorMessages.py
+++ b/src/device/service/ErrorMessages.py
@@ -14,9 +14,9 @@
 
 _DEVICE_ID          = 'DeviceId({device_uuid:s})'
 _ENDPOINT_ID        = 'EndpointId({endpoint_uuid:s})'
-_ENDPOINT_DATA      = 'EndpointId({endpoint_data:s})'
 _KPI                = 'Kpi({kpi_uuid:s})'
 _DEVICE_ENDPOINT_ID = _DEVICE_ID + '/' + _ENDPOINT_ID
+_RESOURCE           = 'Resource({resource_data:s})'
 _RESOURCE_KEY       = 'Resource(key={resource_key:s})'
 _RESOURCE_KEY_VALUE = 'Resource(key={resource_key:s}, value={resource_value:s})'
 _SUBSCRIPTION       = 'Subscription(key={subscr_key:s}, duration={subscr_duration:s}, interval={subscr_interval:s})'
@@ -26,7 +26,8 @@ _ERROR              = 'Error({error:s})'
 ERROR_MISSING_DRIVER = _DEVICE_ID + ' has not been added to this Device instance'
 ERROR_MISSING_KPI    = _KPI + ' not found'
 
-ERROR_BAD_ENDPOINT   = _DEVICE_ID + ': GetConfig retrieved malformed ' + _ENDPOINT_DATA
+ERROR_BAD_RESOURCE   = _DEVICE_ID + ': GetConfig retrieved malformed ' + _RESOURCE
+ERROR_UNSUP_RESOURCE = _DEVICE_ID + ': GetConfig retrieved unsupported ' + _RESOURCE
 
 ERROR_GET            = _DEVICE_ID + ': Unable to Get ' + _RESOURCE_KEY + '; ' + _ERROR
 ERROR_GET_INIT       = _DEVICE_ID + ': Unable to Get Initial ' + _RESOURCE_KEY + '; ' + _ERROR
diff --git a/src/device/service/Tools.py b/src/device/service/Tools.py
index 571e8acdab7fc243c22923a69202c89db88c8ce3..cd3af07e3324e50ff43eb5e653c4c46771a5507e 100644
--- a/src/device/service/Tools.py
+++ b/src/device/service/Tools.py
@@ -13,19 +13,20 @@
 # limitations under the License.
 
 import json, logging
-from typing import Any, Dict, List, Tuple, Union
+from typing import Any, Dict, List, Optional, Tuple, Union
 from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME
 from common.method_wrappers.ServiceExceptions import InvalidArgumentException
-from common.proto.context_pb2 import ConfigActionEnum, Device, DeviceConfig
+from common.proto.context_pb2 import ConfigActionEnum, Device, DeviceConfig, Link
 from common.proto.device_pb2 import MonitoringSettings
 from common.proto.kpi_sample_types_pb2 import KpiSampleType
 from common.tools.grpc.ConfigRules import update_config_rule_custom
 from common.tools.grpc.Tools import grpc_message_to_json
+from context.client.ContextClient import ContextClient
 from .driver_api._Driver import _Driver, RESOURCE_ENDPOINTS
 from .monitoring.MonitoringLoops import MonitoringLoops
 from .ErrorMessages import (
-    ERROR_BAD_ENDPOINT, ERROR_DELETE, ERROR_GET, ERROR_GET_INIT, ERROR_MISSING_KPI, ERROR_SAMPLETYPE, ERROR_SET,
-    ERROR_SUBSCRIBE, ERROR_UNSUBSCRIBE
+    ERROR_BAD_RESOURCE, ERROR_DELETE, ERROR_GET, ERROR_GET_INIT, ERROR_MISSING_KPI, ERROR_SAMPLETYPE, ERROR_SET,
+    ERROR_SUBSCRIBE, ERROR_UNSUBSCRIBE, ERROR_UNSUP_RESOURCE
 )
 
 LOGGER = logging.getLogger(__name__)
@@ -77,19 +78,51 @@ def check_no_endpoints(device_endpoints) -> None:
         extra_details='RPC method AddDevice does not accept Endpoints. Endpoints are discovered through '\
                         'interrogation of the physical device.')
 
-def populate_endpoints(device : Device, driver : _Driver, monitoring_loops : MonitoringLoops) -> List[str]:
+def get_device_controller_uuid(device : Device) -> Optional[str]:
+    for config_rule in device.device_config.config_rules:
+        if config_rule.WhichOneof('config_rule') != 'custom': continue
+        if config_rule.custom.resource_key != '_controller': continue
+        device_controller_id = json.loads(config_rule.custom.resource_value)
+        return device_controller_id['uuid']
+    return None
+
+def populate_endpoints(
+    device : Device, driver : _Driver, monitoring_loops : MonitoringLoops,
+    new_sub_devices : Dict[str, Device], new_sub_links : Dict[str, Link]
+) -> List[str]:
     device_uuid = device.device_id.device_uuid.uuid
+    device_name = device.name
 
     resources_to_get = [RESOURCE_ENDPOINTS]
     results_getconfig = driver.GetConfig(resources_to_get)
+    LOGGER.debug('results_getconfig = {:s}'.format(str(results_getconfig)))
+
+    # first quick pass to identify need of mgmt endpoints and links
+    add_mgmt_port = False
+    for resource_data in results_getconfig:
+        if len(resource_data) != 2: continue
+        resource_key, _ = resource_data
+        if resource_key.startswith('/devices/device'):
+            add_mgmt_port = True
+            break
+
+    if add_mgmt_port:
+        # add mgmt port to main device
+        device_mgmt_endpoint = device.device_endpoints.add()
+        device_mgmt_endpoint.endpoint_id.topology_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME
+        device_mgmt_endpoint.endpoint_id.topology_id.topology_uuid.uuid = DEFAULT_TOPOLOGY_NAME
+        device_mgmt_endpoint.endpoint_id.device_id.device_uuid.uuid = device_uuid
+        device_mgmt_endpoint.endpoint_id.endpoint_uuid.uuid = 'mgmt'
+        device_mgmt_endpoint.name = 'mgmt'
+        device_mgmt_endpoint.endpoint_type = 'mgmt'
 
     errors : List[str] = list()
-    for endpoint in results_getconfig:
-        if len(endpoint) != 2:
-            errors.append(ERROR_BAD_ENDPOINT.format(device_uuid=device_uuid, endpoint_data=str(endpoint)))
+    for resource_data in results_getconfig:
+        if len(resource_data) != 2:
+            errors.append(ERROR_BAD_RESOURCE.format(device_uuid=device_uuid, resource_data=str(resource_data)))
             continue
 
-        resource_key, resource_value = endpoint
+        resource_key, resource_value = resource_data
         if isinstance(resource_value, Exception):
             errors.append(ERROR_GET.format(
                 device_uuid=device_uuid, resource_key=str(resource_key), error=str(resource_value)))
@@ -97,19 +130,88 @@ def populate_endpoints(device : Device, driver : _Driver, monitoring_loops : Mon
         if resource_value is None:
             continue
 
-        endpoint_uuid = resource_value.get('uuid')
-
-        device_endpoint = device.device_endpoints.add()
-        device_endpoint.endpoint_id.topology_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME
-        device_endpoint.endpoint_id.topology_id.topology_uuid.uuid = DEFAULT_TOPOLOGY_NAME
-        device_endpoint.endpoint_id.device_id.device_uuid.uuid = device_uuid
-        device_endpoint.endpoint_id.endpoint_uuid.uuid = endpoint_uuid
-        device_endpoint.endpoint_type = resource_value.get('type')
+        if resource_key.startswith('/devices/device'):
+            # create sub-device
+            _sub_device_uuid = resource_value['uuid']
+            _sub_device = Device()
+            _sub_device.device_id.device_uuid.uuid = _sub_device_uuid           # pylint: disable=no-member
+            _sub_device.name = resource_value['name']
+            _sub_device.device_type = resource_value['type']
+            _sub_device.device_operational_status = resource_value['status']
+            
+            # Sub-devices should not have a driver assigned. Instead, they should have
+            # a config rule specifying their controller.
+            #_sub_device.device_drivers.extend(resource_value['drivers'])        # pylint: disable=no-member
+            controller_config_rule = _sub_device.device_config.config_rules.add()
+            controller_config_rule.action = ConfigActionEnum.CONFIGACTION_SET
+            controller_config_rule.custom.resource_key = '_controller'
+            controller = {'uuid': device_uuid, 'name': device_name}
+            controller_config_rule.custom.resource_value = json.dumps(controller, indent=0, sort_keys=True)
+
+            new_sub_devices[_sub_device_uuid] = _sub_device
+
+            # add mgmt port to sub-device
+            _sub_device_mgmt_endpoint = _sub_device.device_endpoints.add()      # pylint: disable=no-member
+            _sub_device_mgmt_endpoint.endpoint_id.topology_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME
+            _sub_device_mgmt_endpoint.endpoint_id.topology_id.topology_uuid.uuid = DEFAULT_TOPOLOGY_NAME
+            _sub_device_mgmt_endpoint.endpoint_id.device_id.device_uuid.uuid = _sub_device_uuid
+            _sub_device_mgmt_endpoint.endpoint_id.endpoint_uuid.uuid = 'mgmt'
+            _sub_device_mgmt_endpoint.name = 'mgmt'
+            _sub_device_mgmt_endpoint.endpoint_type = 'mgmt'
+
+            # add mgmt link
+            _mgmt_link_uuid = '{:s}/{:s}=={:s}/{:s}'.format(device_name, 'mgmt', _sub_device.name, 'mgmt')
+            _mgmt_link = Link()
+            _mgmt_link.link_id.link_uuid.uuid = _mgmt_link_uuid                         # pylint: disable=no-member
+            _mgmt_link.name = _mgmt_link_uuid
+            _mgmt_link.link_endpoint_ids.append(device_mgmt_endpoint.endpoint_id)       # pylint: disable=no-member
+            _mgmt_link.link_endpoint_ids.append(_sub_device_mgmt_endpoint.endpoint_id)  # pylint: disable=no-member
+            new_sub_links[_mgmt_link_uuid] = _mgmt_link
+
+        elif resource_key.startswith('/endpoints/endpoint'):
+            endpoint_uuid = resource_value['uuid']
+            _device_uuid = resource_value.get('device_uuid')
+            endpoint_name = resource_value.get('name')
+
+            if _device_uuid is None:
+                # add endpoint to current device
+                device_endpoint = device.device_endpoints.add()
+                device_endpoint.endpoint_id.device_id.device_uuid.uuid = device_uuid
+            else:
+                # add endpoint to specified device
+                device_endpoint = new_sub_devices[_device_uuid].device_endpoints.add()
+                device_endpoint.endpoint_id.device_id.device_uuid.uuid = _device_uuid
+
+            device_endpoint.endpoint_id.topology_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME
+            device_endpoint.endpoint_id.topology_id.topology_uuid.uuid = DEFAULT_TOPOLOGY_NAME
+            
+            device_endpoint.endpoint_id.endpoint_uuid.uuid = endpoint_uuid
+            if endpoint_name is not None: device_endpoint.name = endpoint_name
+            device_endpoint.endpoint_type = resource_value.get('type', '-')
+
+            sample_types : Dict[int, str] = resource_value.get('sample_types', {})
+            for kpi_sample_type, monitor_resource_key in sample_types.items():
+                device_endpoint.kpi_sample_types.append(kpi_sample_type)
+                monitoring_loops.add_resource_key(device_uuid, endpoint_uuid, kpi_sample_type, monitor_resource_key)
+
+        elif resource_key.startswith('/links/link'):
+            # create sub-link
+            _sub_link_uuid = resource_value['uuid']
+            _sub_link = Link()
+            _sub_link.link_id.link_uuid.uuid = _sub_link_uuid           # pylint: disable=no-member
+            _sub_link.name = resource_value['name']
+            new_sub_links[_sub_link_uuid] = _sub_link
+
+            for device_uuid,endpoint_uuid in resource_value['endpoints']:
+                _sub_link_endpoint_id = _sub_link.link_endpoint_ids.add()      # pylint: disable=no-member
+                _sub_link_endpoint_id.topology_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME
+                _sub_link_endpoint_id.topology_id.topology_uuid.uuid = DEFAULT_TOPOLOGY_NAME
+                _sub_link_endpoint_id.device_id.device_uuid.uuid = device_uuid
+                _sub_link_endpoint_id.endpoint_uuid.uuid = endpoint_uuid
 
-        sample_types : Dict[int, str] = resource_value.get('sample_types', {})
-        for kpi_sample_type, monitor_resource_key in sample_types.items():
-            device_endpoint.kpi_sample_types.append(kpi_sample_type)
-            monitoring_loops.add_resource_key(device_uuid, endpoint_uuid, kpi_sample_type, monitor_resource_key)
+        else:
+            errors.append(ERROR_UNSUP_RESOURCE.format(device_uuid=device_uuid, resource_data=str(resource_data)))
+            continue
 
     return errors
 
diff --git a/src/device/service/driver_api/ImportTopologyEnum.py b/src/device/service/driver_api/ImportTopologyEnum.py
new file mode 100644
index 0000000000000000000000000000000000000000..06f0ff9c2db1f1baccc4b46c5babc4458ca6ffb6
--- /dev/null
+++ b/src/device/service/driver_api/ImportTopologyEnum.py
@@ -0,0 +1,37 @@
+# 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.
+
+from enum import Enum
+from typing import Dict
+
+class ImportTopologyEnum(Enum):
+    # While importing underlying resources, the driver just imports endpoints and exposes them directly.
+    DISABLED = 'disabled'
+
+    # While importing underlying resources, the driver just imports imports sub-devices but not links
+    # connecting them. The endpoints are exposed in virtual nodes representing the sub-devices.
+    # (a remotely-controlled transport domain might exist between nodes)
+    DEVICES = 'devices'
+
+    # While importing underlying resources, the driver just imports imports sub-devices and links
+    # connecting them. The endpoints are exposed in virtual nodes representing the sub-devices.
+    # (enables to define constrained connectivity between the sub-devices)
+    TOPOLOGY = 'topology'
+
+def get_import_topology(settings : Dict, default : ImportTopologyEnum = ImportTopologyEnum.DISABLED):
+    str_import_topology = settings.get('import_topology')
+    if str_import_topology is None: return default
+    import_topology = ImportTopologyEnum._value2member_map_.get(str_import_topology) # pylint: disable=no-member
+    if import_topology is None: raise Exception('Unexpected setting value')
+    return import_topology
diff --git a/src/device/service/driver_api/_Driver.py b/src/device/service/driver_api/_Driver.py
index cc9f7a2c63f2f841b864cbe4fa596464a6783cec..947bc8570a941f8f666c87647d89c315b1bd202a 100644
--- a/src/device/service/driver_api/_Driver.py
+++ b/src/device/service/driver_api/_Driver.py
@@ -22,6 +22,7 @@ RESOURCE_ENDPOINTS = '__endpoints__'
 RESOURCE_INTERFACES = '__interfaces__'
 RESOURCE_NETWORK_INSTANCES = '__network_instances__'
 RESOURCE_ROUTING_POLICIES = '__routing_policies__'
+RESOURCE_SERVICES = '__services__'
 RESOURCE_ACL = '__acl__'
 
 
diff --git a/src/device/service/drivers/__init__.py b/src/device/service/drivers/__init__.py
index 469abcad387dc055ba17770e4f405db1d1ceaa3b..b3b485a471899dd96a4985fedb4bb6ede2432921 100644
--- a/src/device/service/drivers/__init__.py
+++ b/src/device/service/drivers/__init__.py
@@ -74,6 +74,15 @@ DRIVERS.append(
         #}
     ]))
 
+from .ietf_l2vpn.IetfL2VpnDriver import IetfL2VpnDriver # pylint: disable=wrong-import-position
+DRIVERS.append(
+    (IetfL2VpnDriver, [
+        {
+            FilterFieldEnum.DEVICE_TYPE: DeviceTypeEnum.TERAFLOWSDN_CONTROLLER,
+            FilterFieldEnum.DRIVER: DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN,
+        }
+    ]))
+
 if LOAD_ALL_DEVICE_DRIVERS:
     from .openconfig.OpenConfigDriver import OpenConfigDriver # pylint: disable=wrong-import-position
     DRIVERS.append(
diff --git a/src/device/service/drivers/ietf_l2vpn/IetfL2VpnDriver.py b/src/device/service/drivers/ietf_l2vpn/IetfL2VpnDriver.py
new file mode 100644
index 0000000000000000000000000000000000000000..96dfd2c15f6b359e254a6d6a24dfe42a546833ce
--- /dev/null
+++ b/src/device/service/drivers/ietf_l2vpn/IetfL2VpnDriver.py
@@ -0,0 +1,188 @@
+# 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 json, logging, threading
+from typing import Any, Iterator, List, Optional, Tuple, Union
+from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method
+from common.tools.object_factory.Device import json_device_id
+from common.tools.object_factory.EndPoint import json_endpoint_id
+from common.type_checkers.Checkers import chk_string, chk_type
+from device.service.driver_api._Driver import _Driver, RESOURCE_ENDPOINTS, RESOURCE_SERVICES
+from device.service.drivers.ietf_l2vpn.TfsDebugApiClient import TfsDebugApiClient
+from .Tools import connection_point, wim_mapping
+from .WimconnectorIETFL2VPN import WimconnectorIETFL2VPN
+
+LOGGER = logging.getLogger(__name__)
+
+def service_exists(wim : WimconnectorIETFL2VPN, service_uuid : str) -> bool:
+    try:
+        wim.get_connectivity_service_status(service_uuid)
+        return True
+    except: # pylint: disable=bare-except
+        return False
+
+ALL_RESOURCE_KEYS = [
+    RESOURCE_ENDPOINTS,
+    RESOURCE_SERVICES,
+]
+
+SERVICE_TYPE = 'ELINE'
+
+METRICS_POOL = MetricsPool('Device', 'Driver', labels={'driver': 'ietf_l2vpn'})
+
+class IetfL2VpnDriver(_Driver):
+    def __init__(self, address: str, port: int, **settings) -> None:    # pylint: disable=super-init-not-called
+        self.__lock = threading.Lock()
+        self.__started = threading.Event()
+        self.__terminate = threading.Event()
+        username = settings.get('username')
+        password = settings.get('password')
+        scheme = settings.get('scheme', 'http')
+        wim = {'wim_url': '{:s}://{:s}:{:d}'.format(scheme, address, int(port))}
+        wim_account = {'user': username, 'password': password}
+        # Mapping updated dynamically with each request
+        config = {'mapping_not_needed': False, 'service_endpoint_mapping': []}
+        self.dac = TfsDebugApiClient(address, int(port), scheme=scheme, username=username, password=password)
+        self.wim = WimconnectorIETFL2VPN(wim, wim_account, config=config)
+        self.conn_info = {} # internal database emulating OSM storage provided to WIM Connectors
+
+    def Connect(self) -> bool:
+        with self.__lock:
+            try:
+                self.wim.check_credentials()
+            except Exception:  # pylint: disable=broad-except
+                LOGGER.exception('Exception checking credentials')
+                return False
+            else:
+                self.__started.set()
+                return True
+
+    def Disconnect(self) -> bool:
+        with self.__lock:
+            self.__terminate.set()
+            return True
+
+    @metered_subclass_method(METRICS_POOL)
+    def GetInitialConfig(self) -> List[Tuple[str, Any]]:
+        with self.__lock:
+            return []
+
+    @metered_subclass_method(METRICS_POOL)
+    def GetConfig(self, resource_keys : List[str] = []) -> List[Tuple[str, Union[Any, None, Exception]]]:
+        chk_type('resources', resource_keys, list)
+        results = []
+        with self.__lock:
+            self.wim.check_credentials()
+            if len(resource_keys) == 0: resource_keys = ALL_RESOURCE_KEYS
+            for i, resource_key in enumerate(resource_keys):
+                str_resource_name = 'resource_key[#{:d}]'.format(i)
+                try:
+                    chk_string(str_resource_name, resource_key, allow_empty=False)
+                    if resource_key == RESOURCE_ENDPOINTS:
+                        # return endpoints through debug-api and list-devices method
+                        results.extend(self.dac.get_devices_endpoints())
+                    elif resource_key == RESOURCE_SERVICES:
+                        # return all services through 
+                        reply = self.wim.get_all_active_connectivity_services()
+                        results.extend(reply.json())
+                    else:
+                        # assume single-service retrieval
+                        reply = self.wim.get_connectivity_service(resource_key)
+                        results.append(reply.json())
+                except Exception as e: # pylint: disable=broad-except
+                    LOGGER.exception('Unhandled error processing resource_key({:s})'.format(str(resource_key)))
+                    results.append((resource_key, e))
+        return results
+
+    @metered_subclass_method(METRICS_POOL)
+    def SetConfig(self, resources: List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+        results = []
+        if len(resources) == 0: return results
+        with self.__lock:
+            self.wim.check_credentials()
+            for resource in resources:
+                LOGGER.info('resource = {:s}'.format(str(resource)))
+                resource_key,resource_value = resource
+                try:
+                    resource_value = json.loads(resource_value)
+                    service_uuid = resource_value['uuid']
+
+                    if service_exists(self.wim, service_uuid):
+                        exc = NotImplementedError('IETF L2VPN Service Update is still not supported')
+                        results.append((resource[0], exc))
+                        continue
+
+                    src_device_name   = resource_value['src_device_name']
+                    src_endpoint_name = resource_value['src_endpoint_name']
+                    dst_device_name   = resource_value['dst_device_name']
+                    dst_endpoint_name = resource_value['dst_endpoint_name']
+                    encap_type        = resource_value['encapsulation_type']
+                    vlan_id           = resource_value['vlan_id']
+
+                    src_endpoint_id = json_endpoint_id(json_device_id(src_device_name), src_endpoint_name)
+                    src_service_endpoint_id, src_mapping = wim_mapping('1', src_endpoint_id)
+                    self.wim.mappings[src_service_endpoint_id] = src_mapping
+
+                    dst_endpoint_id = json_endpoint_id(json_device_id(dst_device_name), dst_endpoint_name)
+                    dst_service_endpoint_id, dst_mapping = wim_mapping('2', dst_endpoint_id)
+                    self.wim.mappings[dst_service_endpoint_id] = dst_mapping
+
+                    connection_points = [
+                        connection_point(src_service_endpoint_id, encap_type, vlan_id),
+                        connection_point(dst_service_endpoint_id, encap_type, vlan_id),
+                    ]
+
+                    self.wim.create_connectivity_service(service_uuid, SERVICE_TYPE, connection_points)
+                    results.append((resource_key, True))
+                except Exception as e: # pylint: disable=broad-except
+                    LOGGER.exception('Unhandled error processing resource_key({:s})'.format(str(resource_key)))
+                    results.append((resource_key, e))
+        return results
+
+    @metered_subclass_method(METRICS_POOL)
+    def DeleteConfig(self, resources: List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+        results = []
+        if len(resources) == 0: return results
+        with self.__lock:
+            self.wim.check_credentials()
+            for resource in resources:
+                LOGGER.info('resource = {:s}'.format(str(resource)))
+                resource_key,resource_value = resource
+                try:
+                    resource_value = json.loads(resource_value)
+                    service_uuid = resource_value['uuid']
+
+                    if service_exists(self.wim, service_uuid):
+                        self.wim.delete_connectivity_service(service_uuid)
+                    results.append((resource_key, True))
+                except Exception as e: # pylint: disable=broad-except
+                    LOGGER.exception('Unhandled error processing resource_key({:s})'.format(str(resource_key)))
+                    results.append((resource_key, e))
+        return results
+
+    @metered_subclass_method(METRICS_POOL)
+    def SubscribeState(self, subscriptions : List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]:
+        # TODO: IETF L2VPN does not support monitoring by now
+        return [False for _ in subscriptions]
+
+    @metered_subclass_method(METRICS_POOL)
+    def UnsubscribeState(self, subscriptions : List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]:
+        # TODO: IETF L2VPN does not support monitoring by now
+        return [False for _ in subscriptions]
+
+    def GetState(
+        self, blocking=False, terminate : Optional[threading.Event] = None
+    ) -> Iterator[Tuple[float, str, Any]]:
+        # TODO: IETF L2VPN does not support monitoring by now
+        return []
diff --git a/src/device/service/drivers/ietf_l2vpn/TfsDebugApiClient.py b/src/device/service/drivers/ietf_l2vpn/TfsDebugApiClient.py
new file mode 100644
index 0000000000000000000000000000000000000000..4bf40af030fda990f96efe0ff8ab2ce54f82c312
--- /dev/null
+++ b/src/device/service/drivers/ietf_l2vpn/TfsDebugApiClient.py
@@ -0,0 +1,92 @@
+# 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 logging, requests
+from requests.auth import HTTPBasicAuth
+from typing import Dict, List, Optional
+
+GET_DEVICES_URL = '{:s}://{:s}:{:d}/restconf/debug-api/devices'
+TIMEOUT = 30
+
+HTTP_OK_CODES = {
+    200,    # OK
+    201,    # Created
+    202,    # Accepted
+    204,    # No Content
+}
+
+MAPPING_STATUS = {
+    'DEVICEOPERATIONALSTATUS_UNDEFINED': 0,
+    'DEVICEOPERATIONALSTATUS_DISABLED' : 1,
+    'DEVICEOPERATIONALSTATUS_ENABLED'  : 2,
+}
+
+MAPPING_DRIVER = {
+    'DEVICEDRIVER_UNDEFINED'            : 0,
+    'DEVICEDRIVER_OPENCONFIG'           : 1,
+    'DEVICEDRIVER_TRANSPORT_API'        : 2,
+    'DEVICEDRIVER_P4'                   : 3,
+    'DEVICEDRIVER_IETF_NETWORK_TOPOLOGY': 4,
+    'DEVICEDRIVER_ONF_TR_352'           : 5,
+    'DEVICEDRIVER_XR'                   : 6,
+    'DEVICEDRIVER_IETF_L2VPN'           : 7,
+}
+
+MSG_ERROR = 'Could not retrieve devices in remote TeraFlowSDN instance({:s}). status_code={:s} reply={:s}'
+
+LOGGER = logging.getLogger(__name__)
+
+class TfsDebugApiClient:
+    def __init__(
+        self, address : str, port : int, scheme : str = 'http',
+        username : Optional[str] = None, password : Optional[str] = None
+    ) -> None:
+        self._url = GET_DEVICES_URL.format(scheme, address, port)
+        self._auth = HTTPBasicAuth(username, password) if username is not None and password is not None else None
+
+    def get_devices_endpoints(self) -> List[Dict]:
+        reply = requests.get(self._url, timeout=TIMEOUT, verify=False, auth=self._auth)
+        if reply.status_code not in HTTP_OK_CODES:
+            msg = MSG_ERROR.format(str(self._url), str(reply.status_code), str(reply))
+            LOGGER.error(msg)
+            raise Exception(msg)
+
+        result = list()
+        for json_device in reply.json()['devices']:
+            device_uuid : str = json_device['device_id']['device_uuid']['uuid']
+            device_type : str = json_device['device_type']
+            #if not device_type.startswith('emu-'): device_type = 'emu-' + device_type
+            device_status = json_device['device_operational_status']
+            device_url = '/devices/device[{:s}]'.format(device_uuid)
+            device_data = {
+                'uuid': json_device['device_id']['device_uuid']['uuid'],
+                'name': json_device['name'],
+                'type': device_type,
+                'status': MAPPING_STATUS[device_status],
+                'drivers': [MAPPING_DRIVER[driver] for driver in json_device['device_drivers']],
+            }
+            result.append((device_url, device_data))
+
+            for json_endpoint in json_device['device_endpoints']:
+                endpoint_uuid = json_endpoint['endpoint_id']['endpoint_uuid']['uuid']
+                endpoint_url = '/endpoints/endpoint[{:s}]'.format(endpoint_uuid)
+                endpoint_data = {
+                    'device_uuid': device_uuid,
+                    'uuid': endpoint_uuid,
+                    'name': json_endpoint['name'],
+                    'type': json_endpoint['endpoint_type'],
+                }
+                result.append((endpoint_url, endpoint_data))
+
+        return result
diff --git a/src/device/service/drivers/ietf_l2vpn/Tools.py b/src/device/service/drivers/ietf_l2vpn/Tools.py
new file mode 100644
index 0000000000000000000000000000000000000000..45dfa23c984e175c01efa77371e94454b98ea94e
--- /dev/null
+++ b/src/device/service/drivers/ietf_l2vpn/Tools.py
@@ -0,0 +1,48 @@
+# 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.
+
+from typing import Dict, Optional
+
+def compose_service_endpoint_id(site_id : str, endpoint_id : Dict):
+    device_uuid = endpoint_id['device_id']['device_uuid']['uuid']
+    endpoint_uuid = endpoint_id['endpoint_uuid']['uuid']
+    return ':'.join([site_id, device_uuid, endpoint_uuid])
+
+def wim_mapping(site_id, ce_endpoint_id, pe_device_id : Optional[Dict] = None, priority=None, redundant=[]):
+    ce_device_uuid = ce_endpoint_id['device_id']['device_uuid']['uuid']
+    ce_endpoint_uuid = ce_endpoint_id['endpoint_uuid']['uuid']
+    service_endpoint_id = compose_service_endpoint_id(site_id, ce_endpoint_id)
+    if pe_device_id is None:
+        bearer = '{:s}:{:s}'.format(ce_device_uuid, ce_endpoint_uuid)
+    else:
+        pe_device_uuid = pe_device_id['device_uuid']['uuid']
+        bearer = '{:s}:{:s}'.format(ce_device_uuid, pe_device_uuid)
+    mapping = {
+        'service_endpoint_id': service_endpoint_id,
+        'datacenter_id': site_id, 'device_id': ce_device_uuid, 'device_interface_id': ce_endpoint_uuid,
+        'service_mapping_info': {
+            'site-id': site_id,
+            'bearer': {'bearer-reference': bearer},
+        }
+    }
+    if priority is not None: mapping['service_mapping_info']['priority'] = priority
+    if len(redundant) > 0: mapping['service_mapping_info']['redundant'] = redundant
+    return service_endpoint_id, mapping
+
+def connection_point(service_endpoint_id : str, encapsulation_type : str, vlan_id : int):
+    return {
+        'service_endpoint_id': service_endpoint_id,
+        'service_endpoint_encapsulation_type': encapsulation_type,
+        'service_endpoint_encapsulation_info': {'vlan': vlan_id}
+    }
diff --git a/src/device/service/drivers/ietf_l2vpn/WimconnectorIETFL2VPN.py b/src/device/service/drivers/ietf_l2vpn/WimconnectorIETFL2VPN.py
new file mode 100644
index 0000000000000000000000000000000000000000..34ff184c022f379e7420de237bd08fc1dc6282a6
--- /dev/null
+++ b/src/device/service/drivers/ietf_l2vpn/WimconnectorIETFL2VPN.py
@@ -0,0 +1,565 @@
+# -*- coding: utf-8 -*-
+##
+# Copyright 2018 Telefonica
+# All Rights Reserved.
+#
+# Contributors: Oscar Gonzalez de Dios, Manuel Lopez Bravo, Guillermo Pajares Martin
+# 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.
+#
+# This work has been performed in the context of the Metro-Haul project -
+# funded by the European Commission under Grant number 761727 through the
+# Horizon 2020 program.
+##
+"""The SDN/WIM connector is responsible for establishing wide area network
+connectivity.
+
+This SDN/WIM connector implements the standard IETF RFC 8466 "A YANG Data
+ Model for Layer 2 Virtual Private Network (L2VPN) Service Delivery"
+
+It receives the endpoints and the necessary details to request
+the Layer 2 service.
+"""
+import requests
+import uuid
+import logging
+import copy
+#from osm_ro_plugin.sdnconn import SdnConnectorBase, SdnConnectorError
+from .sdnconn import SdnConnectorBase, SdnConnectorError
+
+"""Check layer where we move it"""
+
+
+class WimconnectorIETFL2VPN(SdnConnectorBase):
+    def __init__(self, wim, wim_account, config=None, logger=None):
+        """IETF L2VPN WIM connector
+
+        Arguments: (To be completed)
+            wim (dict): WIM record, as stored in the database
+            wim_account (dict): WIM account record, as stored in the database
+        """
+        self.logger = logging.getLogger("ro.sdn.ietfl2vpn")
+        super().__init__(wim, wim_account, config, logger)
+        self.headers = {"Content-Type": "application/json"}
+        self.mappings = {
+            m["service_endpoint_id"]: m for m in self.service_endpoint_mapping
+        }
+        self.user = wim_account.get("user")
+        self.passwd = wim_account.get("password")
+
+        if self.user is not None and self.passwd is not None:
+            self.auth = (self.user, self.passwd)
+        else:
+            self.auth = None
+
+        self.logger.info("IETFL2VPN Connector Initialized.")
+
+    def check_credentials(self):
+        endpoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
+            self.wim["wim_url"]
+        )
+
+        try:
+            response = requests.get(endpoint, auth=self.auth)
+            http_code = response.status_code
+        except requests.exceptions.RequestException as e:
+            raise SdnConnectorError(e.response, http_code=503)
+
+        if http_code != 200:
+            raise SdnConnectorError("Failed while authenticating", http_code=http_code)
+
+        self.logger.info("Credentials checked")
+
+    def get_connectivity_service_status(self, service_uuid, conn_info=None):
+        """Monitor the status of the connectivity service stablished
+
+        Arguments:
+            service_uuid: Connectivity service unique identifier
+
+        Returns:
+            Examples::
+                {'sdn_status': 'ACTIVE'}
+                {'sdn_status': 'INACTIVE'}
+                {'sdn_status': 'DOWN'}
+                {'sdn_status': 'ERROR'}
+        """
+        try:
+            self.logger.info("Sending get connectivity service stuatus")
+            servicepoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services/vpn-service={}/".format(
+                self.wim["wim_url"], service_uuid
+            )
+            response = requests.get(servicepoint, auth=self.auth)
+            self.logger.warning('response.status_code={:s}'.format(str(response.status_code)))
+            if response.status_code != requests.codes.ok:
+                raise SdnConnectorError(
+                    "Unable to obtain connectivity servcice status",
+                    http_code=response.status_code,
+                )
+
+            service_status = {"sdn_status": "ACTIVE"}
+
+            return service_status
+        except requests.exceptions.ConnectionError:
+            raise SdnConnectorError("Request Timeout", http_code=408)
+
+    def search_mapp(self, connection_point):
+        id = connection_point["service_endpoint_id"]
+        if id not in self.mappings:
+            raise SdnConnectorError("Endpoint {} not located".format(str(id)))
+        else:
+            return self.mappings[id]
+
+    def create_connectivity_service(self, service_uuid, service_type, connection_points, **kwargs):
+        """Stablish WAN connectivity between the endpoints
+
+        Arguments:
+            service_type (str): ``ELINE`` (L2), ``ELAN`` (L2), ``ETREE`` (L2),
+                ``L3``.
+            connection_points (list): each point corresponds to
+                an entry point from the DC to the transport network. One
+                connection point serves to identify the specific access and
+                some other service parameters, such as encapsulation type.
+                Represented by a dict as follows::
+
+                    {
+                      "service_endpoint_id": ..., (str[uuid])
+                      "service_endpoint_encapsulation_type": ...,
+                           (enum: none, dot1q, ...)
+                      "service_endpoint_encapsulation_info": {
+                        ... (dict)
+                        "vlan": ..., (int, present if encapsulation is dot1q)
+                        "vni": ... (int, present if encapsulation is vxlan),
+                        "peers": [(ipv4_1), (ipv4_2)]
+                            (present if encapsulation is vxlan)
+                      }
+                    }
+
+              The service endpoint ID should be previously informed to the WIM
+              engine in the RO when the WIM port mapping is registered.
+
+        Keyword Arguments:
+            bandwidth (int): value in kilobytes
+            latency (int): value in milliseconds
+
+        Other QoS might be passed as keyword arguments.
+
+        Returns:
+            tuple: ``conn_info``:
+               - *conn_info* (dict or None): Information to be stored at the
+                 database (or ``None``). This information will be provided to
+                 the :meth:`~.edit_connectivity_service` and :obj:`~.delete`.
+                 **MUST** be JSON/YAML-serializable (plain data structures).
+
+        Raises:
+            SdnConnectorException: In case of error.
+        """
+        SETTINGS = {    # min_endpoints, max_endpoints, vpn_service_type
+            'ELINE': (2,    2, 'vpws'), # Virtual Private Wire Service
+            'ELAN' : (2, None, 'vpls'), # Virtual Private LAN  Service
+        }
+        settings = SETTINGS.get(service_type)
+        if settings is None: raise NotImplementedError('Unsupported service_type({:s})'.format(str(service_type)))
+        min_endpoints, max_endpoints, vpn_service_type = settings
+
+        if max_endpoints is not None and len(connection_points) > max_endpoints:
+            msg = "Connections between more than {:d} endpoints are not supported for service_type {:s}"
+            raise SdnConnectorError(msg.format(max_endpoints, service_type))
+
+        if min_endpoints is not None and len(connection_points) < min_endpoints:
+            msg = "Connections must be of at least {:d} endpoints for service_type {:s}"
+            raise SdnConnectorError(msg.format(min_endpoints, service_type))
+
+        """First step, create the vpn service"""
+        vpn_service = {}
+        vpn_service["vpn-id"] = service_uuid
+        vpn_service["vpn-svc-type"] = vpn_service_type
+        vpn_service["svc-topo"] = "any-to-any"
+        vpn_service["customer-name"] = "osm"
+        vpn_service_list = []
+        vpn_service_list.append(vpn_service)
+        vpn_service_l = {"ietf-l2vpn-svc:vpn-service": vpn_service_list}
+        response_service_creation = None
+        conn_info = []
+        self.logger.info("Sending vpn-service :{}".format(vpn_service_l))
+
+        try:
+            endpoint_service_creation = (
+                "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
+                    self.wim["wim_url"]
+                )
+            )
+            response_service_creation = requests.post(
+                endpoint_service_creation,
+                headers=self.headers,
+                json=vpn_service_l,
+                auth=self.auth,
+            )
+        except requests.exceptions.ConnectionError:
+            raise SdnConnectorError(
+                "Request to create service Timeout", http_code=408
+            )
+
+        if response_service_creation.status_code == 409:
+            raise SdnConnectorError(
+                "Service already exists",
+                http_code=response_service_creation.status_code,
+            )
+        elif response_service_creation.status_code != requests.codes.created:
+            raise SdnConnectorError(
+                "Request to create service not accepted",
+                http_code=response_service_creation.status_code,
+            )
+
+        self.logger.info('connection_points = {:s}'.format(str(connection_points)))
+
+        # Check if protected paths are requested
+        extended_connection_points = []
+        for connection_point in connection_points:
+            extended_connection_points.append(connection_point)
+
+            connection_point_wan_info = self.search_mapp(connection_point)
+            service_mapping_info = connection_point_wan_info.get('service_mapping_info', {})
+            redundant_service_endpoint_ids = service_mapping_info.get('redundant')
+
+            if redundant_service_endpoint_ids is None: continue
+            if len(redundant_service_endpoint_ids) == 0: continue
+
+            for redundant_service_endpoint_id in redundant_service_endpoint_ids:
+                redundant_connection_point = copy.deepcopy(connection_point)
+                redundant_connection_point['service_endpoint_id'] = redundant_service_endpoint_id
+                extended_connection_points.append(redundant_connection_point)
+
+        self.logger.info('extended_connection_points = {:s}'.format(str(extended_connection_points)))
+
+        """Second step, create the connections and vpn attachments"""
+        for connection_point in extended_connection_points:
+            connection_point_wan_info = self.search_mapp(connection_point)
+            site_network_access = {}
+            connection = {}
+
+            if connection_point["service_endpoint_encapsulation_type"] != "none":
+                if (
+                    connection_point["service_endpoint_encapsulation_type"]
+                    == "dot1q"
+                ):
+                    """The connection is a VLAN"""
+                    connection["encapsulation-type"] = "dot1q-vlan-tagged"
+                    tagged = {}
+                    tagged_interf = {}
+                    service_endpoint_encapsulation_info = connection_point[
+                        "service_endpoint_encapsulation_info"
+                    ]
+
+                    if service_endpoint_encapsulation_info["vlan"] is None:
+                        raise SdnConnectorError("VLAN must be provided")
+
+                    tagged_interf["cvlan-id"] = service_endpoint_encapsulation_info[
+                        "vlan"
+                    ]
+                    tagged["dot1q-vlan-tagged"] = tagged_interf
+                    connection["tagged-interface"] = tagged
+                else:
+                    raise NotImplementedError("Encapsulation type not implemented")
+
+            site_network_access["connection"] = connection
+            self.logger.info("Sending connection:{}".format(connection))
+            vpn_attach = {}
+            vpn_attach["vpn-id"] = service_uuid
+            vpn_attach["site-role"] = vpn_service["svc-topo"] + "-role"
+            site_network_access["vpn-attachment"] = vpn_attach
+            self.logger.info("Sending vpn-attachement :{}".format(vpn_attach))
+            uuid_sna = str(uuid.uuid4())
+            site_network_access["network-access-id"] = uuid_sna
+            site_network_access["bearer"] = connection_point_wan_info[
+                "service_mapping_info"
+            ]["bearer"]
+
+            access_priority = connection_point_wan_info["service_mapping_info"].get("priority")
+            if access_priority is not None:
+                availability = {}
+                availability["access-priority"] = access_priority
+                availability["single-active"] = [None]
+                site_network_access["availability"] = availability
+
+                constraint = {}
+                constraint['constraint-type'] = 'end-to-end-diverse'
+                constraint['target'] = {'all-other-accesses': [None]}
+
+                access_diversity = {}
+                access_diversity['constraints'] = {'constraint': []}
+                access_diversity['constraints']['constraint'].append(constraint)
+                site_network_access["access-diversity"] = access_diversity
+
+            site_network_accesses = {}
+            site_network_access_list = []
+            site_network_access_list.append(site_network_access)
+            site_network_accesses[
+                "ietf-l2vpn-svc:site-network-access"
+            ] = site_network_access_list
+            conn_info_d = {}
+            conn_info_d["site"] = connection_point_wan_info["service_mapping_info"][
+                "site-id"
+            ]
+            conn_info_d["site-network-access-id"] = site_network_access[
+                "network-access-id"
+            ]
+            conn_info_d["mapping"] = None
+            conn_info.append(conn_info_d)
+
+            try:
+                endpoint_site_network_access_creation = (
+                    "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/"
+                    "sites/site={}/site-network-accesses/".format(
+                        self.wim["wim_url"],
+                        connection_point_wan_info["service_mapping_info"][
+                            "site-id"
+                        ],
+                    )
+                )
+                response_endpoint_site_network_access_creation = requests.post(
+                    endpoint_site_network_access_creation,
+                    headers=self.headers,
+                    json=site_network_accesses,
+                    auth=self.auth,
+                )
+
+                if (
+                    response_endpoint_site_network_access_creation.status_code
+                    == 409
+                ):
+                    self.delete_connectivity_service(vpn_service["vpn-id"])
+
+                    raise SdnConnectorError(
+                        "Site_Network_Access with ID '{}' already exists".format(
+                            site_network_access["network-access-id"]
+                        ),
+                        http_code=response_endpoint_site_network_access_creation.status_code,
+                    )
+                elif (
+                    response_endpoint_site_network_access_creation.status_code
+                    == 400
+                ):
+                    self.delete_connectivity_service(vpn_service["vpn-id"])
+
+                    raise SdnConnectorError(
+                        "Site {} does not exist".format(
+                            connection_point_wan_info["service_mapping_info"][
+                                "site-id"
+                            ]
+                        ),
+                        http_code=response_endpoint_site_network_access_creation.status_code,
+                    )
+                elif (
+                    response_endpoint_site_network_access_creation.status_code
+                    != requests.codes.created
+                    and response_endpoint_site_network_access_creation.status_code
+                    != requests.codes.no_content
+                ):
+                    self.delete_connectivity_service(vpn_service["vpn-id"])
+
+                    raise SdnConnectorError(
+                        "Request not accepted",
+                        http_code=response_endpoint_site_network_access_creation.status_code,
+                    )
+            except requests.exceptions.ConnectionError:
+                self.delete_connectivity_service(vpn_service["vpn-id"])
+
+                raise SdnConnectorError("Request Timeout", http_code=408)
+
+        return conn_info
+
+    def delete_connectivity_service(self, service_uuid, conn_info=None):
+        """Disconnect multi-site endpoints previously connected
+
+        This method should receive as the first argument the UUID generated by
+        the ``create_connectivity_service``
+        """
+        try:
+            self.logger.info("Sending delete")
+            servicepoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services/vpn-service={}/".format(
+                self.wim["wim_url"], service_uuid
+            )
+            response = requests.delete(servicepoint, auth=self.auth)
+
+            if response.status_code != requests.codes.no_content:
+                raise SdnConnectorError(
+                    "Error in the request", http_code=response.status_code
+                )
+        except requests.exceptions.ConnectionError:
+            raise SdnConnectorError("Request Timeout", http_code=408)
+
+    def edit_connectivity_service(
+        self, service_uuid, conn_info=None, connection_points=None, **kwargs
+    ):
+        """Change an existing connectivity service, see
+        ``create_connectivity_service``"""
+        # sites = {"sites": {}}
+        # site_list = []
+        vpn_service = {}
+        vpn_service["svc-topo"] = "any-to-any"
+        counter = 0
+
+        for connection_point in connection_points:
+            site_network_access = {}
+            connection_point_wan_info = self.search_mapp(connection_point)
+            params_site = {}
+            params_site["site-id"] = connection_point_wan_info["service_mapping_info"][
+                "site-id"
+            ]
+            params_site["site-vpn-flavor"] = "site-vpn-flavor-single"
+            device_site = {}
+            device_site["device-id"] = connection_point_wan_info["device-id"]
+            params_site["devices"] = device_site
+            # network_access = {}
+            connection = {}
+
+            if connection_point["service_endpoint_encapsulation_type"] != "none":
+                if connection_point["service_endpoint_encapsulation_type"] == "dot1q":
+                    """The connection is a VLAN"""
+                    connection["encapsulation-type"] = "dot1q-vlan-tagged"
+                    tagged = {}
+                    tagged_interf = {}
+                    service_endpoint_encapsulation_info = connection_point[
+                        "service_endpoint_encapsulation_info"
+                    ]
+
+                    if service_endpoint_encapsulation_info["vlan"] is None:
+                        raise SdnConnectorError("VLAN must be provided")
+
+                    tagged_interf["cvlan-id"] = service_endpoint_encapsulation_info[
+                        "vlan"
+                    ]
+                    tagged["dot1q-vlan-tagged"] = tagged_interf
+                    connection["tagged-interface"] = tagged
+                else:
+                    raise NotImplementedError("Encapsulation type not implemented")
+
+            site_network_access["connection"] = connection
+            vpn_attach = {}
+            vpn_attach["vpn-id"] = service_uuid
+            vpn_attach["site-role"] = vpn_service["svc-topo"] + "-role"
+            site_network_access["vpn-attachment"] = vpn_attach
+            uuid_sna = conn_info[counter]["site-network-access-id"]
+            site_network_access["network-access-id"] = uuid_sna
+            site_network_access["bearer"] = connection_point_wan_info[
+                "service_mapping_info"
+            ]["bearer"]
+            site_network_accesses = {}
+            site_network_access_list = []
+            site_network_access_list.append(site_network_access)
+            site_network_accesses[
+                "ietf-l2vpn-svc:site-network-access"
+            ] = site_network_access_list
+
+            try:
+                endpoint_site_network_access_edit = (
+                    "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/"
+                    "sites/site={}/site-network-accesses/".format(
+                        self.wim["wim_url"],
+                        connection_point_wan_info["service_mapping_info"]["site-id"],
+                    )
+                )
+                response_endpoint_site_network_access_creation = requests.put(
+                    endpoint_site_network_access_edit,
+                    headers=self.headers,
+                    json=site_network_accesses,
+                    auth=self.auth,
+                )
+
+                if response_endpoint_site_network_access_creation.status_code == 400:
+                    raise SdnConnectorError(
+                        "Service does not exist",
+                        http_code=response_endpoint_site_network_access_creation.status_code,
+                    )
+                elif (
+                    response_endpoint_site_network_access_creation.status_code != 201
+                    and response_endpoint_site_network_access_creation.status_code
+                    != 204
+                ):
+                    raise SdnConnectorError(
+                        "Request no accepted",
+                        http_code=response_endpoint_site_network_access_creation.status_code,
+                    )
+            except requests.exceptions.ConnectionError:
+                raise SdnConnectorError("Request Timeout", http_code=408)
+
+            counter += 1
+
+        return None
+
+    def clear_all_connectivity_services(self):
+        """Delete all WAN Links corresponding to a WIM"""
+        try:
+            self.logger.info("Sending clear all connectivity services")
+            servicepoint = (
+                "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
+                    self.wim["wim_url"]
+                )
+            )
+            response = requests.delete(servicepoint, auth=self.auth)
+
+            if response.status_code != requests.codes.no_content:
+                raise SdnConnectorError(
+                    "Unable to clear all connectivity services",
+                    http_code=response.status_code,
+                )
+        except requests.exceptions.ConnectionError:
+            raise SdnConnectorError("Request Timeout", http_code=408)
+
+    def get_all_active_connectivity_services(self):
+        """Provide information about all active connections provisioned by a
+        WIM
+        """
+        try:
+            self.logger.info("Sending get all connectivity services")
+            servicepoint = (
+                "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
+                    self.wim["wim_url"]
+                )
+            )
+            response = requests.get(servicepoint, auth=self.auth)
+
+            if response.status_code != requests.codes.ok:
+                raise SdnConnectorError(
+                    "Unable to get all connectivity services",
+                    http_code=response.status_code,
+                )
+
+            return response
+        except requests.exceptions.ConnectionError:
+            raise SdnConnectorError("Request Timeout", http_code=408)
+
+    def get_connectivity_service(self, service_uuid, conn_info=None):
+        """Provide information about a specific connection provisioned by a WIM.
+
+        This method should receive as the first argument the UUID generated by
+        the ``create_connectivity_service``
+        """
+        try:
+            self.logger.info("Sending get connectivity service")
+            servicepoint = (
+                "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services/vpn-service={}/".format(
+                    self.wim["wim_url"], service_uuid
+                )
+            )
+            response = requests.get(servicepoint, auth=self.auth)
+
+            if response.status_code != requests.codes.ok:
+                raise SdnConnectorError(
+                    "Unable to get connectivity service {:s}".format(str(service_uuid)),
+                    http_code=response.status_code,
+                )
+
+            return response
+        except requests.exceptions.ConnectionError:
+            raise SdnConnectorError("Request Timeout", http_code=408)
diff --git a/src/device/service/drivers/ietf_l2vpn/__init__.py b/src/device/service/drivers/ietf_l2vpn/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..38d04994fb0fa1951fb465bc127eb72659dc2eaf
--- /dev/null
+++ b/src/device/service/drivers/ietf_l2vpn/__init__.py
@@ -0,0 +1,13 @@
+# 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.
diff --git a/src/device/service/drivers/ietf_l2vpn/acknowledgements.txt b/src/device/service/drivers/ietf_l2vpn/acknowledgements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3a7ed47ad6626ad13f4176bd696ec7e7dbab20ee
--- /dev/null
+++ b/src/device/service/drivers/ietf_l2vpn/acknowledgements.txt
@@ -0,0 +1,3 @@
+IETF L2VPN Driver is based on source code taken from:
+https://osm.etsi.org/gitlab/osm/ro/-/blob/master/RO-plugin/osm_ro_plugin/sdnconn.py
+https://osm.etsi.org/gitlab/osm/ro/-/blob/master/RO-SDN-ietfl2vpn/osm_rosdn_ietfl2vpn/wimconn_ietfl2vpn.py
diff --git a/src/device/service/drivers/ietf_l2vpn/sdnconn.py b/src/device/service/drivers/ietf_l2vpn/sdnconn.py
new file mode 100644
index 0000000000000000000000000000000000000000..a1849c9ef3e1a1260ff42bbadabc99f91a6435d7
--- /dev/null
+++ b/src/device/service/drivers/ietf_l2vpn/sdnconn.py
@@ -0,0 +1,242 @@
+# -*- coding: utf-8 -*-
+##
+# Copyright 2018 University of Bristol - High Performance Networks Research
+# Group
+# All Rights Reserved.
+#
+# Contributors: Anderson Bravalheri, Dimitrios Gkounis, Abubakar Siddique
+# Muqaddas, Navdeep Uniyal, Reza Nejabati and Dimitra Simeonidou
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: <highperformance-networks@bristol.ac.uk>
+#
+# Neither the name of the University of Bristol nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# This work has been performed in the context of DCMS UK 5G Testbeds
+# & Trials Programme and in the framework of the Metro-Haul project -
+# funded by the European Commission under Grant number 761727 through the
+# Horizon 2020 and 5G-PPP programmes.
+##
+"""The SDN connector is responsible for establishing both wide area network connectivity (WIM)
+and intranet SDN connectivity.
+
+It receives information from ports to be connected .
+"""
+
+import logging
+from http import HTTPStatus
+
+
+class SdnConnectorError(Exception):
+    """Base Exception for all connector related errors
+    provide the parameter 'http_code' (int) with the error code:
+        Bad_Request = 400
+        Unauthorized = 401  (e.g. credentials are not valid)
+        Not_Found = 404    (e.g. try to edit or delete a non existing connectivity service)
+        Forbidden = 403
+        Method_Not_Allowed = 405
+        Not_Acceptable = 406
+        Request_Timeout = 408  (e.g timeout reaching server, or cannot reach the server)
+        Conflict = 409
+        Service_Unavailable = 503
+        Internal_Server_Error = 500
+    """
+
+    def __init__(self, message, http_code=HTTPStatus.INTERNAL_SERVER_ERROR.value):
+        Exception.__init__(self, message)
+        self.http_code = http_code
+
+
+class SdnConnectorBase(object):
+    """Abstract base class for all the SDN connectors
+
+    Arguments:
+        wim (dict): WIM record, as stored in the database
+        wim_account (dict): WIM account record, as stored in the database
+        config
+    The arguments of the constructor are converted to object attributes.
+    An extra property, ``service_endpoint_mapping`` is created from ``config``.
+    """
+
+    def __init__(self, wim, wim_account, config=None, logger=None):
+        """
+        :param wim: (dict). Contains among others 'wim_url'
+        :param wim_account: (dict). Contains among others 'uuid' (internal id), 'name',
+            'sdn' (True if is intended for SDN-assist or False if intended for WIM), 'user', 'password'.
+        :param config: (dict or None): Particular information of plugin. These keys if present have a common meaning:
+            'mapping_not_needed': (bool) False by default or if missing, indicates that mapping is not needed.
+            'service_endpoint_mapping': (list) provides the internal endpoint mapping. The meaning is:
+                KEY                     meaning for WIM             meaning for SDN assist
+                --------                --------                    --------
+                device_id               pop_switch_dpid             compute_id
+                device_interface_id     pop_switch_port             compute_pci_address
+                service_endpoint_id     wan_service_endpoint_id     SDN_service_endpoint_id
+                service_mapping_info    wan_service_mapping_info    SDN_service_mapping_info
+                    contains extra information if needed. Text in Yaml format
+                switch_dpid             wan_switch_dpid             SDN_switch_dpid
+                switch_port             wan_switch_port             SDN_switch_port
+                datacenter_id           vim_account                 vim_account
+            id: (internal, do not use)
+            wim_id: (internal, do not use)
+        :param logger (logging.Logger): optional logger object. If none is passed 'openmano.sdn.sdnconn' is used.
+        """
+        self.logger = logger or logging.getLogger("ro.sdn")
+        self.wim = wim
+        self.wim_account = wim_account
+        self.config = config or {}
+        self.service_endpoint_mapping = self.config.get("service_endpoint_mapping", [])
+
+    def check_credentials(self):
+        """Check if the connector itself can access the SDN/WIM with the provided url (wim.wim_url),
+            user (wim_account.user), and password (wim_account.password)
+
+        Raises:
+            SdnConnectorError: Issues regarding authorization, access to
+                external URLs, etc are detected.
+        """
+        raise NotImplementedError
+
+    def get_connectivity_service_status(self, service_uuid, conn_info=None):
+        """Monitor the status of the connectivity service established
+
+        Arguments:
+            service_uuid (str): UUID of the connectivity service
+            conn_info (dict or None): Information returned by the connector
+                during the service creation/edition and subsequently stored in
+                the database.
+
+        Returns:
+            dict: JSON/YAML-serializable dict that contains a mandatory key
+                ``sdn_status`` associated with one of the following values::
+
+                    {'sdn_status': 'ACTIVE'}
+                        # The service is up and running.
+
+                    {'sdn_status': 'INACTIVE'}
+                        # The service was created, but the connector
+                        # cannot determine yet if connectivity exists
+                        # (ideally, the caller needs to wait and check again).
+
+                    {'sdn_status': 'DOWN'}
+                        # Connection was previously established,
+                        # but an error/failure was detected.
+
+                    {'sdn_status': 'ERROR'}
+                        # An error occurred when trying to create the service/
+                        # establish the connectivity.
+
+                    {'sdn_status': 'BUILD'}
+                        # Still trying to create the service, the caller
+                        # needs to wait and check again.
+
+                Additionally ``error_msg``(**str**) and ``sdn_info``(**dict**)
+                keys can be used to provide additional status explanation or
+                new information available for the connectivity service.
+        """
+        raise NotImplementedError
+
+    def create_connectivity_service(self, service_type, connection_points, **kwargs):
+        """
+        Establish SDN/WAN connectivity between the endpoints
+        :param service_type: (str): ``ELINE`` (L2), ``ELAN`` (L2), ``ETREE`` (L2), ``L3``.
+        :param connection_points:  (list): each point corresponds to
+            an entry point to be connected. For WIM: from the DC to the transport network.
+            For SDN: Compute/PCI to the transport network. One
+            connection point serves to identify the specific access and
+            some other service parameters, such as encapsulation type.
+            Each item of the list is a dict with:
+                "service_endpoint_id": (str)(uuid)  Same meaning that for 'service_endpoint_mapping' (see __init__)
+                    In case the config attribute mapping_not_needed is True, this value is not relevant. In this case
+                    it will contain the string "device_id:device_interface_id"
+                "service_endpoint_encapsulation_type": None, "dot1q", ...
+                "service_endpoint_encapsulation_info": (dict) with:
+                    "vlan": ..., (int, present if encapsulation is dot1q)
+                    "vni": ... (int, present if encapsulation is vxlan),
+                    "peers": [(ipv4_1), (ipv4_2)] (present if encapsulation is vxlan)
+                    "mac": ...
+                    "device_id": ..., same meaning that for 'service_endpoint_mapping' (see __init__)
+                    "device_interface_id": same meaning that for 'service_endpoint_mapping' (see __init__)
+                    "switch_dpid": ..., present if mapping has been found for this device_id,device_interface_id
+                    "swith_port": ... present if mapping has been found for this device_id,device_interface_id
+                    "service_mapping_info": present if mapping has been found for this device_id,device_interface_id
+        :param kwargs: For future versions:
+            bandwidth (int): value in kilobytes
+            latency (int): value in milliseconds
+            Other QoS might be passed as keyword arguments.
+        :return: tuple: ``(service_id, conn_info)`` containing:
+            - *service_uuid* (str): UUID of the established connectivity service
+            - *conn_info* (dict or None): Information to be stored at the database (or ``None``).
+                This information will be provided to the :meth:`~.edit_connectivity_service` and :obj:`~.delete`.
+                **MUST** be JSON/YAML-serializable (plain data structures).
+        :raises: SdnConnectorException: In case of error. Nothing should be created in this case.
+            Provide the parameter http_code
+        """
+        raise NotImplementedError
+
+    def delete_connectivity_service(self, service_uuid, conn_info=None):
+        """
+        Disconnect multi-site endpoints previously connected
+
+        :param service_uuid: The one returned by create_connectivity_service
+        :param conn_info: The one returned by last call to 'create_connectivity_service' or 'edit_connectivity_service'
+            if they do not return None
+        :return: None
+        :raises: SdnConnectorException: In case of error. The parameter http_code must be filled
+        """
+        raise NotImplementedError
+
+    def edit_connectivity_service(
+        self, service_uuid, conn_info=None, connection_points=None, **kwargs
+    ):
+        """Change an existing connectivity service.
+
+        This method's arguments and return value follow the same convention as
+        :meth:`~.create_connectivity_service`.
+
+        :param service_uuid: UUID of the connectivity service.
+        :param conn_info: (dict or None): Information previously returned by last call to create_connectivity_service
+            or edit_connectivity_service
+        :param connection_points: (list): If provided, the old list of connection points will be replaced.
+        :param kwargs: Same meaning that create_connectivity_service
+        :return: dict or None: Information to be updated and stored at the database.
+                When ``None`` is returned, no information should be changed.
+                When an empty dict is returned, the database record will be deleted.
+                **MUST** be JSON/YAML-serializable (plain data structures).
+        Raises:
+            SdnConnectorException: In case of error.
+        """
+
+    def clear_all_connectivity_services(self):
+        """Delete all WAN Links in a WIM.
+
+        This method is intended for debugging only, and should delete all the
+        connections controlled by the WIM/SDN, not only the  connections that
+        a specific RO is aware of.
+
+        Raises:
+            SdnConnectorException: In case of error.
+        """
+        raise NotImplementedError
+
+    def get_all_active_connectivity_services(self):
+        """Provide information about all active connections provisioned by a
+        WIM.
+
+        Raises:
+            SdnConnectorException: In case of error.
+        """
+        raise NotImplementedError
diff --git a/src/device/service/drivers/microwave/Tools.py b/src/device/service/drivers/microwave/Tools.py
index 711fb55fd4bd9e1bcb16e851aa73f3a61f4bf4bd..4490c0f63fe6a517e5f31a5acd62208013bbaad0 100644
--- a/src/device/service/drivers/microwave/Tools.py
+++ b/src/device/service/drivers/microwave/Tools.py
@@ -14,7 +14,7 @@
 
 import json, logging, requests
 from requests.auth import HTTPBasicAuth
-from typing import Optional, Set
+from typing import Dict, Optional, Set
 from device.service.driver_api._Driver import RESOURCE_ENDPOINTS
 
 LOGGER = logging.getLogger(__name__)
@@ -43,6 +43,14 @@ def is_exportable_endpoint(node, termination_point_id, links):
             return False
     return True
 
+VLAN_CLASSIFICATION_TYPES = {'ietf-eth-tran-types:vlan-classification', 'vlan-classification'}
+OUTER_TAG_C_TYPE = {'ietf-eth-tran-types:classify-c-vlan', 'classify-c-vlan'}
+def get_vlan_outer_tag(endpoint : Dict) -> Optional[int]:
+    if endpoint.get('service-classification-type', '') not in VLAN_CLASSIFICATION_TYPES: return None
+    outer_tag = endpoint.get('outer-tag', {})
+    if outer_tag.get('tag-type', '') not in OUTER_TAG_C_TYPE: return None
+    return outer_tag.get('vlan-value')
+
 def config_getter(
     root_url : str, resource_key : str, auth : Optional[HTTPBasicAuth] = None, timeout : Optional[int] = None,
     node_ids : Set[str] = set()
@@ -92,7 +100,35 @@ def config_getter(
             for service in service_instances:
                 service_name = service['etht-svc-name']
                 resource_key = '/services/service[{:s}]'.format(service_name)
-                result.append((resource_key, service))
+                resource_value = {'uuid': service.get('etht-svc-name', '<UNDEFINED>')}
+
+                for endpoint in service.get('etht-svc-end-points', []):
+                    _vlan_id = get_vlan_outer_tag(endpoint)
+                    if _vlan_id is not None:
+                        vlan_id = resource_value.get('vlan_id')
+                        if vlan_id is None:
+                            resource_value['vlan_id'] = _vlan_id
+                        elif vlan_id != _vlan_id:
+                            raise Exception('Incompatible VLAN Ids: {:s}'.format(str(service)))
+                    access_points = endpoint.get('etht-svc-access-points', [])
+                    for access_point in access_points:
+                        if access_point['access-point-id'] == '1':
+                            resource_value['node_id_src'] = access_point['access-node-id']
+                            resource_value['tp_id_src']   = access_point['access-ltp-id']
+                        elif access_point['access-point-id'] == '2':
+                            resource_value['node_id_dst'] = access_point['access-node-id']
+                            resource_value['tp_id_dst']   = access_point['access-ltp-id']
+
+                if len(node_ids) > 0:
+                    node_id_src = resource_value.get('node_id_src')
+                    if node_id_src is None: continue
+                    if node_id_src not in node_ids: continue
+
+                    node_id_dst = resource_value.get('node_id_dst')
+                    if node_id_dst is None: continue
+                    if node_id_dst not in node_ids: continue
+
+                result.append((resource_key, resource_value))
         except requests.exceptions.Timeout:
             LOGGER.exception('Timeout connecting {:s}'.format(url))
         except Exception as e:  # pylint: disable=broad-except
diff --git a/src/device/service/drivers/openconfig/OpenConfigDriver.py b/src/device/service/drivers/openconfig/OpenConfigDriver.py
index ac03527529b603089c4f8233cb185f6427e0c360..569a0400ee709ebe3b0fa0694dd80801efc4f7fa 100644
--- a/src/device/service/drivers/openconfig/OpenConfigDriver.py
+++ b/src/device/service/drivers/openconfig/OpenConfigDriver.py
@@ -113,9 +113,11 @@ class NetconfSessionHandler:
                 config, target=target, default_operation=default_operation, test_option=test_option,
                 error_option=error_option, format=format)
 
+    @RETRY_DECORATOR
     def locked(self, target):
         return self.__manager.locked(target=target)
 
+    @RETRY_DECORATOR
     def commit(self, confirmed=False, timeout=None, persist=None, persist_id=None):
         return self.__manager.commit(confirmed=confirmed, timeout=timeout, persist=persist, persist_id=persist_id)
 
diff --git a/src/device/service/drivers/openconfig/templates/EndPoints.py b/src/device/service/drivers/openconfig/templates/EndPoints.py
index 02fda8f0e195c267fddb1109f184c8a06e4a6787..f16f0ffcd09a07f6c109328b1c5f0ee101af545a 100644
--- a/src/device/service/drivers/openconfig/templates/EndPoints.py
+++ b/src/device/service/drivers/openconfig/templates/EndPoints.py
@@ -55,5 +55,5 @@ def parse(xml_data : ET.Element) -> List[Tuple[str, Dict[str, Any]]]:
         add_value_from_collection(endpoint, 'sample_types', sample_types)
 
         if len(endpoint) == 0: continue
-        response.append(('/endpoint[{:s}]'.format(endpoint['uuid']), endpoint))
+        response.append(('/endpoints/endpoint[{:s}]'.format(endpoint['uuid']), endpoint))
     return response
diff --git a/src/device/service/drivers/transport_api/Tools.py b/src/device/service/drivers/transport_api/Tools.py
index 4943648dcab21159b213f9ee938987995be61b0e..bbd4247f0debdd17885c5aadccafc32607e4cbe5 100644
--- a/src/device/service/drivers/transport_api/Tools.py
+++ b/src/device/service/drivers/transport_api/Tools.py
@@ -15,7 +15,7 @@
 import json, logging, operator, requests
 from requests.auth import HTTPBasicAuth
 from typing import Optional
-from device.service.driver_api._Driver import RESOURCE_ENDPOINTS
+from device.service.driver_api._Driver import RESOURCE_ENDPOINTS, RESOURCE_SERVICES
 
 LOGGER = logging.getLogger(__name__)
 
@@ -52,27 +52,74 @@ def config_getter(
         result.append((resource_key, e))
         return result
 
-    if resource_key != RESOURCE_ENDPOINTS: return result
-
-    if 'tapi-common:context' in context:
-        context = context['tapi-common:context']
-    elif 'context' in context:
-        context = context['context']
-
-    for sip in context['service-interface-point']:
-        layer_protocol_name = sip.get('layer-protocol-name', '?')
-        supportable_spectrum = sip.get('tapi-photonic-media:media-channel-service-interface-point-spec', {})
-        supportable_spectrum = supportable_spectrum.get('mc-pool', {})
-        supportable_spectrum = supportable_spectrum.get('supportable-spectrum', [])
-        supportable_spectrum = supportable_spectrum[0] if len(supportable_spectrum) == 1 else {}
-        grid_type = supportable_spectrum.get('frequency-constraint', {}).get('grid-type')
-        granularity = supportable_spectrum.get('frequency-constraint', {}).get('adjustment-granularity')
-        direction = sip.get('direction', '?')
-        endpoint_type = [layer_protocol_name, grid_type, granularity, direction]
-        str_endpoint_type = ':'.join(filter(lambda i: operator.is_not(i, None), endpoint_type))
-        endpoint_url = '/endpoints/endpoint[{:s}]'.format(sip['uuid'])
-        endpoint_data = {'uuid': sip['uuid'], 'type': str_endpoint_type}
-        result.append((endpoint_url, endpoint_data))
+    if resource_key == RESOURCE_ENDPOINTS:
+        if 'tapi-common:context' in context:
+            context = context['tapi-common:context']
+        elif 'context' in context:
+            context = context['context']
+
+        for sip in context['service-interface-point']:
+            layer_protocol_name = sip.get('layer-protocol-name', '?')
+            supportable_spectrum = sip.get('tapi-photonic-media:media-channel-service-interface-point-spec', {})
+            supportable_spectrum = supportable_spectrum.get('mc-pool', {})
+            supportable_spectrum = supportable_spectrum.get('supportable-spectrum', [])
+            supportable_spectrum = supportable_spectrum[0] if len(supportable_spectrum) == 1 else {}
+            grid_type = supportable_spectrum.get('frequency-constraint', {}).get('grid-type')
+            granularity = supportable_spectrum.get('frequency-constraint', {}).get('adjustment-granularity')
+            direction = sip.get('direction', '?')
+
+            endpoint_type = [layer_protocol_name, grid_type, granularity, direction]
+            str_endpoint_type = ':'.join(filter(lambda i: operator.is_not(i, None), endpoint_type))
+            sip_uuid = sip['uuid']
+
+            sip_names = sip.get('name', [])
+            sip_name = next(iter([
+                sip_name['value']
+                for sip_name in sip_names
+                if sip_name['value-name'] == 'local-name'
+            ]), sip_uuid)
+
+            endpoint_url = '/endpoints/endpoint[{:s}]'.format(sip_uuid)
+            endpoint_data = {'uuid': sip_uuid, 'name': sip_name, 'type': str_endpoint_type}
+            result.append((endpoint_url, endpoint_data))
+
+    elif resource_key == RESOURCE_SERVICES:
+        if 'tapi-common:context' in context:
+            context = context['tapi-common:context']
+        elif 'context' in context:
+            context = context['context']
+
+        if 'tapi-connectivity:connectivity-context' in context:
+            context = context['tapi-connectivity:connectivity-context']
+        elif 'connectivity-context' in context:
+            context = context['connectivity-context']
+
+        for conn_svc in context['connectivity-service']:
+            service_uuid = conn_svc['uuid']
+            constraints = conn_svc.get('connectivity-constraint', {})
+            total_req_cap = constraints.get('requested-capacity', {}).get('total-size', {})
+
+            service_url = '/services/service[{:s}]'.format(service_uuid)
+            service_data = {
+                'uuid': service_uuid,
+                'direction': constraints.get('connectivity-direction', 'UNIDIRECTIONAL'),
+                'capacity_unit': total_req_cap.get('unit', '<UNDEFINED>'),
+                'capacity_value': total_req_cap.get('value', '<UNDEFINED>'),
+            }
+
+            for i,endpoint in enumerate(conn_svc.get('end-point', [])):
+                layer_protocol_name = endpoint.get('layer-protocol-name')
+                if layer_protocol_name is not None:
+                    service_data['layer_protocol_name'] = layer_protocol_name
+
+                layer_protocol_qualifier = endpoint.get('layer-protocol-qualifier')
+                if layer_protocol_qualifier is not None:
+                    service_data['layer_protocol_qualifier'] = layer_protocol_qualifier
+
+                sip = endpoint['service-interface-point']['service-interface-point-uuid']
+                service_data['input_sip' if i == 0 else 'output_sip'] = sip
+
+            result.append((service_url, service_data))
 
     return result
 
diff --git a/src/device/service/drivers/transport_api/TransportApiDriver.py b/src/device/service/drivers/transport_api/TransportApiDriver.py
index 8b84274e075e10af04924cefa03768d1c340fb52..1991a34d0d797c48b6c2296435c0ebd0f3a8125a 100644
--- a/src/device/service/drivers/transport_api/TransportApiDriver.py
+++ b/src/device/service/drivers/transport_api/TransportApiDriver.py
@@ -85,9 +85,9 @@ class TransportApiDriver(_Driver):
             for resource in resources:
                 LOGGER.info('resource = {:s}'.format(str(resource)))
 
-                input_sip = find_key(resource, 'input_sip')
-                output_sip = find_key(resource, 'output_sip')
                 uuid = find_key(resource, 'uuid')
+                input_sip = find_key(resource, 'input_sip_uuid')
+                output_sip = find_key(resource, 'output_sip_uuid')
                 capacity_value = find_key(resource, 'capacity_value')
                 capacity_unit = find_key(resource, 'capacity_unit')
                 layer_protocol_name = find_key(resource, 'layer_protocol_name')
diff --git a/src/device/service/drivers/transport_api/__init__.py b/src/device/service/drivers/transport_api/__init__.py
index 2d3f6df3276f063cd9b414f47bba41b656682049..d5073c330b89bed63f08b0da86c4a7649c87b3dd 100644
--- a/src/device/service/drivers/transport_api/__init__.py
+++ b/src/device/service/drivers/transport_api/__init__.py
@@ -12,16 +12,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from device.service.driver_api._Driver import RESOURCE_ENDPOINTS, RESOURCE_INTERFACES, RESOURCE_NETWORK_INSTANCES
+from device.service.driver_api._Driver import RESOURCE_ENDPOINTS, RESOURCE_SERVICES
 
 ALL_RESOURCE_KEYS = [
     RESOURCE_ENDPOINTS,
-    RESOURCE_INTERFACES,
-    RESOURCE_NETWORK_INSTANCES,
+    RESOURCE_SERVICES,
 ]
-
-RESOURCE_KEY_MAPPINGS = {
-    RESOURCE_ENDPOINTS        : 'component',
-    RESOURCE_INTERFACES       : 'interface',
-    RESOURCE_NETWORK_INSTANCES: 'network_instance',
-}
diff --git a/src/device/service/drivers/xr/XrDriver.py b/src/device/service/drivers/xr/XrDriver.py
index 605f4ce8d0f9c875a4b1736ff0aaa02fcb468778..2b53de8e71f84aada460f444a3663238290f6ea5 100644
--- a/src/device/service/drivers/xr/XrDriver.py
+++ b/src/device/service/drivers/xr/XrDriver.py
@@ -16,10 +16,13 @@
 import logging
 import threading
 import json
-from typing import Any, Iterator, List, Optional, Tuple, Union
+from typing import Any, Iterator, List, Optional, Set, Tuple, Union
 import urllib3
+from common.DeviceTypes import DeviceTypeEnum
 from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method
+from common.proto.context_pb2 import DeviceDriverEnum, DeviceOperationalStatusEnum
 from common.type_checkers.Checkers import chk_type
+from device.service.driver_api.ImportTopologyEnum import ImportTopologyEnum, get_import_topology
 from device.service.driver_api._Driver import _Driver
 from .cm.cm_connection import CmConnection, ConsistencyMode
 from .cm import tf
@@ -46,6 +49,14 @@ class XrDriver(_Driver):
         username = settings.get("username", "xr-user-1")
         password = settings.get("password", "xr-user-1")
         
+        # Options are:
+        #    disabled --> just import endpoints as usual
+        #    devices  --> imports sub-devices but not links connecting them.
+        #                 (a remotely-controlled transport domain might exist between them)
+        #    topology --> imports sub-devices and links connecting them.
+        #                 (not supported by XR driver)
+        self.__import_topology = get_import_topology(settings, default=ImportTopologyEnum.DISABLED)
+
         # Options are:
         #    asynchronous --> operation considered complete when IPM responds with suitable status code,
         #                     including "accepted", that only means request is semantically good and queued.
@@ -98,7 +109,56 @@ class XrDriver(_Driver):
             constellation = self.__cm_connection.get_constellation_by_hub_name(self.__hub_module_name)
             if constellation:
                 self.__constellation = constellation
-                return [(f"/endpoints/endpoint[{ifname}]", {'uuid': ifname, 'type': 'optical', 'sample_types': {}}) for ifname in constellation.ifnames()]
+                if self.__import_topology == ImportTopologyEnum.DISABLED:
+                    return [
+                        (f"/endpoints/endpoint[{ifname}]", {'uuid': ifname, 'type': 'optical', 'sample_types': {}})
+                        for ifname in constellation.ifnames()
+                    ]
+                elif self.__import_topology == ImportTopologyEnum.DEVICES:
+                    devices : Set[str] = set()
+                    pluggables : Set[str] = set()
+                    devices_and_endpoints = []
+                    for ifname in constellation.ifnames():
+                        device_name,pluggable_name = ifname.split('|')
+
+                        if device_name not in devices:
+                            device_url = '/devices/device[{:s}]'.format(device_name)
+                            device_data = {
+                                'uuid': device_name, 'name': device_name,
+                                'type': DeviceTypeEnum.EMULATED_PACKET_ROUTER.value,
+                                'status': DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED,
+                                'drivers': [DeviceDriverEnum.DEVICEDRIVER_UNDEFINED],
+                            }
+                            devices_and_endpoints.append((device_url, device_data))
+
+                            for copper_if_index in range(4):
+                                copper_ifname = '1/{:d}'.format(copper_if_index + 1)
+                                endpoint_url = '/endpoints/endpoint[{:s}]'.format(copper_ifname)
+                                endpoint_data = {
+                                    'device_uuid': device_name, 'uuid': copper_ifname, 'name': copper_ifname,
+                                    'type': 'copper/internal', 'sample_types': {}
+                                }
+                                devices_and_endpoints.append((endpoint_url, endpoint_data))
+
+                            devices.add(device_name)
+
+                        if ifname not in pluggables:
+                            endpoint_url = '/endpoints/endpoint[{:s}]'.format(ifname)
+                            if 'hub' in ifname.lower():
+                                endpoint_type = 'optical/xr-hub'
+                            elif 'leaf' in ifname.lower():
+                                endpoint_type = 'optical/xr-leaf'
+                            else:
+                                endpoint_type = 'optical/xr'
+                            endpoint_data = {
+                                'device_uuid': device_name, 'uuid': pluggable_name, 'name': pluggable_name,
+                                'type': endpoint_type, 'sample_types': {}
+                            }
+                            devices_and_endpoints.append((endpoint_url, endpoint_data))
+
+                    return devices_and_endpoints
+                else:
+                    raise Exception('Unsupported import_topology mode: {:s}'.format(str(self.__import_topology)))
             else:
                 return []
 
diff --git a/src/device/service/drivers/xr/cm-cli.py b/src/device/service/drivers/xr/cm-cli.py
index 924ca0c966bbefd8b72c655ea788bdfd0ed08c5d..14c6d24b6da05a6f506152d0099d4739c2858e2c 100755
--- a/src/device/service/drivers/xr/cm-cli.py
+++ b/src/device/service/drivers/xr/cm-cli.py
@@ -160,8 +160,8 @@ if args.emulate_tf_set_config_service:
     hub_module_name, uuid, input_sip, output_sip, capacity_value  = eargs[0:5]
     capacity_value = int(capacity_value)
     config = {
-        "input_sip": input_sip,
-        "output_sip": output_sip,
+        "input_sip_name": input_sip,
+        "output_sip_name": output_sip,
         "capacity_value": capacity_value,
         "capacity_unit": "gigabit"
     }
diff --git a/src/device/service/drivers/xr/cm/tests/test_xr_service_set_config.py b/src/device/service/drivers/xr/cm/tests/test_xr_service_set_config.py
index e9b16b62034bcd42061907d920b757b59766f562..05f55d0df471115ea2e5ace7c3014333aa94e0fe 100644
--- a/src/device/service/drivers/xr/cm/tests/test_xr_service_set_config.py
+++ b/src/device/service/drivers/xr/cm/tests/test_xr_service_set_config.py
@@ -44,8 +44,8 @@ def mock_cm():
 
 uuid = "12345ABCDEFGHIJKLMN"
 config = {
-    "input_sip": "XR HUB 1|XR-T4;",
-    "output_sip": "XR LEAF 1|XR-T1",
+    "input_sip_name": "XR HUB 1|XR-T4;",
+    "output_sip_name": "XR LEAF 1|XR-T1",
     "capacity_value": 125,
     "capacity_unit": "gigabit"
 }
diff --git a/src/device/service/drivers/xr/cm/tf.py b/src/device/service/drivers/xr/cm/tf.py
index c44cb0c9f3ce0e755ce375908a520374e639e40f..ace3cd2888809e2b498a82b1ade9983419033c20 100644
--- a/src/device/service/drivers/xr/cm/tf.py
+++ b/src/device/service/drivers/xr/cm/tf.py
@@ -38,7 +38,7 @@ def _get_capacity(config) -> int:
 
 def set_config_for_service(cm_connection: CmConnection, constellation: Constellation, uuid: str, config: Dict[str, any]) -> Union[bool, Exception]:
     try:
-        service = TFService(uuid, config["input_sip"], config["output_sip"], _get_capacity(config))
+        service = TFService(uuid, config["input_sip_name"], config["output_sip_name"], _get_capacity(config))
         if constellation.is_vti_mode():
             desired_tc = TransportCapacity(from_tf_service=service)
             active_tc = cm_connection.get_transport_capacity_by_name(service.name())
diff --git a/src/pathcomp/backend/pathComp_RESTapi.c b/src/pathcomp/backend/pathComp_RESTapi.c
index 8ee7f6d82537b7eab297334a0a0dcfad13f8af44..82d4b38a840cf813dab850c4cb136ff05b503cbd 100644
--- a/src/pathcomp/backend/pathComp_RESTapi.c
+++ b/src/pathcomp/backend/pathComp_RESTapi.c
@@ -1211,7 +1211,9 @@ void parsing_json_obj_pathComp_request(cJSON * root, GIOChannel * source)
 
 		// In the context information, if solely the list of links are passed for a single direction, 
 		// the reverse direction MUST be created sythetically 
-		generate_reverse_linkList();
+		
+		// LGR: deactivated; link duplication needs to be done smartly with TAPI. done manually in topology by now
+		//generate_reverse_linkList();
 	}
 	return;
 }
diff --git a/src/pathcomp/backend/pathComp_sp.c b/src/pathcomp/backend/pathComp_sp.c
index 447b0d2a6d002d12808f80c855c74f8d0b489743..b143b04933f1ac9099af3edf3af087cc58e32c5b 100644
--- a/src/pathcomp/backend/pathComp_sp.c
+++ b/src/pathcomp/backend/pathComp_sp.c
@@ -296,8 +296,8 @@ void sp_execution_services(struct compRouteOutputList_t* oPathList)
 			 continue;
 		 }
 		 struct path_t* path = &(pathService->paths[pathService->numPaths - 1]);
-		 allocate_graph_resources(path, service, g);
-		 allocate_graph_reverse_resources(path, service, g);
+		 //allocate_graph_resources(path, service, g);			// LGR: crashes in some cases with assymetric topos
+		 //allocate_graph_reverse_resources(path, service, g);	// LGR: crashes in some cases with assymetric topos
 		 print_graph(g);
 	}
 	return;
diff --git a/src/pathcomp/backend/pathComp_tools.h b/src/pathcomp/backend/pathComp_tools.h
index b6bcea04c8aa01b6cf730460e0075327f872f344..b770788910a04f76a8625a7e2d74fca5f2f6ecad 100644
--- a/src/pathcomp/backend/pathComp_tools.h
+++ b/src/pathcomp/backend/pathComp_tools.h
@@ -117,7 +117,7 @@ struct map_nodes_t {
 };
 
 #define MAX_NUM_VERTICES				20 // 100 # LGR: reduced from 100 to 20 to divide by 5 the memory used
-#define MAX_NUM_EDGES					40 // 100 # LGR: reduced from 100 to 40 to divide by 2.5 the memory used
+#define MAX_NUM_EDGES					5 // 100 # LGR: reduced from 100 to 5 to divide by 20 the memory used
 // Structures for the graph composition
 struct targetNodes_t {
 	// remote / targeted node
@@ -247,7 +247,7 @@ struct endPoint_t {
 // Structure for the device contents
 ///////////////////////////////////////////////////////////////////
 #define MAX_DEV_TYPE_SIZE				128
-#define MAX_DEV_ENDPOINT_LENGTH			40	// 10 # LGR: controllers might have large number of endpoints
+#define MAX_DEV_ENDPOINT_LENGTH			50	// 10 # LGR: controllers might have large number of endpoints
 struct device_t {
 	gchar deviceId[UUID_CHAR_LENGTH]; // device ID using UUID (128 bits)
 
diff --git a/src/pathcomp/frontend/service/algorithms/_Algorithm.py b/src/pathcomp/frontend/service/algorithms/_Algorithm.py
index b6316774921171eb8ed6cf3faafd4b607bdcb831..b486ec1b59457b1ac575fb6197c7713b10c306e3 100644
--- a/src/pathcomp/frontend/service/algorithms/_Algorithm.py
+++ b/src/pathcomp/frontend/service/algorithms/_Algorithm.py
@@ -15,17 +15,20 @@
 import json, logging, requests
 from typing import Dict, List, Optional, Tuple, Union
 from common.proto.context_pb2 import (
-    ConfigRule, Connection, Device, DeviceList, EndPointId, Link, LinkList, Service, ServiceStatusEnum,
-    ServiceTypeEnum)
+    Connection, Device, DeviceList, EndPointId, Link, LinkList, Service, ServiceStatusEnum, ServiceTypeEnum)
 from common.proto.pathcomp_pb2 import PathCompReply, PathCompRequest
-from common.tools.object_factory.ConfigRule import json_config_rule_set
 from pathcomp.frontend.Config import BACKEND_URL
-from pathcomp.frontend.service.algorithms.tools.ConstantsMappings import DEVICE_LAYER_TO_SERVICE_TYPE, DeviceLayerEnum
 from .tools.EroPathToHops import eropath_to_hops
+from .tools.ComposeConfigRules import (
+    compose_device_config_rules, compose_l2nm_config_rules, compose_l3nm_config_rules, compose_tapi_config_rules)
 from .tools.ComposeRequest import compose_device, compose_link, compose_service
 from .tools.ComputeSubServices import (
     convert_explicit_path_hops_to_connections, convert_explicit_path_hops_to_plain_connection)
 
+SRC_END = 'src'
+DST_END = 'dst'
+SENSE = [SRC_END, DST_END]
+
 class _Algorithm:
     def __init__(self, algorithm_id : str, sync_paths : bool, class_name=__name__) -> None:
         # algorithm_id: algorithm to be executed
@@ -45,7 +48,7 @@ class _Algorithm:
         self.endpoint_name_mapping : Dict[Tuple[str, str], str] = dict()
         self.link_list : List[Dict] = list()
         self.link_dict : Dict[str, Tuple[Dict, Link]] = dict()
-        self.endpoint_to_link_dict : Dict[Tuple[str, str], Tuple[Dict, Link]] = dict()
+        self.endpoint_to_link_dict : Dict[Tuple[str, str, str], Tuple[Dict, Link]] = dict()
         self.service_list : List[Dict] = list()
         self.service_dict : Dict[Tuple[str, str], Tuple[Dict, Service]] = dict()
 
@@ -61,6 +64,7 @@ class _Algorithm:
             _device_uuid = grpc_device.device_id.device_uuid.uuid
             _device_name = grpc_device.name
             self.device_name_mapping[_device_name] = _device_uuid
+            self.device_name_mapping[_device_uuid] = _device_uuid
 
             device_endpoint_dict : Dict[str, Tuple[Dict, EndPointId]] = dict()
             for json_endpoint,grpc_endpoint in zip(json_device['device_endpoints'], grpc_device.device_endpoints):
@@ -72,12 +76,16 @@ class _Algorithm:
                 _endpoint_name = grpc_endpoint.name
                 self.endpoint_name_mapping[(_device_uuid, _endpoint_name)] = _endpoint_uuid
                 self.endpoint_name_mapping[(_device_name, _endpoint_name)] = _endpoint_uuid
+                self.endpoint_name_mapping[(_device_uuid, _endpoint_uuid)] = _endpoint_uuid
+                self.endpoint_name_mapping[(_device_name, _endpoint_uuid)] = _endpoint_uuid
 
             self.endpoint_dict[device_uuid] = device_endpoint_dict
 
     def add_links(self, grpc_links : Union[List[Link], LinkList]) -> None:
         if isinstance(grpc_links, LinkList): grpc_links = grpc_links.links
         for grpc_link in grpc_links:
+            if 'mgmt' in grpc_link.name.lower(): continue
+
             json_link = compose_link(grpc_link)
             if len(json_link['link_endpoint_ids']) != 2: continue
             self.link_list.append(json_link)
@@ -85,11 +93,11 @@ class _Algorithm:
             link_uuid = json_link['link_Id']
             self.link_dict[link_uuid] = (json_link, grpc_link)
 
-            for link_endpoint_id in json_link['link_endpoint_ids']:
+            for i,link_endpoint_id in enumerate(json_link['link_endpoint_ids']):
                 link_endpoint_id = link_endpoint_id['endpoint_id']
                 device_uuid = link_endpoint_id['device_id']
                 endpoint_uuid = link_endpoint_id['endpoint_uuid']
-                endpoint_key = (device_uuid, endpoint_uuid)
+                endpoint_key = (device_uuid, endpoint_uuid, SENSE[i])
                 link_tuple = (json_link, grpc_link)
                 self.endpoint_to_link_dict[endpoint_key] = link_tuple
 
@@ -148,9 +156,8 @@ class _Algorithm:
         return connection
 
     def add_service_to_reply(
-        self, reply : PathCompReply, context_uuid : str, service_uuid : str,
-        device_layer : Optional[DeviceLayerEnum] = None, path_hops : List[Dict] = [],
-        config_rules : List = []
+        self, reply : PathCompReply, context_uuid : str, service_uuid : str, service_type : ServiceTypeEnum,
+        path_hops : List[Dict] = [], config_rules : List = []
     ) -> Service:
         # TODO: implement support for multi-point services
         # Control deactivated to enable disjoint paths with multiple redundant endpoints on each side
@@ -159,44 +166,43 @@ class _Algorithm:
 
         service_key = (context_uuid, service_uuid)
         tuple_service = self.service_dict.get(service_key)
-        if tuple_service is not None:
-            service = reply.services.add()
-            service.CopyFrom(tuple_service[1])
+
+        service = reply.services.add()
+        service.service_id.context_id.context_uuid.uuid = context_uuid
+        service.service_id.service_uuid.uuid = service_uuid
+        service.service_type = service_type
+
+        if service_type == ServiceTypeEnum.SERVICETYPE_L2NM:
+            compose_l2nm_config_rules(config_rules, service.service_config.config_rules)
+        elif service_type == ServiceTypeEnum.SERVICETYPE_L3NM:
+            compose_l3nm_config_rules(config_rules, service.service_config.config_rules)
+        elif service_type == ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE:
+            compose_tapi_config_rules(config_rules, service.service_config.config_rules)
         else:
-            service = reply.services.add()
-            service.service_id.context_id.context_uuid.uuid = context_uuid
-            service.service_id.service_uuid.uuid = service_uuid
-
-            if device_layer is not None:
-                service_type = DEVICE_LAYER_TO_SERVICE_TYPE.get(device_layer.value)
-                if service_type is None:
-                    MSG = 'Unable to map DeviceLayer({:s}) to ServiceType'
-                    raise Exception(MSG.format(str(device_layer)))
-                service.service_type = service_type
-
-                if service_type == ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE:
-                    json_tapi_settings = {
-                        'capacity_value'  : 50.0,
-                        'capacity_unit'   : 'GHz',
-                        'layer_proto_name': 'PHOTONIC_MEDIA',
-                        'layer_proto_qual': 'tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_NMC',
-                        'direction'       : 'UNIDIRECTIONAL',
-                    }
-                    config_rule = ConfigRule(**json_config_rule_set('/settings', json_tapi_settings))
-                    service.service_config.config_rules.append(config_rule)
-                else:
-                    service.service_config.config_rules.extend(config_rules)
+            MSG = 'Unhandled generic Config Rules for service {:s} {:s}'
+            self.logger.warning(MSG.format(str(service_uuid), str(ServiceTypeEnum.Name(service_type))))
 
-            service.service_status.service_status = ServiceStatusEnum.SERVICESTATUS_PLANNED
+        compose_device_config_rules(
+            config_rules, service.service_config.config_rules, path_hops,
+            self.device_name_mapping, self.endpoint_name_mapping)
 
-            if path_hops is not None and len(path_hops) > 0:
-                ingress_endpoint_id = service.service_endpoint_ids.add()
-                ingress_endpoint_id.device_id.device_uuid.uuid = path_hops[0]['device']
-                ingress_endpoint_id.endpoint_uuid.uuid = path_hops[0]['ingress_ep']
+        if path_hops is not None and len(path_hops) > 0:
+            ingress_endpoint_id = service.service_endpoint_ids.add()
+            ingress_endpoint_id.device_id.device_uuid.uuid = path_hops[0]['device']
+            ingress_endpoint_id.endpoint_uuid.uuid = path_hops[0]['ingress_ep']
 
-                egress_endpoint_id = service.service_endpoint_ids.add()
-                egress_endpoint_id.device_id.device_uuid.uuid = path_hops[-1]['device']
-                egress_endpoint_id.endpoint_uuid.uuid = path_hops[-1]['egress_ep']
+            egress_endpoint_id = service.service_endpoint_ids.add()
+            egress_endpoint_id.device_id.device_uuid.uuid = path_hops[-1]['device']
+            egress_endpoint_id.endpoint_uuid.uuid = path_hops[-1]['egress_ep']
+
+        if tuple_service is None:
+            service.service_status.service_status = ServiceStatusEnum.SERVICESTATUS_PLANNED
+        else:
+            service.name = tuple_service[1].name
+            service.service_status.CopyFrom(tuple_service[1].service_status)
+            service.timestamp.CopyFrom(tuple_service[1].timestamp)
+            for constraint in tuple_service[1].service_constraints:
+                service.service_constraints.add().CopyFrom(constraint)
 
         return service
 
@@ -206,41 +212,54 @@ class _Algorithm:
         grpc_services : Dict[Tuple[str, str], Service] = {}
         grpc_connections : Dict[str, Connection] = {}
         for response in response_list:
-            service_id = response['serviceId']
-            context_uuid = service_id['contextId']
-            service_uuid = service_id['service_uuid']
-            service_key = (context_uuid, service_uuid)
-            upper_service = self.add_service_to_reply(reply, context_uuid, service_uuid)
-            grpc_services[service_key] = upper_service
+            orig_service_id = response['serviceId']
+            context_uuid = orig_service_id['contextId']
+            main_service_uuid = orig_service_id['service_uuid']
+            orig_service_key = (context_uuid, main_service_uuid)
+            _,grpc_orig_service = self.service_dict[orig_service_key]
+            main_service_type = grpc_orig_service.service_type
 
             no_path_issue = response.get('noPath', {}).get('issue')
             if no_path_issue is not None:
                 # no path found: leave connection with no endpoints
                 # no_path_issue == 1 => no path due to a constraint
+                grpc_services[orig_service_key] = grpc_orig_service
                 continue
 
+            orig_config_rules = grpc_orig_service.service_config.config_rules
+
             for service_path_ero in response['path']:
+                self.logger.debug('service_path_ero["devices"] = {:s}'.format(str(service_path_ero['devices'])))
+                _endpoint_to_link_dict = {k:v[0] for k,v in self.endpoint_to_link_dict.items()}
+                self.logger.debug('self.endpoint_to_link_dict = {:s}'.format(str(_endpoint_to_link_dict)))
                 path_hops = eropath_to_hops(service_path_ero['devices'], self.endpoint_to_link_dict)
+                self.logger.debug('path_hops = {:s}'.format(str(path_hops)))
                 try:
-                    connections = convert_explicit_path_hops_to_connections(path_hops, self.device_dict, service_uuid)
+                    _device_dict = {k:v[0] for k,v in self.device_dict.items()}
+                    self.logger.debug('self.device_dict = {:s}'.format(str(_device_dict)))
+                    connections = convert_explicit_path_hops_to_connections(
+                        path_hops, self.device_dict, main_service_uuid, main_service_type)
+                    self.logger.debug('EXTRAPOLATED connections = {:s}'.format(str(connections)))
                 except: # pylint: disable=bare-except
-                    # if not able to extrapolate sub-services and sub-connections,
-                    # assume single service and single connection
-                    connections = convert_explicit_path_hops_to_plain_connection(path_hops, service_uuid)
+                    MSG = ' '.join([
+                        'Unable to Extrapolate sub-services and sub-connections.',
+                        'Assuming single-service and single-connection.',
+                    ])
+                    self.logger.exception(MSG)
+                    connections = convert_explicit_path_hops_to_plain_connection(
+                        path_hops, main_service_uuid, main_service_type)
+                    self.logger.debug('BASIC connections = {:s}'.format(str(connections)))
 
                 for connection in connections:
-                    connection_uuid,device_layer,path_hops,_ = connection
+                    connection_uuid,service_type,path_hops,_ = connection
                     service_key = (context_uuid, connection_uuid)
-                    grpc_service = grpc_services.get(service_key)
-                    if grpc_service is None:
-                        config_rules = upper_service.service_config.config_rules
-                        grpc_service = self.add_service_to_reply(
-                            reply, context_uuid, connection_uuid, device_layer=device_layer, path_hops=path_hops,
-                            config_rules=config_rules)
-                        grpc_services[service_key] = grpc_service
+                    grpc_service = self.add_service_to_reply(
+                        reply, context_uuid, connection_uuid, service_type, path_hops=path_hops,
+                        config_rules=orig_config_rules)
+                    grpc_services[service_key] = grpc_service
 
                 for connection in connections:
-                    connection_uuid,device_layer,path_hops,dependencies = connection
+                    connection_uuid,_,path_hops,dependencies = connection
 
                     service_key = (context_uuid, connection_uuid)
                     grpc_service = grpc_services.get(service_key)
@@ -251,8 +270,8 @@ class _Algorithm:
                     grpc_connection = self.add_connection_to_reply(reply, connection_uuid, grpc_service, path_hops)
                     grpc_connections[connection_uuid] = grpc_connection
 
-                    for service_uuid in dependencies:
-                        sub_service_key = (context_uuid, service_uuid)
+                    for sub_service_uuid in dependencies:
+                        sub_service_key = (context_uuid, sub_service_uuid)
                         grpc_sub_service = grpc_services.get(sub_service_key)
                         if grpc_sub_service is None:
                             raise Exception('Service({:s}) not found'.format(str(sub_service_key)))
diff --git a/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py b/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py
new file mode 100644
index 0000000000000000000000000000000000000000..91367e23f29a02aa3e9605fcd0d2864b9191d800
--- /dev/null
+++ b/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py
@@ -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.
+
+import itertools, json, re
+from typing import Dict, List, Optional, Tuple
+from common.proto.context_pb2 import ConfigRule
+from common.tools.object_factory.ConfigRule import json_config_rule_set
+
+SETTINGS_RULE_NAME = '/settings'
+
+DEV_EP_SETTINGS = re.compile(r'\/device\[([^\]]+)\]\/endpoint\[([^\]]+)\]\/settings')
+
+L2NM_SETTINGS_FIELD_DEFAULTS = {
+    'encapsulation_type': 'dot1q',
+    'vlan_id'           : 100,
+    'mtu'               : 1450,
+}
+
+L3NM_SETTINGS_FIELD_DEFAULTS = {
+    'encapsulation_type': 'dot1q',
+    'vlan_id'           : 100,
+    'mtu'               : 1450,
+}
+
+TAPI_SETTINGS_FIELD_DEFAULTS = {
+    'capacity_value'  : 50.0,
+    'capacity_unit'   : 'GHz',
+    'layer_proto_name': 'PHOTONIC_MEDIA',
+    'layer_proto_qual': 'tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_NMC',
+    'direction'       : 'UNIDIRECTIONAL',
+}
+
+def find_custom_config_rule(config_rules : List, resource_name : str) -> Optional[Dict]:
+    resource_value : Optional[Dict] = None
+    for config_rule in config_rules:
+        if config_rule.WhichOneof('config_rule') != 'custom': continue
+        if config_rule.custom.resource_key != resource_name: continue
+        resource_value = json.loads(config_rule.custom.resource_value)
+    return resource_value
+
+def compose_config_rules(
+    main_service_config_rules : List, subservice_config_rules : List, field_defaults : Dict
+) -> None:
+    settings = find_custom_config_rule(main_service_config_rules, SETTINGS_RULE_NAME)
+    if settings is None: return
+
+    json_settings = {}
+    for field_name,default_value in field_defaults.items():
+        json_settings[field_name] = settings.get(field_name, default_value)
+
+    config_rule = ConfigRule(**json_config_rule_set('/settings', json_settings))
+    subservice_config_rules.append(config_rule)
+
+def compose_l2nm_config_rules(main_service_config_rules : List, subservice_config_rules : List) -> None:
+    compose_config_rules(main_service_config_rules, subservice_config_rules, L2NM_SETTINGS_FIELD_DEFAULTS)
+
+def compose_l3nm_config_rules(main_service_config_rules : List, subservice_config_rules : List) -> None:
+    compose_config_rules(main_service_config_rules, subservice_config_rules, L3NM_SETTINGS_FIELD_DEFAULTS)
+
+def compose_tapi_config_rules(main_service_config_rules : List, subservice_config_rules : List) -> None:
+    compose_config_rules(main_service_config_rules, subservice_config_rules, TAPI_SETTINGS_FIELD_DEFAULTS)
+
+def compose_device_config_rules(
+    config_rules : List, subservice_config_rules : List, path_hops : List,
+    device_name_mapping : Dict[str, str], endpoint_name_mapping : Dict[Tuple[str, str], str]
+) -> None:
+
+    endpoints_traversed = set()
+    for path_hop in path_hops:
+        device_uuid_or_name = path_hop['device']
+        endpoints_traversed.add((device_uuid_or_name, path_hop['ingress_ep']))
+        endpoints_traversed.add((device_uuid_or_name, path_hop['egress_ep']))
+
+    for config_rule in config_rules:
+        if config_rule.WhichOneof('config_rule') != 'custom': continue
+        match = DEV_EP_SETTINGS.match(config_rule.custom.resource_key)
+        if match is None: continue
+
+        device_uuid_or_name = match.group(1)
+        device_name_or_uuid = device_name_mapping[device_uuid_or_name]
+        device_keys = {device_uuid_or_name, device_name_or_uuid}
+
+        endpoint_uuid_or_name = match.group(2)
+        endpoint_name_or_uuid_1 = endpoint_name_mapping[(device_uuid_or_name, endpoint_uuid_or_name)]
+        endpoint_name_or_uuid_2 = endpoint_name_mapping[(device_name_or_uuid, endpoint_uuid_or_name)]
+        endpoint_keys = {endpoint_uuid_or_name, endpoint_name_or_uuid_1, endpoint_name_or_uuid_2}
+
+        device_endpoint_keys = set(itertools.product(device_keys, endpoint_keys))
+        if len(device_endpoint_keys.intersection(endpoints_traversed)) == 0: continue
+        subservice_config_rules.append(config_rule)
diff --git a/src/pathcomp/frontend/service/algorithms/tools/ComposeRequest.py b/src/pathcomp/frontend/service/algorithms/tools/ComposeRequest.py
index ee85f0bb083500c655e78798bbcd2bd00e8a4501..e2c6dc13804703d89242b27156763ce887aa4884 100644
--- a/src/pathcomp/frontend/service/algorithms/tools/ComposeRequest.py
+++ b/src/pathcomp/frontend/service/algorithms/tools/ComposeRequest.py
@@ -118,11 +118,11 @@ def compose_link(grpc_link : Link) -> Dict:
         for link_endpoint_id in grpc_link.link_endpoint_ids
     ]
 
-    forwarding_direction = LinkForwardingDirection.BIDIRECTIONAL.value
+    forwarding_direction = LinkForwardingDirection.UNIDIRECTIONAL.value
     total_potential_capacity = compose_capacity(200, CapacityUnit.MBPS.value)
     available_capacity = compose_capacity(200, CapacityUnit.MBPS.value)
     cost_characteristics = compose_cost_characteristics('linkcost', '1', '0')
-    latency_characteristics = compose_latency_characteristics('2')
+    latency_characteristics = compose_latency_characteristics('1')
 
     return {
         'link_Id': link_uuid, 'link_endpoint_ids': endpoint_ids, 'forwarding_direction': forwarding_direction,
diff --git a/src/pathcomp/frontend/service/algorithms/tools/ComputeSubServices.py b/src/pathcomp/frontend/service/algorithms/tools/ComputeSubServices.py
index b92a19b52c4887e01f7f1bc58de897c783683eeb..40cb0857617983df4cfd926baebcbff85e169894 100644
--- a/src/pathcomp/frontend/service/algorithms/tools/ComputeSubServices.py
+++ b/src/pathcomp/frontend/service/algorithms/tools/ComputeSubServices.py
@@ -30,55 +30,77 @@
 # ]
 #
 # connections=[
-#     (UUID('7548edf7-ee7c-4adf-ac0f-c7a0c0dfba8e'), <DeviceLayerEnum.OPTICAL_CONTROLLER: 1>, [
+#     (UUID('7548edf7-ee7c-4adf-ac0f-c7a0c0dfba8e'), ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE, [
 #             {'device': 'TN-OLS', 'ingress_ep': '833760219d0f', 'egress_ep': 'cf176771a4b9'}
 #         ], []),
-#     (UUID('c2e57966-5d82-4705-a5fe-44cf6487219e'), <DeviceLayerEnum.PACKET_DEVICE: 30>, [
+#     (UUID('c2e57966-5d82-4705-a5fe-44cf6487219e'), ServiceTypeEnum.SERVICETYPE_L2NM, [
 #             {'device': 'CS1-GW1', 'ingress_ep': '10/1', 'egress_ep': '1/2'},
 #             {'device': 'TN-R2', 'ingress_ep': '1/2', 'egress_ep': '2/1'},
 #             {'device': 'TN-R3', 'ingress_ep': '2/1', 'egress_ep': '1/1'},
 #             {'device': 'CS2-GW1', 'ingress_ep': '1/1', 'egress_ep': '10/1'}
 #         ], [UUID('7548edf7-ee7c-4adf-ac0f-c7a0c0dfba8e')]),
-#     (UUID('1e205c82-f6ea-4977-9e97-dc27ef1f4802'), <DeviceLayerEnum.APPLICATION_DEVICE: 40>, [
+#     (UUID('1e205c82-f6ea-4977-9e97-dc27ef1f4802'), ServiceTypeEnum.SERVICETYPE_L2NM, [
 #             {'device': 'DC1-GW', 'ingress_ep': 'int', 'egress_ep': 'eth1'},
 #             {'device': 'DC2-GW', 'ingress_ep': 'eth1', 'egress_ep': 'int'}
 #         ], [UUID('c2e57966-5d82-4705-a5fe-44cf6487219e')])
 # ]
 
-import queue, uuid
-from typing import Dict, List, Tuple
-from common.proto.context_pb2 import Device
-from .ConstantsMappings import DEVICE_TYPE_TO_LAYER, DeviceLayerEnum
+import logging, queue, uuid
+from typing import Dict, List, Optional, Tuple
+from common.DeviceTypes import DeviceTypeEnum
+from common.proto.context_pb2 import Device, ServiceTypeEnum
+from .ResourceGroups import IGNORED_DEVICE_TYPES, get_resource_classification
+from .ServiceTypes import get_service_type
+
+LOGGER = logging.getLogger(__name__)
 
 def convert_explicit_path_hops_to_connections(
-    path_hops : List[Dict], device_dict : Dict[str, Tuple[Dict, Device]], main_connection_uuid : str
-) -> List[Tuple[str, DeviceLayerEnum, List[str], List[str]]]:
+    path_hops : List[Dict], device_dict : Dict[str, Tuple[Dict, Device]],
+    main_service_uuid : str, main_service_type : ServiceTypeEnum
+) -> List[Tuple[str, int, List[str], List[str]]]:
+
+    LOGGER.debug('path_hops={:s}'.format(str(path_hops)))
 
     connection_stack = queue.LifoQueue()
-    connections : List[Tuple[str, DeviceLayerEnum, List[str], List[str]]] = list()
-    old_device_layer = None
-    last_device_uuid = None
+    connections : List[Tuple[str, int, List[str], List[str]]] = list()
+    prv_device_uuid = None
+    prv_res_class : Tuple[Optional[int], Optional[DeviceTypeEnum], Optional[str]] = None, None, None
+
     for path_hop in path_hops:
         device_uuid = path_hop['device']
-        if last_device_uuid == device_uuid: continue
+        if prv_device_uuid == device_uuid: continue
         device_tuple = device_dict.get(device_uuid)
         if device_tuple is None: raise Exception('Device({:s}) not found'.format(str(device_uuid)))
-        json_device,_ = device_tuple
-        device_type = json_device['device_type']
-        device_layer = DEVICE_TYPE_TO_LAYER.get(device_type)
-        if device_layer is None: raise Exception('Undefined Layer for DeviceType({:s})'.format(str(device_type)))
+        _,grpc_device = device_tuple
 
-        if old_device_layer is None:
+        res_class = get_resource_classification(grpc_device, device_dict)
+        if res_class[1] in IGNORED_DEVICE_TYPES: continue
+
+        if prv_res_class[0] is None:
             # path ingress
-            connection_stack.put((main_connection_uuid, device_layer, [path_hop], []))
-        elif old_device_layer > device_layer:
-            # underlying connection begins
+            connection_stack.put((main_service_uuid, main_service_type, [path_hop], []))
+        elif prv_res_class[0] > res_class[0]:
+            # create underlying connection
             connection_uuid = str(uuid.uuid4())
-            connection_stack.put((connection_uuid, device_layer, [path_hop], []))
-        elif old_device_layer == device_layer:
-            # same connection continues
-            connection_stack.queue[-1][2].append(path_hop)
-        elif old_device_layer < device_layer:
+            prv_service_type = connection_stack.queue[-1][1]
+            service_type = get_service_type(res_class[1], prv_service_type)
+            connection_stack.put((connection_uuid, service_type, [path_hop], []))
+        elif prv_res_class[0] == res_class[0]:
+            # same resource group kind
+            if prv_res_class[1] == res_class[1] and prv_res_class[2] == res_class[2]:
+                # same device type and device controller: connection continues
+                connection_stack.queue[-1][2].append(path_hop)
+            else:
+                # different device type or device controller: chain connections
+                connection = connection_stack.get()
+                connections.append(connection)
+                connection_stack.queue[-1][3].append(connection[0])
+
+                connection_uuid = str(uuid.uuid4())
+                prv_service_type = connection_stack.queue[-1][1]
+                service_type = get_service_type(res_class[1], prv_service_type)
+                connection_stack.put((connection_uuid, service_type, [path_hop], []))
+        elif prv_res_class[0] < res_class[0]:
             # underlying connection ended
             connection = connection_stack.get()
             connections.append(connection)
@@ -87,26 +109,27 @@ def convert_explicit_path_hops_to_connections(
         else:
             raise Exception('Uncontrolled condition')
 
-        old_device_layer = device_layer
-        last_device_uuid = device_uuid
+        prv_device_uuid = device_uuid
+        prv_res_class = res_class
 
     # path egress
     connections.append(connection_stack.get())
+    LOGGER.debug('connections={:s}'.format(str(connections)))
     assert connection_stack.empty()
     return connections
 
 def convert_explicit_path_hops_to_plain_connection(
-    path_hops : List[Dict], main_connection_uuid : str
-) -> List[Tuple[str, DeviceLayerEnum, List[str], List[str]]]:
+    path_hops : List[Dict], main_service_uuid : str, main_service_type : ServiceTypeEnum
+) -> List[Tuple[str, int, List[str], List[str]]]:
 
-    connection : Tuple[str, DeviceLayerEnum, List[str], List[str]] = \
-        (main_connection_uuid, DeviceLayerEnum.PACKET_DEVICE, [], [])
+    connection : Tuple[str, int, List[str], List[str]] = \
+        (main_service_uuid, main_service_type, [], [])
 
-    last_device_uuid = None
+    prv_device_uuid = None
     for path_hop in path_hops:
         device_uuid = path_hop['device']
-        if last_device_uuid == device_uuid: continue
+        if prv_device_uuid == device_uuid: continue
         connection[2].append(path_hop)
-        last_device_uuid = device_uuid
+        prv_device_uuid = device_uuid
 
     return [connection]
diff --git a/src/pathcomp/frontend/service/algorithms/tools/ConstantsMappings.py b/src/pathcomp/frontend/service/algorithms/tools/ConstantsMappings.py
index cd1956a873dd2170c7a75db0c677db34162449ee..bd06e6ba19b3da9e2d38d5b83e1d7d3a806ff14f 100644
--- a/src/pathcomp/frontend/service/algorithms/tools/ConstantsMappings.py
+++ b/src/pathcomp/frontend/service/algorithms/tools/ConstantsMappings.py
@@ -13,8 +13,6 @@
 # limitations under the License.
 
 from enum import IntEnum
-from common.DeviceTypes import DeviceTypeEnum
-from common.proto.context_pb2 import ServiceTypeEnum
 
 class CapacityUnit(IntEnum):
     TB   = 0
@@ -66,50 +64,3 @@ class LinkForwardingDirection(IntEnum):
     BIDIRECTIONAL  = 0
     UNIDIRECTIONAL = 1
     UNKNOWN        = 2
-
-class DeviceLayerEnum(IntEnum):
-    APPLICATION_CONTROLLER = 41     # Layer 4 domain controller
-    APPLICATION_DEVICE     = 40     # Layer 4 domain device
-    PACKET_CONTROLLER      = 31     # Layer 3 domain controller
-    PACKET_DEVICE          = 30     # Layer 3 domain device
-    MAC_LAYER_CONTROLLER   = 21     # Layer 2 domain controller
-    MAC_LAYER_DEVICE       = 20     # Layer 2 domain device
-    OPTICAL_CONTROLLER     =  1     # Layer 0 domain controller
-    OPTICAL_DEVICE         =  0     # Layer 0 domain device
-
-DEVICE_TYPE_TO_LAYER = {
-    DeviceTypeEnum.EMULATED_DATACENTER.value             : DeviceLayerEnum.APPLICATION_DEVICE,
-    DeviceTypeEnum.DATACENTER.value                      : DeviceLayerEnum.APPLICATION_DEVICE,
-    DeviceTypeEnum.NETWORK.value                         : DeviceLayerEnum.APPLICATION_DEVICE,
-
-    DeviceTypeEnum.EMULATED_PACKET_ROUTER.value          : DeviceLayerEnum.PACKET_DEVICE,
-    DeviceTypeEnum.PACKET_ROUTER.value                   : DeviceLayerEnum.PACKET_DEVICE,
-    DeviceTypeEnum.EMULATED_PACKET_SWITCH.value          : DeviceLayerEnum.MAC_LAYER_DEVICE,
-    DeviceTypeEnum.PACKET_SWITCH.value                   : DeviceLayerEnum.MAC_LAYER_DEVICE,
-
-    DeviceTypeEnum.EMULATED_P4_SWITCH.value              : DeviceLayerEnum.MAC_LAYER_DEVICE,
-    DeviceTypeEnum.P4_SWITCH.value                       : DeviceLayerEnum.MAC_LAYER_DEVICE,
-
-    DeviceTypeEnum.EMULATED_MICROWAVE_RADIO_SYSTEM.value : DeviceLayerEnum.MAC_LAYER_CONTROLLER,
-    DeviceTypeEnum.MICROWAVE_RADIO_SYSTEM.value          : DeviceLayerEnum.MAC_LAYER_CONTROLLER,
-
-    DeviceTypeEnum.EMULATED_OPEN_LINE_SYSTEM.value       : DeviceLayerEnum.OPTICAL_CONTROLLER,
-    DeviceTypeEnum.OPEN_LINE_SYSTEM.value                : DeviceLayerEnum.OPTICAL_CONTROLLER,
-    DeviceTypeEnum.XR_CONSTELLATION.value                : DeviceLayerEnum.OPTICAL_CONTROLLER,
-
-    DeviceTypeEnum.EMULATED_OPTICAL_ROADM.value          : DeviceLayerEnum.OPTICAL_DEVICE,
-    DeviceTypeEnum.OPTICAL_ROADM.value                   : DeviceLayerEnum.OPTICAL_DEVICE,
-    DeviceTypeEnum.EMULATED_OPTICAL_TRANSPONDER.value    : DeviceLayerEnum.OPTICAL_DEVICE,
-    DeviceTypeEnum.OPTICAL_TRANSPONDER.value             : DeviceLayerEnum.OPTICAL_DEVICE,
-}
-
-DEVICE_LAYER_TO_SERVICE_TYPE = {
-    DeviceLayerEnum.APPLICATION_DEVICE.value   : ServiceTypeEnum.SERVICETYPE_L3NM,
-    DeviceLayerEnum.PACKET_DEVICE.value        : ServiceTypeEnum.SERVICETYPE_L3NM,
-
-    DeviceLayerEnum.MAC_LAYER_CONTROLLER.value : ServiceTypeEnum.SERVICETYPE_L2NM,
-    DeviceLayerEnum.MAC_LAYER_DEVICE.value     : ServiceTypeEnum.SERVICETYPE_L2NM,
-
-    DeviceLayerEnum.OPTICAL_CONTROLLER.value   : ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE,
-    DeviceLayerEnum.OPTICAL_DEVICE.value       : ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE,
-}
diff --git a/src/pathcomp/frontend/service/algorithms/tools/EroPathToHops.py b/src/pathcomp/frontend/service/algorithms/tools/EroPathToHops.py
index c8a902999ddfb5011fd7ec09fa99ff6fa697ea40..670757d76b7d21ecf28f6ead4e8bc4e21951d18e 100644
--- a/src/pathcomp/frontend/service/algorithms/tools/EroPathToHops.py
+++ b/src/pathcomp/frontend/service/algorithms/tools/EroPathToHops.py
@@ -43,13 +43,17 @@
 #
 
 import logging
-from typing import Dict, List
+from typing import Dict, List, Tuple
+from common.proto.context_pb2 import Link
 
 LOGGER = logging.getLogger(__name__)
 
-def eropath_to_hops(ero_path : List[Dict], endpoint_to_link_dict : Dict) -> List[Dict]:
+def eropath_to_hops(
+    ero_path : List[Dict], endpoint_to_link_dict : Dict[Tuple[str, str, str], Tuple[Dict, Link]]
+) -> List[Dict]:
     try:
         path_hops = []
+        num_ero_hops = len(ero_path)
         for endpoint in ero_path:
             device_uuid = endpoint['device_id']
             endpoint_uuid = endpoint['endpoint_uuid']
@@ -59,23 +63,17 @@ def eropath_to_hops(ero_path : List[Dict], endpoint_to_link_dict : Dict) -> List
                 continue
 
             last_hop = path_hops[-1]
-            if (last_hop['device'] == device_uuid):
-                if ('ingress_ep' not in last_hop) or ('egress_ep' in last_hop): continue
-                last_hop['egress_ep'] = endpoint_uuid
-                continue
+            if last_hop['device'] != device_uuid: raise Exception('Malformed path')
+            last_hop['egress_ep'] = endpoint_uuid
+
+            if num_ero_hops - 1 == len(path_hops): break
 
-            endpoint_key = (last_hop['device'], last_hop['egress_ep'])
-            link_tuple = endpoint_to_link_dict.get(endpoint_key)
-            ingress = next(iter([
-                ep_id for ep_id in link_tuple[0]['link_endpoint_ids']
-                if (ep_id['endpoint_id']['device_id'] == device_uuid) and\
-                    (ep_id['endpoint_id']['endpoint_uuid'] != endpoint_uuid)
-            ]), None)
-            if ingress['endpoint_id']['device_id'] != device_uuid: raise Exception('Malformed path')
+            link_tuple = endpoint_to_link_dict[(device_uuid, endpoint_uuid, 'src')]
+            if link_tuple is None: raise Exception('Malformed path')
+            ingress = link_tuple[0]['link_endpoint_ids'][-1]
             path_hops.append({
                 'device': ingress['endpoint_id']['device_id'],
-                'ingress_ep': ingress['endpoint_id']['endpoint_uuid'],
-                'egress_ep': endpoint_uuid,
+                'ingress_ep': ingress['endpoint_id']['endpoint_uuid']
             })
         return path_hops
     except:
diff --git a/src/pathcomp/frontend/service/algorithms/tools/ResourceGroups.py b/src/pathcomp/frontend/service/algorithms/tools/ResourceGroups.py
new file mode 100644
index 0000000000000000000000000000000000000000..53c89cd124cb7d3431b37a50596b0b793cfa83eb
--- /dev/null
+++ b/src/pathcomp/frontend/service/algorithms/tools/ResourceGroups.py
@@ -0,0 +1,94 @@
+# 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 json
+from typing import Dict, Optional, Tuple
+from common.DeviceTypes import DeviceTypeEnum
+from common.proto.context_pb2 import Device
+from common.tools.grpc.Tools import grpc_message_to_json_string
+
+DEVICE_TYPE_TO_DEEPNESS = {
+    DeviceTypeEnum.EMULATED_DATACENTER.value             : 90,
+    DeviceTypeEnum.DATACENTER.value                      : 90,
+    DeviceTypeEnum.NETWORK.value                         : 90,
+
+    DeviceTypeEnum.TERAFLOWSDN_CONTROLLER.value          : 80,
+    DeviceTypeEnum.EMULATED_PACKET_ROUTER.value          : 70,
+    DeviceTypeEnum.PACKET_ROUTER.value                   : 70,
+
+    DeviceTypeEnum.EMULATED_PACKET_SWITCH.value          : 60,
+    DeviceTypeEnum.PACKET_SWITCH.value                   : 60,
+    DeviceTypeEnum.EMULATED_P4_SWITCH.value              : 60,
+    DeviceTypeEnum.P4_SWITCH.value                       : 60,
+
+    DeviceTypeEnum.EMULATED_MICROWAVE_RADIO_SYSTEM.value : 40,
+    DeviceTypeEnum.MICROWAVE_RADIO_SYSTEM.value          : 40,
+
+    DeviceTypeEnum.EMULATED_XR_CONSTELLATION.value       : 40,
+    DeviceTypeEnum.XR_CONSTELLATION.value                : 40,
+
+    DeviceTypeEnum.EMULATED_OPEN_LINE_SYSTEM.value       : 30,
+    DeviceTypeEnum.OPEN_LINE_SYSTEM.value                : 30,
+
+    DeviceTypeEnum.EMULATED_PACKET_RADIO_ROUTER.value    : 10,
+    DeviceTypeEnum.PACKET_RADIO_ROUTER.value             : 10,
+    DeviceTypeEnum.EMULATED_OPTICAL_TRANSPONDER.value    : 10,
+    DeviceTypeEnum.OPTICAL_TRANSPONDER.value             : 10,
+    DeviceTypeEnum.EMULATED_OPTICAL_ROADM.value          : 10,
+    DeviceTypeEnum.OPTICAL_ROADM.value                   : 10,
+
+    DeviceTypeEnum.EMULATED_OPTICAL_SPLITTER.value       :  0,
+}
+
+IGNORED_DEVICE_TYPES = {DeviceTypeEnum.EMULATED_OPTICAL_SPLITTER}
+
+def get_device_controller_uuid(
+    device : Device
+) -> Optional[str]:
+    for config_rule in device.device_config.config_rules:
+        if config_rule.WhichOneof('config_rule') != 'custom': continue
+        if config_rule.custom.resource_key != '_controller': continue
+        device_controller_id = json.loads(config_rule.custom.resource_value)
+        return device_controller_id['uuid']
+    return None
+
+def _map_device_type(device : Device) -> DeviceTypeEnum:
+    device_type = DeviceTypeEnum._value2member_map_.get(device.device_type) # pylint: disable=no-member
+    if device_type is None:
+        MSG = 'Unsupported DeviceType({:s}) for Device({:s})'
+        raise Exception(MSG.format(str(device.device_type), grpc_message_to_json_string(device)))
+    return device_type
+
+def _map_resource_to_deepness(device_type : DeviceTypeEnum) -> int:
+    deepness = DEVICE_TYPE_TO_DEEPNESS.get(device_type.value)
+    if deepness is None: raise Exception('Unsupported DeviceType({:s})'.format(str(device_type.value)))
+    return deepness
+
+def get_device_type(
+    device : Device, device_dict : Dict[str, Tuple[Dict, Device]], device_controller_uuid : Optional[str]
+) -> DeviceTypeEnum:
+    if device_controller_uuid is None: return _map_device_type(device)
+    device_controller_tuple = device_dict.get(device_controller_uuid)
+    if device_controller_tuple is None: raise Exception('Device({:s}) not found'.format(str(device_controller_uuid)))
+    _,device = device_controller_tuple
+    return _map_device_type(device)
+
+def get_resource_classification(
+    device : Device, device_dict : Dict[str, Tuple[Dict, Device]]
+) -> Tuple[int, DeviceTypeEnum, Optional[str]]:
+    device_controller_uuid = get_device_controller_uuid(device)
+    device_type = get_device_type(device, device_dict, device_controller_uuid)
+    resource_deepness = _map_resource_to_deepness(device_type)
+    return resource_deepness, device_type, device_controller_uuid
diff --git a/src/pathcomp/frontend/service/algorithms/tools/ServiceTypes.py b/src/pathcomp/frontend/service/algorithms/tools/ServiceTypes.py
new file mode 100644
index 0000000000000000000000000000000000000000..463b8039b6c8c611b579bdb74933c06fb0f99507
--- /dev/null
+++ b/src/pathcomp/frontend/service/algorithms/tools/ServiceTypes.py
@@ -0,0 +1,53 @@
+# 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.
+
+
+from common.DeviceTypes import DeviceTypeEnum
+from common.proto.context_pb2 import ServiceTypeEnum
+
+PACKET_DEVICE_TYPES = {
+    DeviceTypeEnum.TERAFLOWSDN_CONTROLLER,
+    DeviceTypeEnum.PACKET_ROUTER, DeviceTypeEnum.EMULATED_PACKET_ROUTER,
+    DeviceTypeEnum.PACKET_SWITCH, DeviceTypeEnum.EMULATED_PACKET_SWITCH,
+}
+
+L2_DEVICE_TYPES = {
+    DeviceTypeEnum.PACKET_SWITCH, DeviceTypeEnum.EMULATED_PACKET_SWITCH,
+    DeviceTypeEnum.MICROWAVE_RADIO_SYSTEM, DeviceTypeEnum.EMULATED_MICROWAVE_RADIO_SYSTEM,
+    DeviceTypeEnum.PACKET_RADIO_ROUTER, DeviceTypeEnum.EMULATED_PACKET_RADIO_ROUTER,
+    DeviceTypeEnum.P4_SWITCH, DeviceTypeEnum.EMULATED_P4_SWITCH,
+}
+
+OPTICAL_DEVICE_TYPES = {
+    DeviceTypeEnum.OPEN_LINE_SYSTEM, DeviceTypeEnum.EMULATED_OPEN_LINE_SYSTEM,
+    DeviceTypeEnum.XR_CONSTELLATION, DeviceTypeEnum.EMULATED_XR_CONSTELLATION,
+    DeviceTypeEnum.OPTICAL_ROADM, DeviceTypeEnum.EMULATED_OPTICAL_ROADM,
+    DeviceTypeEnum.OPTICAL_TRANSPONDER, DeviceTypeEnum.EMULATED_OPTICAL_TRANSPONDER,
+}
+
+SERVICE_TYPE_L2NM = {ServiceTypeEnum.SERVICETYPE_L2NM}
+SERVICE_TYPE_L3NM = {ServiceTypeEnum.SERVICETYPE_L3NM}
+SERVICE_TYPE_LXNM = {ServiceTypeEnum.SERVICETYPE_L3NM, ServiceTypeEnum.SERVICETYPE_L2NM}
+SERVICE_TYPE_TAPI = {ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE}
+
+def get_service_type(device_type : DeviceTypeEnum, prv_service_type : ServiceTypeEnum) -> ServiceTypeEnum:
+    if device_type in PACKET_DEVICE_TYPES and prv_service_type in SERVICE_TYPE_LXNM: return prv_service_type
+    if device_type in L2_DEVICE_TYPES: return ServiceTypeEnum.SERVICETYPE_L2NM
+    if device_type in OPTICAL_DEVICE_TYPES: return ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE
+
+    str_fields = ', '.join([
+        'device_type={:s}'.format(str(device_type)),
+        'prv_service_type={:s}'.format(str(prv_service_type)),
+    ])
+    raise Exception('Undefined Service Type for ({:s})'.format(str_fields))
diff --git a/src/pathcomp/frontend/tests/test_pathcomp/__init__.py b/src/pathcomp/frontend/tests/test_pathcomp/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..1549d9811aa5d1c193a44ad45d0d7773236c0612
--- /dev/null
+++ b/src/pathcomp/frontend/tests/test_pathcomp/__init__.py
@@ -0,0 +1,14 @@
+# 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.
+
diff --git a/src/pathcomp/frontend/tests/test_pathcomp/__main__.py b/src/pathcomp/frontend/tests/test_pathcomp/__main__.py
new file mode 100644
index 0000000000000000000000000000000000000000..ba1cc4a2c1838248882b983c1ed25c27b395aa9a
--- /dev/null
+++ b/src/pathcomp/frontend/tests/test_pathcomp/__main__.py
@@ -0,0 +1,33 @@
+# 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 logging, sys
+from common.proto.context_pb2 import ServiceTypeEnum
+from pathcomp.frontend.service.algorithms.tools.ComputeSubServices import convert_explicit_path_hops_to_connections
+from .data import path_hops, device_dict
+
+logging.basicConfig(level=logging.DEBUG)
+LOGGER = logging.getLogger(__name__)
+
+def main():
+    service_uuid = 'dc-2-dc-svc'
+    service_type = ServiceTypeEnum.SERVICETYPE_L2NM
+    connections = convert_explicit_path_hops_to_connections(path_hops, device_dict, service_uuid, service_type)
+    str_connections = '\n'.join(['  ' + str(connection) for connection in connections])
+    LOGGER.debug('connections = [\n{:s}\n]'.format(str_connections))
+    return 0
+
+if __name__ == '__main__':
+    sys.exit(main())
diff --git a/src/pathcomp/frontend/tests/test_pathcomp/data.py b/src/pathcomp/frontend/tests/test_pathcomp/data.py
new file mode 100644
index 0000000000000000000000000000000000000000..aeac5e38a222fb2dfc3f7ae98b2737b47f855ee4
--- /dev/null
+++ b/src/pathcomp/frontend/tests/test_pathcomp/data.py
@@ -0,0 +1,70 @@
+# 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 json
+from typing import Dict, Tuple
+from common.DeviceTypes import DeviceTypeEnum
+from common.proto.context_pb2 import ConfigActionEnum, Device
+
+path_hops = [
+    {'device': 'DC1',      'ingress_ep': 'int',                  'egress_ep': 'eth1'                 },
+    {'device': 'PE1',      'ingress_ep': '1/1',                  'egress_ep': '1/2'                  },
+    {'device': 'MW1-2',    'ingress_ep': '172.18.0.1:1',         'egress_ep': '172.18.0.2:1'         },
+    {'device': 'HUB1',     'ingress_ep': '1/1',                  'egress_ep': 'XR-T1'                },
+    {'device': 'splitter', 'ingress_ep': 'common',               'egress_ep': 'leaf1'                },
+    {'device': 'OLS',      'ingress_ep': 'node_1_port_13-input', 'egress_ep': 'node_4_port_13-output'},
+    {'device': 'LEAF2',    'ingress_ep': 'XR-T1',                'egress_ep': '1/1'                  },
+    {'device': 'PE4',      'ingress_ep': '1/1',                  'egress_ep': '1/2'                  },
+    {'device': 'DC2',      'ingress_ep': 'eth2',                 'egress_ep': 'int'                  }
+]
+
+device_data = {
+    'TFS'     : {'controller_uuid': None,  'device_type': DeviceTypeEnum.TERAFLOWSDN_CONTROLLER   },
+    'IPM'     : {'controller_uuid': None,  'device_type': DeviceTypeEnum.XR_CONSTELLATION         },
+    'OLS'     : {'controller_uuid': None,  'device_type': DeviceTypeEnum.OPEN_LINE_SYSTEM         },
+    'MW1-2'   : {'controller_uuid': None,  'device_type': DeviceTypeEnum.MICROWAVE_RADIO_SYSTEM   },
+    'MW3-4'   : {'controller_uuid': None,  'device_type': DeviceTypeEnum.MICROWAVE_RADIO_SYSTEM   },
+
+    'DC1'     : {'controller_uuid': None,  'device_type': DeviceTypeEnum.EMULATED_DATACENTER      },
+    'DC2'     : {'controller_uuid': None,  'device_type': DeviceTypeEnum.EMULATED_DATACENTER      },
+
+    'PE1'     : {'controller_uuid': 'TFS', 'device_type': DeviceTypeEnum.PACKET_ROUTER            },
+    'PE2'     : {'controller_uuid': 'TFS', 'device_type': DeviceTypeEnum.PACKET_ROUTER            },
+    'PE3'     : {'controller_uuid': 'TFS', 'device_type': DeviceTypeEnum.PACKET_ROUTER            },
+    'PE4'     : {'controller_uuid': 'TFS', 'device_type': DeviceTypeEnum.PACKET_ROUTER            },
+
+    'HUB1'    : {'controller_uuid': 'IPM', 'device_type': DeviceTypeEnum.PACKET_ROUTER            },
+    'LEAF1'   : {'controller_uuid': 'IPM', 'device_type': DeviceTypeEnum.PACKET_ROUTER            },
+    'LEAF2'   : {'controller_uuid': 'IPM', 'device_type': DeviceTypeEnum.PACKET_ROUTER            },
+
+    'splitter': {'controller_uuid': None,  'device_type': DeviceTypeEnum.EMULATED_OPTICAL_SPLITTER},
+}
+
+def process_device(device_uuid, json_device) -> Tuple[Dict, Device]:
+    grpc_device = Device()
+    grpc_device.device_id.device_uuid.uuid = device_uuid            # pylint: disable=no-member
+    grpc_device.device_type = json_device['device_type'].value
+    controller_uuid = json_device.get('controller_uuid')
+    if controller_uuid is not None:
+        config_rule = grpc_device.device_config.config_rules.add()  # pylint: disable=no-member
+        config_rule.action = ConfigActionEnum.CONFIGACTION_SET
+        config_rule.custom.resource_key = '_controller'
+        config_rule.custom.resource_value = json.dumps({'uuid': controller_uuid})
+    return json_device, grpc_device
+
+device_dict = {
+    device_uuid:process_device(device_uuid, json_device)
+    for device_uuid,json_device in device_data.items()
+}
diff --git a/src/policy/src/main/java/eu/teraflow/policy/Serializer.java b/src/policy/src/main/java/eu/teraflow/policy/Serializer.java
index 529ec633426e41f3218857642aa6751ac574ab23..967d1d6e604e312fe9d8314beea023f902af776b 100644
--- a/src/policy/src/main/java/eu/teraflow/policy/Serializer.java
+++ b/src/policy/src/main/java/eu/teraflow/policy/Serializer.java
@@ -2245,6 +2245,8 @@ public class Serializer {
                 return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352;
             case XR:
                 return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_XR;
+            case IETF_L2VPN:
+                return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN;
             case UNDEFINED:
             default:
                 return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED;
@@ -2266,6 +2268,8 @@ public class Serializer {
                 return DeviceDriverEnum.ONF_TR_352;
             case DEVICEDRIVER_XR:
                 return DeviceDriverEnum.XR;
+            case DEVICEDRIVER_IETF_L2VPN:
+                return DeviceDriverEnum.IETF_L2VPN;
             case DEVICEDRIVER_UNDEFINED:
             case UNRECOGNIZED:
             default:
diff --git a/src/policy/src/test/java/eu/teraflow/policy/SerializerTest.java b/src/policy/src/test/java/eu/teraflow/policy/SerializerTest.java
index d284840b8dedb0320d49a5d5c6a1943c10d2afed..64102646119585e1f837b12a9be022d95a29c54f 100644
--- a/src/policy/src/test/java/eu/teraflow/policy/SerializerTest.java
+++ b/src/policy/src/test/java/eu/teraflow/policy/SerializerTest.java
@@ -3600,6 +3600,8 @@ class SerializerTest {
                         DeviceDriverEnum.ONF_TR_352,
                         ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352),
                 Arguments.of(DeviceDriverEnum.XR, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_XR),
+                Arguments.of(
+                        DeviceDriverEnum.IETF_L2VPN, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN),
                 Arguments.of(
                         DeviceDriverEnum.UNDEFINED, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED));
     }
diff --git a/src/policy/target/generated-sources/grpc/context/ContextOuterClass.java b/src/policy/target/generated-sources/grpc/context/ContextOuterClass.java
index fbbba62a2baa1c2fe2b3c3fe090883d6542996e4..53252341b30dc093c79d5a54baf98b82e6a24b75 100644
--- a/src/policy/target/generated-sources/grpc/context/ContextOuterClass.java
+++ b/src/policy/target/generated-sources/grpc/context/ContextOuterClass.java
@@ -177,6 +177,10 @@ public final class ContextOuterClass {
      * <code>DEVICEDRIVER_XR = 6;</code>
      */
     DEVICEDRIVER_XR(6),
+    /**
+     * <code>DEVICEDRIVER_IETF_L2VPN = 7;</code>
+     */
+    DEVICEDRIVER_IETF_L2VPN(7),
     UNRECOGNIZED(-1),
     ;
 
@@ -212,6 +216,10 @@ public final class ContextOuterClass {
      * <code>DEVICEDRIVER_XR = 6;</code>
      */
     public static final int DEVICEDRIVER_XR_VALUE = 6;
+    /**
+     * <code>DEVICEDRIVER_IETF_L2VPN = 7;</code>
+     */
+    public static final int DEVICEDRIVER_IETF_L2VPN_VALUE = 7;
 
 
     public final int getNumber() {
@@ -245,6 +253,7 @@ public final class ContextOuterClass {
         case 4: return DEVICEDRIVER_IETF_NETWORK_TOPOLOGY;
         case 5: return DEVICEDRIVER_ONF_TR_352;
         case 6: return DEVICEDRIVER_XR;
+        case 7: return DEVICEDRIVER_IETF_L2VPN;
         default: return null;
       }
     }
diff --git a/src/service/service/service_handler_api/FilterFields.py b/src/service/service/service_handler_api/FilterFields.py
index a73ec53f37d68e0414eeb1df146373c6906273c5..3ec71dc64536e28457c4f1adbf3679186285786d 100644
--- a/src/service/service/service_handler_api/FilterFields.py
+++ b/src/service/service/service_handler_api/FilterFields.py
@@ -33,7 +33,8 @@ DEVICE_DRIVER_VALUES = {
     DeviceDriverEnum.DEVICEDRIVER_P4,
     DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY,
     DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352,
-    DeviceDriverEnum.DEVICEDRIVER_XR
+    DeviceDriverEnum.DEVICEDRIVER_XR,
+    DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN,
 }
 
 # Map allowed filter fields to allowed values per Filter field. If no restriction (free text) None is specified
diff --git a/src/service/service/service_handler_api/ServiceHandlerFactory.py b/src/service/service/service_handler_api/ServiceHandlerFactory.py
index 6aa21b49920254383fad5f28aa234b6ec0cad5a3..64ea204a2600a71b08c8c373a15640f5e2134787 100644
--- a/src/service/service/service_handler_api/ServiceHandlerFactory.py
+++ b/src/service/service/service_handler_api/ServiceHandlerFactory.py
@@ -73,6 +73,9 @@ class ServiceHandlerFactory:
             if field_indice is None: continue
             if not isinstance(field_values, Iterable) or isinstance(field_values, str):
                 field_values = [field_values]
+            if len(field_values) == 0:
+                # do not allow empty fields; might cause wrong selection
+                raise UnsatisfiedFilterException(filter_fields)
 
             field_enum_values = FILTER_FIELD_ALLOWED_VALUES.get(field_name)
 
diff --git a/src/service/service/service_handlers/__init__.py b/src/service/service/service_handlers/__init__.py
index 4c9059779d6b7031685e1de76b0a7ed651af6c5f..257bc138fe932e7e5abee00981848248039d0b3f 100644
--- a/src/service/service/service_handlers/__init__.py
+++ b/src/service/service/service_handlers/__init__.py
@@ -15,12 +15,14 @@
 from common.proto.context_pb2 import DeviceDriverEnum, ServiceTypeEnum
 from ..service_handler_api.FilterFields import FilterFieldEnum
 from .l2nm_emulated.L2NMEmulatedServiceHandler import L2NMEmulatedServiceHandler
+from .l2nm_ietfl2vpn.L2NM_IETFL2VPN_ServiceHandler import L2NM_IETFL2VPN_ServiceHandler
 from .l2nm_openconfig.L2NMOpenConfigServiceHandler import L2NMOpenConfigServiceHandler
 from .l3nm_emulated.L3NMEmulatedServiceHandler import L3NMEmulatedServiceHandler
 from .l3nm_openconfig.L3NMOpenConfigServiceHandler import L3NMOpenConfigServiceHandler
+from .microwave.MicrowaveServiceHandler import MicrowaveServiceHandler
 from .p4.p4_service_handler import P4ServiceHandler
 from .tapi_tapi.TapiServiceHandler import TapiServiceHandler
-from .microwave.MicrowaveServiceHandler import MicrowaveServiceHandler
+from .tapi_xr.TapiXrServiceHandler import TapiXrServiceHandler
 
 SERVICE_HANDLERS = [
     (L2NMEmulatedServiceHandler, [
@@ -50,13 +52,19 @@ SERVICE_HANDLERS = [
     (TapiServiceHandler, [
         {
             FilterFieldEnum.SERVICE_TYPE  : ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE,
-            FilterFieldEnum.DEVICE_DRIVER : [DeviceDriverEnum.DEVICEDRIVER_TRANSPORT_API, DeviceDriverEnum.DEVICEDRIVER_XR],
+            FilterFieldEnum.DEVICE_DRIVER : [DeviceDriverEnum.DEVICEDRIVER_TRANSPORT_API],
+        }
+    ]),
+    (TapiXrServiceHandler, [
+        {
+            FilterFieldEnum.SERVICE_TYPE  : ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE,
+            FilterFieldEnum.DEVICE_DRIVER : [DeviceDriverEnum.DEVICEDRIVER_XR],
         }
     ]),
     (MicrowaveServiceHandler, [
         {
             FilterFieldEnum.SERVICE_TYPE  : ServiceTypeEnum.SERVICETYPE_L2NM,
-            FilterFieldEnum.DEVICE_DRIVER : DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY,
+            FilterFieldEnum.DEVICE_DRIVER : [DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY, DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352],
         }
     ]),
     (P4ServiceHandler, [
@@ -65,4 +73,10 @@ SERVICE_HANDLERS = [
             FilterFieldEnum.DEVICE_DRIVER: DeviceDriverEnum.DEVICEDRIVER_P4,
         }
     ]),
+    (L2NM_IETFL2VPN_ServiceHandler, [
+        {
+            FilterFieldEnum.SERVICE_TYPE  : ServiceTypeEnum.SERVICETYPE_L2NM,
+            FilterFieldEnum.DEVICE_DRIVER : [DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN],
+        }
+    ]),
 ]
diff --git a/src/service/service/service_handlers/l2nm_emulated/ConfigRules.py b/src/service/service/service_handlers/l2nm_emulated/ConfigRules.py
index c2ea6e213ee8d18b4507089fb2762c913e03039a..363983b8653e1cfa553279d2df74d6ac893a4fec 100644
--- a/src/service/service/service_handlers/l2nm_emulated/ConfigRules.py
+++ b/src/service/service/service_handlers/l2nm_emulated/ConfigRules.py
@@ -21,15 +21,18 @@ def setup_config_rules(
     service_settings : TreeNode, endpoint_settings : TreeNode
 ) -> List[Dict]:
 
-    json_settings          : Dict = {} if service_settings  is None else service_settings.value
-    json_endpoint_settings : Dict = {} if endpoint_settings is None else endpoint_settings.value
+    if service_settings  is None: return []
+    if endpoint_settings is None: return []
 
-    mtu                 = json_settings.get('mtu',                 1450 )    # 1512
+    #json_settings          : Dict = service_settings.value
+    json_endpoint_settings : Dict = endpoint_settings.value
+
+    #mtu                 = json_settings.get('mtu',                 1450 )    # 1512
     #address_families    = json_settings.get('address_families',    []   )    # ['IPV4']
     #bgp_as              = json_settings.get('bgp_as',              0    )    # 65000
     #bgp_route_target    = json_settings.get('bgp_route_target',    '0:0')    # 65000:333
 
-    router_id           = json_endpoint_settings.get('router_id',           '0.0.0.0')  # '10.95.0.10'
+    #router_id           = json_endpoint_settings.get('router_id',           '0.0.0.0')  # '10.95.0.10'
     #route_distinguisher = json_endpoint_settings.get('route_distinguisher', '0:0'    )  # '60001:801'
     sub_interface_index = json_endpoint_settings.get('sub_interface_index', 0        )  # 1
     vlan_id             = json_endpoint_settings.get('vlan_id',             1        )  # 400
@@ -43,17 +46,17 @@ def setup_config_rules(
     connection_point_id   = 'VC-1'
 
     json_config_rules = [
-        json_config_rule_set(
-            '/network_instance[default]',
-            {'name': 'default', 'type': 'DEFAULT_INSTANCE', 'router_id': router_id}),
+        #json_config_rule_set(
+        #    '/network_instance[default]',
+        #    {'name': 'default', 'type': 'DEFAULT_INSTANCE', 'router_id': router_id}),
 
-        json_config_rule_set(
-            '/network_instance[default]/protocols[OSPF]',
-            {'name': 'default', 'identifier': 'OSPF', 'protocol_name': 'OSPF'}),
+        #json_config_rule_set(
+        #    '/network_instance[default]/protocols[OSPF]',
+        #    {'name': 'default', 'identifier': 'OSPF', 'protocol_name': 'OSPF'}),
 
-        json_config_rule_set(
-            '/network_instance[default]/protocols[STATIC]',
-            {'name': 'default', 'identifier': 'STATIC', 'protocol_name': 'STATIC'}),
+        #json_config_rule_set(
+        #    '/network_instance[default]/protocols[STATIC]',
+        #    {'name': 'default', 'identifier': 'STATIC', 'protocol_name': 'STATIC'}),
 
         json_config_rule_set(
             '/network_instance[{:s}]'.format(network_instance_name),
@@ -66,7 +69,7 @@ def setup_config_rules(
         json_config_rule_set(
             '/network_instance[{:s}]/interface[{:s}]'.format(network_instance_name, if_cirid_name),
             {'name': network_instance_name, 'id': if_cirid_name, 'interface': if_cirid_name,
-            'subinterface': sub_interface_index}),
+             'subinterface': sub_interface_index}),
 
         json_config_rule_set(
             '/network_instance[{:s}]/connection_point[{:s}]'.format(network_instance_name, connection_point_id),
@@ -80,15 +83,18 @@ def teardown_config_rules(
     service_settings : TreeNode, endpoint_settings : TreeNode
 ) -> List[Dict]:
 
-    #json_settings          : Dict = {} if service_settings  is None else service_settings.value
-    json_endpoint_settings : Dict = {} if endpoint_settings is None else endpoint_settings.value
+    if service_settings  is None: return []
+    if endpoint_settings is None: return []
+
+    #json_settings          : Dict = service_settings.value
+    json_endpoint_settings : Dict = endpoint_settings.value
 
     #mtu                 = json_settings.get('mtu',                 1450 )    # 1512
     #address_families    = json_settings.get('address_families',    []   )    # ['IPV4']
     #bgp_as              = json_settings.get('bgp_as',              0    )    # 65000
     #bgp_route_target    = json_settings.get('bgp_route_target',    '0:0')    # 65000:333
 
-    router_id           = json_endpoint_settings.get('router_id',           '0.0.0.0')  # '10.95.0.10'
+    #router_id           = json_endpoint_settings.get('router_id',           '0.0.0.0')  # '10.95.0.10'
     #route_distinguisher = json_endpoint_settings.get('route_distinguisher', '0:0'    )  # '60001:801'
     sub_interface_index = json_endpoint_settings.get('sub_interface_index', 0        )  # 1
     #vlan_id             = json_endpoint_settings.get('vlan_id',             1        )  # 400
@@ -99,36 +105,36 @@ def teardown_config_rules(
 
     if_cirid_name         = '{:s}.{:s}'.format(endpoint_name, str(circuit_id))
     network_instance_name = 'ELAN-AC:{:s}'.format(str(circuit_id))
-    connection_point_id   = 'VC-1'
+    #connection_point_id   = 'VC-1'
 
     json_config_rules = [
-        json_config_rule_delete(
-            '/network_instance[{:s}]/connection_point[{:s}]'.format(network_instance_name, connection_point_id),
-            {'name': network_instance_name, 'connection_point': connection_point_id}),
+        #json_config_rule_delete(
+        #    '/network_instance[{:s}]/connection_point[{:s}]'.format(network_instance_name, connection_point_id),
+        #    {'name': network_instance_name, 'connection_point': connection_point_id}),
 
-        json_config_rule_delete(
-            '/network_instance[{:s}]/interface[{:s}]'.format(network_instance_name, if_cirid_name),
-            {'name': network_instance_name, 'id': if_cirid_name, 'interface': if_cirid_name,
-            'subinterface': sub_interface_index}),
-
-        json_config_rule_delete(
-            '/interface[{:s}]/subinterface[{:d}]'.format(if_cirid_name, sub_interface_index),
-            {'name': if_cirid_name, 'index': sub_interface_index}),
+        #json_config_rule_delete(
+        #    '/network_instance[{:s}]/interface[{:s}]'.format(network_instance_name, if_cirid_name),
+        #    {'name': network_instance_name, 'id': if_cirid_name, 'interface': if_cirid_name,
+        #    'subinterface': sub_interface_index}),
 
         json_config_rule_delete(
             '/network_instance[{:s}]'.format(network_instance_name),
             {'name': network_instance_name}),
 
         json_config_rule_delete(
-            '/network_instance[default]/protocols[STATIC]',
-            {'name': 'default', 'identifier': 'STATIC', 'protocol_name': 'STATIC'}),
+            '/interface[{:s}]/subinterface[{:d}]'.format(if_cirid_name, sub_interface_index),
+            {'name': if_cirid_name, 'index': sub_interface_index}),
 
-        json_config_rule_delete(
-            '/network_instance[default]/protocols[OSPF]',
-            {'name': 'default', 'identifier': 'OSPF', 'protocol_name': 'OSPF'}),
+        #json_config_rule_delete(
+        #    '/network_instance[default]/protocols[STATIC]',
+        #    {'name': 'default', 'identifier': 'STATIC', 'protocol_name': 'STATIC'}),
 
-        json_config_rule_delete(
-            '/network_instance[default]',
-            {'name': 'default', 'type': 'DEFAULT_INSTANCE', 'router_id': router_id}),
+        #json_config_rule_delete(
+        #    '/network_instance[default]/protocols[OSPF]',
+        #    {'name': 'default', 'identifier': 'OSPF', 'protocol_name': 'OSPF'}),
+
+        #json_config_rule_delete(
+        #    '/network_instance[default]',
+        #    {'name': 'default', 'type': 'DEFAULT_INSTANCE', 'router_id': router_id}),
     ]
     return json_config_rules
diff --git a/src/service/service/service_handlers/l2nm_emulated/L2NMEmulatedServiceHandler.py b/src/service/service/service_handlers/l2nm_emulated/L2NMEmulatedServiceHandler.py
index 9de6c607b1336a4b3fb43867efc16d30048177e0..0a2261ceb4e1a63c984cf833121e3a87e13c0e9f 100644
--- a/src/service/service/service_handlers/l2nm_emulated/L2NMEmulatedServiceHandler.py
+++ b/src/service/service/service_handlers/l2nm_emulated/L2NMEmulatedServiceHandler.py
@@ -75,10 +75,12 @@ class L2NMEmulatedServiceHandler(_ServiceHandler):
                     service_uuid, connection_uuid, device_uuid, endpoint_uuid, endpoint_name,
                     settings, endpoint_settings)
 
-                del device_obj.device_config.config_rules[:]
-                for json_config_rule in json_config_rules:
-                    device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
-                self.__task_executor.configure_device(device_obj)
+                if len(json_config_rules) > 0:
+                    del device_obj.device_config.config_rules[:]
+                    for json_config_rule in json_config_rules:
+                        device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
+                    self.__task_executor.configure_device(device_obj)
+
                 results.append(True)
             except Exception as e: # pylint: disable=broad-except
                 LOGGER.exception('Unable to SetEndpoint({:s})'.format(str(endpoint)))
@@ -110,10 +112,12 @@ class L2NMEmulatedServiceHandler(_ServiceHandler):
                     service_uuid, connection_uuid, device_uuid, endpoint_uuid, endpoint_name,
                     settings, endpoint_settings)
 
-                del device_obj.device_config.config_rules[:]
-                for json_config_rule in json_config_rules:
-                    device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
-                self.__task_executor.configure_device(device_obj)
+                if len(json_config_rules) > 0:
+                    del device_obj.device_config.config_rules[:]
+                    for json_config_rule in json_config_rules:
+                        device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
+                    self.__task_executor.configure_device(device_obj)
+
                 results.append(True)
             except Exception as e: # pylint: disable=broad-except
                 LOGGER.exception('Unable to DeleteEndpoint({:s})'.format(str(endpoint)))
diff --git a/src/service/service/service_handlers/l2nm_ietfl2vpn/L2NM_IETFL2VPN_ServiceHandler.py b/src/service/service/service_handlers/l2nm_ietfl2vpn/L2NM_IETFL2VPN_ServiceHandler.py
new file mode 100644
index 0000000000000000000000000000000000000000..880a6c5a20d618c9d9f21701b7ef3886dbb9fc21
--- /dev/null
+++ b/src/service/service/service_handlers/l2nm_ietfl2vpn/L2NM_IETFL2VPN_ServiceHandler.py
@@ -0,0 +1,173 @@
+# 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 json, logging
+from typing import Any, Dict, List, Optional, Tuple, Union
+from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method
+from common.proto.context_pb2 import ConfigRule, DeviceId, Service
+from common.tools.object_factory.ConfigRule import json_config_rule_delete, json_config_rule_set
+from common.tools.object_factory.Device import json_device_id
+from common.type_checkers.Checkers import chk_type
+from service.service.service_handler_api.Tools import get_device_endpoint_uuids, get_endpoint_matching
+from service.service.service_handler_api._ServiceHandler import _ServiceHandler
+from service.service.service_handler_api.SettingsHandler import SettingsHandler
+from service.service.task_scheduler.TaskExecutor import TaskExecutor
+
+LOGGER = logging.getLogger(__name__)
+
+METRICS_POOL = MetricsPool('Service', 'Handler', labels={'handler': 'tapi_tapi'})
+
+class L2NM_IETFL2VPN_ServiceHandler(_ServiceHandler):
+    def __init__(   # pylint: disable=super-init-not-called
+        self, service : Service, task_executor : TaskExecutor, **settings
+    ) -> None:
+        self.__service = service
+        self.__task_executor = task_executor
+        self.__settings_handler = SettingsHandler(service.service_config, **settings)
+
+    @metered_subclass_method(METRICS_POOL)
+    def SetEndpoint(
+        self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None
+    ) -> List[Union[bool, Exception]]:
+
+        chk_type('endpoints', endpoints, list)
+        if len(endpoints) < 2: return []
+
+        service_uuid = self.__service.service_id.service_uuid.uuid
+        settings = self.__settings_handler.get('/settings')
+        json_settings : Dict = {} if settings is None else settings.value
+        encap_type = json_settings.get('encapsulation_type', '')
+        vlan_id    = json_settings.get('vlan_id', 100)
+
+        results = []
+        try:
+            src_device_uuid, src_endpoint_uuid = get_device_endpoint_uuids(endpoints[0])
+            src_device = self.__task_executor.get_device(DeviceId(**json_device_id(src_device_uuid)))
+            src_endpoint = get_endpoint_matching(src_device, src_endpoint_uuid)
+            src_controller = self.__task_executor.get_device_controller(src_device)
+
+            dst_device_uuid, dst_endpoint_uuid = get_device_endpoint_uuids(endpoints[-1])
+            dst_device = self.__task_executor.get_device(DeviceId(**json_device_id(dst_device_uuid)))
+            dst_endpoint = get_endpoint_matching(dst_device, dst_endpoint_uuid)
+            dst_controller = self.__task_executor.get_device_controller(dst_device)
+
+            if src_controller.device_id.device_uuid.uuid != dst_controller.device_id.device_uuid.uuid:
+                raise Exception('Different Src-Dst devices not supported by now')
+            controller = src_controller
+
+            json_config_rule = json_config_rule_set('/services/service[{:s}]'.format(service_uuid), {
+                'uuid'              : service_uuid,
+                'src_device_name'   : src_device.name,
+                'src_endpoint_name' : src_endpoint.name,
+                'dst_device_name'   : dst_device.name,
+                'dst_endpoint_name' : dst_endpoint.name,
+                'encapsulation_type': encap_type,
+                'vlan_id'           : vlan_id,
+            })
+            del controller.device_config.config_rules[:]
+            controller.device_config.config_rules.append(ConfigRule(**json_config_rule))
+            self.__task_executor.configure_device(controller)
+            results.append(True)
+        except Exception as e: # pylint: disable=broad-except
+            LOGGER.exception('Unable to SetEndpoint for Service({:s})'.format(str(service_uuid)))
+            results.append(e)
+
+        return results
+
+    @metered_subclass_method(METRICS_POOL)
+    def DeleteEndpoint(
+        self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None
+    ) -> List[Union[bool, Exception]]:
+
+        chk_type('endpoints', endpoints, list)
+        if len(endpoints) < 2: return []
+
+        service_uuid = self.__service.service_id.service_uuid.uuid
+
+        results = []
+        try:
+            src_device_uuid, _ = get_device_endpoint_uuids(endpoints[0])
+            src_device = self.__task_executor.get_device(DeviceId(**json_device_id(src_device_uuid)))
+            src_controller = self.__task_executor.get_device_controller(src_device)
+
+            dst_device_uuid, _ = get_device_endpoint_uuids(endpoints[1])
+            dst_device = self.__task_executor.get_device(DeviceId(**json_device_id(dst_device_uuid)))
+            dst_controller = self.__task_executor.get_device_controller(dst_device)
+
+            if src_controller.device_id.device_uuid.uuid != dst_controller.device_id.device_uuid.uuid:
+                raise Exception('Different Src-Dst devices not supported by now')
+            controller = src_controller
+
+            json_config_rule = json_config_rule_delete('/services/service[{:s}]'.format(service_uuid), {
+                'uuid': service_uuid
+            })
+            del controller.device_config.config_rules[:]
+            controller.device_config.config_rules.append(ConfigRule(**json_config_rule))
+            self.__task_executor.configure_device(controller)
+            results.append(True)
+        except Exception as e: # pylint: disable=broad-except
+            LOGGER.exception('Unable to DeleteEndpoint for Service({:s})'.format(str(service_uuid)))
+            results.append(e)
+
+        return results
+
+    @metered_subclass_method(METRICS_POOL)
+    def SetConstraint(self, constraints : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+        chk_type('constraints', constraints, list)
+        if len(constraints) == 0: return []
+
+        msg = '[SetConstraint] Method not implemented. Constraints({:s}) are being ignored.'
+        LOGGER.warning(msg.format(str(constraints)))
+        return [True for _ in range(len(constraints))]
+
+    @metered_subclass_method(METRICS_POOL)
+    def DeleteConstraint(self, constraints : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+        chk_type('constraints', constraints, list)
+        if len(constraints) == 0: return []
+
+        msg = '[DeleteConstraint] Method not implemented. Constraints({:s}) are being ignored.'
+        LOGGER.warning(msg.format(str(constraints)))
+        return [True for _ in range(len(constraints))]
+
+    @metered_subclass_method(METRICS_POOL)
+    def SetConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+        chk_type('resources', resources, list)
+        if len(resources) == 0: return []
+
+        results = []
+        for resource in resources:
+            try:
+                resource_value = json.loads(resource[1])
+                self.__settings_handler.set(resource[0], resource_value)
+                results.append(True)
+            except Exception as e: # pylint: disable=broad-except
+                LOGGER.exception('Unable to SetConfig({:s})'.format(str(resource)))
+                results.append(e)
+
+        return results
+
+    @metered_subclass_method(METRICS_POOL)
+    def DeleteConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+        chk_type('resources', resources, list)
+        if len(resources) == 0: return []
+
+        results = []
+        for resource in resources:
+            try:
+                self.__settings_handler.delete(resource[0])
+            except Exception as e: # pylint: disable=broad-except
+                LOGGER.exception('Unable to DeleteConfig({:s})'.format(str(resource)))
+                results.append(e)
+
+        return results
diff --git a/src/service/service/service_handlers/l2nm_ietfl2vpn/__init__.py b/src/service/service/service_handlers/l2nm_ietfl2vpn/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..1549d9811aa5d1c193a44ad45d0d7773236c0612
--- /dev/null
+++ b/src/service/service/service_handlers/l2nm_ietfl2vpn/__init__.py
@@ -0,0 +1,14 @@
+# 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.
+
diff --git a/src/service/service/service_handlers/l2nm_openconfig/ConfigRules.py b/src/service/service/service_handlers/l2nm_openconfig/ConfigRules.py
index 07e78d73631342d101d77697098e83961c7dcf26..63e818a8303f9939abe8f776cd34b3066cb84ec1 100644
--- a/src/service/service/service_handlers/l2nm_openconfig/ConfigRules.py
+++ b/src/service/service/service_handlers/l2nm_openconfig/ConfigRules.py
@@ -20,16 +20,13 @@ def setup_config_rules(
     service_uuid : str, connection_uuid : str, device_uuid : str, endpoint_uuid : str, endpoint_name : str,
     service_settings : TreeNode, endpoint_settings : TreeNode
 ) -> List[Dict]:
-    
+
     if service_settings  is None: return []
     if endpoint_settings is None: return []
 
-    json_settings          : Dict = service_settings.value
+    #json_settings          : Dict = service_settings.value
     json_endpoint_settings : Dict = endpoint_settings.value
 
-    json_settings          : Dict = {} if service_settings  is None else service_settings.value
-    json_endpoint_settings : Dict = {} if endpoint_settings is None else endpoint_settings.value
-
     #mtu                 = json_settings.get('mtu',                 1450 )    # 1512
     #address_families    = json_settings.get('address_families',    []   )    # ['IPV4']
     #bgp_as              = json_settings.get('bgp_as',              0    )    # 65000
@@ -41,43 +38,32 @@ def setup_config_rules(
     vlan_id             = json_endpoint_settings.get('vlan_id',             1        )  # 400
     #address_ip          = json_endpoint_settings.get('address_ip',          '0.0.0.0')  # '2.2.2.1'
     #address_prefix      = json_endpoint_settings.get('address_prefix',      24       )  # 30
-    remote_router       = json_endpoint_settings.get('remote_router',       '5.5.5.5')  # '5.5.5.5'
-    circuit_id          = json_endpoint_settings.get('circuit_id',          '111'    )  # '111'
-    
+    remote_router       = json_endpoint_settings.get('remote_router',       '0.0.0.0')  # '5.5.5.5'
+    circuit_id          = json_endpoint_settings.get('circuit_id',          '000'    )  # '111'
 
     if_cirid_name         = '{:s}.{:s}'.format(endpoint_name, str(circuit_id))
     network_instance_name = 'ELAN-AC:{:s}'.format(str(circuit_id))
     connection_point_id   = 'VC-1'
 
     json_config_rules = [
-        
+
         json_config_rule_set(
             '/network_instance[{:s}]'.format(network_instance_name),
-            {'name': network_instance_name, 
-             'type': 'L2VSI'}),
+            {'name': network_instance_name, 'type': 'L2VSI'}),
 
         json_config_rule_set(
-            '/interface[{:s}]/subinterface[0]'.format(if_cirid_name),
-            {'name': if_cirid_name, 
-             'type': 'l2vlan', 
-             'index': sub_interface_index, 
-             'vlan_id': vlan_id}),
+            '/interface[{:s}]/subinterface[{:d}]'.format(if_cirid_name, sub_interface_index),
+            {'name': if_cirid_name, 'type': 'l2vlan', 'index': sub_interface_index, 'vlan_id': vlan_id}),
 
         json_config_rule_set(
             '/network_instance[{:s}]/interface[{:s}]'.format(network_instance_name, if_cirid_name),
-            {'name': network_instance_name, 
-             'id': if_cirid_name, 
-             'interface': if_cirid_name,
-             'subinterface': 0
-            }),
+            {'name': network_instance_name, 'id': if_cirid_name, 'interface': if_cirid_name,
+             'subinterface': sub_interface_index}),
 
         json_config_rule_set(
             '/network_instance[{:s}]/connection_point[{:s}]'.format(network_instance_name, connection_point_id),
-            {'name': network_instance_name, 
-             'connection_point': connection_point_id, 
-             'VC_ID': circuit_id,
-             'remote_system': remote_router
-            }),
+            {'name': network_instance_name, 'connection_point': connection_point_id, 'VC_ID': circuit_id,
+             'remote_system': remote_router}),
     ]
     return json_config_rules
 
@@ -86,8 +72,11 @@ def teardown_config_rules(
     service_settings : TreeNode, endpoint_settings : TreeNode
 ) -> List[Dict]:
 
-    #json_settings          : Dict = {} if service_settings  is None else service_settings.value
-    json_endpoint_settings : Dict = {} if endpoint_settings is None else endpoint_settings.value
+    if service_settings  is None: return []
+    if endpoint_settings is None: return []
+
+    #json_settings          : Dict = service_settings.value
+    json_endpoint_settings : Dict = endpoint_settings.value
 
     #mtu                 = json_settings.get('mtu',                 1450 )    # 1512
     #address_families    = json_settings.get('address_families',    []   )    # ['IPV4']
@@ -96,7 +85,7 @@ def teardown_config_rules(
 
     #router_id           = json_endpoint_settings.get('router_id',           '0.0.0.0')  # '10.95.0.10'
     #route_distinguisher = json_endpoint_settings.get('route_distinguisher', '0:0'    )  # '60001:801'
-    #sub_interface_index = json_endpoint_settings.get('sub_interface_index', 0        )  # 1
+    sub_interface_index = json_endpoint_settings.get('sub_interface_index', 0        )  # 1
     #vlan_id             = json_endpoint_settings.get('vlan_id',             1        )  # 400
     #address_ip          = json_endpoint_settings.get('address_ip',          '0.0.0.0')  # '2.2.2.1'
     #address_prefix      = json_endpoint_settings.get('address_prefix',      24       )  # 30
@@ -105,17 +94,26 @@ def teardown_config_rules(
 
     if_cirid_name         = '{:s}.{:s}'.format(endpoint_name, str(circuit_id))
     network_instance_name = 'ELAN-AC:{:s}'.format(str(circuit_id))
-    #connection_point_id   = 'VC-1'
+    connection_point_id   = 'VC-1'
 
     json_config_rules = [
+
+        json_config_rule_delete(
+            '/network_instance[{:s}]/connection_point[{:s}]'.format(network_instance_name, connection_point_id),
+            {'name': network_instance_name, 'connection_point': connection_point_id, 'VC_ID': circuit_id}),
+
+        json_config_rule_delete(
+            '/network_instance[{:s}]/interface[{:s}]'.format(network_instance_name, if_cirid_name),
+            {'name': network_instance_name, 'id': if_cirid_name, 'interface': if_cirid_name,
+             'subinterface': sub_interface_index}),
+
+        json_config_rule_delete(
+            '/interface[{:s}]/subinterface[{:d}]'.format(if_cirid_name, sub_interface_index),
+            {'name': if_cirid_name, 'index': sub_interface_index}),
+
         json_config_rule_delete(
             '/network_instance[{:s}]'.format(network_instance_name),
             {'name': network_instance_name}),
-        
-        json_config_rule_delete(
-            '/interface[{:s}]/subinterface[0]'.format(if_cirid_name),{
-            'name': if_cirid_name,
-        }),
-        
+
     ]
     return json_config_rules
diff --git a/src/service/service/service_handlers/l3nm_emulated/ConfigRules.py b/src/service/service/service_handlers/l3nm_emulated/ConfigRules.py
index 903ad8cd5ae442a03d54fb49083f3837a3c8187c..f4a46112e778bd01aa76322384d8adee942aaa5b 100644
--- a/src/service/service/service_handlers/l3nm_emulated/ConfigRules.py
+++ b/src/service/service/service_handlers/l3nm_emulated/ConfigRules.py
@@ -21,8 +21,11 @@ def setup_config_rules(
     service_settings : TreeNode, endpoint_settings : TreeNode
 ) -> List[Dict]:
 
-    json_settings          : Dict = {} if service_settings  is None else service_settings.value
-    json_endpoint_settings : Dict = {} if endpoint_settings is None else endpoint_settings.value
+    if service_settings  is None: return []
+    if endpoint_settings is None: return []
+
+    json_settings          : Dict = service_settings.value
+    json_endpoint_settings : Dict = endpoint_settings.value
 
     service_short_uuid        = service_uuid.split('-')[-1]
     network_instance_name     = '{:s}-NetInst'.format(service_short_uuid)
@@ -142,8 +145,11 @@ def teardown_config_rules(
     service_settings : TreeNode, endpoint_settings : TreeNode
 ) -> List[Dict]:
 
-    json_settings          : Dict = {} if service_settings  is None else service_settings.value
-    json_endpoint_settings : Dict = {} if endpoint_settings is None else endpoint_settings.value
+    if service_settings  is None: return []
+    if endpoint_settings is None: return []
+
+    json_settings          : Dict = service_settings.value
+    json_endpoint_settings : Dict = endpoint_settings.value
 
     #mtu                 = json_settings.get('mtu',                 1450 )    # 1512
     #address_families    = json_settings.get('address_families',    []   )    # ['IPV4']
diff --git a/src/service/service/service_handlers/l3nm_emulated/L3NMEmulatedServiceHandler.py b/src/service/service/service_handlers/l3nm_emulated/L3NMEmulatedServiceHandler.py
index 47de9c94fbb8a5ddac848336c2ed7936d0126b45..18da03b08c0ea490b60c41df3894392022ddf228 100644
--- a/src/service/service/service_handlers/l3nm_emulated/L3NMEmulatedServiceHandler.py
+++ b/src/service/service/service_handlers/l3nm_emulated/L3NMEmulatedServiceHandler.py
@@ -75,10 +75,12 @@ class L3NMEmulatedServiceHandler(_ServiceHandler):
                     service_uuid, connection_uuid, device_uuid, endpoint_uuid, endpoint_name,
                     settings, endpoint_settings)
 
-                del device_obj.device_config.config_rules[:]
-                for json_config_rule in json_config_rules:
-                    device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
-                self.__task_executor.configure_device(device_obj)
+                if len(json_config_rules) > 0:
+                    del device_obj.device_config.config_rules[:]
+                    for json_config_rule in json_config_rules:
+                        device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
+                    self.__task_executor.configure_device(device_obj)
+
                 results.append(True)
             except Exception as e: # pylint: disable=broad-except
                 LOGGER.exception('Unable to SetEndpoint({:s})'.format(str(endpoint)))
@@ -110,10 +112,12 @@ class L3NMEmulatedServiceHandler(_ServiceHandler):
                     service_uuid, connection_uuid, device_uuid, endpoint_uuid, endpoint_name,
                     settings, endpoint_settings)
 
-                del device_obj.device_config.config_rules[:]
-                for json_config_rule in json_config_rules:
-                    device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
-                self.__task_executor.configure_device(device_obj)
+                if len(json_config_rules) > 0:
+                    del device_obj.device_config.config_rules[:]
+                    for json_config_rule in json_config_rules:
+                        device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
+                    self.__task_executor.configure_device(device_obj)
+
                 results.append(True)
             except Exception as e: # pylint: disable=broad-except
                 LOGGER.exception('Unable to DeleteEndpoint({:s})'.format(str(endpoint)))
diff --git a/src/service/service/service_handlers/l3nm_openconfig/ConfigRules.py b/src/service/service/service_handlers/l3nm_openconfig/ConfigRules.py
index ef93dcdda8145cab15ff21c24b6318e9eb00e098..5d260bf86b82c66be8eb2f0caa683a72d8bd0ba5 100644
--- a/src/service/service/service_handlers/l3nm_openconfig/ConfigRules.py
+++ b/src/service/service/service_handlers/l3nm_openconfig/ConfigRules.py
@@ -187,8 +187,8 @@ def teardown_config_rules(
     if service_settings  is None: return []
     if endpoint_settings is None: return []
 
-    json_settings          : Dict = {} if service_settings  is None else service_settings.value
-    json_endpoint_settings : Dict = {} if endpoint_settings is None else endpoint_settings.value
+    json_settings          : Dict = service_settings.value
+    json_endpoint_settings : Dict = endpoint_settings.value
 
     service_short_uuid        = service_uuid.split('-')[-1]
     network_instance_name     = '{:s}-NetInst'.format(service_short_uuid)
diff --git a/src/service/service/service_handlers/microwave/MicrowaveServiceHandler.py b/src/service/service/service_handlers/microwave/MicrowaveServiceHandler.py
index ee64d2fa4ff0110aea9ee4beee97fa83915ab57d..40c87eeee2c8dd1ddd5a39162f8ff7f117344e3b 100644
--- a/src/service/service/service_handlers/microwave/MicrowaveServiceHandler.py
+++ b/src/service/service/service_handlers/microwave/MicrowaveServiceHandler.py
@@ -61,7 +61,7 @@ class MicrowaveServiceHandler(_ServiceHandler):
             device_uuid_dst, endpoint_uuid_dst = get_device_endpoint_uuids(endpoints[1])
 
             if device_uuid_src != device_uuid_dst:
-                raise Exception('Diferent Src-Dst devices not supported by now')
+                raise Exception('Different Src-Dst devices not supported by now')
             device_uuid = device_uuid_src
 
             device_obj = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid)))
@@ -106,7 +106,7 @@ class MicrowaveServiceHandler(_ServiceHandler):
             device_uuid_dst, _ = get_device_endpoint_uuids(endpoints[1])
 
             if device_uuid_src != device_uuid_dst:
-                raise Exception('Diferent Src-Dst devices not supported by now')
+                raise Exception('Different Src-Dst devices not supported by now')
             device_uuid = device_uuid_src
 
             device_obj = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid)))
diff --git a/src/service/service/service_handlers/tapi_tapi/TapiServiceHandler.py b/src/service/service/service_handlers/tapi_tapi/TapiServiceHandler.py
index 8abd12b2a24c49a6c5e50cebe7a2d65dc7ce4eb1..af7d4bc949fc98f057ade66b58d8b9b38e0707ed 100644
--- a/src/service/service/service_handlers/tapi_tapi/TapiServiceHandler.py
+++ b/src/service/service/service_handlers/tapi_tapi/TapiServiceHandler.py
@@ -19,7 +19,7 @@ from common.proto.context_pb2 import ConfigRule, DeviceId, Service
 from common.tools.object_factory.ConfigRule import json_config_rule_delete, json_config_rule_set
 from common.tools.object_factory.Device import json_device_id
 from common.type_checkers.Checkers import chk_type
-from service.service.service_handler_api.Tools import get_device_endpoint_uuids, get_endpoint_matching
+from service.service.service_handler_api.Tools import get_device_endpoint_uuids
 from service.service.service_handler_api._ServiceHandler import _ServiceHandler
 from service.service.service_handler_api.SettingsHandler import SettingsHandler
 from service.service.task_scheduler.TaskExecutor import TaskExecutor
@@ -42,7 +42,7 @@ class TapiServiceHandler(_ServiceHandler):
     ) -> List[Union[bool, Exception]]:
 
         chk_type('endpoints', endpoints, list)
-        if len(endpoints) != 2: return []
+        if len(endpoints) < 2: return []
 
         service_uuid = self.__service.service_id.service_uuid.uuid
         settings = self.__settings_handler.get('/settings')
@@ -55,30 +55,33 @@ class TapiServiceHandler(_ServiceHandler):
 
         results = []
         try:
-            device_uuid_src, endpoint_uuid_src = get_device_endpoint_uuids(endpoints[0])
-            device_uuid_dst, endpoint_uuid_dst = get_device_endpoint_uuids(endpoints[1])
+            src_device_uuid, src_endpoint_uuid = get_device_endpoint_uuids(endpoints[0])
+            src_device = self.__task_executor.get_device(DeviceId(**json_device_id(src_device_uuid)))
+            src_controller = self.__task_executor.get_device_controller(src_device)
+            if src_controller is None: src_controller = src_device
 
-            if device_uuid_src != device_uuid_dst:
-                raise Exception('Diferent Src-Dst devices not supported by now')
-            device_uuid = device_uuid_src
+            dst_device_uuid, dst_endpoint_uuid = get_device_endpoint_uuids(endpoints[-1])
+            dst_device = self.__task_executor.get_device(DeviceId(**json_device_id(dst_device_uuid)))
+            dst_controller = self.__task_executor.get_device_controller(dst_device)
+            if dst_controller is None: dst_controller = dst_device
 
-            device_obj = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid)))
-            endpoint_name_src = get_endpoint_matching(device_obj, endpoint_uuid_src).name
-            endpoint_name_dst = get_endpoint_matching(device_obj, endpoint_uuid_dst).name
+            if src_controller.device_id.device_uuid.uuid != dst_controller.device_id.device_uuid.uuid:
+                raise Exception('Different Src-Dst devices not supported by now')
+            controller = src_controller
 
             json_config_rule = json_config_rule_set('/services/service[{:s}]'.format(service_uuid), {
                 'uuid'                    : service_uuid,
-                'input_sip'               : endpoint_name_src,
-                'output_sip'              : endpoint_name_dst,
+                'input_sip_uuid'          : src_endpoint_uuid,
+                'output_sip_uuid'         : dst_endpoint_uuid,
                 'capacity_unit'           : capacity_unit,
                 'capacity_value'          : capacity_value,
                 'layer_protocol_name'     : layer_proto_name,
                 'layer_protocol_qualifier': layer_proto_qual,
                 'direction'               : direction,
             })
-            del device_obj.device_config.config_rules[:]
-            device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
-            self.__task_executor.configure_device(device_obj)
+            del controller.device_config.config_rules[:]
+            controller.device_config.config_rules.append(ConfigRule(**json_config_rule))
+            self.__task_executor.configure_device(controller)
             results.append(True)
         except Exception as e: # pylint: disable=broad-except
             LOGGER.exception('Unable to SetEndpoint for Service({:s})'.format(str(service_uuid)))
@@ -92,27 +95,32 @@ class TapiServiceHandler(_ServiceHandler):
     ) -> List[Union[bool, Exception]]:
 
         chk_type('endpoints', endpoints, list)
-        if len(endpoints) != 2: return []
+        if len(endpoints) < 2: return []
 
         service_uuid = self.__service.service_id.service_uuid.uuid
 
         results = []
         try:
-            device_uuid_src, _ = get_device_endpoint_uuids(endpoints[0])
-            device_uuid_dst, _ = get_device_endpoint_uuids(endpoints[1])
+            src_device_uuid, _ = get_device_endpoint_uuids(endpoints[0])
+            src_device = self.__task_executor.get_device(DeviceId(**json_device_id(src_device_uuid)))
+            src_controller = self.__task_executor.get_device_controller(src_device)
+            if src_controller is None: src_controller = src_device
 
-            if device_uuid_src != device_uuid_dst:
-                raise Exception('Diferent Src-Dst devices not supported by now')
-            device_uuid = device_uuid_src
+            dst_device_uuid, _ = get_device_endpoint_uuids(endpoints[1])
+            dst_device = self.__task_executor.get_device(DeviceId(**json_device_id(dst_device_uuid)))
+            dst_controller = self.__task_executor.get_device_controller(dst_device)
+            if dst_controller is None: dst_controller = dst_device
 
-            device_obj = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid)))
+            if src_controller.device_id.device_uuid.uuid != dst_controller.device_id.device_uuid.uuid:
+                raise Exception('Different Src-Dst devices not supported by now')
+            controller = src_controller
 
             json_config_rule = json_config_rule_delete('/services/service[{:s}]'.format(service_uuid), {
                 'uuid': service_uuid
             })
-            del device_obj.device_config.config_rules[:]
-            device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
-            self.__task_executor.configure_device(device_obj)
+            del controller.device_config.config_rules[:]
+            controller.device_config.config_rules.append(ConfigRule(**json_config_rule))
+            self.__task_executor.configure_device(controller)
             results.append(True)
         except Exception as e: # pylint: disable=broad-except
             LOGGER.exception('Unable to DeleteEndpoint for Service({:s})'.format(str(service_uuid)))
diff --git a/src/service/service/service_handlers/tapi_xr/TapiXrServiceHandler.py b/src/service/service/service_handlers/tapi_xr/TapiXrServiceHandler.py
new file mode 100644
index 0000000000000000000000000000000000000000..a1e1b8a6fff9436d6cdff13b95b0ecd43f6fa661
--- /dev/null
+++ b/src/service/service/service_handlers/tapi_xr/TapiXrServiceHandler.py
@@ -0,0 +1,176 @@
+# 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 json, logging
+from typing import Any, Dict, List, Optional, Tuple, Union
+from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method
+from common.proto.context_pb2 import ConfigRule, DeviceId, Service
+from common.tools.object_factory.ConfigRule import json_config_rule_delete, json_config_rule_set
+from common.tools.object_factory.Device import json_device_id
+from common.type_checkers.Checkers import chk_type
+from service.service.service_handler_api.Tools import get_device_endpoint_uuids, get_endpoint_matching
+from service.service.service_handler_api._ServiceHandler import _ServiceHandler
+from service.service.service_handler_api.SettingsHandler import SettingsHandler
+from service.service.task_scheduler.TaskExecutor import TaskExecutor
+
+LOGGER = logging.getLogger(__name__)
+
+METRICS_POOL = MetricsPool('Service', 'Handler', labels={'handler': 'tapi_xr'})
+
+class TapiXrServiceHandler(_ServiceHandler):
+    def __init__(   # pylint: disable=super-init-not-called
+        self, service : Service, task_executor : TaskExecutor, **settings
+    ) -> None:
+        self.__service = service
+        self.__task_executor = task_executor
+        self.__settings_handler = SettingsHandler(service.service_config, **settings)
+
+    @metered_subclass_method(METRICS_POOL)
+    def SetEndpoint(
+        self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None
+    ) -> List[Union[bool, Exception]]:
+
+        chk_type('endpoints', endpoints, list)
+        if len(endpoints) != 4: return []
+
+        service_uuid = self.__service.service_id.service_uuid.uuid
+        settings = self.__settings_handler.get('/settings')
+        json_settings : Dict = {} if settings is None else settings.value
+        capacity_value   = json_settings.get('capacity_value', 50.0)
+        capacity_unit    = json_settings.get('capacity_unit',  'GHz')
+
+        results = []
+        try:
+            src_device_uuid, src_endpoint_uuid = get_device_endpoint_uuids(endpoints[1])
+            src_device = self.__task_executor.get_device(DeviceId(**json_device_id(src_device_uuid)))
+            src_endpoint = get_endpoint_matching(src_device, src_endpoint_uuid)
+            src_controller = self.__task_executor.get_device_controller(src_device)
+            if src_controller is None: src_controller = src_device
+
+            dst_device_uuid, dst_endpoint_uuid = get_device_endpoint_uuids(endpoints[2])
+            dst_device = self.__task_executor.get_device(DeviceId(**json_device_id(dst_device_uuid)))
+            dst_endpoint = get_endpoint_matching(dst_device, dst_endpoint_uuid)
+            dst_controller = self.__task_executor.get_device_controller(dst_device)
+            if dst_controller is None: dst_controller = dst_device
+
+            if src_controller.device_id.device_uuid.uuid != dst_controller.device_id.device_uuid.uuid:
+                raise Exception('Different Src-Dst devices not supported by now')
+            controller = src_controller
+
+            json_config_rule = json_config_rule_set('/services/service[{:s}]'.format(service_uuid), {
+                'uuid'           : service_uuid,
+                'input_sip_name' : '|'.join([src_device.name, src_endpoint.name]),
+                'output_sip_name': '|'.join([dst_device.name, dst_endpoint.name]),
+                'capacity_unit'  : capacity_unit,
+                'capacity_value' : capacity_value,
+            })
+
+            del controller.device_config.config_rules[:]
+            controller.device_config.config_rules.append(ConfigRule(**json_config_rule))
+            self.__task_executor.configure_device(controller)
+            results.append(True)
+        except Exception as e: # pylint: disable=broad-except
+            LOGGER.exception('Unable to SetEndpoint for Service({:s})'.format(str(service_uuid)))
+            results.append(e)
+
+        return results
+
+    @metered_subclass_method(METRICS_POOL)
+    def DeleteEndpoint(
+        self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None
+    ) -> List[Union[bool, Exception]]:
+
+        chk_type('endpoints', endpoints, list)
+        if len(endpoints) < 2: return []
+
+        service_uuid = self.__service.service_id.service_uuid.uuid
+
+        results = []
+        try:
+            src_device_uuid, _ = get_device_endpoint_uuids(endpoints[0])
+            src_device = self.__task_executor.get_device(DeviceId(**json_device_id(src_device_uuid)))
+            src_controller = self.__task_executor.get_device_controller(src_device)
+            if src_controller is None: src_controller = src_device
+
+            dst_device_uuid, _ = get_device_endpoint_uuids(endpoints[1])
+            dst_device = self.__task_executor.get_device(DeviceId(**json_device_id(dst_device_uuid)))
+            dst_controller = self.__task_executor.get_device_controller(dst_device)
+            if dst_controller is None: dst_controller = dst_device
+
+            if src_controller.device_id.device_uuid.uuid != dst_controller.device_id.device_uuid.uuid:
+                raise Exception('Different Src-Dst devices not supported by now')
+            controller = src_controller
+
+            json_config_rule = json_config_rule_delete('/services/service[{:s}]'.format(service_uuid), {
+                'uuid': service_uuid
+            })
+            del controller.device_config.config_rules[:]
+            controller.device_config.config_rules.append(ConfigRule(**json_config_rule))
+            self.__task_executor.configure_device(controller)
+            results.append(True)
+        except Exception as e: # pylint: disable=broad-except
+            LOGGER.exception('Unable to DeleteEndpoint for Service({:s})'.format(str(service_uuid)))
+            results.append(e)
+
+        return results
+
+    @metered_subclass_method(METRICS_POOL)
+    def SetConstraint(self, constraints : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+        chk_type('constraints', constraints, list)
+        if len(constraints) == 0: return []
+
+        msg = '[SetConstraint] Method not implemented. Constraints({:s}) are being ignored.'
+        LOGGER.warning(msg.format(str(constraints)))
+        return [True for _ in range(len(constraints))]
+
+    @metered_subclass_method(METRICS_POOL)
+    def DeleteConstraint(self, constraints : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+        chk_type('constraints', constraints, list)
+        if len(constraints) == 0: return []
+
+        msg = '[DeleteConstraint] Method not implemented. Constraints({:s}) are being ignored.'
+        LOGGER.warning(msg.format(str(constraints)))
+        return [True for _ in range(len(constraints))]
+
+    @metered_subclass_method(METRICS_POOL)
+    def SetConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+        chk_type('resources', resources, list)
+        if len(resources) == 0: return []
+
+        results = []
+        for resource in resources:
+            try:
+                resource_value = json.loads(resource[1])
+                self.__settings_handler.set(resource[0], resource_value)
+                results.append(True)
+            except Exception as e: # pylint: disable=broad-except
+                LOGGER.exception('Unable to SetConfig({:s})'.format(str(resource)))
+                results.append(e)
+
+        return results
+
+    @metered_subclass_method(METRICS_POOL)
+    def DeleteConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+        chk_type('resources', resources, list)
+        if len(resources) == 0: return []
+
+        results = []
+        for resource in resources:
+            try:
+                self.__settings_handler.delete(resource[0])
+            except Exception as e: # pylint: disable=broad-except
+                LOGGER.exception('Unable to DeleteConfig({:s})'.format(str(resource)))
+                results.append(e)
+
+        return results
diff --git a/src/service/service/service_handlers/tapi_xr/__init__.py b/src/service/service/service_handlers/tapi_xr/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..1549d9811aa5d1c193a44ad45d0d7773236c0612
--- /dev/null
+++ b/src/service/service/service_handlers/tapi_xr/__init__.py
@@ -0,0 +1,14 @@
+# 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.
+
diff --git a/src/service/service/task_scheduler/TaskExecutor.py b/src/service/service/task_scheduler/TaskExecutor.py
index 932c56e2b1934e12e7849a60c22d3ca1be7f8093..3d157e3145d4b195c0251a4ab79f710a38726569 100644
--- a/src/service/service/task_scheduler/TaskExecutor.py
+++ b/src/service/service/task_scheduler/TaskExecutor.py
@@ -12,10 +12,12 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import json
 from enum import Enum
 from typing import TYPE_CHECKING, Any, Dict, Optional, Union
 from common.method_wrappers.ServiceExceptions import NotFoundException
 from common.proto.context_pb2 import Connection, ConnectionId, Device, DeviceId, Service, ServiceId
+from common.tools.object_factory.Device import json_device_id
 from context.client.ContextClient import ContextClient
 from device.client.DeviceClient import DeviceClient
 from service.service.service_handler_api.ServiceHandlerFactory import ServiceHandlerFactory, get_service_handler_class
@@ -103,13 +105,38 @@ class TaskExecutor:
         self._device_client.ConfigureDevice(device)
         self._store_grpc_object(CacheableObjectType.DEVICE, device_key, device)
 
-    def get_devices_from_connection(self, connection : Connection) -> Dict[str, Device]:
+    def get_device_controller(self, device : Device) -> Optional[Device]:
+        json_controller = None
+        for config_rule in device.device_config.config_rules:
+            if config_rule.WhichOneof('config_rule') != 'custom': continue
+            if config_rule.custom.resource_key != '_controller': continue
+            json_controller = json.loads(config_rule.custom.resource_value)
+            break
+
+        if json_controller is None: return None
+
+        controller_uuid = json_controller['uuid']
+        controller = self.get_device(DeviceId(**json_device_id(controller_uuid)))
+        controller_uuid = controller.device_id.device_uuid.uuid
+        if controller is None: raise Exception('Device({:s}) not found'.format(str(controller_uuid)))
+        return controller
+
+    def get_devices_from_connection(
+        self, connection : Connection, exclude_managed_by_controller : bool = False
+    ) -> Dict[str, Device]:
         devices = dict()
         for endpoint_id in connection.path_hops_endpoint_ids:
             device = self.get_device(endpoint_id.device_id)
             device_uuid = endpoint_id.device_id.device_uuid.uuid
             if device is None: raise Exception('Device({:s}) not found'.format(str(device_uuid)))
-            devices[device_uuid] = device
+
+            controller = self.get_device_controller(device)
+            if controller is None:
+                devices[device_uuid] = device
+            else:
+                if not exclude_managed_by_controller:
+                    devices[device_uuid] = device
+                devices[controller.device_id.device_uuid.uuid] = controller
         return devices
 
     # ----- Service-related methods ------------------------------------------------------------------------------------
@@ -139,6 +166,6 @@ class TaskExecutor:
     def get_service_handler(
         self, connection : Connection, service : Service, **service_handler_settings
     ) -> '_ServiceHandler':
-        connection_devices = self.get_devices_from_connection(connection)
+        connection_devices = self.get_devices_from_connection(connection, exclude_managed_by_controller=True)
         service_handler_class = get_service_handler_class(self._service_handler_factory, service, connection_devices)
         return service_handler_class(service, self, **service_handler_settings)
diff --git a/src/service/service/task_scheduler/tasks/Task_ConnectionConfigure.py b/src/service/service/task_scheduler/tasks/Task_ConnectionConfigure.py
index 5a47005b304836050dd8c0882214dd9cebd5d8b5..4367ffdee4d6d5b9edfc9fd30d0d6b6f48da8a75 100644
--- a/src/service/service/task_scheduler/tasks/Task_ConnectionConfigure.py
+++ b/src/service/service/task_scheduler/tasks/Task_ConnectionConfigure.py
@@ -32,7 +32,7 @@ class Task_ConnectionConfigure(_Task):
     def connection_id(self) -> ConnectionId: return self._connection_id
 
     @staticmethod
-    def build_key(connection_id : ConnectionId) -> str:
+    def build_key(connection_id : ConnectionId) -> str: # pylint: disable=arguments-differ
         str_connection_id = get_connection_key(connection_id)
         return KEY_TEMPLATE.format(connection_id=str_connection_id)
 
diff --git a/src/service/service/task_scheduler/tasks/Task_ConnectionDeconfigure.py b/src/service/service/task_scheduler/tasks/Task_ConnectionDeconfigure.py
index 5736054febd2fb9e8a36b5a2235ca3f412e0e174..70f41566ef5e69605a527cc0392b77acb866ec2c 100644
--- a/src/service/service/task_scheduler/tasks/Task_ConnectionDeconfigure.py
+++ b/src/service/service/task_scheduler/tasks/Task_ConnectionDeconfigure.py
@@ -32,7 +32,7 @@ class Task_ConnectionDeconfigure(_Task):
     def connection_id(self) -> ConnectionId: return self._connection_id
 
     @staticmethod
-    def build_key(connection_id : ConnectionId) -> str:
+    def build_key(connection_id : ConnectionId) -> str: # pylint: disable=arguments-differ
         str_connection_id = get_connection_key(connection_id)
         return KEY_TEMPLATE.format(connection_id=str_connection_id)
 
diff --git a/src/service/service/task_scheduler/tasks/Task_ServiceDelete.py b/src/service/service/task_scheduler/tasks/Task_ServiceDelete.py
index 6a4e11b540cd9b85028d92cf86899ee098056c36..0f021b6ca65da1c6b5e44d8577bf9dd6875eb17a 100644
--- a/src/service/service/task_scheduler/tasks/Task_ServiceDelete.py
+++ b/src/service/service/task_scheduler/tasks/Task_ServiceDelete.py
@@ -28,7 +28,7 @@ class Task_ServiceDelete(_Task):
     def service_id(self) -> ServiceId: return self._service_id
 
     @staticmethod
-    def build_key(service_id : ServiceId) -> str:
+    def build_key(service_id : ServiceId) -> str:   # pylint: disable=arguments-differ
         str_service_id = get_service_key(service_id)
         return KEY_TEMPLATE.format(service_id=str_service_id)
 
diff --git a/src/service/service/task_scheduler/tasks/Task_ServiceSetStatus.py b/src/service/service/task_scheduler/tasks/Task_ServiceSetStatus.py
index 815cb33c3d540755704153b661e889fc2660d268..d5360fe85eae68085298406fc0ed19dd105f187e 100644
--- a/src/service/service/task_scheduler/tasks/Task_ServiceSetStatus.py
+++ b/src/service/service/task_scheduler/tasks/Task_ServiceSetStatus.py
@@ -32,7 +32,7 @@ class Task_ServiceSetStatus(_Task):
     def new_status(self) -> ServiceStatusEnum: return self._new_status
 
     @staticmethod
-    def build_key(service_id : ServiceId, new_status : ServiceStatusEnum) -> str:
+    def build_key(service_id : ServiceId, new_status : ServiceStatusEnum) -> str:   # pylint: disable=arguments-differ
         str_service_id = get_service_key(service_id)
         str_new_status = ServiceStatusEnum.Name(new_status)
         return KEY_TEMPLATE.format(service_id=str_service_id, new_status=str_new_status)
diff --git a/src/slice/service/slice_grouper/SliceGrouper.py b/src/slice/service/slice_grouper/SliceGrouper.py
index 735d028993eb11e83138caebde1e32ebc830093f..2f1a791819f6a8d0951e9e93ca22d071ea66c1f7 100644
--- a/src/slice/service/slice_grouper/SliceGrouper.py
+++ b/src/slice/service/slice_grouper/SliceGrouper.py
@@ -29,6 +29,7 @@ class SliceGrouper:
     def __init__(self) -> None:
         self._lock = threading.Lock()
         self._is_enabled = is_slice_grouping_enabled()
+        LOGGER.info('Slice Grouping: {:s}'.format('ENABLED' if self._is_enabled else 'DISABLED'))
         if not self._is_enabled: return
 
         metrics_exporter = MetricsExporter()
diff --git a/src/tests/ofc23/.gitignore b/src/tests/ofc23/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..0a3f4400d5c88b1af32c7667d69d2fdc12d5424e
--- /dev/null
+++ b/src/tests/ofc23/.gitignore
@@ -0,0 +1,2 @@
+# Add here your files containing confidential testbed details such as IP addresses, ports, usernames, passwords, etc.
+descriptors_real.json
diff --git a/src/tests/ofc23/MultiIngressController.txt b/src/tests/ofc23/MultiIngressController.txt
new file mode 100644
index 0000000000000000000000000000000000000000..190e6df7425983db43d8b1888f29861ec9056ed6
--- /dev/null
+++ b/src/tests/ofc23/MultiIngressController.txt
@@ -0,0 +1,23 @@
+# Ref: https://kubernetes.github.io/ingress-nginx/user-guide/multiple-ingress/
+# Ref: https://fabianlee.org/2021/07/29/kubernetes-microk8s-with-multiple-metallb-endpoints-and-nginx-ingress-controllers/
+
+# Check node limits
+kubectl describe nodes
+
+# Create secondary ingress controllers
+kubectl apply -f ofc23/nginx-ingress-controller-parent.yaml
+kubectl apply -f ofc23/nginx-ingress-controller-child.yaml
+
+# Delete secondary ingress controllers
+kubectl delete -f ofc23/nginx-ingress-controller-parent.yaml
+kubectl delete -f ofc23/nginx-ingress-controller-child.yaml
+
+source ofc23/deploy_specs_parent.sh
+./deploy/all.sh
+
+source ofc23/deploy_specs_child.sh
+./deploy/all.sh
+
+# Manually deploy ingresses for instances
+kubectl --namespace tfs-parent apply -f ofc23/tfs-ingress-parent.yaml
+kubectl --namespace tfs-child apply -f ofc23/tfs-ingress-child.yaml
diff --git a/src/tests/ofc23/__init__.py b/src/tests/ofc23/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..1549d9811aa5d1c193a44ad45d0d7773236c0612
--- /dev/null
+++ b/src/tests/ofc23/__init__.py
@@ -0,0 +1,14 @@
+# 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.
+
diff --git a/src/tests/ofc23/delete_hierar.sh b/src/tests/ofc23/delete_hierar.sh
new file mode 100755
index 0000000000000000000000000000000000000000..4a03dad1cc29cff72347f68bc7b1a082924a9211
--- /dev/null
+++ b/src/tests/ofc23/delete_hierar.sh
@@ -0,0 +1,22 @@
+#!/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.
+
+
+# Delete old namespaces
+kubectl delete namespace tfs-parent tfs-child
+
+# Delete secondary ingress controllers
+kubectl delete -f ofc23/nginx-ingress-controller-parent.yaml
+kubectl delete -f ofc23/nginx-ingress-controller-child.yaml
diff --git a/src/tests/ofc23/delete_sligrp.sh b/src/tests/ofc23/delete_sligrp.sh
new file mode 100755
index 0000000000000000000000000000000000000000..cce0bd53febc4765f9d455619f49ea4de8dfe870
--- /dev/null
+++ b/src/tests/ofc23/delete_sligrp.sh
@@ -0,0 +1,18 @@
+#!/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.
+
+
+# Delete old namespaces
+kubectl delete namespace tfs
diff --git a/src/tests/ofc23/deploy_child.sh b/src/tests/ofc23/deploy_child.sh
new file mode 100755
index 0000000000000000000000000000000000000000..9b05ed88739114bf9029d8afaf491d7fec726bff
--- /dev/null
+++ b/src/tests/ofc23/deploy_child.sh
@@ -0,0 +1,29 @@
+#!/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.
+
+
+# Delete old namespaces
+kubectl delete namespace tfs-child
+
+# Delete secondary ingress controllers
+kubectl delete -f ofc23/nginx-ingress-controller-child.yaml
+
+# Create secondary ingress controllers
+kubectl apply -f ofc23/nginx-ingress-controller-child.yaml
+
+# Deploy TFS for Child
+source ofc23/deploy_specs_child.sh
+./deploy/all.sh
+mv tfs_runtime_env_vars.sh tfs_runtime_env_vars_child.sh
diff --git a/src/tests/ofc23/deploy_hierar.sh b/src/tests/ofc23/deploy_hierar.sh
new file mode 100755
index 0000000000000000000000000000000000000000..4874688ad7561156b1b5fc4c80b72a9745feb6a0
--- /dev/null
+++ b/src/tests/ofc23/deploy_hierar.sh
@@ -0,0 +1,36 @@
+#!/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.
+
+
+# Delete old namespaces
+kubectl delete namespace tfs-parent tfs-child
+
+# Delete secondary ingress controllers
+kubectl delete -f ofc23/nginx-ingress-controller-parent.yaml
+kubectl delete -f ofc23/nginx-ingress-controller-child.yaml
+
+# Create secondary ingress controllers
+kubectl apply -f ofc23/nginx-ingress-controller-parent.yaml
+kubectl apply -f ofc23/nginx-ingress-controller-child.yaml
+
+# Deploy TFS for Parent
+source ofc23/deploy_specs_parent.sh
+./deploy/all.sh
+mv tfs_runtime_env_vars.sh tfs_runtime_env_vars_parent.sh
+
+# Deploy TFS for Child
+source ofc23/deploy_specs_child.sh
+./deploy/all.sh
+mv tfs_runtime_env_vars.sh tfs_runtime_env_vars_child.sh
diff --git a/src/tests/ofc23/deploy_parent.sh b/src/tests/ofc23/deploy_parent.sh
new file mode 100755
index 0000000000000000000000000000000000000000..ac4a2954213cf577efe1b1a8e499635d80ea3548
--- /dev/null
+++ b/src/tests/ofc23/deploy_parent.sh
@@ -0,0 +1,29 @@
+#!/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.
+
+
+# Delete old namespaces
+kubectl delete namespace tfs-parent
+
+# Delete secondary ingress controllers
+kubectl delete -f ofc23/nginx-ingress-controller-parent.yaml
+
+# Create secondary ingress controllers
+kubectl apply -f ofc23/nginx-ingress-controller-parent.yaml
+
+# Deploy TFS for Parent
+source ofc23/deploy_specs_parent.sh
+./deploy/all.sh
+mv tfs_runtime_env_vars.sh tfs_runtime_env_vars_parent.sh
diff --git a/src/tests/ofc23/deploy_sligrp.sh b/src/tests/ofc23/deploy_sligrp.sh
new file mode 100755
index 0000000000000000000000000000000000000000..62a9df5cf006af856f168add4058d63eaa905784
--- /dev/null
+++ b/src/tests/ofc23/deploy_sligrp.sh
@@ -0,0 +1,23 @@
+#!/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.
+
+
+# Delete old namespaces
+kubectl delete namespace tfs-sligrp
+
+# Deploy TFS for Slice Goruping
+source ofc23/deploy_specs_sligrp.sh
+./deploy/all.sh
+mv tfs_runtime_env_vars.sh tfs_runtime_env_vars_sligrp.sh
diff --git a/src/tests/ofc23/deploy_specs_child.sh b/src/tests/ofc23/deploy_specs_child.sh
new file mode 100755
index 0000000000000000000000000000000000000000..4d2b3502294925d82f675263fd6bddea62ec181a
--- /dev/null
+++ b/src/tests/ofc23/deploy_specs_child.sh
@@ -0,0 +1,118 @@
+#!/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.
+
+
+# ----- 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.
+#automation monitoring load_generator
+export TFS_COMPONENTS="context device pathcomp service slice compute webui"
+
+# 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-child"
+
+# Set additional manifest files to be applied after the deployment
+export TFS_EXTRA_MANIFESTS="ofc23/tfs-ingress-child.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="YES"
+
+
+# ----- 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_child"
+
+# 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-child"
+
+# Set the external port NATS Client interface will be exposed to.
+export NATS_EXT_PORT_CLIENT="4224"
+
+# Set the external port NATS HTTP Mgmt GUI interface will be exposed to.
+export NATS_EXT_PORT_HTTP="8224"
+
+# Disable flag for re-deploying NATS from scratch.
+export NATS_REDEPLOY=""
+
+
+# ----- QuestDB ----------------------------------------------------------------
+
+# Set the namespace where QuestDB will be deployed.
+export QDB_NAMESPACE="qdb-child"
+
+# Set the external port QuestDB Postgre SQL interface will be exposed to.
+export QDB_EXT_PORT_SQL="8814"
+
+# Set the external port QuestDB Influx Line Protocol interface will be exposed to.
+export QDB_EXT_PORT_ILP="9012"
+
+# Set the external port QuestDB HTTP Mgmt GUI interface will be exposed to.
+export QDB_EXT_PORT_HTTP="9002"
+
+# 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=""
diff --git a/src/tests/ofc23/deploy_specs_parent.sh b/src/tests/ofc23/deploy_specs_parent.sh
new file mode 100755
index 0000000000000000000000000000000000000000..808f4e28734be71e6eb7fb2aced39211fd8e7f24
--- /dev/null
+++ b/src/tests/ofc23/deploy_specs_parent.sh
@@ -0,0 +1,118 @@
+#!/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.
+
+
+# ----- 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.
+#automation monitoring load_generator
+export TFS_COMPONENTS="context device pathcomp service slice compute webui"
+
+# 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-parent"
+
+# Set additional manifest files to be applied after the deployment
+export TFS_EXTRA_MANIFESTS="ofc23/tfs-ingress-parent.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_parent"
+
+# 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-parent"
+
+# Set the external port NATS Client interface will be exposed to.
+export NATS_EXT_PORT_CLIENT="4223"
+
+# Set the external port NATS HTTP Mgmt GUI interface will be exposed to.
+export NATS_EXT_PORT_HTTP="8223"
+
+# Disable flag for re-deploying NATS from scratch.
+export NATS_REDEPLOY=""
+
+
+# ----- QuestDB ----------------------------------------------------------------
+
+# Set the namespace where QuestDB will be deployed.
+export QDB_NAMESPACE="qdb-parent"
+
+# Set the external port QuestDB Postgre SQL interface will be exposed to.
+export QDB_EXT_PORT_SQL="8813"
+
+# Set the external port QuestDB Influx Line Protocol interface will be exposed to.
+export QDB_EXT_PORT_ILP="9011"
+
+# Set the external port QuestDB HTTP Mgmt GUI interface will be exposed to.
+export QDB_EXT_PORT_HTTP="9001"
+
+# 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=""
diff --git a/src/tests/ofc23/deploy_specs_sligrp.sh b/src/tests/ofc23/deploy_specs_sligrp.sh
new file mode 100755
index 0000000000000000000000000000000000000000..90bea4567bd35d845abf943670f8aa33070dff57
--- /dev/null
+++ b/src/tests/ofc23/deploy_specs_sligrp.sh
@@ -0,0 +1,118 @@
+#!/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.
+
+
+# ----- 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.
+#automation monitoring load_generator
+export TFS_COMPONENTS="context device pathcomp service slice webui load_generator"
+
+# 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-sligrp"
+
+# Set additional manifest files to be applied after the deployment
+export TFS_EXTRA_MANIFESTS="manifests/nginx_ingress_http.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_sligrp"
+
+# 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-sligrp"
+
+# 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-sligrp"
+
+# 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="9010"
+
+# 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=""
diff --git a/src/tests/ofc23/descriptors/adva-interfaces.txt b/src/tests/ofc23/descriptors/adva-interfaces.txt
new file mode 100644
index 0000000000000000000000000000000000000000..a634735058aa9490ddbd43e8e9a0752fbd4a6ee6
--- /dev/null
+++ b/src/tests/ofc23/descriptors/adva-interfaces.txt
@@ -0,0 +1,89 @@
+R199
+eth-1/0/1
+eth-1/0/10
+eth-1/0/11
+eth-1/0/12
+eth-1/0/13
+eth-1/0/14
+eth-1/0/15
+eth-1/0/16
+eth-1/0/17
+eth-1/0/18
+eth-1/0/19
+eth-1/0/2
+eth-1/0/20
+eth-1/0/21
+eth-1/0/22
+eth-1/0/23
+eth-1/0/24
+eth-1/0/25
+eth-1/0/26
+eth-1/0/27
+eth-1/0/28
+eth-1/0/29
+eth-1/0/3
+eth-1/0/30
+eth-1/0/4
+eth-1/0/5
+eth-1/0/6
+eth-1/0/7
+eth-1/0/8
+eth-1/0/9
+
+R155
+eth-1/0/1
+eth-1/0/10
+eth-1/0/11
+eth-1/0/12
+eth-1/0/13
+eth-1/0/14
+eth-1/0/15
+eth-1/0/16
+eth-1/0/17
+eth-1/0/18
+eth-1/0/19
+eth-1/0/2
+eth-1/0/20
+eth-1/0/21
+eth-1/0/22
+eth-1/0/23
+eth-1/0/24
+eth-1/0/25
+eth-1/0/26
+eth-1/0/27
+eth-1/0/3
+eth-1/0/4
+eth-1/0/5
+eth-1/0/6
+eth-1/0/7
+eth-1/0/8
+eth-1/0/9
+
+R149
+eth-1/0/1
+eth-1/0/10
+eth-1/0/11
+eth-1/0/12
+eth-1/0/13
+eth-1/0/14
+eth-1/0/15
+eth-1/0/16
+eth-1/0/17
+eth-1/0/18
+eth-1/0/19
+eth-1/0/2
+eth-1/0/20
+eth-1/0/21
+eth-1/0/22
+eth-1/0/23
+eth-1/0/24
+eth-1/0/25
+eth-1/0/26
+eth-1/0/27
+eth-1/0/3
+eth-1/0/4
+eth-1/0/5
+eth-1/0/6
+eth-1/0/7
+eth-1/0/8
+eth-1/0/9
diff --git a/src/tests/ofc23/descriptors/backup/dc-2-dc-service.json b/src/tests/ofc23/descriptors/backup/dc-2-dc-service.json
new file mode 100644
index 0000000000000000000000000000000000000000..3a83afa6de81f137204aecc5f0eca476aad71e61
--- /dev/null
+++ b/src/tests/ofc23/descriptors/backup/dc-2-dc-service.json
@@ -0,0 +1,37 @@
+{
+    "services": [
+        {
+            "service_id": {
+                "context_id": {"context_uuid": {"uuid": "admin"}}, "service_uuid": {"uuid": "dc-2-dc-svc"}
+            },
+            "service_type": 2,
+            "service_status": {"service_status": 1},
+            "service_endpoint_ids": [
+                {"device_id":{"device_uuid":{"uuid":"DC1"}},"endpoint_uuid":{"uuid":"int"}},
+                {"device_id":{"device_uuid":{"uuid":"DC2"}},"endpoint_uuid":{"uuid":"int"}}
+            ],
+            "service_constraints": [
+                {"sla_capacity": {"capacity_gbps": 10.0}},
+                {"sla_latency": {"e2e_latency_ms": 15.2}}
+            ],
+            "service_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "/settings", "resource_value": {
+                    "address_families": ["IPV4"], "bgp_as": 65000, "bgp_route_target": "65000:123",
+                    "mtu": 1512, "vlan_id": 111
+                }}},
+                {"action": 1, "custom": {"resource_key": "/device[R149]/endpoint[eth-1/0/22]/settings", "resource_value": {
+                    "route_distinguisher": "65000:123", "router_id": "5.5.5.5",
+                    "address_ip": "172.16.4.1", "address_prefix": 24, "sub_interface_index": 0, "vlan_id": 111
+                }}},
+                {"action": 1, "custom": {"resource_key": "/device[R155]/endpoint[eth-1/0/22]/settings", "resource_value": {
+                    "route_distinguisher": "65000:123", "router_id": "5.5.5.1",
+                    "address_ip": "172.16.2.1", "address_prefix": 24, "sub_interface_index": 0, "vlan_id": 111
+                }}},
+                {"action": 1, "custom": {"resource_key": "/device[R199]/endpoint[eth-1/0/21]/settings", "resource_value": {
+                    "route_distinguisher": "65000:123", "router_id": "5.5.5.6",
+                    "address_ip": "172.16.1.1", "address_prefix": 24, "sub_interface_index": 0, "vlan_id": 111
+                }}}
+            ]}
+        }
+    ]
+}
diff --git a/src/tests/ofc23/descriptors/backup/descriptor_child.json b/src/tests/ofc23/descriptors/backup/descriptor_child.json
new file mode 100644
index 0000000000000000000000000000000000000000..eea9571531cfbebfcc53dba0679d1bd1b6900b2f
--- /dev/null
+++ b/src/tests/ofc23/descriptors/backup/descriptor_child.json
@@ -0,0 +1,183 @@
+{
+    "contexts": [
+        {"context_id": {"context_uuid": {"uuid": "admin"}}}
+    ],
+    "topologies": [
+        {"topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}}
+    ],
+    "devices": [
+        {
+            "device_id": {"device_uuid": {"uuid": "R199"}}, "device_type": "packet-router", "device_drivers": [1],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.95.86.199"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "830"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+                    "username": "admin", "password": "admin",
+                    "force_running": false, "hostkey_verify": false, "look_for_keys": false,
+                    "allow_agent": false, "commit_per_rule": true, "device_params": {"name": "huaweiyang"},
+                    "manager_params": {"timeout" : 120},
+                    "endpoints": [
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/1"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/2"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/3"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/4"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/5"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/6"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/7"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/8"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/9"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/10"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/11"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/12"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/13"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/14"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/15"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/16"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/17"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/18"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/19"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/20"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/21"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/22"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/23"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/24"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/25"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/26"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/27"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/28"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/29"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/30"}
+                    ]
+                }}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "R155"}}, "device_type": "packet-router", "device_drivers": [1],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.95.86.155"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "830"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+                    "username": "admin", "password": "admin",
+                    "force_running": false, "hostkey_verify": false, "look_for_keys": false,
+                    "allow_agent": false, "commit_per_rule": true, "device_params": {"name": "huaweiyang"},
+                    "manager_params": {"timeout" : 120},
+                    "endpoints": [
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/1"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/2"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/3"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/4"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/5"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/6"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/7"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/8"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/9"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/10"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/11"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/12"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/13"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/14"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/15"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/16"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/17"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/18"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/19"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/20"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/21"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/22"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/23"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/24"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/25"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/26"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/27"}
+                    ]
+                }}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "R149"}}, "device_type": "packet-router", "device_drivers": [1],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.95.86.149"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "830"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+                    "username": "admin", "password": "admin",
+                    "force_running": false, "hostkey_verify": false, "look_for_keys": false,
+                    "allow_agent": false, "commit_per_rule": true, "device_params": {"name": "huaweiyang"},
+                    "manager_params": {"timeout" : 120},
+                    "endpoints": [
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/1"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/2"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/3"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/4"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/5"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/6"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/7"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/8"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/9"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/10"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/11"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/12"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/13"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/14"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/15"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/16"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/17"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/18"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/19"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/20"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/21"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/22"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/23"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/24"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/25"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/26"},
+                        {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/27"}
+                    ]
+                }}}
+            ]}
+        }
+    ],
+    "links": [
+        {
+            "link_id": {"link_uuid": {"uuid": "R199/eth-1/0/19==R155/eth-1/0/19"}},
+            "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/19"}},
+                {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/19"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "R155/eth-1/0/19==R199/eth-1/0/19"}},
+            "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/19"}},
+                {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/19"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "R199/eth-1/0/20==R149/eth-1/0/20"}},
+            "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}},
+                {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "R149/eth-1/0/20==R199/eth-1/0/20"}},
+            "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}},
+                {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "R149/eth-1/0/25==R155/eth-1/0/25"}},
+            "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}},
+                {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "R155/eth-1/0/25==R149/eth-1/0/25"}},
+            "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}},
+                {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}}
+            ]
+        }
+    ]
+}
diff --git a/src/tests/ofc23/descriptors/backup/descriptor_parent.json b/src/tests/ofc23/descriptors/backup/descriptor_parent.json
new file mode 100644
index 0000000000000000000000000000000000000000..42b60e3cf09285955fbfbc567d977e60f78956be
--- /dev/null
+++ b/src/tests/ofc23/descriptors/backup/descriptor_parent.json
@@ -0,0 +1,258 @@
+{
+    "contexts": [
+        {"context_id": {"context_uuid": {"uuid": "admin"}}}
+    ],
+    "topologies": [
+        {"topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}}
+    ],
+    "devices": [
+        {
+            "device_id": {"device_uuid": {"uuid": "TFS-IP"}}, "device_type": "teraflowsdn", "device_drivers": [7],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8002"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+                    "scheme": "http", "username": "admin", "password": "admin"
+                }}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "MW"}}, "device_type": "microwave-radio-system", "device_drivers": [4, 5],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8443"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+                    "username": "nms5ux", "password": "nms5ux", "timeout": 120, "scheme": "https",
+                    "node_ids": ["192.168.27.139", "192.168.27.140"]
+                }}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "OLS"}}, "device_type": "open-line-system", "device_drivers": [2],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "cttc-ols.cttc-ols.svc.cluster.local"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "4900"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"timeout": 120}}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "IPM"}}, "device_type": "xr-constellation", "device_drivers": [6],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8444"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+                    "username": "xr-user-1", "password": "xr-user-1", "hub_module_name": "OFC HUB 1",
+                    "consistency-mode": "lifecycle", "import_topology": "devices"
+                }}}
+            ]}
+        },
+
+
+        {
+            "device_id": {"device_uuid": {"uuid": "DC1"}}, "device_type": "emu-datacenter", "device_drivers": [0],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.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/internal", "uuid": "eth1"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "eth2"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "int"}
+                ]}}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "device_type": "emu-optical-splitter", "device_drivers": [0],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
+                    {"sample_types": [], "type": "optical/internal", "uuid": "common"},
+                    {"sample_types": [], "type": "optical/internal", "uuid": "leaf1"},
+                    {"sample_types": [], "type": "optical/internal", "uuid": "leaf2"},
+                    {"sample_types": [], "type": "optical/internal", "uuid": "leaf3"},
+                    {"sample_types": [], "type": "optical/internal", "uuid": "leaf4"}
+                ]}}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "DC2"}}, "device_type": "emu-datacenter", "device_drivers": [0],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.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/internal", "uuid": "eth1"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "eth2"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "int"}
+                ]}}}
+            ]}
+        }
+    ],
+    "links": [
+        {
+            "link_id": {"link_uuid": {"uuid": "DC1/eth1==R149/eth-1/0/22"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}},
+                {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "R149/eth-1/0/22==DC1/eth1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}},
+                {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "R149/eth-1/0/9==MW/192.168.27.140:5"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/9"}},
+                {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.140:5"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "MW/192.168.27.140:5==R149/eth-1/0/9"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.140:5"}},
+                {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/9"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "MW/192.168.27.139:5==OFC HUB 1/1/1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.139:5"}},
+                {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "OFC HUB 1/1/1==MW/192.168.27.139:5"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+                {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.139:5"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "OFC HUB 1/XR-T1==Optical-Splitter/common"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+                {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "common"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "Optical-Splitter/common==OFC HUB 1/XR-T1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "common"}},
+                {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "Optical-Splitter/leaf1==OLS/aade6001-f00b-5e2f-a357-6a0a9d3de870"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf1"}},
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "aade6001-f00b-5e2f-a357-6a0a9d3de870"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "OLS/79516f5e-55a0-5671-977a-1f5cc934e700==Optical-Splitter/leaf1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "79516f5e-55a0-5671-977a-1f5cc934e700"}},
+                {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "Optical-Splitter/leaf2==OLS/eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf2"}},
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "OLS/30d9323e-b916-51ce-a9a8-cf88f62eb77f==Optical-Splitter/leaf2"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "30d9323e-b916-51ce-a9a8-cf88f62eb77f"}},
+                {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf2"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "OFC LEAF 1/XR-T1==OLS/0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "OLS/68ac012e-54d4-5846-b5dc-6ec356404f90==OFC LEAF 1/XR-T1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "68ac012e-54d4-5846-b5dc-6ec356404f90"}},
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "OFC LEAF 2/XR-T1==OLS/50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "OLS/367b19b1-3172-54d8-bdd4-12d3ac5604f6==OFC LEAF 2/XR-T1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "367b19b1-3172-54d8-bdd4-12d3ac5604f6"}},
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "OFC LEAF 1/1/1==R155/eth-1/0/25"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+                {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "R155/eth-1/0/25==OFC LEAF 1/1/1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}},
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "OFC LEAF 2/1/1==R199/eth-1/0/20"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "1/1"}},
+                {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "R199/eth-1/0/20==OFC LEAF 2/1/1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}},
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "1/1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "R155/eth-1/0/22==DC2/eth1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}},
+                {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "DC2/eth1==R155/eth-1/0/22"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}},
+                {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "R199/eth-1/0/21==DC2/eth2"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/21"}},
+                {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth2"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "DC2/eth2==R199/eth-1/0/21"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth2"}},
+                {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/21"}}
+            ]
+        }
+    ]
+}
diff --git a/src/tests/ofc23/descriptors/emulated/dc-2-dc-service.json b/src/tests/ofc23/descriptors/emulated/dc-2-dc-service.json
new file mode 100644
index 0000000000000000000000000000000000000000..7c3be015d0965d4bdaed8e225e79da072a7de6f3
--- /dev/null
+++ b/src/tests/ofc23/descriptors/emulated/dc-2-dc-service.json
@@ -0,0 +1,41 @@
+{
+    "services": [
+        {
+            "service_id": {
+                "context_id": {"context_uuid": {"uuid": "admin"}}, "service_uuid": {"uuid": "dc-2-dc-svc"}
+            },
+            "service_type": 2,
+            "service_status": {"service_status": 1},
+            "service_endpoint_ids": [
+                {"device_id":{"device_uuid":{"uuid":"DC1"}},"endpoint_uuid":{"uuid":"int"}},
+                {"device_id":{"device_uuid":{"uuid":"DC2"}},"endpoint_uuid":{"uuid":"int"}}
+            ],
+            "service_constraints": [
+                {"sla_capacity": {"capacity_gbps": 10.0}},
+                {"sla_latency": {"e2e_latency_ms": 15.2}}
+            ],
+            "service_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "/settings", "resource_value": {
+                    "address_families": ["IPV4"], "bgp_as": 65000, "bgp_route_target": "65000:123",
+                    "mtu": 1512, "vlan_id": 300
+                }}},
+                {"action": 1, "custom": {"resource_key": "/device[PE1]/endpoint[1/1]/settings", "resource_value": {
+                    "route_distinguisher": "65000:123", "router_id": "10.0.0.1",
+                    "address_ip": "3.3.1.1", "address_prefix": 24, "sub_interface_index": 1, "vlan_id": 300
+                }}},
+                {"action": 1, "custom": {"resource_key": "/device[PE2]/endpoint[1/1]/settings", "resource_value": {
+                    "route_distinguisher": "65000:123", "router_id": "10.0.0.2",
+                    "address_ip": "3.3.2.1", "address_prefix": 24, "sub_interface_index": 1, "vlan_id": 300
+                }}},
+                {"action": 1, "custom": {"resource_key": "/device[PE3]/endpoint[1/1]/settings", "resource_value": {
+                    "route_distinguisher": "65000:123", "router_id": "10.0.0.3",
+                    "address_ip": "3.3.3.1", "address_prefix": 24, "sub_interface_index": 1, "vlan_id": 300
+                }}},
+                {"action": 1, "custom": {"resource_key": "/device[PE4]/endpoint[1/1]/settings", "resource_value": {
+                    "route_distinguisher": "65000:123", "router_id": "10.0.0.4",
+                    "address_ip": "3.3.4.1", "address_prefix": 24, "sub_interface_index": 1, "vlan_id": 300
+                }}}
+            ]}
+        }
+    ]
+}
diff --git a/src/tests/ofc23/descriptors/emulated/descriptor_child.json b/src/tests/ofc23/descriptors/emulated/descriptor_child.json
new file mode 100644
index 0000000000000000000000000000000000000000..1dc6fd35531db1989b9b85c846b6fc8d0524f08f
--- /dev/null
+++ b/src/tests/ofc23/descriptors/emulated/descriptor_child.json
@@ -0,0 +1,149 @@
+{
+    "contexts": [
+        {"context_id": {"context_uuid": {"uuid": "admin"}}}
+    ],
+    "topologies": [
+        {"topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}}
+    ],
+    "devices": [
+        {
+            "device_id": {"device_uuid": {"uuid": "PE1"}}, "device_type": "emu-packet-router", "device_drivers": [0],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.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/internal", "uuid": "1/1"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "1/2"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "2/1"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "2/2"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "2/3"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "2/4"}
+                ]}}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "PE2"}}, "device_type": "emu-packet-router", "device_drivers": [0],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.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/internal", "uuid": "1/1"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "1/2"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "2/1"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "2/2"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "2/3"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "2/4"}
+                ]}}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "PE3"}}, "device_type": "emu-packet-router", "device_drivers": [0],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.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/internal", "uuid": "1/1"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "1/2"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "2/1"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "2/2"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "2/3"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "2/4"}
+                ]}}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "PE4"}}, "device_type": "emu-packet-router", "device_drivers": [0],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.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/internal", "uuid": "1/1"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "1/2"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "2/1"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "2/2"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "2/3"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "2/4"}
+                ]}}}
+            ]}
+        }
+    ],
+    "links": [
+
+        {
+            "link_id": {"link_uuid": {"uuid": "PE1/2/2==PE2/2/1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "2/2"}},
+                {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "2/1"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "PE1/2/3==PE3/2/1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "2/3"}},
+                {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "2/1"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "PE1/2/4==PE4/2/1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "2/4"}},
+                {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "2/1"}}
+            ]
+        },
+
+        {
+            "link_id": {"link_uuid": {"uuid": "PE2/2/1==PE1/2/2"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "2/1"}},
+                {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "2/2"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "PE2/2/3==PE3/2/2"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "2/3"}},
+                {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "2/2"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "PE2/2/4==PE4/2/2"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "2/4"}},
+                {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "2/2"}}
+            ]
+        },
+
+        {
+            "link_id": {"link_uuid": {"uuid": "PE3/2/1==PE1/2/3"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "2/1"}},
+                {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "2/3"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "PE3/2/2==PE2/2/3"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "2/2"}},
+                {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "2/3"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "PE4/2/2==PE2/2/4"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "2/2"}},
+                {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "2/4"}}
+            ]
+        },
+
+        {
+            "link_id": {"link_uuid": {"uuid": "PE4/2/1==PE1/2/4"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "2/1"}},
+                {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "2/4"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "PE4/2/2==PE2/2/4"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "2/2"}},
+                {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "2/4"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "PE4/2/3==PE3/2/4"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "2/3"}},
+                {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "2/4"}}
+            ]
+        }
+
+    ]
+}
diff --git a/src/tests/ofc23/descriptors/emulated/descriptor_parent.json b/src/tests/ofc23/descriptors/emulated/descriptor_parent.json
new file mode 100644
index 0000000000000000000000000000000000000000..1b1f5dbfd57b2e1543e86ba8d2633a0e944fced5
--- /dev/null
+++ b/src/tests/ofc23/descriptors/emulated/descriptor_parent.json
@@ -0,0 +1,258 @@
+{
+    "contexts": [
+        {"context_id": {"context_uuid": {"uuid": "admin"}}}
+    ],
+    "topologies": [
+        {"topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}}
+    ],
+    "devices": [
+        {
+            "device_id": {"device_uuid": {"uuid": "TFS-IP"}}, "device_type": "teraflowsdn", "device_drivers": [7],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8002"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+                    "scheme": "http", "username": "admin", "password": "admin"
+                }}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "MW"}}, "device_type": "microwave-radio-system", "device_drivers": [4, 5],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8443"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+                    "username": "admin", "password": "admin", "timeout": 120, "scheme": "https",
+                    "node_ids": ["192.168.27.139", "192.168.27.140"]
+                }}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "OLS"}}, "device_type": "open-line-system", "device_drivers": [2],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "cttc-ols.cttc-ols.svc.cluster.local"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "4900"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"timeout": 120}}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "IPM"}}, "device_type": "xr-constellation", "device_drivers": [6],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8444"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+                    "username": "xr-user-1", "password": "xr-user-1", "hub_module_name": "OFC HUB 1",
+                    "consistency-mode": "lifecycle", "import_topology": "devices"
+                }}}
+            ]}
+        },
+
+
+        {
+            "device_id": {"device_uuid": {"uuid": "DC1"}}, "device_type": "emu-datacenter", "device_drivers": [0],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.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/internal", "uuid": "eth1"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "eth2"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "int"}
+                ]}}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "device_type": "emu-optical-splitter", "device_drivers": [0],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
+                    {"sample_types": [], "type": "optical/internal", "uuid": "common"},
+                    {"sample_types": [], "type": "optical/internal", "uuid": "leaf1"},
+                    {"sample_types": [], "type": "optical/internal", "uuid": "leaf2"},
+                    {"sample_types": [], "type": "optical/internal", "uuid": "leaf3"},
+                    {"sample_types": [], "type": "optical/internal", "uuid": "leaf4"}
+                ]}}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "DC2"}}, "device_type": "emu-datacenter", "device_drivers": [0],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.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/internal", "uuid": "eth1"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "eth2"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "int"}
+                ]}}}
+            ]}
+        }
+    ],
+    "links": [
+        {
+            "link_id": {"link_uuid": {"uuid": "DC1/eth1==R149/eth-1/0/22"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}},
+                {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "R149/eth-1/0/22==DC1/eth1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}},
+                {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "R149/eth-1/0/9==MW/192.168.27.140:5"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/9"}},
+                {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.140:5"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "MW/192.168.27.140:5==R149/eth-1/0/9"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.140:5"}},
+                {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/9"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "MW/192.168.27.139:5==OFC HUB 1/1/1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.139:5"}},
+                {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "OFC HUB 1/1/1==MW/192.168.27.139:5"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+                {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.139:5"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "OFC HUB 1/XR-T1==Optical-Splitter/common"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+                {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "common"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "Optical-Splitter/common==OFC HUB 1/XR-T1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "common"}},
+                {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "Optical-Splitter/leaf1==OLS/aade6001-f00b-5e2f-a357-6a0a9d3de870"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf1"}},
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "aade6001-f00b-5e2f-a357-6a0a9d3de870"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "OLS/79516f5e-55a0-5671-977a-1f5cc934e700==Optical-Splitter/leaf1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "79516f5e-55a0-5671-977a-1f5cc934e700"}},
+                {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "Optical-Splitter/leaf2==OLS/eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf2"}},
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "OLS/30d9323e-b916-51ce-a9a8-cf88f62eb77f==Optical-Splitter/leaf2"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "30d9323e-b916-51ce-a9a8-cf88f62eb77f"}},
+                {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf2"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "OFC LEAF 1/XR-T1==OLS/0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "OLS/68ac012e-54d4-5846-b5dc-6ec356404f90==OFC LEAF 1/XR-T1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "68ac012e-54d4-5846-b5dc-6ec356404f90"}},
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "OFC LEAF 2/XR-T1==OLS/50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "OLS/367b19b1-3172-54d8-bdd4-12d3ac5604f6==OFC LEAF 2/XR-T1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "367b19b1-3172-54d8-bdd4-12d3ac5604f6"}},
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "OFC LEAF 1/1/1==R155/eth-1/0/25"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+                {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "R155/eth-1/0/25==OFC LEAF 1/1/1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}},
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "OFC LEAF 2/1/1==R199/eth-1/0/20"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "1/1"}},
+                {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "R199/eth-1/0/20==OFC LEAF 2/1/1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}},
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "1/1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "R155/eth-1/0/22==DC2/eth1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}},
+                {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "DC2/eth1==R155/eth-1/0/22"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}},
+                {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "R199/eth-1/0/21==DC2/eth2"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/21"}},
+                {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth2"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "DC2/eth2==R199/eth-1/0/21"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth2"}},
+                {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/21"}}
+            ]
+        }
+    ]
+}
diff --git a/src/tests/ofc23/descriptors/emulated/descriptor_parent_noxr.json b/src/tests/ofc23/descriptors/emulated/descriptor_parent_noxr.json
new file mode 100644
index 0000000000000000000000000000000000000000..c4a6646ede081fc2f6ee449d7771de3dbcbd77ec
--- /dev/null
+++ b/src/tests/ofc23/descriptors/emulated/descriptor_parent_noxr.json
@@ -0,0 +1,332 @@
+{
+    "contexts": [
+        {"context_id": {"context_uuid": {"uuid": "admin"}}}
+    ],
+    "topologies": [
+        {"topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}}
+    ],
+    "devices": [
+        {
+            "device_id": {"device_uuid": {"uuid": "TFS-IP"}}, "device_type": "teraflowsdn", "device_drivers": [7],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8002"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+                    "scheme": "http", "username": "admin", "password": "admin"
+                }}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "MW1-2"}}, "device_type": "microwave-radio-system", "device_drivers": [5],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8443"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+                    "username": "admin", "password": "admin", "timeout": 120, "scheme": "https",
+                    "node_ids": ["172.18.0.1", "172.18.0.2"]
+                }}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "MW3-4"}}, "device_type": "microwave-radio-system", "device_drivers": [5],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8443"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+                    "username": "admin", "password": "admin", "timeout": 120, "scheme": "https",
+                    "node_ids": ["172.18.0.3", "172.18.0.4"]
+                }}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "OLS"}}, "device_type": "open-line-system", "device_drivers": [2],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "cttc-ols.cttc-ols.svc.cluster.local"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "4900"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"timeout": 120}}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "DC1"}}, "device_type": "emu-datacenter", "device_drivers": [0],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.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/internal", "uuid": "eth1"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "eth2"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "int"}
+                ]}}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "R1"}}, "device_type": "emu-packet-router", "device_drivers": [0],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.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/internal", "uuid": "1/1"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "1/2"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "1/3"}
+                ]}}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "device_type": "emu-optical-splitter", "device_drivers": [0],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
+                    {"sample_types": [], "type": "optical/internal", "uuid": "common"},
+                    {"sample_types": [], "type": "optical/internal", "uuid": "leaf1"},
+                    {"sample_types": [], "type": "optical/internal", "uuid": "leaf2"},
+                    {"sample_types": [], "type": "optical/internal", "uuid": "leaf3"},
+                    {"sample_types": [], "type": "optical/internal", "uuid": "leaf4"}
+                ]}}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "R2"}}, "device_type": "emu-packet-router", "device_drivers": [0],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.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/internal", "uuid": "1/1"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "1/2"}
+                ]}}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "R3"}}, "device_type": "emu-packet-router", "device_drivers": [0],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.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/internal", "uuid": "1/1"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "1/2"}
+                ]}}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "DC2"}}, "device_type": "emu-datacenter", "device_drivers": [0],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.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/internal", "uuid": "eth1"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "eth2"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "int"}
+                ]}}}
+            ]}
+        }
+    ],
+    "links": [
+        {
+            "link_id": {"link_uuid": {"uuid": "DC1/eth1==PE1/1/1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}},
+                {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "PE1/1/1==DC1/eth1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+                {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "DC1/eth2==PE2/1/1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth2"}},
+                {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "1/1"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "PE2/1/1==DC1/eth2"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "1/1"}},
+                {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth2"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "PE1/1/2==MW1-2/172.18.0.1:1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "1/2"}},
+                {"device_id": {"device_uuid": {"uuid": "MW1-2"}}, "endpoint_uuid": {"uuid": "172.18.0.1:1"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "MW1-2/172.18.0.1:1==PE1/1/2"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "MW1-2"}}, "endpoint_uuid": {"uuid": "172.18.0.1:1"}},
+                {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "1/2"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "MW1-2/172.18.0.2:1==R1/1/1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "MW1-2"}}, "endpoint_uuid": {"uuid": "172.18.0.2:1"}},
+                {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "R1/1/1==MW1-2/172.18.0.2:1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+                {"device_id": {"device_uuid": {"uuid": "MW1-2"}}, "endpoint_uuid": {"uuid": "172.18.0.2:1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "PE2/1/2==MW3-4/172.18.0.3:1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "1/2"}},
+                {"device_id": {"device_uuid": {"uuid": "MW3-4"}}, "endpoint_uuid": {"uuid": "172.18.0.3:1"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "MW3-4/172.18.0.3:1==PE2/1/2"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "MW3-4"}}, "endpoint_uuid": {"uuid": "172.18.0.3:1"}},
+                {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "1/2"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "MW3-4/172.18.0.4:1==R1/1/2"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "MW3-4"}}, "endpoint_uuid": {"uuid": "172.18.0.4:1"}},
+                {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "1/2"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "R1/1/2==MW3-4/172.18.0.4:1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "1/2"}},
+                {"device_id": {"device_uuid": {"uuid": "MW3-4"}}, "endpoint_uuid": {"uuid": "172.18.0.4:1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "R1/1/3==Optical-Splitter/common"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "1/3"}},
+                {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "common"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "Optical-Splitter/common==R1/1/3"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "common"}},
+                {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "1/3"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "Optical-Splitter/leaf1==OLS/aade6001-f00b-5e2f-a357-6a0a9d3de870"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf1"}},
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "aade6001-f00b-5e2f-a357-6a0a9d3de870"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "OLS/79516f5e-55a0-5671-977a-1f5cc934e700==Optical-Splitter/leaf1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "79516f5e-55a0-5671-977a-1f5cc934e700"}},
+                {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "Optical-Splitter/leaf2==OLS/eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf2"}},
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "OLS/30d9323e-b916-51ce-a9a8-cf88f62eb77f==Optical-Splitter/leaf2"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "30d9323e-b916-51ce-a9a8-cf88f62eb77f"}},
+                {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf2"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "R2/1/1==OLS/0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "1/1"}},
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "OLS/68ac012e-54d4-5846-b5dc-6ec356404f90==R2/1/1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "68ac012e-54d4-5846-b5dc-6ec356404f90"}},
+                {"device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "1/1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "R3/1/1==OLS/50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "1/1"}},
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "OLS/367b19b1-3172-54d8-bdd4-12d3ac5604f6==R3/1/1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "367b19b1-3172-54d8-bdd4-12d3ac5604f6"}},
+                {"device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "1/1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "R2/1/2==PE3/1/2"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "1/2"}},
+                {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "1/2"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "PE3/1/2==R2/1/2"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "1/2"}},
+                {"device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "1/2"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "R3/1/2==PE4/1/2"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "1/2"}},
+                {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "1/2"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "PE4/1/2==R3/1/1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "1/2"}},
+                {"device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "1/1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "PE3/1/1==DC2/eth1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "1/1"}},
+                {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "DC2/eth1==PE3/1/1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}},
+                {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "1/1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "PE4/1/1==DC2/eth2"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "1/1"}},
+                {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth2"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "DC2/eth2==PE4/1/1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth2"}},
+                {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "1/1"}}
+            ]
+        }
+    ]
+}
diff --git a/src/tests/ofc23/descriptors/emulated/ipm-ctrl.json b/src/tests/ofc23/descriptors/emulated/ipm-ctrl.json
new file mode 100644
index 0000000000000000000000000000000000000000..91e9de611dac2627525bb11f81755ea651887e74
--- /dev/null
+++ b/src/tests/ofc23/descriptors/emulated/ipm-ctrl.json
@@ -0,0 +1,25 @@
+{
+    "contexts": [
+        {"context_id": {"context_uuid": {"uuid": "admin"}}}
+    ],
+    "topologies": [
+        {"topology_id": {"topology_uuid": {"uuid": "admin"}, "context_id": {"context_uuid": {"uuid": "admin"}}}}
+    ],
+    "devices": [
+        {
+            "device_id": {"device_uuid": {"uuid": "XR-CONSTELLATION"}},
+            "device_type": "xr-constellation",
+            "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8444"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+                    "username": "xr-user-1", "password": "xr-user-1", "hub_module_name": "OFC HUB 1",
+                    "consistency-mode": "lifecycle"
+                }}}
+            ]},
+            "device_operational_status": 1,
+            "device_drivers": [6],
+            "device_endpoints": []
+        }
+    ]
+}
diff --git a/src/tests/ofc23/descriptors/emulated/old/descriptor_parent.json b/src/tests/ofc23/descriptors/emulated/old/descriptor_parent.json
new file mode 100644
index 0000000000000000000000000000000000000000..413b7566292d7841777547aeb665c7eb3b8ca293
--- /dev/null
+++ b/src/tests/ofc23/descriptors/emulated/old/descriptor_parent.json
@@ -0,0 +1,311 @@
+{
+    "contexts": [
+        {"context_id": {"context_uuid": {"uuid": "admin"}}}
+    ],
+    "topologies": [
+        {"topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}}
+    ],
+    "devices": [
+        {
+            "device_id": {"device_uuid": {"uuid": "TFS-IP"}}, "device_type": "teraflowsdn", "device_drivers": [7],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8002"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+                    "scheme": "http", "username": "admin", "password": "admin"
+                }}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "MW1-2"}}, "device_type": "microwave-radio-system", "device_drivers": [5],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8443"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+                    "username": "admin", "password": "admin", "timeout": 120, "scheme": "https",
+                    "node_ids": ["172.18.0.1", "172.18.0.2"]
+                }}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "MW3-4"}}, "device_type": "microwave-radio-system", "device_drivers": [5],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8443"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+                    "username": "admin", "password": "admin", "timeout": 120, "scheme": "https",
+                    "node_ids": ["172.18.0.3", "172.18.0.4"]
+                }}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "OLS"}}, "device_type": "open-line-system", "device_drivers": [2],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "cttc-ols.cttc-ols.svc.cluster.local"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "4900"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"timeout": 120}}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "IPM"}}, "device_type": "xr-constellation", "device_drivers": [6],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8444"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+                    "username": "xr-user-1", "password": "xr-user-1", "hub_module_name": "OFC HUB 1",
+                    "consistency-mode": "lifecycle", "import_topology": "devices"
+                }}}
+            ]}
+        },
+
+
+        {
+            "device_id": {"device_uuid": {"uuid": "DC1"}}, "device_type": "emu-datacenter", "device_drivers": [0],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.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/internal", "uuid": "eth1"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "eth2"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "int"}
+                ]}}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "device_type": "emu-optical-splitter", "device_drivers": [0],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
+                    {"sample_types": [], "type": "optical/internal", "uuid": "common"},
+                    {"sample_types": [], "type": "optical/internal", "uuid": "leaf1"},
+                    {"sample_types": [], "type": "optical/internal", "uuid": "leaf2"},
+                    {"sample_types": [], "type": "optical/internal", "uuid": "leaf3"},
+                    {"sample_types": [], "type": "optical/internal", "uuid": "leaf4"}
+                ]}}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "DC2"}}, "device_type": "emu-datacenter", "device_drivers": [0],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.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/internal", "uuid": "eth1"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "eth2"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "int"}
+                ]}}}
+            ]}
+        }
+    ],
+    "links": [
+        {
+            "link_id": {"link_uuid": {"uuid": "DC1/eth1==PE1/1/1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}},
+                {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "PE1/1/1==DC1/eth1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+                {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "DC1/eth2==PE2/1/1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth2"}},
+                {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "1/1"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "PE2/1/1==DC1/eth2"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "1/1"}},
+                {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth2"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "PE1/1/2==MW1-2/172.18.0.1:1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "1/2"}},
+                {"device_id": {"device_uuid": {"uuid": "MW1-2"}}, "endpoint_uuid": {"uuid": "172.18.0.1:1"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "MW1-2/172.18.0.1:1==PE1/1/2"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "MW1-2"}}, "endpoint_uuid": {"uuid": "172.18.0.1:1"}},
+                {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "1/2"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "MW1-2/172.18.0.2:1==OFC HUB 1/1/1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "MW1-2"}}, "endpoint_uuid": {"uuid": "172.18.0.2:1"}},
+                {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "OFC HUB 1/1/1==MW1-2/172.18.0.2:1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+                {"device_id": {"device_uuid": {"uuid": "MW1-2"}}, "endpoint_uuid": {"uuid": "172.18.0.2:1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "PE2/1/2==MW3-4/172.18.0.3:1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "1/2"}},
+                {"device_id": {"device_uuid": {"uuid": "MW3-4"}}, "endpoint_uuid": {"uuid": "172.18.0.3:1"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "MW3-4/172.18.0.3:1==PE2/1/2"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "MW3-4"}}, "endpoint_uuid": {"uuid": "172.18.0.3:1"}},
+                {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "1/2"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "MW3-4/172.18.0.4:1==OFC HUB 1/1/2"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "MW3-4"}}, "endpoint_uuid": {"uuid": "172.18.0.4:1"}},
+                {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "1/2"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "OFC HUB 1/1/2==MW3-4/172.18.0.4:1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "1/2"}},
+                {"device_id": {"device_uuid": {"uuid": "MW3-4"}}, "endpoint_uuid": {"uuid": "172.18.0.4:1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "OFC HUB 1/XR-T1==Optical-Splitter/common"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+                {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "common"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "Optical-Splitter/common==OFC HUB 1/XR-T1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "common"}},
+                {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "Optical-Splitter/leaf1==OLS/aade6001-f00b-5e2f-a357-6a0a9d3de870"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf1"}},
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "aade6001-f00b-5e2f-a357-6a0a9d3de870"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "OLS/79516f5e-55a0-5671-977a-1f5cc934e700==Optical-Splitter/leaf1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "79516f5e-55a0-5671-977a-1f5cc934e700"}},
+                {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "Optical-Splitter/leaf2==OLS/eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf2"}},
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "OLS/30d9323e-b916-51ce-a9a8-cf88f62eb77f==Optical-Splitter/leaf2"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "30d9323e-b916-51ce-a9a8-cf88f62eb77f"}},
+                {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf2"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "OFC LEAF 1/XR-T1==OLS/0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "OLS/68ac012e-54d4-5846-b5dc-6ec356404f90==OFC LEAF 1/XR-T1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "68ac012e-54d4-5846-b5dc-6ec356404f90"}},
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "OFC LEAF 2/XR-T1==OLS/50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "OLS/367b19b1-3172-54d8-bdd4-12d3ac5604f6==OFC LEAF 2/XR-T1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "367b19b1-3172-54d8-bdd4-12d3ac5604f6"}},
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "OFC LEAF 1/1/1==PE3/1/2"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+                {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "1/2"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "PE3/1/2==OFC LEAF 1/1/1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "1/2"}},
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "OFC LEAF 2/1/1==PE4/1/2"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "1/1"}},
+                {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "1/2"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "PE4/1/2==OFC LEAF 2/1/1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "1/2"}},
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "1/1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "PE3/1/1==DC2/eth1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "1/1"}},
+                {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "DC2/eth1==PE3/1/1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}},
+                {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "1/1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "PE4/1/1==DC2/eth2"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "1/1"}},
+                {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth2"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "DC2/eth2==PE4/1/1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth2"}},
+                {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "1/1"}}
+            ]
+        }
+    ]
+}
diff --git a/src/tests/ofc23/descriptors/real/dc-2-dc-service.json b/src/tests/ofc23/descriptors/real/dc-2-dc-service.json
new file mode 100644
index 0000000000000000000000000000000000000000..3a83afa6de81f137204aecc5f0eca476aad71e61
--- /dev/null
+++ b/src/tests/ofc23/descriptors/real/dc-2-dc-service.json
@@ -0,0 +1,37 @@
+{
+    "services": [
+        {
+            "service_id": {
+                "context_id": {"context_uuid": {"uuid": "admin"}}, "service_uuid": {"uuid": "dc-2-dc-svc"}
+            },
+            "service_type": 2,
+            "service_status": {"service_status": 1},
+            "service_endpoint_ids": [
+                {"device_id":{"device_uuid":{"uuid":"DC1"}},"endpoint_uuid":{"uuid":"int"}},
+                {"device_id":{"device_uuid":{"uuid":"DC2"}},"endpoint_uuid":{"uuid":"int"}}
+            ],
+            "service_constraints": [
+                {"sla_capacity": {"capacity_gbps": 10.0}},
+                {"sla_latency": {"e2e_latency_ms": 15.2}}
+            ],
+            "service_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "/settings", "resource_value": {
+                    "address_families": ["IPV4"], "bgp_as": 65000, "bgp_route_target": "65000:123",
+                    "mtu": 1512, "vlan_id": 111
+                }}},
+                {"action": 1, "custom": {"resource_key": "/device[R149]/endpoint[eth-1/0/22]/settings", "resource_value": {
+                    "route_distinguisher": "65000:123", "router_id": "5.5.5.5",
+                    "address_ip": "172.16.4.1", "address_prefix": 24, "sub_interface_index": 0, "vlan_id": 111
+                }}},
+                {"action": 1, "custom": {"resource_key": "/device[R155]/endpoint[eth-1/0/22]/settings", "resource_value": {
+                    "route_distinguisher": "65000:123", "router_id": "5.5.5.1",
+                    "address_ip": "172.16.2.1", "address_prefix": 24, "sub_interface_index": 0, "vlan_id": 111
+                }}},
+                {"action": 1, "custom": {"resource_key": "/device[R199]/endpoint[eth-1/0/21]/settings", "resource_value": {
+                    "route_distinguisher": "65000:123", "router_id": "5.5.5.6",
+                    "address_ip": "172.16.1.1", "address_prefix": 24, "sub_interface_index": 0, "vlan_id": 111
+                }}}
+            ]}
+        }
+    ]
+}
diff --git a/src/tests/ofc23/descriptors/real/descriptor_child.json b/src/tests/ofc23/descriptors/real/descriptor_child.json
new file mode 100644
index 0000000000000000000000000000000000000000..8d695cfd2d419f263b554d0f2bf648b92cdde672
--- /dev/null
+++ b/src/tests/ofc23/descriptors/real/descriptor_child.json
@@ -0,0 +1,93 @@
+{
+    "contexts": [
+        {"context_id": {"context_uuid": {"uuid": "admin"}}}
+    ],
+    "topologies": [
+        {"topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}}
+    ],
+    "devices": [
+        {
+            "device_id": {"device_uuid": {"uuid": "R199"}}, "device_type": "packet-router", "device_drivers": [1],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.95.86.199"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "830"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+                    "username": "admin", "password": "admin",
+                    "force_running": false, "hostkey_verify": false, "look_for_keys": false,
+                    "allow_agent": false, "commit_per_rule": true, "device_params": {"name": "huaweiyang"},
+                    "manager_params": {"timeout" : 86400}
+                }}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "R149"}}, "device_type": "packet-router", "device_drivers": [1],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.95.86.149"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "830"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+                    "username": "admin", "password": "admin",
+                    "force_running": false, "hostkey_verify": false, "look_for_keys": false,
+                    "allow_agent": false, "commit_per_rule": true, "device_params": {"name": "huaweiyang"},
+                    "manager_params": {"timeout" : 86400}
+                }}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "R155"}}, "device_type": "packet-router", "device_drivers": [1],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.95.86.155"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "830"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+                    "username": "admin", "password": "admin",
+                    "force_running": false, "hostkey_verify": false, "look_for_keys": false,
+                    "allow_agent": false, "commit_per_rule": true, "device_params": {"name": "huaweiyang"},
+                    "manager_params": {"timeout" : 86400}
+                }}}
+            ]}
+        }
+    ],
+    "links": [
+        {
+            "link_id": {"link_uuid": {"uuid": "R199/eth-1/0/19==R155/eth-1/0/19"}},
+            "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/19"}},
+                {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/19"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "R155/eth-1/0/19==R199/eth-1/0/19"}},
+            "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/19"}},
+                {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/19"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "R199/eth-1/0/20==R149/eth-1/0/20"}},
+            "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}},
+                {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "R149/eth-1/0/20==R199/eth-1/0/20"}},
+            "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}},
+                {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "R149/eth-1/0/25==R155/eth-1/0/25"}},
+            "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}},
+                {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "R155/eth-1/0/25==R149/eth-1/0/25"}},
+            "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}},
+                {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}}
+            ]
+        }
+    ]
+}
diff --git a/src/tests/ofc23/descriptors/real/descriptor_parent.json b/src/tests/ofc23/descriptors/real/descriptor_parent.json
new file mode 100644
index 0000000000000000000000000000000000000000..3317d46edaf6d270125d8b094c3b6384a3dd52fd
--- /dev/null
+++ b/src/tests/ofc23/descriptors/real/descriptor_parent.json
@@ -0,0 +1,258 @@
+{
+    "contexts": [
+        {"context_id": {"context_uuid": {"uuid": "admin"}}}
+    ],
+    "topologies": [
+        {"topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}}
+    ],
+    "devices": [
+        {
+            "device_id": {"device_uuid": {"uuid": "TFS-IP"}}, "device_type": "teraflowsdn", "device_drivers": [7],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8002"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+                    "scheme": "http", "username": "admin", "password": "admin"
+                }}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "MW"}}, "device_type": "microwave-radio-system", "device_drivers": [4, 5],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "192.168.27.136"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8443"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+                    "username": "nms5ux", "password": "nms5ux", "timeout": 120, "scheme": "https",
+                    "node_ids": ["192.168.27.139", "192.168.27.140"]
+                }}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "OLS"}}, "device_type": "open-line-system", "device_drivers": [2],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "cttc-ols.cttc-ols.svc.cluster.local"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "4900"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"timeout": 120}}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "IPM"}}, "device_type": "xr-constellation", "device_drivers": [6],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.95.86.126"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "443"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+                    "username": "xr-user-1", "password": "xr-user-1", "hub_module_name": "OFC HUB 1",
+                    "consistency-mode": "lifecycle", "import_topology": "devices"
+                }}}
+            ]}
+        },
+
+
+        {
+            "device_id": {"device_uuid": {"uuid": "DC1"}}, "device_type": "emu-datacenter", "device_drivers": [0],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.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/internal", "uuid": "eth1"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "eth2"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "int"}
+                ]}}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "device_type": "emu-optical-splitter", "device_drivers": [0],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
+                {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
+                {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
+                    {"sample_types": [], "type": "optical/internal", "uuid": "common"},
+                    {"sample_types": [], "type": "optical/internal", "uuid": "leaf1"},
+                    {"sample_types": [], "type": "optical/internal", "uuid": "leaf2"},
+                    {"sample_types": [], "type": "optical/internal", "uuid": "leaf3"},
+                    {"sample_types": [], "type": "optical/internal", "uuid": "leaf4"}
+                ]}}}
+            ]}
+        },
+        {
+            "device_id": {"device_uuid": {"uuid": "DC2"}}, "device_type": "emu-datacenter", "device_drivers": [0],
+            "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+                {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.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/internal", "uuid": "eth1"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "eth2"},
+                    {"sample_types": [], "type": "copper/internal", "uuid": "int"}
+                ]}}}
+            ]}
+        }
+    ],
+    "links": [
+        {
+            "link_id": {"link_uuid": {"uuid": "DC1/eth1==R149/eth-1/0/22"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}},
+                {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "R149/eth-1/0/22==DC1/eth1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}},
+                {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "R149/eth-1/0/9==MW/192.168.27.140:5"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/9"}},
+                {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.140:5"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "MW/192.168.27.140:5==R149/eth-1/0/9"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.140:5"}},
+                {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/9"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "MW/192.168.27.139:5==OFC HUB 1/1/1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.139:5"}},
+                {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "OFC HUB 1/1/1==MW/192.168.27.139:5"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+                {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.139:5"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "OFC HUB 1/XR-T1==Optical-Splitter/common"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+                {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "common"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "Optical-Splitter/common==OFC HUB 1/XR-T1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "common"}},
+                {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "Optical-Splitter/leaf1==OLS/aade6001-f00b-5e2f-a357-6a0a9d3de870"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf1"}},
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "aade6001-f00b-5e2f-a357-6a0a9d3de870"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "OLS/79516f5e-55a0-5671-977a-1f5cc934e700==Optical-Splitter/leaf1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "79516f5e-55a0-5671-977a-1f5cc934e700"}},
+                {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "Optical-Splitter/leaf2==OLS/eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf2"}},
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "OLS/30d9323e-b916-51ce-a9a8-cf88f62eb77f==Optical-Splitter/leaf2"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "30d9323e-b916-51ce-a9a8-cf88f62eb77f"}},
+                {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf2"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "OFC LEAF 1/XR-T1==OLS/0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "OLS/68ac012e-54d4-5846-b5dc-6ec356404f90==OFC LEAF 1/XR-T1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "68ac012e-54d4-5846-b5dc-6ec356404f90"}},
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "OFC LEAF 2/XR-T1==OLS/50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "OLS/367b19b1-3172-54d8-bdd4-12d3ac5604f6==OFC LEAF 2/XR-T1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "367b19b1-3172-54d8-bdd4-12d3ac5604f6"}},
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "OFC LEAF 1/1/1==R155/eth-1/0/25"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+                {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "R155/eth-1/0/25==OFC LEAF 1/1/1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}},
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "OFC LEAF 2/1/1==R199/eth-1/0/20"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "1/1"}},
+                {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "R199/eth-1/0/20==OFC LEAF 2/1/1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}},
+                {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "1/1"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "R155/eth-1/0/22==DC2/eth1"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}},
+                {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "DC2/eth1==R155/eth-1/0/22"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}},
+                {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}}
+            ]
+        },
+
+
+        {
+            "link_id": {"link_uuid": {"uuid": "R199/eth-1/0/21==DC2/eth2"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/21"}},
+                {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth2"}}
+            ]
+        },
+        {
+            "link_id": {"link_uuid": {"uuid": "DC2/eth2==R199/eth-1/0/21"}}, "link_endpoint_ids": [
+                {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth2"}},
+                {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/21"}}
+            ]
+        }
+    ]
+}
diff --git a/src/tests/ofc23/dump_logs.sh b/src/tests/ofc23/dump_logs.sh
new file mode 100755
index 0000000000000000000000000000000000000000..cc3162b337c1b35e9ff158b400a8d5c47931bdca
--- /dev/null
+++ b/src/tests/ofc23/dump_logs.sh
@@ -0,0 +1,39 @@
+#!/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.
+
+
+rm -rf tmp/exec
+
+echo "Collecting logs for Parent..."
+mkdir -p tmp/exec/parent
+kubectl --namespace tfs-parent logs deployments/contextservice server > tmp/exec/parent/context.log
+kubectl --namespace tfs-parent logs deployments/deviceservice server > tmp/exec/parent/device.log
+kubectl --namespace tfs-parent logs deployments/serviceservice server > tmp/exec/parent/service.log
+kubectl --namespace tfs-parent logs deployments/pathcompservice frontend > tmp/exec/parent/pathcomp-frontend.log
+kubectl --namespace tfs-parent logs deployments/pathcompservice backend > tmp/exec/parent/pathcomp-backend.log
+kubectl --namespace tfs-parent logs deployments/sliceservice server > tmp/exec/parent/slice.log
+printf "\n"
+
+echo "Collecting logs for Child..."
+mkdir -p tmp/exec/child
+kubectl --namespace tfs-child logs deployments/contextservice server > tmp/exec/child/context.log
+kubectl --namespace tfs-child logs deployments/deviceservice server > tmp/exec/child/device.log
+kubectl --namespace tfs-child logs deployments/serviceservice server > tmp/exec/child/service.log
+kubectl --namespace tfs-child logs deployments/pathcompservice frontend > tmp/exec/child/pathcomp-frontend.log
+kubectl --namespace tfs-child logs deployments/pathcompservice backend > tmp/exec/child/pathcomp-backend.log
+kubectl --namespace tfs-child logs deployments/sliceservice server > tmp/exec/child/slice.log
+printf "\n"
+
+echo "Done!"
diff --git a/src/tests/ofc23/fast_redeploy.sh b/src/tests/ofc23/fast_redeploy.sh
new file mode 100755
index 0000000000000000000000000000000000000000..58d1193ded582d4fdff3222d5bcdc0fe510a7034
--- /dev/null
+++ b/src/tests/ofc23/fast_redeploy.sh
@@ -0,0 +1,63 @@
+#!/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.
+
+
+kubectl delete namespace tfs-parent tfs-child
+
+echo "Deploying tfs-parent ..."
+kubectl delete -f ofc23/nginx-ingress-controller-parent.yaml                 > ./tmp/logs/deploy-tfs-parent.log
+kubectl create namespace tfs-parent                                          > ./tmp/logs/deploy-tfs-parent.log
+kubectl apply -f ofc23/nginx-ingress-controller-parent.yaml                  > ./tmp/logs/deploy-tfs-parent.log
+kubectl --namespace tfs-parent apply -f ./tmp/manifests/contextservice.yaml  > ./tmp/logs/deploy-tfs-parent.log
+kubectl --namespace tfs-parent apply -f ./tmp/manifests/deviceservice.yaml   > ./tmp/logs/deploy-tfs-parent.log
+kubectl --namespace tfs-parent apply -f ./tmp/manifests/pathcompservice.yaml > ./tmp/logs/deploy-tfs-parent.log
+kubectl --namespace tfs-parent apply -f ./tmp/manifests/serviceservice.yaml  > ./tmp/logs/deploy-tfs-parent.log
+kubectl --namespace tfs-parent apply -f ./tmp/manifests/sliceservice.yaml    > ./tmp/logs/deploy-tfs-parent.log
+kubectl --namespace tfs-parent apply -f ./tmp/manifests/webuiservice.yaml    > ./tmp/logs/deploy-tfs-parent.log
+kubectl --namespace tfs-parent apply -f ofc23/tfs-ingress-parent.yaml        > ./tmp/logs/deploy-tfs-parent.log
+printf "\n"
+
+echo "Deploying tfs-child ..."
+kubectl delete -f ofc23/nginx-ingress-controller-child.yaml                  > ./tmp/logs/deploy-tfs-child.log
+kubectl create namespace tfs-child                                           > ./tmp/logs/deploy-tfs-child.log
+kubectl apply -f ofc23/nginx-ingress-controller-child.yaml                   > ./tmp/logs/deploy-tfs-child.log
+kubectl --namespace tfs-child apply -f ./tmp/manifests/contextservice.yaml   > ./tmp/logs/deploy-tfs-child.log
+kubectl --namespace tfs-child apply -f ./tmp/manifests/deviceservice.yaml    > ./tmp/logs/deploy-tfs-child.log
+kubectl --namespace tfs-child apply -f ./tmp/manifests/pathcompservice.yaml  > ./tmp/logs/deploy-tfs-child.log
+kubectl --namespace tfs-child apply -f ./tmp/manifests/serviceservice.yaml   > ./tmp/logs/deploy-tfs-child.log
+kubectl --namespace tfs-child apply -f ./tmp/manifests/sliceservice.yaml     > ./tmp/logs/deploy-tfs-child.log
+kubectl --namespace tfs-child apply -f ./tmp/manifests/webuiservice.yaml     > ./tmp/logs/deploy-tfs-child.log
+kubectl --namespace tfs-child apply -f ofc23/tfs-ingress-child.yaml          > ./tmp/logs/deploy-tfs-child.log
+printf "\n"
+
+echo "Waiting tfs-parent ..."
+kubectl wait --namespace tfs-parent --for='condition=available' --timeout=300s deployment/contextservice
+kubectl wait --namespace tfs-parent --for='condition=available' --timeout=300s deployment/deviceservice
+kubectl wait --namespace tfs-parent --for='condition=available' --timeout=300s deployment/pathcompservice
+kubectl wait --namespace tfs-parent --for='condition=available' --timeout=300s deployment/serviceservice
+kubectl wait --namespace tfs-parent --for='condition=available' --timeout=300s deployment/sliceservice
+kubectl wait --namespace tfs-parent --for='condition=available' --timeout=300s deployment/webuiservice
+printf "\n"
+
+echo "Waiting tfs-child ..."
+kubectl wait --namespace tfs-child --for='condition=available' --timeout=300s deployment/contextservice
+kubectl wait --namespace tfs-child --for='condition=available' --timeout=300s deployment/deviceservice
+kubectl wait --namespace tfs-child --for='condition=available' --timeout=300s deployment/pathcompservice
+kubectl wait --namespace tfs-child --for='condition=available' --timeout=300s deployment/serviceservice
+kubectl wait --namespace tfs-child --for='condition=available' --timeout=300s deployment/sliceservice
+kubectl wait --namespace tfs-child --for='condition=available' --timeout=300s deployment/webuiservice
+printf "\n"
+
+echo "Done!"
diff --git a/src/tests/ofc23/nginx-ingress-controller-child.yaml b/src/tests/ofc23/nginx-ingress-controller-child.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..00a64d75e9a9a2cb93cdbd6d89790e26b0730eb6
--- /dev/null
+++ b/src/tests/ofc23/nginx-ingress-controller-child.yaml
@@ -0,0 +1,134 @@
+# 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: v1
+kind: ConfigMap
+metadata:
+  name: nginx-load-balancer-microk8s-conf-child
+  namespace: ingress
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: nginx-ingress-udp-microk8s-conf-child
+  namespace: ingress
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: nginx-ingress-tcp-microk8s-conf-child
+  namespace: ingress
+---
+apiVersion: networking.k8s.io/v1
+kind: IngressClass
+metadata:
+  name: tfs-ingress-class-child
+  annotations:
+    ingressclass.kubernetes.io/is-default-class: "false"
+spec:
+  controller: tfs.etsi.org/controller-class-child
+---
+apiVersion: apps/v1
+kind: DaemonSet
+metadata:
+  name: nginx-ingress-microk8s-controller-child
+  namespace: ingress
+  labels:
+    microk8s-application: nginx-ingress-microk8s-child
+spec:
+  selector:
+    matchLabels:
+      name: nginx-ingress-microk8s-child
+  updateStrategy:
+    rollingUpdate:
+      maxSurge: 0
+      maxUnavailable: 1
+    type: RollingUpdate
+  template:
+    metadata:
+      labels:
+        name: nginx-ingress-microk8s-child
+    spec:
+      terminationGracePeriodSeconds: 60
+      restartPolicy: Always
+      serviceAccountName: nginx-ingress-microk8s-serviceaccount
+      containers:
+      - image: k8s.gcr.io/ingress-nginx/controller:v1.2.0
+        imagePullPolicy: IfNotPresent
+        name: nginx-ingress-microk8s
+        livenessProbe:
+          httpGet:
+            path: /healthz
+            port: 10254
+            scheme: HTTP
+          initialDelaySeconds: 10
+          periodSeconds: 10
+          successThreshold: 1
+          failureThreshold: 3
+          timeoutSeconds: 5
+        readinessProbe:
+          httpGet:
+            path: /healthz
+            port: 10254
+            scheme: HTTP
+          periodSeconds: 10
+          successThreshold: 1
+          failureThreshold: 3
+          timeoutSeconds: 5
+        lifecycle:
+          preStop:
+            exec:
+              command:
+                - /wait-shutdown
+        securityContext:
+          capabilities:
+            add:
+            - NET_BIND_SERVICE
+            drop:
+            - ALL
+          runAsUser: 101 # www-data
+        env:
+          - name: POD_NAME
+            valueFrom:
+              fieldRef:
+                apiVersion: v1
+                fieldPath: metadata.name
+          - name: POD_NAMESPACE
+            valueFrom:
+              fieldRef:
+                apiVersion: v1
+                fieldPath: metadata.namespace
+        ports:
+        - name: http
+          containerPort: 80
+          hostPort: 8002
+          protocol: TCP
+        - name: https
+          containerPort: 443
+          hostPort: 4432
+          protocol: TCP
+        - name: health
+          containerPort: 10254
+          hostPort: 12542
+          protocol: TCP
+        args:
+        - /nginx-ingress-controller
+        - --configmap=$(POD_NAMESPACE)/nginx-load-balancer-microk8s-conf-child
+        - --tcp-services-configmap=$(POD_NAMESPACE)/nginx-ingress-tcp-microk8s-conf-child
+        - --udp-services-configmap=$(POD_NAMESPACE)/nginx-ingress-udp-microk8s-conf-child
+        - --election-id=ingress-controller-leader-child
+        - --controller-class=tfs.etsi.org/controller-class-child
+        - --ingress-class=tfs-ingress-class-child
+        - ' '
+        - --publish-status-address=127.0.0.1
diff --git a/src/tests/ofc23/nginx-ingress-controller-parent.yaml b/src/tests/ofc23/nginx-ingress-controller-parent.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..c504c2e6766c1ad5b81a2479b4d05a09ba46d906
--- /dev/null
+++ b/src/tests/ofc23/nginx-ingress-controller-parent.yaml
@@ -0,0 +1,134 @@
+# 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: v1
+kind: ConfigMap
+metadata:
+  name: nginx-load-balancer-microk8s-conf-parent
+  namespace: ingress
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: nginx-ingress-udp-microk8s-conf-parent
+  namespace: ingress
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: nginx-ingress-tcp-microk8s-conf-parent
+  namespace: ingress
+---
+apiVersion: networking.k8s.io/v1
+kind: IngressClass
+metadata:
+  name: tfs-ingress-class-parent
+  annotations:
+    ingressclass.kubernetes.io/is-default-class: "false"
+spec:
+  controller: tfs.etsi.org/controller-class-parent
+---
+apiVersion: apps/v1
+kind: DaemonSet
+metadata:
+  name: nginx-ingress-microk8s-controller-parent
+  namespace: ingress
+  labels:
+    microk8s-application: nginx-ingress-microk8s-parent
+spec:
+  selector:
+    matchLabels:
+      name: nginx-ingress-microk8s-parent
+  updateStrategy:
+    rollingUpdate:
+      maxSurge: 0
+      maxUnavailable: 1
+    type: RollingUpdate
+  template:
+    metadata:
+      labels:
+        name: nginx-ingress-microk8s-parent
+    spec:
+      terminationGracePeriodSeconds: 60
+      restartPolicy: Always
+      serviceAccountName: nginx-ingress-microk8s-serviceaccount
+      containers:
+      - image: k8s.gcr.io/ingress-nginx/controller:v1.2.0
+        imagePullPolicy: IfNotPresent
+        name: nginx-ingress-microk8s
+        livenessProbe:
+          httpGet:
+            path: /healthz
+            port: 10254
+            scheme: HTTP
+          initialDelaySeconds: 10
+          periodSeconds: 10
+          successThreshold: 1
+          failureThreshold: 3
+          timeoutSeconds: 5
+        readinessProbe:
+          httpGet:
+            path: /healthz
+            port: 10254
+            scheme: HTTP
+          periodSeconds: 10
+          successThreshold: 1
+          failureThreshold: 3
+          timeoutSeconds: 5
+        lifecycle:
+          preStop:
+            exec:
+              command:
+                - /wait-shutdown
+        securityContext:
+          capabilities:
+            add:
+            - NET_BIND_SERVICE
+            drop:
+            - ALL
+          runAsUser: 101 # www-data
+        env:
+          - name: POD_NAME
+            valueFrom:
+              fieldRef:
+                apiVersion: v1
+                fieldPath: metadata.name
+          - name: POD_NAMESPACE
+            valueFrom:
+              fieldRef:
+                apiVersion: v1
+                fieldPath: metadata.namespace
+        ports:
+        - name: http
+          containerPort: 80
+          hostPort: 8001
+          protocol: TCP
+        - name: https
+          containerPort: 443
+          hostPort: 4431
+          protocol: TCP
+        - name: health
+          containerPort: 10254
+          hostPort: 12541
+          protocol: TCP
+        args:
+        - /nginx-ingress-controller
+        - --configmap=$(POD_NAMESPACE)/nginx-load-balancer-microk8s-conf-parent
+        - --tcp-services-configmap=$(POD_NAMESPACE)/nginx-ingress-tcp-microk8s-conf-parent
+        - --udp-services-configmap=$(POD_NAMESPACE)/nginx-ingress-udp-microk8s-conf-parent
+        - --election-id=ingress-controller-leader-parent
+        - --controller-class=tfs.etsi.org/controller-class-parent
+        - --ingress-class=tfs-ingress-class-parent
+        - ' '
+        - --publish-status-address=127.0.0.1
diff --git a/src/tests/ofc23/show_deploy.sh b/src/tests/ofc23/show_deploy.sh
new file mode 100755
index 0000000000000000000000000000000000000000..d4e112b0f494e4a6dba964eda4e31652b8548043
--- /dev/null
+++ b/src/tests/ofc23/show_deploy.sh
@@ -0,0 +1,34 @@
+#!/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.
+
+########################################################################################################################
+# Automated steps start here
+########################################################################################################################
+
+echo "Deployment Resources:"
+kubectl --namespace tfs-parent get all
+printf "\n"
+
+echo "Deployment Ingress:"
+kubectl --namespace tfs-parent get ingress
+printf "\n"
+
+echo "Deployment Resources:"
+kubectl --namespace tfs-child get all
+printf "\n"
+
+echo "Deployment Ingress:"
+kubectl --namespace tfs-child get ingress
+printf "\n"
diff --git a/src/tests/ofc23/show_deploy_sligrp.sh b/src/tests/ofc23/show_deploy_sligrp.sh
new file mode 100755
index 0000000000000000000000000000000000000000..b5e3600ba99ec2e4de4a953f99c44ed2d88bba57
--- /dev/null
+++ b/src/tests/ofc23/show_deploy_sligrp.sh
@@ -0,0 +1,26 @@
+#!/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.
+
+########################################################################################################################
+# Automated steps start here
+########################################################################################################################
+
+echo "Deployment Resources:"
+kubectl --namespace tfs get all
+printf "\n"
+
+echo "Deployment Ingress:"
+kubectl --namespace tfs get ingress
+printf "\n"
diff --git a/src/tests/ofc23/tfs-ingress-child.yaml b/src/tests/ofc23/tfs-ingress-child.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..a93b9321c9f25c78e8423413c4225f78c7aee719
--- /dev/null
+++ b/src/tests/ofc23/tfs-ingress-child.yaml
@@ -0,0 +1,53 @@
+# 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: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  name: tfs-ingress-child
+  annotations:
+    nginx.ingress.kubernetes.io/rewrite-target: /$2
+spec:
+  ingressClassName: tfs-ingress-class-child
+  rules:
+  - http:
+      paths:
+        - path: /webui(/|$)(.*)
+          pathType: Prefix
+          backend:
+            service:
+              name: webuiservice
+              port:
+                number: 8004
+        - path: /grafana(/|$)(.*)
+          pathType: Prefix
+          backend:
+            service:
+              name: webuiservice
+              port:
+                number: 3000
+        - path: /context(/|$)(.*)
+          pathType: Prefix
+          backend:
+            service:
+              name: contextservice
+              port:
+                number: 8080
+        - path: /()(restconf/.*)
+          pathType: Prefix
+          backend:
+            service:
+              name: computeservice
+              port:
+                number: 8080
diff --git a/src/tests/ofc23/tfs-ingress-parent.yaml b/src/tests/ofc23/tfs-ingress-parent.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..baf506dd90f320a1913beb8becd39164daa21370
--- /dev/null
+++ b/src/tests/ofc23/tfs-ingress-parent.yaml
@@ -0,0 +1,53 @@
+# 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: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  name: tfs-ingress-parent
+  annotations:
+    nginx.ingress.kubernetes.io/rewrite-target: /$2
+spec:
+  ingressClassName: tfs-ingress-class-parent
+  rules:
+  - http:
+      paths:
+        - path: /webui(/|$)(.*)
+          pathType: Prefix
+          backend:
+            service:
+              name: webuiservice
+              port:
+                number: 8004
+        - path: /grafana(/|$)(.*)
+          pathType: Prefix
+          backend:
+            service:
+              name: webuiservice
+              port:
+                number: 3000
+        - path: /context(/|$)(.*)
+          pathType: Prefix
+          backend:
+            service:
+              name: contextservice
+              port:
+                number: 8080
+        - path: /()(restconf/.*)
+          pathType: Prefix
+          backend:
+            service:
+              name: computeservice
+              port:
+                number: 8080
diff --git a/src/tests/tools/mock_ipm_sdn_ctrl/MockIPMSdnCtrl.py b/src/tests/tools/mock_ipm_sdn_ctrl/MockIPMSdnCtrl.py
new file mode 100644
index 0000000000000000000000000000000000000000..ecac81be7e3bdff1dcbac458142ea15bf367a1a1
--- /dev/null
+++ b/src/tests/tools/mock_ipm_sdn_ctrl/MockIPMSdnCtrl.py
@@ -0,0 +1,192 @@
+# 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.
+
+# Mock IPM controller (implements minimal support)
+
+import functools, json, logging, sys, time, uuid
+from typing import Any, Dict, Optional, Tuple
+from flask import Flask, jsonify, make_response, request
+from flask_restful import Api, Resource
+
+BIND_ADDRESS = '0.0.0.0'
+BIND_PORT    = 8444
+IPM_USERNAME = 'xr-user-1'
+IPM_PASSWORD = 'xr-user-1'
+STR_ENDPOINT = 'https://{:s}:{:s}'.format(str(BIND_ADDRESS), str(BIND_PORT))
+LOG_LEVEL    = logging.DEBUG
+
+CONSTELLATION = {
+    'id': 'ofc-constellation',
+    'hubModule': {'state': {
+        'module': {'moduleName': 'OFC HUB 1', 'trafficMode': 'L1Mode', 'capacity': 100},
+        'endpoints': [{'moduleIf': {'clientIfAid': 'XR-T1'}}, {'moduleIf': {'clientIfAid': 'XR-T4'}}]
+    }},
+    'leafModules': [
+        {'state': {
+            'module': {'moduleName': 'OFC LEAF 1', 'trafficMode': 'L1Mode', 'capacity': 100},
+            'endpoints': [{'moduleIf': {'clientIfAid': 'XR-T1'}}]
+        }},
+        {'state': {
+            'module': {'moduleName': 'OFC LEAF 2', 'trafficMode': 'L1Mode', 'capacity': 100},
+            'endpoints': [{'moduleIf': {'clientIfAid': 'XR-T1'}}]
+        }}
+    ]
+}
+
+CONNECTIONS : Dict[str, Any] = dict()
+STATE_NAME_TO_CONNECTION : Dict[str, str] = dict()
+
+def select_module_state(module_name : str) -> Optional[Dict]:
+    hub_module_state = CONSTELLATION.get('hubModule', {}).get('state', {})
+    if module_name == hub_module_state.get('module', {}).get('moduleName'): return hub_module_state
+    for leaf_module in CONSTELLATION.get('leafModules', []):
+        leaf_module_state = leaf_module.get('state', {})
+        if module_name == leaf_module_state.get('module', {}).get('moduleName'): return leaf_module_state
+    return None
+
+def select_endpoint(module_state : Dict, module_if : str) -> Optional[Dict]:
+    for endpoint in module_state.get('endpoints', []):
+        if module_if == endpoint.get('moduleIf', {}).get('clientIfAid'): return endpoint
+    return None
+
+def select_module_endpoint(selector : Dict) -> Optional[Tuple[Dict, Dict]]:
+    selected_module_name = selector['moduleIfSelectorByModuleName']['moduleName']
+    selected_module_if = selector['moduleIfSelectorByModuleName']['moduleClientIfAid']
+    module_state = select_module_state(selected_module_name)
+    if module_state is None: return None
+    return module_state, select_endpoint(module_state, selected_module_if)
+
+def compose_endpoint(endpoint_selector : Dict) -> Dict:
+   module, endpoint = select_module_endpoint(endpoint_selector['selector'])
+   return {
+       'href': '/' + str(uuid.uuid4()),
+       'state': {
+            'moduleIf': {
+                'moduleName': module['module']['moduleName'],
+                'clientIfAid': endpoint['moduleIf']['clientIfAid'],
+            },
+            'capacity': module['module']['capacity'],
+        }
+    }
+
+logging.basicConfig(level=LOG_LEVEL, format="[%(asctime)s] %(levelname)s:%(name)s:%(message)s")
+LOGGER = logging.getLogger(__name__)
+
+logging.getLogger('werkzeug').setLevel(logging.WARNING)
+
+def log_request(logger : logging.Logger, response):
+    timestamp = time.strftime('[%Y-%b-%d %H:%M]')
+    logger.info('%s %s %s %s %s', timestamp, request.remote_addr, request.method, request.full_path, response.status)
+    return response
+
+class OpenIdConnect(Resource):
+    ACCESS_TOKENS = {}
+
+    def post(self):
+        if request.content_type != 'application/x-www-form-urlencoded': return make_response('bad content type', 400)
+        if request.content_length == 0: return make_response('bad content length', 400)
+        request_form = request.form
+        if request_form.get('client_id') != 'xr-web-client': return make_response('bad client_id', 403)
+        if request_form.get('client_secret') != 'xr-web-client': return make_response('bad client_secret', 403)
+        if request_form.get('grant_type') != 'password': return make_response('bad grant_type', 403)
+        if request_form.get('username') != IPM_USERNAME: return make_response('bad username', 403)
+        if request_form.get('password') != IPM_PASSWORD: return make_response('bad password', 403)
+        access_token = OpenIdConnect.ACCESS_TOKENS.setdefault(IPM_USERNAME, uuid.uuid4())
+        reply = {'access_token': access_token, 'expires_in': 86400}
+        return make_response(jsonify(reply), 200)
+
+class XrNetworks(Resource):
+    def get(self):
+        query = json.loads(request.args.get('q'))
+        hub_module_name = query.get('hubModule.state.module.moduleName')
+        if hub_module_name != 'OFC HUB 1': return make_response('unexpected hub module', 404)
+        return make_response(jsonify([CONSTELLATION]), 200)
+
+class XrNetworkConnections(Resource):
+    def get(self):
+        query = json.loads(request.args.get('q'))
+        state_name = query.get('state.name')
+        if state_name is None:
+            connections = [connection for connection in CONNECTIONS.values()]
+        else:
+            connection_uuid = STATE_NAME_TO_CONNECTION.get(state_name)
+            if connection_uuid is None: return make_response('state name not found', 404)
+            connection = CONNECTIONS.get(connection_uuid)
+            if connection is None: return make_response('connection for state name not found', 404)
+            connections = [connection]
+        return make_response(jsonify(connections), 200)
+
+    def post(self):
+        if request.content_type != 'application/json': return make_response('bad content type', 400)
+        if request.content_length == 0: return make_response('bad content length', 400)
+        request_json = request.json
+        if not isinstance(request_json, list): return make_response('content is not list', 400)
+        reply = []
+        for connection in request_json:
+            connection_uuid = str(uuid.uuid4())
+            state_name = connection['name']
+
+            if state_name is not None: STATE_NAME_TO_CONNECTION[state_name] = connection_uuid
+            CONNECTIONS[connection_uuid] = {
+                'href': '/network-connections/{:s}'.format(str(connection_uuid)),
+                'config': {
+                    'implicitTransportCapacity': connection['implicitTransportCapacity']
+                    # 'mc': ??
+                },
+                'state': {
+                    'name': state_name,
+                    'serviceMode': connection['serviceMode']
+                    # 'outerVID' : ??
+                },
+                'endpoints': [
+                    compose_endpoint(endpoint)
+                    for endpoint in connection['endpoints']
+                ]
+            }
+            reply.append(CONNECTIONS[connection_uuid])
+        return make_response(jsonify(reply), 202)
+
+class XrNetworkConnection(Resource):
+    def get(self, connection_uuid : str):
+        connection = CONNECTIONS.get(connection_uuid)
+        if connection is None: return make_response('unexpected connection id', 404)
+        return make_response(jsonify(connection), 200)
+
+    def delete(self, connection_uuid : str):
+        connection = CONNECTIONS.pop(connection_uuid, None)
+        if connection is None: return make_response('unexpected connection id', 404)
+        state_name = connection['state']['name']
+        STATE_NAME_TO_CONNECTION.pop(state_name, None)
+        return make_response(jsonify({}), 202)
+
+def main():
+    LOGGER.info('Starting...')
+    
+    app = Flask(__name__)
+    app.after_request(functools.partial(log_request, LOGGER))
+
+    api = Api(app)
+    api.add_resource(OpenIdConnect,        '/realms/xr-cm/protocol/openid-connect/token')
+    api.add_resource(XrNetworks,           '/api/v1/xr-networks')
+    api.add_resource(XrNetworkConnections, '/api/v1/network-connections')
+    api.add_resource(XrNetworkConnection,  '/api/v1/network-connections/<string:connection_uuid>')
+
+    LOGGER.info('Listening on {:s}...'.format(str(STR_ENDPOINT)))
+    app.run(debug=True, host=BIND_ADDRESS, port=BIND_PORT, ssl_context='adhoc')
+
+    LOGGER.info('Bye')
+    return 0
+
+if __name__ == '__main__':
+    sys.exit(main())
diff --git a/src/tests/tools/mock_ipm_sdn_ctrl/run.sh b/src/tests/tools/mock_ipm_sdn_ctrl/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..2aa78712c58d8cc255b60202d1576de683798d2e
--- /dev/null
+++ b/src/tests/tools/mock_ipm_sdn_ctrl/run.sh
@@ -0,0 +1,16 @@
+#!/usr/bin/env 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.
+
+python MockIPMSdnCtrl.py
diff --git a/src/tests/tools/mock_sdn_ctrl/MockMWSdnCtrl.py b/src/tests/tools/mock_mw_sdn_ctrl/MockMWSdnCtrl.py
similarity index 54%
rename from src/tests/tools/mock_sdn_ctrl/MockMWSdnCtrl.py
rename to src/tests/tools/mock_mw_sdn_ctrl/MockMWSdnCtrl.py
index 63be214b671c2ee9b222d92e6f964a4203b8a587..c20dde1b9958fb92e4c6f026fbfe55f9ba348ba1 100644
--- a/src/tests/tools/mock_sdn_ctrl/MockMWSdnCtrl.py
+++ b/src/tests/tools/mock_mw_sdn_ctrl/MockMWSdnCtrl.py
@@ -25,8 +25,7 @@
 # Ref: https://blog.miguelgrinberg.com/post/designing-a-restful-api-using-flask-restful
 
 import functools, logging, sys, time
-from flask import Flask, abort, request
-from flask.json import jsonify
+from flask import Flask, abort, jsonify, make_response, request
 from flask_restful import Api, Resource
 
 BIND_ADDRESS = '0.0.0.0'
@@ -36,31 +35,51 @@ STR_ENDPOINT = 'https://{:s}:{:s}{:s}'.format(str(BIND_ADDRESS), str(BIND_PORT),
 LOG_LEVEL    = logging.DEBUG
 
 NETWORK_NODES = [
-    {'node-id': '172.18.0.1', 'ietf-network-topology:termination-point': [
-        {'tp-id': '172.18.0.1:1', 'ietf-te-topology:te': {'name': 'ethernet'}},
-        {'tp-id': '172.18.0.1:2', 'ietf-te-topology:te': {'name': 'antena'  }},
+    {'node-id': '192.168.27.139', 'ietf-network-topology:termination-point': [
+        {'tp-id': '1', 'ietf-te-topology:te': {'name': 'ethernet'}},
+        {'tp-id': '2', 'ietf-te-topology:te': {'name': 'ethernet'}},
+        {'tp-id': '3', 'ietf-te-topology:te': {'name': 'ethernet'}},
+        {'tp-id': '4', 'ietf-te-topology:te': {'name': 'ethernet'}},
+        {'tp-id': '5', 'ietf-te-topology:te': {'name': 'ethernet'}},
+        {'tp-id': '6', 'ietf-te-topology:te': {'name': 'ethernet'}},
+        {'tp-id': '10', 'ietf-te-topology:te': {'name': 'antena' }},
     ]},
-    {'node-id': '172.18.0.2', 'ietf-network-topology:termination-point': [
-        {'tp-id': '172.18.0.2:1', 'ietf-te-topology:te': {'name': 'ethernet'}},
-        {'tp-id': '172.18.0.2:2', 'ietf-te-topology:te': {'name': 'antena'  }},
+    {'node-id': '192.168.27.140', 'ietf-network-topology:termination-point': [
+        {'tp-id': '1', 'ietf-te-topology:te': {'name': 'ethernet'}},
+        {'tp-id': '2', 'ietf-te-topology:te': {'name': 'ethernet'}},
+        {'tp-id': '3', 'ietf-te-topology:te': {'name': 'ethernet'}},
+        {'tp-id': '4', 'ietf-te-topology:te': {'name': 'ethernet'}},
+        {'tp-id': '5', 'ietf-te-topology:te': {'name': 'ethernet'}},
+        {'tp-id': '6', 'ietf-te-topology:te': {'name': 'ethernet'}},
+        {'tp-id': '10', 'ietf-te-topology:te': {'name': 'antena' }},
     ]},
-    {'node-id': '172.18.0.3', 'ietf-network-topology:termination-point': [
-        {'tp-id': '172.18.0.3:1', 'ietf-te-topology:te': {'name': 'ethernet'}},
-        {'tp-id': '172.18.0.3:2', 'ietf-te-topology:te': {'name': 'antena'  }},
+    {'node-id': '192.168.27.141', 'ietf-network-topology:termination-point': [
+        {'tp-id': '1', 'ietf-te-topology:te': {'name': 'ethernet'}},
+        {'tp-id': '2', 'ietf-te-topology:te': {'name': 'ethernet'}},
+        {'tp-id': '3', 'ietf-te-topology:te': {'name': 'ethernet'}},
+        {'tp-id': '4', 'ietf-te-topology:te': {'name': 'ethernet'}},
+        {'tp-id': '5', 'ietf-te-topology:te': {'name': 'ethernet'}},
+        {'tp-id': '6', 'ietf-te-topology:te': {'name': 'ethernet'}},
+        {'tp-id': '10', 'ietf-te-topology:te': {'name': 'antena' }},
     ]},
-    {'node-id': '172.18.0.4', 'ietf-network-topology:termination-point': [
-        {'tp-id': '172.18.0.4:1', 'ietf-te-topology:te': {'name': 'ethernet'}},
-        {'tp-id': '172.18.0.4:2', 'ietf-te-topology:te': {'name': 'antena'  }},
+    {'node-id': '192.168.27.142', 'ietf-network-topology:termination-point': [
+        {'tp-id': '1', 'ietf-te-topology:te': {'name': 'ethernet'}},
+        {'tp-id': '2', 'ietf-te-topology:te': {'name': 'ethernet'}},
+        {'tp-id': '3', 'ietf-te-topology:te': {'name': 'ethernet'}},
+        {'tp-id': '4', 'ietf-te-topology:te': {'name': 'ethernet'}},
+        {'tp-id': '5', 'ietf-te-topology:te': {'name': 'ethernet'}},
+        {'tp-id': '6', 'ietf-te-topology:te': {'name': 'ethernet'}},
+        {'tp-id': '10', 'ietf-te-topology:te': {'name': 'antena' }},
     ]}
 ]
 NETWORK_LINKS = [
-    {
-        'source'     : {'source-node': '172.18.0.1', 'source-tp': '172.18.0.1:2'},
-        'destination': {'dest-node'  : '172.18.0.2', 'dest-tp'  : '172.18.0.2:2'},
+    {   'link-id'    : '192.168.27.139:10--192.168.27.140:10',
+        'source'     : {'source-node': '192.168.27.139', 'source-tp': '10'},
+        'destination': {'dest-node'  : '192.168.27.140', 'dest-tp'  : '10'},
     },
-    {
-        'source'     : {'source-node': '172.18.0.3', 'source-tp': '172.18.0.3:2'},
-        'destination': {'dest-node'  : '172.18.0.4', 'dest-tp'  : '172.18.0.4:2'},
+    {   'link-id'    : '192.168.27.141:10--192.168.27.142:10',
+        'source'     : {'source-node': '192.168.27.141', 'source-tp': '10'},
+        'destination': {'dest-node'  : '192.168.27.142', 'dest-tp'  : '10'},
     }
 ]
 NETWORK_SERVICES = {}
@@ -77,21 +96,22 @@ def log_request(logger : logging.Logger, response):
     return response
 
 class Health(Resource):
-    def get(self): return jsonify({})
+    def get(self):
+        return make_response(jsonify({}), 200)
 
 class Network(Resource):
     def get(self, network_uuid : str):
         if network_uuid != 'SIAE-ETH-TOPOLOGY': abort(400)
         network = {'node': NETWORK_NODES, 'ietf-network-topology:link': NETWORK_LINKS}
-        return jsonify({'ietf-network:network': network})
+        return make_response(jsonify({'ietf-network:network': network}), 200)
 
 class Services(Resource):
     def get(self):
         services = [service for service in NETWORK_SERVICES.values()]
-        return jsonify({'ietf-eth-tran-service:etht-svc': {'etht-svc-instances': services}})
+        return make_response(jsonify({'ietf-eth-tran-service:etht-svc': {'etht-svc-instances': services}}), 200)
 
     def post(self):
-        json_request = request.json
+        json_request = request.get_json()
         if not json_request: abort(400)
         if not isinstance(json_request, dict): abort(400)
         if 'etht-svc-instances' not in json_request: abort(400)
@@ -101,12 +121,12 @@ class Services(Resource):
         svc_data = json_services[0]
         etht_svc_name = svc_data['etht-svc-name']
         NETWORK_SERVICES[etht_svc_name] = svc_data
-        return jsonify({}), 201
+        return make_response(jsonify({}), 201)
 
 class DelServices(Resource):
     def delete(self, service_uuid : str):
         NETWORK_SERVICES.pop(service_uuid, None)
-        return jsonify({}), 204
+        return make_response(jsonify({}), 204)
 
 def main():
     LOGGER.info('Starting...')
diff --git a/src/tests/tools/mock_sdn_ctrl/README.md b/src/tests/tools/mock_mw_sdn_ctrl/README.md
similarity index 94%
rename from src/tests/tools/mock_sdn_ctrl/README.md
rename to src/tests/tools/mock_mw_sdn_ctrl/README.md
index d8a6fe6b279553e54f13792cbf12f15b2b380dc2..8568c89ed22e2010bc565e28dc42821181dd6e0a 100644
--- a/src/tests/tools/mock_sdn_ctrl/README.md
+++ b/src/tests/tools/mock_mw_sdn_ctrl/README.md
@@ -12,8 +12,8 @@ Follow the steps below to perform the test:
 ## 1. Deploy TeraFlowSDN controller and the scenario
 Deploy the test scenario "microwave_deploy.sh":
 ```bash
-source src/tests/tools/microwave_deploy.sh
-./deploy.sh
+source src/tests/tools/mock_mw_sdn_ctrl/scenario/microwave_deploy.sh
+./deploy/all.sh
 ```
 
 ## 2. Install requirements and run the Mock MicroWave SDN controller
@@ -27,7 +27,7 @@ pip install Flask==2.1.3 Flask-RESTful==0.3.9
 
 Run the Mock MicroWave SDN Controller as follows:
 ```bash
-python src/tests/tools/mock_sdn_ctrl/MockMWSdnCtrl.py
+python src/tests/tools/mock_mw_sdn_ctrl/MockMWSdnCtrl.py
 ```
 
 ## 3. Deploy the test descriptors
diff --git a/src/tests/tools/mock_mw_sdn_ctrl/run.sh b/src/tests/tools/mock_mw_sdn_ctrl/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..415fc1751f132478889fba2e0ec1f5da742e23f1
--- /dev/null
+++ b/src/tests/tools/mock_mw_sdn_ctrl/run.sh
@@ -0,0 +1,16 @@
+#!/usr/bin/env 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.
+
+python MockMWSdnCtrl.py
diff --git a/src/tests/tools/mock_sdn_ctrl/microwave_deploy.sh b/src/tests/tools/mock_mw_sdn_ctrl/scenario/microwave_deploy.sh
similarity index 100%
rename from src/tests/tools/mock_sdn_ctrl/microwave_deploy.sh
rename to src/tests/tools/mock_mw_sdn_ctrl/scenario/microwave_deploy.sh
diff --git a/src/tests/tools/mock_sdn_ctrl/network_descriptors.json b/src/tests/tools/mock_mw_sdn_ctrl/scenario/network_descriptors.json
similarity index 100%
rename from src/tests/tools/mock_sdn_ctrl/network_descriptors.json
rename to src/tests/tools/mock_mw_sdn_ctrl/scenario/network_descriptors.json
diff --git a/src/tests/tools/mock_sdn_ctrl/service_descriptor.json b/src/tests/tools/mock_mw_sdn_ctrl/scenario/service_descriptor.json
similarity index 100%
rename from src/tests/tools/mock_sdn_ctrl/service_descriptor.json
rename to src/tests/tools/mock_mw_sdn_ctrl/scenario/service_descriptor.json
diff --git a/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/Dockerfile b/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..ad214b97c091bf82a8bfe5c0ce4183d0bae2766e
--- /dev/null
+++ b/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/Dockerfile
@@ -0,0 +1,35 @@
+# 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.
+
+FROM python:3.9-slim
+
+# Set Python to show logs as they occur
+ENV PYTHONUNBUFFERED=0
+
+# Get generic Python packages
+RUN python3 -m pip install --upgrade pip
+RUN python3 -m pip install --upgrade setuptools wheel
+RUN python3 -m pip install --upgrade pip-tools
+
+# Create component sub-folder, and copy content
+RUN mkdir -p /var/teraflow/mock_mw_sdn_ctrl
+WORKDIR /var/teraflow/mock_mw_sdn_ctrl
+COPY . .
+
+# Get specific Python packages
+RUN pip-compile --quiet --output-file=requirements.txt requirements.in
+RUN python3 -m pip install -r requirements.txt
+
+# Start the service
+ENTRYPOINT ["python", "MockMWSdnCtrl.py"]
diff --git a/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/build.sh b/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/build.sh
new file mode 100755
index 0000000000000000000000000000000000000000..4df315cec178cef13eaa059a739bc22efc011d4d
--- /dev/null
+++ b/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/build.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+docker build -t mock-mw-sdn-ctrl:test -f Dockerfile .
+docker tag mock-mw-sdn-ctrl:test localhost:32000/tfs/mock-mw-sdn-ctrl:test
+docker push localhost:32000/tfs/mock-mw-sdn-ctrl:test
diff --git a/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/deploy.sh b/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/deploy.sh
new file mode 100755
index 0000000000000000000000000000000000000000..ded232e5c50f8cd5ed448ec0193f58c43626f4ad
--- /dev/null
+++ b/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/deploy.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+kubectl delete namespace mocks
+kubectl --namespace mocks apply -f mock-mw-sdn-ctrl.yaml
diff --git a/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/mock-mw-sdn-ctrl.yaml b/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/mock-mw-sdn-ctrl.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..05b89f949e940ae55ad592b9cc0e82a6eea2e343
--- /dev/null
+++ b/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/mock-mw-sdn-ctrl.yaml
@@ -0,0 +1,46 @@
+kind: Namespace
+apiVersion: v1
+metadata:
+  name: mocks
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: mock-mw-sdn-ctrl
+spec:
+  selector:
+    matchLabels:
+      app: mock-mw-sdn-ctrl
+  replicas: 1
+  template:
+    metadata:
+      labels:
+        app: mock-mw-sdn-ctrl
+    spec:
+      terminationGracePeriodSeconds: 5
+      containers:
+      - name: server
+        image: localhost:32000/tfs/mock-mw-sdn-ctrl:test
+        imagePullPolicy: IfNotPresent
+        ports:
+        - containerPort: 8443
+        resources:
+          requests:
+            cpu: 250m
+            memory: 512Mi
+          limits:
+            cpu: 700m
+            memory: 1024Mi
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: mock-mw-sdn-ctrl
+spec:
+  type: ClusterIP
+  selector:
+    app: mock-mw-sdn-ctrl
+  ports:
+  - name: https
+    port: 8443
+    targetPort: 8443
diff --git a/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/requirements.in b/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/requirements.in
new file mode 100644
index 0000000000000000000000000000000000000000..f4bc191062c8385b2eeb003832b284313305c795
--- /dev/null
+++ b/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/requirements.in
@@ -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.
+
+cryptography==39.0.1
+pyopenssl==23.0.0
+Flask==2.1.3
+Flask-HTTPAuth==4.5.0
+Flask-RESTful==0.3.9
+jsonschema==4.4.0
+requests==2.27.1
diff --git a/src/tests/tools/mock_mw_sdn_ctrl/test_mw.py b/src/tests/tools/mock_mw_sdn_ctrl/test_mw.py
new file mode 100644
index 0000000000000000000000000000000000000000..0329d30ad234398200c0fe29aac46f72f5a2e924
--- /dev/null
+++ b/src/tests/tools/mock_mw_sdn_ctrl/test_mw.py
@@ -0,0 +1,84 @@
+import json, logging, requests
+from requests.auth import HTTPBasicAuth
+from typing import Optional
+
+LOGGER = logging.getLogger(__name__)
+
+HTTP_OK_CODES = {
+    200,    # OK
+    201,    # Created
+    202,    # Accepted
+    204,    # No Content
+}
+
+def create_connectivity_service(
+    root_url, uuid, node_id_src, tp_id_src, node_id_dst, tp_id_dst, vlan_id,
+    auth : Optional[HTTPBasicAuth] = None, timeout : Optional[int] = None
+):
+
+    url = '{:s}/nmswebs/restconf/data/ietf-eth-tran-service:etht-svc'.format(root_url)
+    headers = {'content-type': 'application/json'}
+    data = {
+        'etht-svc-instances': [
+            {
+                'etht-svc-name': uuid,
+                'etht-svc-type': 'ietf-eth-tran-types:p2p-svc',
+                'etht-svc-end-points': [
+                    {
+                        'etht-svc-access-points': [
+                            {'access-node-id': node_id_src, 'access-ltp-id': tp_id_src, 'access-point-id': '1'}
+                        ],
+                        'outer-tag': {'vlan-value': vlan_id, 'tag-type': 'ietf-eth-tran-types:classify-c-vlan'},
+                        'etht-svc-end-point-name': '{:s}:{:s}'.format(str(node_id_src), str(tp_id_src)),
+                        'service-classification-type': 'ietf-eth-tran-types:vlan-classification'
+                    },
+                    {
+                        'etht-svc-access-points': [
+                            {'access-node-id': node_id_dst, 'access-ltp-id': tp_id_dst, 'access-point-id': '2'}
+                        ],
+                        'outer-tag': {'vlan-value': vlan_id, 'tag-type': 'ietf-eth-tran-types:classify-c-vlan'},
+                        'etht-svc-end-point-name': '{:s}:{:s}'.format(str(node_id_dst), str(tp_id_dst)),
+                        'service-classification-type': 'ietf-eth-tran-types:vlan-classification'
+                    }
+                ]
+            }
+        ]
+    }
+    results = []
+    try:
+        LOGGER.info('Connectivity service {:s}: {:s}'.format(str(uuid), str(data)))
+        response = requests.post(
+            url=url, data=json.dumps(data), timeout=timeout, headers=headers, verify=False, auth=auth)
+        LOGGER.info('Microwave Driver response: {:s}'.format(str(response)))
+    except Exception as e:  # pylint: disable=broad-except
+        LOGGER.exception('Exception creating ConnectivityService(uuid={:s}, data={:s})'.format(str(uuid), str(data)))
+        results.append(e)
+    else:
+        if response.status_code not in HTTP_OK_CODES:
+            msg = 'Could not create ConnectivityService(uuid={:s}, data={:s}). status_code={:s} reply={:s}'
+            LOGGER.error(msg.format(str(uuid), str(data), str(response.status_code), str(response)))
+        results.append(response.status_code in HTTP_OK_CODES)
+    return results
+
+def delete_connectivity_service(root_url, uuid, auth : Optional[HTTPBasicAuth] = None, timeout : Optional[int] = None):
+    url = '{:s}/nmswebs/restconf/data/ietf-eth-tran-service:etht-svc/etht-svc-instances={:s}'
+    url = url.format(root_url, uuid)
+    results = []
+    try:
+        response = requests.delete(url=url, timeout=timeout, verify=False, auth=auth)
+    except Exception as e:  # pylint: disable=broad-except
+        LOGGER.exception('Exception deleting ConnectivityService(uuid={:s})'.format(str(uuid)))
+        results.append(e)
+    else:
+        if response.status_code not in HTTP_OK_CODES:
+            msg = 'Could not delete ConnectivityService(uuid={:s}). status_code={:s} reply={:s}'
+            LOGGER.error(msg.format(str(uuid), str(response.status_code), str(response)))
+        results.append(response.status_code in HTTP_OK_CODES)
+    return results
+
+if __name__ == '__main__':
+    ROOT_URL = 'https://127.0.0.1:8443'
+    SERVICE_UUID = 'my-service'
+
+    create_connectivity_service(ROOT_URL, SERVICE_UUID, '172.18.0.1', '1', '172.18.0.2', '2', 300)
+    delete_connectivity_service(ROOT_URL, SERVICE_UUID)
diff --git a/src/webui/service/device/forms.py b/src/webui/service/device/forms.py
index c6bacac9bc1723a020f3057fad9c9e8306c9dbca..24bc92b3a5a4aec4321c07b17830f6111be7176d 100644
--- a/src/webui/service/device/forms.py
+++ b/src/webui/service/device/forms.py
@@ -29,6 +29,7 @@ class AddDeviceForm(FlaskForm):
     device_drivers_ietf_network_topology = BooleanField('IETF_NETWORK_TOPOLOGY')
     device_drivers_onf_tr_352 = BooleanField('ONF_TR_352')
     device_drivers_xr = BooleanField('XR')
+    device_drivers_ietf_l2vpn = BooleanField('IETF L2VPN')
     device_config_address = StringField('connect/address',default='127.0.0.1',validators=[DataRequired(), Length(min=5)])
     device_config_port = StringField('connect/port',default='0',validators=[DataRequired(), Length(min=1)])
     device_config_settings = TextAreaField('connect/settings',default='{}',validators=[DataRequired(), Length(min=2)])
diff --git a/src/webui/service/device/routes.py b/src/webui/service/device/routes.py
index ebf77a35ffdf9c2546ddbdd1bac0c8c1f54a2b56..bc46847704b28fb6ef44de0aae030ccb67935928 100644
--- a/src/webui/service/device/routes.py
+++ b/src/webui/service/device/routes.py
@@ -120,6 +120,8 @@ def add():
             device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352)
         if form.device_drivers_xr.data:
             device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_XR)
+        if form.device_drivers_ietf_l2vpn.data:
+            device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN)
         device_obj.device_drivers.extend(device_drivers) # pylint: disable=no-member
 
         try:
diff --git a/src/webui/service/static/topology_icons/Acknowledgements.txt b/src/webui/service/static/topology_icons/Acknowledgements.txt
index b285d225957b0a4e8c14ac4ae5e078597d2a1b27..de69c89cee25dc93856761ac2dd08d9988a35095 100644
--- a/src/webui/service/static/topology_icons/Acknowledgements.txt
+++ b/src/webui/service/static/topology_icons/Acknowledgements.txt
@@ -24,3 +24,10 @@ https://symbols.getvecta.com/stencil_241/213_programmable-sw.32d3794d56.png => e
 
 https://symbols.getvecta.com/stencil_240/275_wae.c06b769cd7.png => optical-transponder.png
 https://symbols.getvecta.com/stencil_241/289_wae.216d930c17.png => emu-optical-transponder.png
+
+https://symbols.getvecta.com/stencil_240/128_localdirector.c1e561769f.png => optical-splitter.png
+https://symbols.getvecta.com/stencil_241/158_local-director.6b38eab9e4.png => emu-optical-splitter.png
+
+
+https://symbols.getvecta.com/stencil_240/197_radio-tower.b6138c8c29.png => radio-router.png
+https://symbols.getvecta.com/stencil_241/216_radio-tower.5159339bc0.png => emu-radio-router.png
\ No newline at end of file
diff --git a/src/webui/service/static/topology_icons/emu-optical-splitter.png b/src/webui/service/static/topology_icons/emu-optical-splitter.png
new file mode 100644
index 0000000000000000000000000000000000000000..12b7727d68ef749b52fcdd592c0427f63b58dc75
Binary files /dev/null and b/src/webui/service/static/topology_icons/emu-optical-splitter.png differ
diff --git a/src/webui/service/static/topology_icons/emu-packet-radio-router.png b/src/webui/service/static/topology_icons/emu-packet-radio-router.png
new file mode 100644
index 0000000000000000000000000000000000000000..00257d0e2ee357dbdd392a408cfdbe07e006ff2a
Binary files /dev/null and b/src/webui/service/static/topology_icons/emu-packet-radio-router.png differ
diff --git a/src/webui/service/static/topology_icons/emu-xr-constellation.png b/src/webui/service/static/topology_icons/emu-xr-constellation.png
new file mode 100644
index 0000000000000000000000000000000000000000..d3bea498a4cd6d8a455d997e4833079f3e6b714f
Binary files /dev/null and b/src/webui/service/static/topology_icons/emu-xr-constellation.png differ
diff --git a/src/webui/service/static/topology_icons/optical-splitter.png b/src/webui/service/static/topology_icons/optical-splitter.png
new file mode 100644
index 0000000000000000000000000000000000000000..90a3d79b8ed4b8ae15f3d4a349cd08d741dcfdaf
Binary files /dev/null and b/src/webui/service/static/topology_icons/optical-splitter.png differ
diff --git a/src/webui/service/static/topology_icons/packet-radio-router.png b/src/webui/service/static/topology_icons/packet-radio-router.png
new file mode 100644
index 0000000000000000000000000000000000000000..025172a587890341061b11ae57ce30184b8bc2f0
Binary files /dev/null and b/src/webui/service/static/topology_icons/packet-radio-router.png differ
diff --git a/src/webui/service/static/topology_icons/teraflowsdn.png b/src/webui/service/static/topology_icons/teraflowsdn.png
new file mode 100644
index 0000000000000000000000000000000000000000..ed2232e8223a39eb0d829e0e50975a697b0660fc
Binary files /dev/null and b/src/webui/service/static/topology_icons/teraflowsdn.png differ
diff --git a/src/webui/service/templates/js/topology.js b/src/webui/service/templates/js/topology.js
index 50486d2a6826fedace55f7a62592fa083e7256a6..1b34f2b2c737c96ef18e925bd76ef28451f4120f 100644
--- a/src/webui/service/templates/js/topology.js
+++ b/src/webui/service/templates/js/topology.js
@@ -31,8 +31,8 @@ const margin = {top: 5, right: 5, bottom: 5, left: 5};
 const icon_width  = 40;
 const icon_height = 40;
 
-width = 1000 - margin.left - margin.right;
-height = 600 - margin.top - margin.bottom;
+width = 1400 - margin.left - margin.right;
+height = 800 - margin.top - margin.bottom;
 
 //function handleZoom(e) {
 //    console.dir(e);
@@ -70,11 +70,21 @@ var simulation = d3.forceSimulation();
 // load the data
 d3.json("{{ url_for('main.topology') }}", function(data) {
     // set the data and properties of link lines and node circles
-    link = svg.append("g").attr("class", "links").style('stroke', '#aaa')
+    link = svg.append("g").attr("class", "links")//.style('stroke', '#aaa')
         .selectAll("line")
         .data(data.links)
         .enter()
-        .append("line");
+        .append("line")
+        .attr("opacity", 1)
+        .attr("stroke", function(l) {
+            return l.name.toLowerCase().includes('mgmt') ? '#AAAAAA' : '#555555';
+        })
+        .attr("stroke-width", function(l) {
+            return l.name.toLowerCase().includes('mgmt') ? 1 : 2;
+        })
+        .attr("stroke-dasharray", function(l) {
+            return l.name.toLowerCase().includes('mgmt') ? "5,5" : "0";
+        });
     node = svg.append("g").attr("class", "devices").attr('r', 20).style('fill', '#69b3a2')
         .selectAll("circle")
         .data(data.devices)
@@ -93,9 +103,9 @@ d3.json("{{ url_for('main.topology') }}", function(data) {
     link.append("title").text(function(l) { return l.name; });
 
     // link style
-    link
-        .attr("stroke-width", forceProperties.link.enabled ? 2 : 1)
-        .attr("opacity", forceProperties.link.enabled ? 1 : 0);
+    //link
+    //    .attr("stroke-width", forceProperties.link.enabled ? 2 : 1)
+    //    .attr("opacity", forceProperties.link.enabled ? 1 : 0);
     
     // set up the simulation and event to update locations after each tick
     simulation.nodes(data.devices);
diff --git a/src/webui/service/templates/link/detail.html b/src/webui/service/templates/link/detail.html
index acac4a55392c2bf7f6261707ae1627a486affd10..916abafde05b3ec990346ff7966f207b1dafc10a 100644
--- a/src/webui/service/templates/link/detail.html
+++ b/src/webui/service/templates/link/detail.html
@@ -37,6 +37,7 @@
                         <thead>
                             <tr>
                                 <th scope="col">Endpoint UUID</th>
+                                <th scope="col">Name</th>
                                 <th scope="col">Device</th>
                                 <th scope="col">Endpoint Type</th>
                             </tr>
@@ -44,6 +45,9 @@
                         <tbody>
                               {% for endpoint in link.link_endpoint_ids %}
                               <tr>
+                                   <td>
+                                        {{ endpoint.endpoint_uuid.uuid }}
+                                   </td>
                                    <td>
                                         {{ endpoints_data.get(endpoint.endpoint_uuid.uuid, (endpoint.endpoint_uuid.uuid, ''))[0] }}
                                    </td>
diff --git a/src/webui/service/templates/service/detail.html b/src/webui/service/templates/service/detail.html
index bee2e93c53896a8eeac826703a60afe02a5aa825..414aa19d0165ed7138f277005d5573c9242daefb 100644
--- a/src/webui/service/templates/service/detail.html
+++ b/src/webui/service/templates/service/detail.html
@@ -55,6 +55,7 @@
             <thead>
                 <tr>
                     <th scope="col">Endpoint UUID</th>
+                    <th scope="col">Name</th>
                     <th scope="col">Device</th>
                     <th scope="col">Endpoint Type</th>
                 </tr>
@@ -62,6 +63,9 @@
             <tbody>
                 {% for endpoint in service.service_endpoint_ids %}
                 <tr>
+                    <td>
+                        {{ endpoint.endpoint_uuid.uuid }}
+                   </td>
                     <td>
                         {{ endpoints_data.get(endpoint.endpoint_uuid.uuid, (endpoint.endpoint_uuid.uuid, ''))[0] }}
                     </td>
diff --git a/src/webui/service/templates/slice/detail.html b/src/webui/service/templates/slice/detail.html
index 8f223e44deda37b177a360a51b1e366f680fac27..13b69defeb95f66aba47a4aa78f98631ca8cc367 100644
--- a/src/webui/service/templates/slice/detail.html
+++ b/src/webui/service/templates/slice/detail.html
@@ -55,6 +55,7 @@
             <thead>
                 <tr>
                     <th scope="col">Endpoint UUID</th>
+                    <th scope="col">Name</th>
                     <th scope="col">Device</th>
                     <th scope="col">Endpoint Type</th>
                 </tr>
@@ -62,6 +63,9 @@
             <tbody>
                 {% for endpoint in slice.slice_endpoint_ids %}
                 <tr>
+                    <td>
+                        {{ endpoint.endpoint_uuid.uuid }}
+                   </td>
                     <td>
                         {{ endpoints_data.get(endpoint.endpoint_uuid.uuid, (endpoint.endpoint_uuid.uuid, ''))[0] }}
                     </td>