diff --git a/src/common/DeviceTypes.py b/src/common/DeviceTypes.py
index f88f931d4c465349a37a92b5891fd0acd8fe6a48..72b3e21fdecd1019099eec03b3b473f56bcd403a 100644
--- a/src/common/DeviceTypes.py
+++ b/src/common/DeviceTypes.py
@@ -22,6 +22,7 @@ class DeviceTypeEnum(Enum):
     # Emulated device types
     EMULATED_CLIENT                 = 'emu-client'
     EMULATED_DATACENTER             = 'emu-datacenter'
+    EMULATED_IP_SDN_CONTROLLER      = 'emu-ip-sdn-controller'
     EMULATED_MICROWAVE_RADIO_SYSTEM = 'emu-microwave-radio-system'
     EMULATED_OPEN_LINE_SYSTEM       = 'emu-open-line-system'
     EMULATED_OPTICAL_ROADM          = 'emu-optical-roadm'
@@ -36,6 +37,7 @@ class DeviceTypeEnum(Enum):
     # Real device types
     CLIENT                          = 'client'
     DATACENTER                      = 'datacenter'
+    IP_SDN_CONTROLLER               = 'ip-sdn-controller'
     MICROWAVE_RADIO_SYSTEM          = 'microwave-radio-system'
     OPEN_LINE_SYSTEM                = 'open-line-system'
     OPTICAL_ROADM                   = 'optical-roadm'
diff --git a/src/common/tools/descriptor/Loader.py b/src/common/tools/descriptor/Loader.py
index c5468c19ccc063387460ed7f7b28ee70c5f1d907..4ab33beae8987ff2b38e5e2bc0252bacf557120c 100644
--- a/src/common/tools/descriptor/Loader.py
+++ b/src/common/tools/descriptor/Loader.py
@@ -46,7 +46,7 @@ from slice.client.SliceClient import SliceClient
 from .Tools import (
     format_device_custom_config_rules, format_service_custom_config_rules, format_slice_custom_config_rules,
     get_descriptors_add_contexts, get_descriptors_add_services, get_descriptors_add_slices,
-    get_descriptors_add_topologies, split_devices_by_rules)
+    get_descriptors_add_topologies, split_controllers_and_network_devices, split_devices_by_rules)
 
 LOGGER = logging.getLogger(__name__)
 LOGGERS = {
@@ -56,9 +56,10 @@ LOGGERS = {
 }
 
 ENTITY_TO_TEXT = {
-    # name   => singular,    plural
+    # name      => singular,     plural
     'context'   : ('Context',    'Contexts'   ),
     'topology'  : ('Topology',   'Topologies' ),
+    'controller': ('Controller', 'Controllers'),
     'device'    : ('Device',     'Devices'    ),
     'link'      : ('Link',       'Links'      ),
     'service'   : ('Service',    'Services'   ),
@@ -68,8 +69,8 @@ ENTITY_TO_TEXT = {
 
 ACTION_TO_TEXT = {
     # action =>  infinitive,  past
-    'add'     : ('Add',       'Added'),
-    'update'  : ('Update',    'Updated'),
+    'add'     : ('Add',       'Added'     ),
+    'update'  : ('Update',    'Updated'   ),
     'config'  : ('Configure', 'Configured'),
 }
 
@@ -231,10 +232,12 @@ class DescriptorLoader:
 
     def _load_dummy_mode(self) -> None:
         # Dummy Mode: used to pre-load databases (WebUI debugging purposes) with no smart or automated tasks.
+        controllers, network_devices = split_controllers_and_network_devices(self.__devices)
         self.__ctx_cli.connect()
         self._process_descr('context',    'add',    self.__ctx_cli.SetContext,    Context,    self.__contexts_add  )
         self._process_descr('topology',   'add',    self.__ctx_cli.SetTopology,   Topology,   self.__topologies_add)
-        self._process_descr('device',     'add',    self.__ctx_cli.SetDevice,     Device,     self.__devices       )
+        self._process_descr('controller', 'add',    self.__ctx_cli.SetDevice,     Device,     controllers          )
+        self._process_descr('device',     'add',    self.__ctx_cli.SetDevice,     Device,     network_devices      )
         self._process_descr('link',       'add',    self.__ctx_cli.SetLink,       Link,       self.__links         )
         self._process_descr('service',    'add',    self.__ctx_cli.SetService,    Service,    self.__services      )
         self._process_descr('slice',      'add',    self.__ctx_cli.SetSlice,      Slice,      self.__slices        )
@@ -262,20 +265,23 @@ class DescriptorLoader:
         self.__services_add = get_descriptors_add_services(self.__services)
         self.__slices_add = get_descriptors_add_slices(self.__slices)
 
+        controllers_add, network_devices_add = split_controllers_and_network_devices(self.__devices_add)
+
         self.__ctx_cli.connect()
         self.__dev_cli.connect()
         self.__svc_cli.connect()
         self.__slc_cli.connect()
 
-        self._process_descr('context',  'add',    self.__ctx_cli.SetContext,      Context,  self.__contexts_add  )
-        self._process_descr('topology', 'add',    self.__ctx_cli.SetTopology,     Topology, self.__topologies_add)
-        self._process_descr('device',   'add',    self.__dev_cli.AddDevice,       Device,   self.__devices_add   )
-        self._process_descr('device',   'config', self.__dev_cli.ConfigureDevice, Device,   self.__devices_config)
-        self._process_descr('link',     'add',    self.__ctx_cli.SetLink,         Link,     self.__links         )
-        self._process_descr('service',  'add',    self.__svc_cli.CreateService,   Service,  self.__services_add  )
-        self._process_descr('service',  'update', self.__svc_cli.UpdateService,   Service,  self.__services      )
-        self._process_descr('slice',    'add',    self.__slc_cli.CreateSlice,     Slice,    self.__slices_add    )
-        self._process_descr('slice',    'update', self.__slc_cli.UpdateSlice,     Slice,    self.__slices        )
+        self._process_descr('context',    'add',    self.__ctx_cli.SetContext,      Context,  self.__contexts_add  )
+        self._process_descr('topology',   'add',    self.__ctx_cli.SetTopology,     Topology, self.__topologies_add)
+        self._process_descr('controller', 'add',    self.__dev_cli.AddDevice,       Device,   controllers_add      )
+        self._process_descr('device',     'add',    self.__dev_cli.AddDevice,       Device,   network_devices_add  )
+        self._process_descr('device',     'config', self.__dev_cli.ConfigureDevice, Device,   self.__devices_config)
+        self._process_descr('link',       'add',    self.__ctx_cli.SetLink,         Link,     self.__links         )
+        self._process_descr('service',    'add',    self.__svc_cli.CreateService,   Service,  self.__services_add  )
+        self._process_descr('service',    'update', self.__svc_cli.UpdateService,   Service,  self.__services      )
+        self._process_descr('slice',      'add',    self.__slc_cli.CreateSlice,     Slice,    self.__slices_add    )
+        self._process_descr('slice',      'update', self.__slc_cli.UpdateSlice,     Slice,    self.__slices        )
 
         # By default the Context component automatically assigns devices and links to topologies based on their
         # endpoints, and assigns topologies, services, and slices to contexts based on their identifiers.
diff --git a/src/common/tools/descriptor/Tools.py b/src/common/tools/descriptor/Tools.py
index 3126f2bcedb1aaeeda660674d41a249af03679fe..b4a76ff4f00d0f6886895cca0ab6f27f7aa8aa43 100644
--- a/src/common/tools/descriptor/Tools.py
+++ b/src/common/tools/descriptor/Tools.py
@@ -14,6 +14,7 @@
 
 import copy, json
 from typing import Dict, List, Optional, Tuple, Union
+from common.DeviceTypes import DeviceTypeEnum
 
 def get_descriptors_add_contexts(contexts : List[Dict]) -> List[Dict]:
     contexts_add = copy.deepcopy(contexts)
@@ -103,3 +104,24 @@ def split_devices_by_rules(devices : List[Dict]) -> Tuple[List[Dict], List[Dict]
             devices_config.append(device)
 
     return devices_add, devices_config
+
+CONTROLLER_DEVICE_TYPES = {
+    DeviceTypeEnum.EMULATED_IP_SDN_CONTROLLER.value,
+    DeviceTypeEnum.EMULATED_MICROWAVE_RADIO_SYSTEM.value,
+    DeviceTypeEnum.EMULATED_OPEN_LINE_SYSTEM.value,
+    DeviceTypeEnum.IP_SDN_CONTROLLER.value,
+    DeviceTypeEnum.MICROWAVE_RADIO_SYSTEM.value,
+    DeviceTypeEnum.OPEN_LINE_SYSTEM.value,
+    DeviceTypeEnum.TERAFLOWSDN_CONTROLLER.value,
+}
+
+def split_controllers_and_network_devices(devices : List[Dict]) -> Tuple[List[Dict], List[Dict]]:
+    controllers     : List[Dict] = list()
+    network_devices : List[Dict] = list()
+    for device in devices:
+        device_type = device.get('device_type')
+        if device_type in CONTROLLER_DEVICE_TYPES:
+            controllers.append(device)
+        else:
+            network_devices.append(device)
+    return controllers, network_devices
diff --git a/src/common/tools/grpc/Constraints.py b/src/common/tools/grpc/Constraints.py
index 07f0b7782dbd93479774af6324683753f906c5a1..63e707c6f0232486de7761cc97b214ce16b524fd 100644
--- a/src/common/tools/grpc/Constraints.py
+++ b/src/common/tools/grpc/Constraints.py
@@ -18,11 +18,12 @@
 
 import json
 from typing import Any, Dict, List, Optional, Tuple
-from common.proto.context_pb2 import Constraint, EndPointId
+from common.proto.context_pb2 import Constraint, ConstraintActionEnum, EndPointId
 from common.tools.grpc.Tools import grpc_message_to_json_string
 
 def update_constraint_custom_scalar(
-    constraints, constraint_type : str, value : Any, raise_if_differs : bool = False
+    constraints, constraint_type : str, value : Any, raise_if_differs : bool = False,
+    new_action : ConstraintActionEnum = ConstraintActionEnum.CONSTRAINTACTION_SET
 ) -> Constraint:
 
     for constraint in constraints:
@@ -36,6 +37,8 @@ def update_constraint_custom_scalar(
         constraint.custom.constraint_type = constraint_type
         json_constraint_value = None
 
+    constraint.action = new_action
+
     if (json_constraint_value is None) or not raise_if_differs:
         # missing or raise_if_differs=False, add/update it
         json_constraint_value = value
@@ -47,7 +50,10 @@ def update_constraint_custom_scalar(
     constraint.custom.constraint_value = json.dumps(json_constraint_value, sort_keys=True)
     return constraint
 
-def update_constraint_custom_dict(constraints, constraint_type : str, fields : Dict[str, Tuple[Any, bool]]) -> Constraint:
+def update_constraint_custom_dict(
+    constraints, constraint_type : str, fields : Dict[str, Tuple[Any, bool]],
+    new_action : ConstraintActionEnum = ConstraintActionEnum.CONSTRAINTACTION_SET
+) -> Constraint:
     # fields: Dict[field_name : str, Tuple[field_value : Any, raise_if_differs : bool]]
 
     for constraint in constraints:
@@ -61,6 +67,8 @@ def update_constraint_custom_dict(constraints, constraint_type : str, fields : D
         constraint.custom.constraint_type = constraint_type
         json_constraint_value = {}
 
+    constraint.action = new_action
+
     for field_name,(field_value, raise_if_differs) in fields.items():
         if (field_name not in json_constraint_value) or not raise_if_differs:
             # missing or raise_if_differs=False, add/update it
@@ -75,7 +83,8 @@ def update_constraint_custom_dict(constraints, constraint_type : str, fields : D
 
 def update_constraint_endpoint_location(
     constraints, endpoint_id : EndPointId,
-    region : Optional[str] = None, gps_position : Optional[Tuple[float, float]] = None
+    region : Optional[str] = None, gps_position : Optional[Tuple[float, float]] = None,
+    new_action : ConstraintActionEnum = ConstraintActionEnum.CONSTRAINTACTION_SET
 ) -> Constraint:
     # gps_position: (latitude, longitude)
     if region is not None and gps_position is not None:
@@ -103,6 +112,8 @@ def update_constraint_endpoint_location(
         _endpoint_id.topology_id.topology_uuid.uuid = topology_uuid
         _endpoint_id.topology_id.context_id.context_uuid.uuid = context_uuid
 
+    constraint.action = new_action
+
     location = constraint.endpoint_location.location
     if region is not None:
         location.region = region
@@ -111,7 +122,10 @@ def update_constraint_endpoint_location(
         location.gps_position.longitude = gps_position[1]
     return constraint
 
-def update_constraint_endpoint_priority(constraints, endpoint_id : EndPointId, priority : int) -> Constraint:
+def update_constraint_endpoint_priority(
+    constraints, endpoint_id : EndPointId, priority : int,
+    new_action : ConstraintActionEnum = ConstraintActionEnum.CONSTRAINTACTION_SET
+) -> Constraint:
     endpoint_uuid = endpoint_id.endpoint_uuid.uuid
     device_uuid = endpoint_id.device_id.device_uuid.uuid
     topology_uuid = endpoint_id.topology_id.topology_uuid.uuid
@@ -134,10 +148,15 @@ def update_constraint_endpoint_priority(constraints, endpoint_id : EndPointId, p
         _endpoint_id.topology_id.topology_uuid.uuid = topology_uuid
         _endpoint_id.topology_id.context_id.context_uuid.uuid = context_uuid
 
+    constraint.action = new_action
+
     constraint.endpoint_priority.priority = priority
     return constraint
 
-def update_constraint_sla_capacity(constraints, capacity_gbps : float) -> Constraint:
+def update_constraint_sla_capacity(
+    constraints, capacity_gbps : float,
+    new_action : ConstraintActionEnum = ConstraintActionEnum.CONSTRAINTACTION_SET
+) -> Constraint:
     for constraint in constraints:
         if constraint.WhichOneof('constraint') != 'sla_capacity': continue
         break   # found, end loop
@@ -145,10 +164,15 @@ def update_constraint_sla_capacity(constraints, capacity_gbps : float) -> Constr
         # not found, add it
         constraint = constraints.add()      # pylint: disable=no-member
 
+    constraint.action = new_action
+
     constraint.sla_capacity.capacity_gbps = capacity_gbps
     return constraint
 
-def update_constraint_sla_latency(constraints, e2e_latency_ms : float) -> Constraint:
+def update_constraint_sla_latency(
+    constraints, e2e_latency_ms : float,
+    new_action : ConstraintActionEnum = ConstraintActionEnum.CONSTRAINTACTION_SET
+) -> Constraint:
     for constraint in constraints:
         if constraint.WhichOneof('constraint') != 'sla_latency': continue
         break   # found, end loop
@@ -156,11 +180,14 @@ def update_constraint_sla_latency(constraints, e2e_latency_ms : float) -> Constr
         # not found, add it
         constraint = constraints.add()      # pylint: disable=no-member
 
+    constraint.action = new_action
+
     constraint.sla_latency.e2e_latency_ms = e2e_latency_ms
     return constraint
 
 def update_constraint_sla_availability(
-    constraints, num_disjoint_paths : int, all_active : bool, availability : float
+    constraints, num_disjoint_paths : int, all_active : bool, availability : float,
+    new_action : ConstraintActionEnum = ConstraintActionEnum.CONSTRAINTACTION_SET
 ) -> Constraint:
     for constraint in constraints:
         if constraint.WhichOneof('constraint') != 'sla_availability': continue
@@ -169,12 +196,17 @@ def update_constraint_sla_availability(
         # not found, add it
         constraint = constraints.add()      # pylint: disable=no-member
 
+    constraint.action = new_action
+
     constraint.sla_availability.num_disjoint_paths = num_disjoint_paths
     constraint.sla_availability.all_active = all_active
     constraint.sla_availability.availability = availability
     return constraint
 
-def update_constraint_sla_isolation(constraints, isolation_levels : List[int]) -> Constraint:
+def update_constraint_sla_isolation(
+    constraints, isolation_levels : List[int],
+    new_action : ConstraintActionEnum = ConstraintActionEnum.CONSTRAINTACTION_SET
+) -> Constraint:
     for constraint in constraints:
         if constraint.WhichOneof('constraint') != 'sla_isolation': continue
         break   # found, end loop
@@ -182,6 +214,8 @@ def update_constraint_sla_isolation(constraints, isolation_levels : List[int]) -
         # not found, add it
         constraint = constraints.add()      # pylint: disable=no-member
 
+    constraint.action = new_action
+
     for isolation_level in isolation_levels:
         if isolation_level in constraint.sla_isolation.isolation_level: continue
         constraint.sla_isolation.isolation_level.append(isolation_level)
diff --git a/src/device/service/DeviceServiceServicerImpl.py b/src/device/service/DeviceServiceServicerImpl.py
index eeffdd7b0592b5166c06c1597e17f79adcfd25bb..3df7c482272804eb2589ed1e7569f0a2e822ad21 100644
--- a/src/device/service/DeviceServiceServicerImpl.py
+++ b/src/device/service/DeviceServiceServicerImpl.py
@@ -73,6 +73,13 @@ class DeviceServiceServicerImpl(DeviceServiceServicer):
             device.device_operational_status = DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_UNDEFINED
             device.device_drivers.extend(request.device_drivers)    # pylint: disable=no-member
             device.device_config.CopyFrom(request.device_config)    # pylint: disable=no-member
+
+            if request.HasField('controller_id'):
+                controller_id = request.controller_id
+                if controller_id.HasField('device_uuid'):
+                    controller_device_uuid = controller_id.device_uuid.uuid
+                    device.controller_id.device_uuid.uuid = controller_device_uuid
+
             device_id = context_client.SetDevice(device)
             device = get_device(context_client, device_id.device_uuid.uuid, rw_copy=True)
 
diff --git a/src/device/service/drivers/ietf_actn/IetfActnDriver.py b/src/device/service/drivers/ietf_actn/IetfActnDriver.py
index a33c403f3202ca5ee3025a7b7808ad53a89ede4a..5f80f5333cc55ccddebd971d4aadbfa1c195ee21 100644
--- a/src/device/service/drivers/ietf_actn/IetfActnDriver.py
+++ b/src/device/service/drivers/ietf_actn/IetfActnDriver.py
@@ -16,7 +16,7 @@ import json, logging, requests, threading
 from typing import Any, Iterator, List, Optional, Tuple, Union
 from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method
 from common.type_checkers.Checkers import chk_string, chk_type
-from device.service.driver_api._Driver import _Driver, RESOURCE_SERVICES
+from device.service.driver_api._Driver import _Driver, RESOURCE_ENDPOINTS, RESOURCE_SERVICES
 from .handlers.EthtServiceHandler import EthtServiceHandler
 from .handlers.OsuTunnelHandler import OsuTunnelHandler
 from .handlers.RestApiClient import RestApiClient
@@ -25,6 +25,7 @@ from .Tools import get_etht_services, get_osu_tunnels, parse_resource_key
 LOGGER = logging.getLogger(__name__)
 
 ALL_RESOURCE_KEYS = [
+    RESOURCE_ENDPOINTS,
     RESOURCE_SERVICES,
 ]
 
@@ -78,7 +79,12 @@ class IetfActnDriver(_Driver):
                 try:
                     _results = list()
 
-                    if resource_key == RESOURCE_SERVICES:
+                    if resource_key == RESOURCE_ENDPOINTS:
+                        # Add mgmt endpoint by default
+                        resource_key = '/endpoints/endpoint[mgmt]'
+                        resource_value = {'uuid': 'mgmt', 'name': 'mgmt', 'type': 'mgmt'}
+                        results.append((resource_key, resource_value))
+                    elif resource_key == RESOURCE_SERVICES:
                         get_osu_tunnels(self._handler_osu_tunnel, _results)
                         get_etht_services(self._handler_etht_service, _results)
                     else:
diff --git a/src/device/service/drivers/openconfig/templates/Interfaces.py b/src/device/service/drivers/openconfig/templates/Interfaces.py
index 3d4c73fc11c686b4d4e181a1f98ed3f5922f7c15..51ee9fc66334a30a450144b4b8079575498aeef9 100644
--- a/src/device/service/drivers/openconfig/templates/Interfaces.py
+++ b/src/device/service/drivers/openconfig/templates/Interfaces.py
@@ -31,10 +31,6 @@ def parse(xml_data : ET.Element) -> List[Tuple[str, Dict[str, Any]]]:
 
         interface = {}
 
-        interface_name = xml_interface.find('oci:name', namespaces=NAMESPACES)
-        if interface_name is None or interface_name.text is None: continue
-        add_value_from_tag(interface, 'name', interface_name)
-
         #interface_type = xml_interface.find('oci:config/oci:type', namespaces=NAMESPACES)
         #add_value_from_tag(interface, 'type', interface_type)
 
@@ -42,8 +38,11 @@ def parse(xml_data : ET.Element) -> List[Tuple[str, Dict[str, Any]]]:
             interface_type = xml_interface.find('oci:config/oci:type', namespaces=NAMESPACES)
         elif xml_interface.find('oci:state/oci:type', namespaces=NAMESPACES) is not None:
             interface_type = xml_interface.find('oci:state/oci:type', namespaces=NAMESPACES)
-        else:
-            interface_type = ''
+        else: continue
+            
+        interface_name = xml_interface.find('oci:name', namespaces=NAMESPACES)
+        if interface_name is None or interface_name.text is None: continue
+        add_value_from_tag(interface, 'name', interface_name)
             
         # Get the type of interface according to the vendor's type
         if 'ianaift:' in interface_type.text:
diff --git a/src/device/service/drivers/openconfig/templates/Inventory.py b/src/device/service/drivers/openconfig/templates/Inventory.py
index 2ae67ba47dad162b8c8e4a15d3004b27359d4ca2..916af0478c51d381d670b7678095867537f3bdc9 100644
--- a/src/device/service/drivers/openconfig/templates/Inventory.py
+++ b/src/device/service/drivers/openconfig/templates/Inventory.py
@@ -54,7 +54,6 @@ XPATH_PORTS = "//ocp:components/ocp:component"
 
 def parse(xml_data : ET.Element) -> List[Tuple[str, Dict[str, Any]]]:
     response = []
-    LOGGER.debug("InventoryPrueba")
     parent_types = {}
     for xml_component in xml_data.xpath(XPATH_PORTS, namespaces=NAMESPACES):
         LOGGER.info('xml_component inventario = {:s}'.format(str(ET.tostring(xml_component))))
@@ -78,9 +77,9 @@ def parse(xml_data : ET.Element) -> List[Tuple[str, Dict[str, Any]]]:
             add_value_from_tag(inventory['attributes'], 'location', component_location)
         
         component_type = xml_component.find('ocp:state/ocp:type', namespaces=NAMESPACES)
-        component_type.text = component_type.text.replace('oc-platform-types:','')
-        if component_type is None: continue
-        add_value_from_tag(inventory, 'class', component_type)
+        if component_type is not None:
+            component_type.text = component_type.text.replace('oc-platform-types:','')
+            add_value_from_tag(inventory, 'class', component_type)
         
         if inventory['class'] == 'CPU' or inventory['class'] == 'STORAGE': continue
 
diff --git a/src/device/service/drivers/openconfig/templates/RoutingPolicy.py b/src/device/service/drivers/openconfig/templates/RoutingPolicy.py
index acafa021824f94f929e849117824e8120974d0b1..96dc1c5a49d0007f4b0a28b10c2f35868a18a71f 100644
--- a/src/device/service/drivers/openconfig/templates/RoutingPolicy.py
+++ b/src/device/service/drivers/openconfig/templates/RoutingPolicy.py
@@ -35,7 +35,7 @@ def parse(xml_data : ET.Element) -> List[Tuple[str, Dict[str, Any]]]:
         #LOGGER.info('xml_policy_definition = {:s}'.format(str(ET.tostring(xml_policy_definition))))
 
         policy_definition = {}
-
+        statement_name = ''
         policy_name = xml_policy_definition.find('ocrp:name', namespaces=NAMESPACES)
         if policy_name is None or policy_name.text is None: continue
         add_value_from_tag(policy_definition, 'policy_name', policy_name)
diff --git a/src/device/tests/data/ietf_actn/config_rules.json b/src/device/tests/data/ietf_actn/config_rules.json
index d73a68674731cf4af7321044d345470c03d68134..d106a5a8f9eb705e975c14aacd489ecfa042625f 100644
--- a/src/device/tests/data/ietf_actn/config_rules.json
+++ b/src/device/tests/data/ietf_actn/config_rules.json
@@ -26,7 +26,7 @@
             ["128.32.20.5", 24, "128.32.33.5"]
         ],
         "dst_node_id": "10.0.30.1", "dst_tp_id": "200", "dst_vlan_tag": 201, "dst_static_routes": [
-            ["172.1.101.22", 24, "172.10.33.5"]
+            ["172.1.201.22", 24, "172.10.33.5"]
         ]
     }}}
 ]
diff --git a/src/device/tests/data/ietf_actn/expected_etht_services.json b/src/device/tests/data/ietf_actn/expected_etht_services.json
index d9f41052692936fd5fffd855b1ba3f3e0478a3b6..72c48e6b3351a4adc5737bbc1f63952363893f96 100644
--- a/src/device/tests/data/ietf_actn/expected_etht_services.json
+++ b/src/device/tests/data/ietf_actn/expected_etht_services.json
@@ -139,7 +139,7 @@
                             "is-terminal": true,
                             "static-route-list": [
                                 {
-                                    "destination": "172.1.101.22",
+                                    "destination": "172.1.201.22",
                                     "destination-mask": 24,
                                     "next-hop": "172.10.33.5"
                                 }
diff --git a/src/device/tests/test_unitary_ietf_actn.py b/src/device/tests/test_unitary_ietf_actn.py
index 5f01a412d88bca142d2bd96ce238947844bc9087..011b3ddbc54d9b68ec6c95cc6b0816385ad93eff 100644
--- a/src/device/tests/test_unitary_ietf_actn.py
+++ b/src/device/tests/test_unitary_ietf_actn.py
@@ -151,6 +151,11 @@ def test_device_ietf_actn_configure(
     retrieved_driver_config_rules = sorted(driver.GetConfig(), key=operator.itemgetter(0))
     LOGGER.info('driver_config = {:s}'.format(str(retrieved_driver_config_rules)))
     assert isinstance(retrieved_driver_config_rules, list)
+    retrieved_driver_config_rules = [
+        (resource_key, resource_value)
+        for resource_key, resource_value in retrieved_driver_config_rules
+        if resource_key != '/endpoints/endpoint[mgmt]'
+    ]
     if len(retrieved_driver_config_rules) > 0:
         LOGGER.error('PRE DRIVER CONFIG RULES - Differences:\n{:s}'.format(str(retrieved_driver_config_rules)))
     assert len(retrieved_driver_config_rules) == 0
@@ -186,6 +191,7 @@ def test_device_ietf_actn_configure(
     retrieved_driver_config_rules = [
         {'action': 1, 'custom': {'resource_key': resource_key, 'resource_value': resource_value}}
         for resource_key, resource_value in retrieved_driver_config_rules
+        if resource_key != '/endpoints/endpoint[mgmt]'
     ]
     with open(DATA_FILE_CONFIG_RULES, 'r', encoding='UTF-8') as f:
         expected_driver_config_rules = sorted(json.load(f), key=lambda cr: cr['custom']['resource_key'])
@@ -231,6 +237,7 @@ def test_device_ietf_actn_deconfigure(
     retrieved_driver_config_rules = [
         {'action': 1, 'custom': {'resource_key': resource_key, 'resource_value': resource_value}}
         for resource_key, resource_value in retrieved_driver_config_rules
+        if resource_key != '/endpoints/endpoint[mgmt]'
     ]
     with open(DATA_FILE_CONFIG_RULES, 'r', encoding='UTF-8') as f:
         expected_driver_config_rules = sorted(json.load(f), key=lambda cr: cr['custom']['resource_key'])
@@ -266,6 +273,11 @@ def test_device_ietf_actn_deconfigure(
     retrieved_driver_config_rules = sorted(driver.GetConfig(), key=operator.itemgetter(0))
     LOGGER.info('retrieved_driver_config_rules = {:s}'.format(str(retrieved_driver_config_rules)))
     assert isinstance(retrieved_driver_config_rules, list)
+    retrieved_driver_config_rules = [
+        (resource_key, resource_value)
+        for resource_key, resource_value in retrieved_driver_config_rules
+        if resource_key != '/endpoints/endpoint[mgmt]'
+    ]
     if len(retrieved_driver_config_rules) > 0:
         LOGGER.error('POST DRIVER CONFIG RULES - Differences:\n{:s}'.format(str(retrieved_driver_config_rules)))
     assert len(retrieved_driver_config_rules) == 0
diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/Handlers.py b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/Handlers.py
index 2192ea94214201697d196ca7546a906a13197a93..80c7b32ddf6cabf8a6c124ec20ddae2f5cd181ad 100644
--- a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/Handlers.py
+++ b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/Handlers.py
@@ -55,7 +55,7 @@ def process_vpn_service(
 
 def update_service_endpoint(
     service_uuid : str, site_id : str, device_uuid : str, endpoint_uuid : str,
-    vlan_tag : int, ipv4_address : str, ipv4_prefix_length : int,
+    vlan_tag : int, ipv4_address : str, neighbor_ipv4_address : str, ipv4_prefix_length : int,
     capacity_gbps : Optional[float] = None, e2e_latency_ms : Optional[float] = None,
     availability : Optional[float] = None, mtu : Optional[int] = None,
     static_routing : Optional[Dict[Tuple[str, str], str]] = None,
@@ -91,12 +91,15 @@ def update_service_endpoint(
             for (ip_range, ip_prefix_len, lan_tag), next_hop in static_routing.items()
         })
 
-    ENDPOINT_SETTINGS_KEY = '/device[{:s}]/endpoint[{:s}]/vlan[{:d}]/settings'
-    endpoint_settings_key = ENDPOINT_SETTINGS_KEY.format(device_uuid, endpoint_uuid, vlan_tag)
+    #ENDPOINT_SETTINGS_KEY = '/device[{:s}]/endpoint[{:s}]/vlan[{:d}]/settings'
+    #endpoint_settings_key = ENDPOINT_SETTINGS_KEY.format(device_uuid, endpoint_uuid, vlan_tag)
+    ENDPOINT_SETTINGS_KEY = '/device[{:s}]/endpoint[{:s}]/settings'
+    endpoint_settings_key = ENDPOINT_SETTINGS_KEY.format(device_uuid, endpoint_uuid)
     field_updates = {}
-    if vlan_tag           is not None: field_updates['vlan_tag'     ] = (vlan_tag,           True)
-    if ipv4_address       is not None: field_updates['ip_address'   ] = (ipv4_address,       True)
-    if ipv4_prefix_length is not None: field_updates['prefix_length'] = (ipv4_prefix_length, True)
+    if vlan_tag              is not None: field_updates['vlan_tag'        ] = (vlan_tag,              True)
+    if ipv4_address          is not None: field_updates['ip_address'      ] = (ipv4_address,          True)
+    if neighbor_ipv4_address is not None: field_updates['neighbor_address'] = (neighbor_ipv4_address, True)
+    if ipv4_prefix_length    is not None: field_updates['prefix_length'   ] = (ipv4_prefix_length,    True)
     update_config_rule_custom(config_rules, endpoint_settings_key, field_updates)
 
     try:
@@ -131,7 +134,7 @@ def process_site_network_access(
         raise NotImplementedError(MSG.format(str(ipv4_allocation['address-allocation-type'])))
     ipv4_allocation_addresses = ipv4_allocation['addresses']
     ipv4_provider_address = ipv4_allocation_addresses['provider-address']
-    #ipv4_customer_address = ipv4_allocation_addresses['customer-address']
+    ipv4_customer_address = ipv4_allocation_addresses['customer-address']
     ipv4_prefix_length    = ipv4_allocation_addresses['prefix-length'   ]
 
     vlan_tag = None
@@ -176,7 +179,8 @@ def process_site_network_access(
         availability       = qos_profile_class['bandwidth']['guaranteed-bw-percent']
 
     exc = update_service_endpoint(
-        service_uuid, site_id, device_uuid, endpoint_uuid, vlan_tag, ipv4_provider_address, ipv4_prefix_length,
+        service_uuid, site_id, device_uuid, endpoint_uuid,
+        vlan_tag, ipv4_customer_address, ipv4_provider_address, ipv4_prefix_length,
         capacity_gbps=service_bandwidth_gbps, e2e_latency_ms=max_e2e_latency_ms, availability=availability,
         mtu=service_mtu, static_routing=site_static_routing
     )
diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/L3VPN_Services.py b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/L3VPN_Services.py
index 13d5c532478e93ef1bd3b1c644d7f3c3ba927af5..6bd57c8238c1af63ed3f504593f3c70cf8a68cc6 100644
--- a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/L3VPN_Services.py
+++ b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/L3VPN_Services.py
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 import logging
-from typing import Dict
+from typing import Dict, List
 from flask import request
 from flask.json import jsonify
 from flask_restful import Resource
@@ -36,11 +36,40 @@ class L3VPN_Services(Resource):
         request_data : Dict = request.json
         LOGGER.debug('Request: {:s}'.format(str(request_data)))
 
+        errors = list()
+        if 'ietf-l3vpn-svc:l3vpn-services' in request_data:
+            # processing multiple L3VPN service requests formatted as:
+            #{
+            #  "ietf-l3vpn-svc:l3vpn-services": {
+            #    "l3vpn-svc": [
+            #      {
+            #        "service-id": "vpn1",
+            #        "vpn-services": {
+            #          "vpn-service": [
+            for l3vpn_svc in request_data['ietf-l3vpn-svc:l3vpn-services']['l3vpn-svc']:
+                l3vpn_svc.pop('service-id', None)
+                l3vpn_svc_request_data = {'ietf-l3vpn-svc:l3vpn-svc': l3vpn_svc}
+                errors.extend(self._process_l3vpn(l3vpn_svc_request_data))
+        elif 'ietf-l3vpn-svc:l3vpn-svc' in request_data:
+            # processing single (standard) L3VPN service request formatted as:
+            #{
+            #  "ietf-l3vpn-svc:l3vpn-svc": {
+            #    "vpn-services": {
+            #      "vpn-service": [
+            errors.extend(self._process_l3vpn(request_data))
+        else:
+            errors.append('unexpected request: {:s}'.format(str(request_data)))
+
+        response = jsonify(errors)
+        response.status_code = HTTP_CREATED if len(errors) == 0 else HTTP_SERVERERROR
+        return response
+
+    def _process_l3vpn(self, request_data : Dict) -> List[Dict]:
         yang_validator = YangValidator('ietf-l3vpn-svc')
         request_data = yang_validator.parse_to_dict(request_data)
         yang_validator.destroy()
 
-        errors = []
+        errors = list()
 
         for vpn_service in request_data['l3vpn-svc']['vpn-services']['vpn-service']:
             process_vpn_service(vpn_service, errors)
@@ -48,6 +77,4 @@ class L3VPN_Services(Resource):
         for site in request_data['l3vpn-svc']['sites']['site']:
             process_site(site, errors)
 
-        response = jsonify(errors)
-        response.status_code = HTTP_CREATED if len(errors) == 0 else HTTP_SERVERERROR
-        return response
+        return errors
diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/yang/ietf_l3vpn_tree.txt b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/yang/ietf_l3vpn_tree.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e811c7c1b1a25214926341dc0205ee9f1b150c63
--- /dev/null
+++ b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/yang/ietf_l3vpn_tree.txt
@@ -0,0 +1,413 @@
+module: ietf-l3vpn-svc
+  +--rw l3vpn-svc
+     +--rw vpn-profiles
+     |  +--rw valid-provider-identifiers
+     |     +--rw cloud-identifier* [id] {cloud-access}?
+     |     |  +--rw id    string
+     |     +--rw encryption-profile-identifier* [id]
+     |     |  +--rw id    string
+     |     +--rw qos-profile-identifier* [id]
+     |     |  +--rw id    string
+     |     +--rw bfd-profile-identifier* [id]
+     |        +--rw id    string
+     +--rw vpn-services
+     |  +--rw vpn-service* [vpn-id]
+     |     +--rw vpn-id                  svc-id
+     |     +--rw customer-name?          string
+     |     +--rw vpn-service-topology?   identityref
+     |     +--rw cloud-accesses {cloud-access}?
+     |     |  +--rw cloud-access* [cloud-identifier]
+     |     |     +--rw cloud-identifier       -> /l3vpn-svc/vpn-profiles/valid-provider-identifiers/cloud-identifier/id
+     |     |     +--rw (list-flavor)?
+     |     |     |  +--:(permit-any)
+     |     |     |  |  +--rw permit-any?   empty
+     |     |     |  +--:(deny-any-except)
+     |     |     |  |  +--rw permit-site*   -> /l3vpn-svc/sites/site/site-id
+     |     |     |  +--:(permit-any-except)
+     |     |     |     +--rw deny-site*   -> /l3vpn-svc/sites/site/site-id
+     |     |     +--rw address-translation
+     |     |        +--rw nat44
+     |     |           +--rw enabled?                  boolean
+     |     |           +--rw nat44-customer-address?   inet:ipv4-address
+     |     +--rw multicast {multicast}?
+     |     |  +--rw enabled?                 boolean
+     |     |  +--rw customer-tree-flavors
+     |     |  |  +--rw tree-flavor*   identityref
+     |     |  +--rw rp
+     |     |     +--rw rp-group-mappings
+     |     |     |  +--rw rp-group-mapping* [id]
+     |     |     |     +--rw id                  uint16
+     |     |     |     +--rw provider-managed
+     |     |     |     |  +--rw enabled?                    boolean
+     |     |     |     |  +--rw rp-redundancy?              boolean
+     |     |     |     |  +--rw optimal-traffic-delivery?   boolean
+     |     |     |     +--rw rp-address          inet:ip-address
+     |     |     |     +--rw groups
+     |     |     |        +--rw group* [id]
+     |     |     |           +--rw id                uint16
+     |     |     |           +--rw (group-format)
+     |     |     |              +--:(singleaddress)
+     |     |     |              |  +--rw group-address?   inet:ip-address
+     |     |     |              +--:(startend)
+     |     |     |                 +--rw group-start?   inet:ip-address
+     |     |     |                 +--rw group-end?     inet:ip-address
+     |     |     +--rw rp-discovery
+     |     |        +--rw rp-discovery-type?   identityref
+     |     |        +--rw bsr-candidates
+     |     |           +--rw bsr-candidate-address*   inet:ip-address
+     |     +--rw carrierscarrier?        boolean {carrierscarrier}?
+     |     +--rw extranet-vpns {extranet-vpn}?
+     |        +--rw extranet-vpn* [vpn-id]
+     |           +--rw vpn-id              svc-id
+     |           +--rw local-sites-role?   identityref
+     +--rw sites
+        +--rw site* [site-id]
+           +--rw site-id                  svc-id
+           +--rw requested-site-start?    yang:date-and-time
+           +--rw requested-site-stop?     yang:date-and-time
+           +--rw locations
+           |  +--rw location* [location-id]
+           |     +--rw location-id     svc-id
+           |     +--rw address?        string
+           |     +--rw postal-code?    string
+           |     +--rw state?          string
+           |     +--rw city?           string
+           |     +--rw country-code?   string
+           +--rw devices
+           |  +--rw device* [device-id]
+           |     +--rw device-id     svc-id
+           |     +--rw location      -> ../../../locations/location/location-id
+           |     +--rw management
+           |        +--rw address-family?   address-family
+           |        +--rw address           inet:ip-address
+           +--rw site-diversity {site-diversity}?
+           |  +--rw groups
+           |     +--rw group* [group-id]
+           |        +--rw group-id    string
+           +--rw management
+           |  +--rw type    identityref
+           +--rw vpn-policies
+           |  +--rw vpn-policy* [vpn-policy-id]
+           |     +--rw vpn-policy-id    svc-id
+           |     +--rw entries* [id]
+           |        +--rw id         svc-id
+           |        +--rw filters
+           |        |  +--rw filter* [type]
+           |        |     +--rw type               identityref
+           |        |     +--rw lan-tag*           string {lan-tag}?
+           |        |     +--rw ipv4-lan-prefix*   inet:ipv4-prefix {ipv4}?
+           |        |     +--rw ipv6-lan-prefix*   inet:ipv6-prefix {ipv6}?
+           |        +--rw vpn* [vpn-id]
+           |           +--rw vpn-id       -> /l3vpn-svc/vpn-services/vpn-service/vpn-id
+           |           +--rw site-role?   identityref
+           +--rw site-vpn-flavor?         identityref
+           +--rw maximum-routes
+           |  +--rw address-family* [af]
+           |     +--rw af                address-family
+           |     +--rw maximum-routes?   uint32
+           +--rw security
+           |  +--rw authentication
+           |  +--rw encryption {encryption}?
+           |     +--rw enabled?              boolean
+           |     +--rw layer?                enumeration
+           |     +--rw encryption-profile
+           |        +--rw (profile)?
+           |           +--:(provider-profile)
+           |           |  +--rw profile-name?   -> /l3vpn-svc/vpn-profiles/valid-provider-identifiers/encryption-profile-identifier/id
+           |           +--:(customer-profile)
+           |              +--rw algorithm?    string
+           |              +--rw (key-type)?
+           |                 +--:(psk)
+           |                    +--rw preshared-key?   string
+           +--rw service
+           |  +--rw qos {qos}?
+           |  |  +--rw qos-classification-policy
+           |  |  |  +--rw rule* [id]
+           |  |  |     +--rw id                 string
+           |  |  |     +--rw (match-type)?
+           |  |  |     |  +--:(match-flow)
+           |  |  |     |  |  +--rw match-flow
+           |  |  |     |  |     +--rw dscp?                inet:dscp
+           |  |  |     |  |     +--rw dot1p?               uint8
+           |  |  |     |  |     +--rw ipv4-src-prefix?     inet:ipv4-prefix
+           |  |  |     |  |     +--rw ipv6-src-prefix?     inet:ipv6-prefix
+           |  |  |     |  |     +--rw ipv4-dst-prefix?     inet:ipv4-prefix
+           |  |  |     |  |     +--rw ipv6-dst-prefix?     inet:ipv6-prefix
+           |  |  |     |  |     +--rw l4-src-port?         inet:port-number
+           |  |  |     |  |     +--rw target-sites*        svc-id {target-sites}?
+           |  |  |     |  |     +--rw l4-src-port-range
+           |  |  |     |  |     |  +--rw lower-port?   inet:port-number
+           |  |  |     |  |     |  +--rw upper-port?   inet:port-number
+           |  |  |     |  |     +--rw l4-dst-port?         inet:port-number
+           |  |  |     |  |     +--rw l4-dst-port-range
+           |  |  |     |  |     |  +--rw lower-port?   inet:port-number
+           |  |  |     |  |     |  +--rw upper-port?   inet:port-number
+           |  |  |     |  |     +--rw protocol-field?      union
+           |  |  |     |  +--:(match-application)
+           |  |  |     |     +--rw match-application?   identityref
+           |  |  |     +--rw target-class-id?   string
+           |  |  +--rw qos-profile
+           |  |     +--rw (qos-profile)?
+           |  |        +--:(standard)
+           |  |        |  +--rw profile?   -> /l3vpn-svc/vpn-profiles/valid-provider-identifiers/qos-profile-identifier/id
+           |  |        +--:(custom)
+           |  |           +--rw classes {qos-custom}?
+           |  |              +--rw class* [class-id]
+           |  |                 +--rw class-id      string
+           |  |                 +--rw direction?    identityref
+           |  |                 +--rw rate-limit?   decimal64
+           |  |                 +--rw latency
+           |  |                 |  +--rw (flavor)?
+           |  |                 |     +--:(lowest)
+           |  |                 |     |  +--rw use-lowest-latency?   empty
+           |  |                 |     +--:(boundary)
+           |  |                 |        +--rw latency-boundary?   uint16
+           |  |                 +--rw jitter
+           |  |                 |  +--rw (flavor)?
+           |  |                 |     +--:(lowest)
+           |  |                 |     |  +--rw use-lowest-jitter?   empty
+           |  |                 |     +--:(boundary)
+           |  |                 |        +--rw latency-boundary?   uint32
+           |  |                 +--rw bandwidth
+           |  |                    +--rw guaranteed-bw-percent    decimal64
+           |  |                    +--rw end-to-end?              empty
+           |  +--rw carrierscarrier {carrierscarrier}?
+           |  |  +--rw signalling-type?   enumeration
+           |  +--rw multicast {multicast}?
+           |     +--rw multicast-site-type?        enumeration
+           |     +--rw multicast-address-family
+           |     |  +--rw ipv4?   boolean {ipv4}?
+           |     |  +--rw ipv6?   boolean {ipv6}?
+           |     +--rw protocol-type?              enumeration
+           +--rw traffic-protection {fast-reroute}?
+           |  +--rw enabled?   boolean
+           +--rw routing-protocols
+           |  +--rw routing-protocol* [type]
+           |     +--rw type      identityref
+           |     +--rw ospf {rtg-ospf}?
+           |     |  +--rw address-family*   address-family
+           |     |  +--rw area-address      yang:dotted-quad
+           |     |  +--rw metric?           uint16
+           |     |  +--rw sham-links {rtg-ospf-sham-link}?
+           |     |     +--rw sham-link* [target-site]
+           |     |        +--rw target-site    svc-id
+           |     |        +--rw metric?        uint16
+           |     +--rw bgp {rtg-bgp}?
+           |     |  +--rw autonomous-system    uint32
+           |     |  +--rw address-family*      address-family
+           |     +--rw static
+           |     |  +--rw cascaded-lan-prefixes
+           |     |     +--rw ipv4-lan-prefixes* [lan next-hop] {ipv4}?
+           |     |     |  +--rw lan         inet:ipv4-prefix
+           |     |     |  +--rw next-hop    inet:ipv4-address
+           |     |     |  +--rw lan-tag?    string
+           |     |     +--rw ipv6-lan-prefixes* [lan next-hop] {ipv6}?
+           |     |        +--rw lan         inet:ipv6-prefix
+           |     |        +--rw next-hop    inet:ipv6-address
+           |     |        +--rw lan-tag?    string
+           |     +--rw rip {rtg-rip}?
+           |     |  +--rw address-family*   address-family
+           |     +--rw vrrp {rtg-vrrp}?
+           |        +--rw address-family*   address-family
+           +--ro actual-site-start?       yang:date-and-time
+           +--ro actual-site-stop?        yang:date-and-time
+           +--rw site-network-accesses
+              +--rw site-network-access* [site-network-access-id]
+                 +--rw site-network-access-id      svc-id
+                 +--rw site-network-access-type?   identityref
+                 +--rw (location-flavor)
+                 |  +--:(location)
+                 |  |  +--rw location-reference?   -> ../../../locations/location/location-id
+                 |  +--:(device)
+                 |     +--rw device-reference?   -> ../../../devices/device/device-id
+                 +--rw access-diversity {site-diversity}?
+                 |  +--rw groups
+                 |  |  +--rw group* [group-id]
+                 |  |     +--rw group-id    string
+                 |  +--rw constraints
+                 |     +--rw constraint* [constraint-type]
+                 |        +--rw constraint-type    identityref
+                 |        +--rw target
+                 |           +--rw (target-flavor)?
+                 |              +--:(id)
+                 |              |  +--rw group* [group-id]
+                 |              |     +--rw group-id    string
+                 |              +--:(all-accesses)
+                 |              |  +--rw all-other-accesses?   empty
+                 |              +--:(all-groups)
+                 |                 +--rw all-other-groups?   empty
+                 +--rw bearer
+                 |  +--rw requested-type {requested-type}?
+                 |  |  +--rw requested-type?   string
+                 |  |  +--rw strict?           boolean
+                 |  +--rw always-on?          boolean {always-on}?
+                 |  +--rw bearer-reference?   string {bearer-reference}?
+                 +--rw ip-connection
+                 |  +--rw ipv4 {ipv4}?
+                 |  |  +--rw address-allocation-type?   identityref
+                 |  |  +--rw provider-dhcp
+                 |  |  |  +--rw provider-address?   inet:ipv4-address
+                 |  |  |  +--rw prefix-length?      uint8
+                 |  |  |  +--rw (address-assign)?
+                 |  |  |     +--:(number)
+                 |  |  |     |  +--rw number-of-dynamic-address?   uint16
+                 |  |  |     +--:(explicit)
+                 |  |  |        +--rw customer-addresses
+                 |  |  |           +--rw address-group* [group-id]
+                 |  |  |              +--rw group-id         string
+                 |  |  |              +--rw start-address?   inet:ipv4-address
+                 |  |  |              +--rw end-address?     inet:ipv4-address
+                 |  |  +--rw dhcp-relay
+                 |  |  |  +--rw provider-address?        inet:ipv4-address
+                 |  |  |  +--rw prefix-length?           uint8
+                 |  |  |  +--rw customer-dhcp-servers
+                 |  |  |     +--rw server-ip-address*   inet:ipv4-address
+                 |  |  +--rw addresses
+                 |  |     +--rw provider-address?   inet:ipv4-address
+                 |  |     +--rw customer-address?   inet:ipv4-address
+                 |  |     +--rw prefix-length?      uint8
+                 |  +--rw ipv6 {ipv6}?
+                 |  |  +--rw address-allocation-type?   identityref
+                 |  |  +--rw provider-dhcp
+                 |  |  |  +--rw provider-address?   inet:ipv6-address
+                 |  |  |  +--rw prefix-length?      uint8
+                 |  |  |  +--rw (address-assign)?
+                 |  |  |     +--:(number)
+                 |  |  |     |  +--rw number-of-dynamic-address?   uint16
+                 |  |  |     +--:(explicit)
+                 |  |  |        +--rw customer-addresses
+                 |  |  |           +--rw address-group* [group-id]
+                 |  |  |              +--rw group-id         string
+                 |  |  |              +--rw start-address?   inet:ipv6-address
+                 |  |  |              +--rw end-address?     inet:ipv6-address
+                 |  |  +--rw dhcp-relay
+                 |  |  |  +--rw provider-address?        inet:ipv6-address
+                 |  |  |  +--rw prefix-length?           uint8
+                 |  |  |  +--rw customer-dhcp-servers
+                 |  |  |     +--rw server-ip-address*   inet:ipv6-address
+                 |  |  +--rw addresses
+                 |  |     +--rw provider-address?   inet:ipv6-address
+                 |  |     +--rw customer-address?   inet:ipv6-address
+                 |  |     +--rw prefix-length?      uint8
+                 |  +--rw oam
+                 |     +--rw bfd {bfd}?
+                 |        +--rw enabled?      boolean
+                 |        +--rw (holdtime)?
+                 |           +--:(fixed)
+                 |           |  +--rw fixed-value?   uint32
+                 |           +--:(profile)
+                 |              +--rw profile-name?   -> /l3vpn-svc/vpn-profiles/valid-provider-identifiers/bfd-profile-identifier/id
+                 +--rw security
+                 |  +--rw authentication
+                 |  +--rw encryption {encryption}?
+                 |     +--rw enabled?              boolean
+                 |     +--rw layer?                enumeration
+                 |     +--rw encryption-profile
+                 |        +--rw (profile)?
+                 |           +--:(provider-profile)
+                 |           |  +--rw profile-name?   -> /l3vpn-svc/vpn-profiles/valid-provider-identifiers/encryption-profile-identifier/id
+                 |           +--:(customer-profile)
+                 |              +--rw algorithm?    string
+                 |              +--rw (key-type)?
+                 |                 +--:(psk)
+                 |                    +--rw preshared-key?   string
+                 +--rw service
+                 |  +--rw svc-input-bandwidth     uint64
+                 |  +--rw svc-output-bandwidth    uint64
+                 |  +--rw svc-mtu                 uint16
+                 |  +--rw qos {qos}?
+                 |  |  +--rw qos-classification-policy
+                 |  |  |  +--rw rule* [id]
+                 |  |  |     +--rw id                 string
+                 |  |  |     +--rw (match-type)?
+                 |  |  |     |  +--:(match-flow)
+                 |  |  |     |  |  +--rw match-flow
+                 |  |  |     |  |     +--rw dscp?                inet:dscp
+                 |  |  |     |  |     +--rw dot1p?               uint8
+                 |  |  |     |  |     +--rw ipv4-src-prefix?     inet:ipv4-prefix
+                 |  |  |     |  |     +--rw ipv6-src-prefix?     inet:ipv6-prefix
+                 |  |  |     |  |     +--rw ipv4-dst-prefix?     inet:ipv4-prefix
+                 |  |  |     |  |     +--rw ipv6-dst-prefix?     inet:ipv6-prefix
+                 |  |  |     |  |     +--rw l4-src-port?         inet:port-number
+                 |  |  |     |  |     +--rw target-sites*        svc-id {target-sites}?
+                 |  |  |     |  |     +--rw l4-src-port-range
+                 |  |  |     |  |     |  +--rw lower-port?   inet:port-number
+                 |  |  |     |  |     |  +--rw upper-port?   inet:port-number
+                 |  |  |     |  |     +--rw l4-dst-port?         inet:port-number
+                 |  |  |     |  |     +--rw l4-dst-port-range
+                 |  |  |     |  |     |  +--rw lower-port?   inet:port-number
+                 |  |  |     |  |     |  +--rw upper-port?   inet:port-number
+                 |  |  |     |  |     +--rw protocol-field?      union
+                 |  |  |     |  +--:(match-application)
+                 |  |  |     |     +--rw match-application?   identityref
+                 |  |  |     +--rw target-class-id?   string
+                 |  |  +--rw qos-profile
+                 |  |     +--rw (qos-profile)?
+                 |  |        +--:(standard)
+                 |  |        |  +--rw profile?   -> /l3vpn-svc/vpn-profiles/valid-provider-identifiers/qos-profile-identifier/id
+                 |  |        +--:(custom)
+                 |  |           +--rw classes {qos-custom}?
+                 |  |              +--rw class* [class-id]
+                 |  |                 +--rw class-id      string
+                 |  |                 +--rw direction?    identityref
+                 |  |                 +--rw rate-limit?   decimal64
+                 |  |                 +--rw latency
+                 |  |                 |  +--rw (flavor)?
+                 |  |                 |     +--:(lowest)
+                 |  |                 |     |  +--rw use-lowest-latency?   empty
+                 |  |                 |     +--:(boundary)
+                 |  |                 |        +--rw latency-boundary?   uint16
+                 |  |                 +--rw jitter
+                 |  |                 |  +--rw (flavor)?
+                 |  |                 |     +--:(lowest)
+                 |  |                 |     |  +--rw use-lowest-jitter?   empty
+                 |  |                 |     +--:(boundary)
+                 |  |                 |        +--rw latency-boundary?   uint32
+                 |  |                 +--rw bandwidth
+                 |  |                    +--rw guaranteed-bw-percent    decimal64
+                 |  |                    +--rw end-to-end?              empty
+                 |  +--rw carrierscarrier {carrierscarrier}?
+                 |  |  +--rw signalling-type?   enumeration
+                 |  +--rw multicast {multicast}?
+                 |     +--rw multicast-site-type?        enumeration
+                 |     +--rw multicast-address-family
+                 |     |  +--rw ipv4?   boolean {ipv4}?
+                 |     |  +--rw ipv6?   boolean {ipv6}?
+                 |     +--rw protocol-type?              enumeration
+                 +--rw routing-protocols
+                 |  +--rw routing-protocol* [type]
+                 |     +--rw type      identityref
+                 |     +--rw ospf {rtg-ospf}?
+                 |     |  +--rw address-family*   address-family
+                 |     |  +--rw area-address      yang:dotted-quad
+                 |     |  +--rw metric?           uint16
+                 |     |  +--rw sham-links {rtg-ospf-sham-link}?
+                 |     |     +--rw sham-link* [target-site]
+                 |     |        +--rw target-site    svc-id
+                 |     |        +--rw metric?        uint16
+                 |     +--rw bgp {rtg-bgp}?
+                 |     |  +--rw autonomous-system    uint32
+                 |     |  +--rw address-family*      address-family
+                 |     +--rw static
+                 |     |  +--rw cascaded-lan-prefixes
+                 |     |     +--rw ipv4-lan-prefixes* [lan next-hop] {ipv4}?
+                 |     |     |  +--rw lan         inet:ipv4-prefix
+                 |     |     |  +--rw next-hop    inet:ipv4-address
+                 |     |     |  +--rw lan-tag?    string
+                 |     |     +--rw ipv6-lan-prefixes* [lan next-hop] {ipv6}?
+                 |     |        +--rw lan         inet:ipv6-prefix
+                 |     |        +--rw next-hop    inet:ipv6-address
+                 |     |        +--rw lan-tag?    string
+                 |     +--rw rip {rtg-rip}?
+                 |     |  +--rw address-family*   address-family
+                 |     +--rw vrrp {rtg-vrrp}?
+                 |        +--rw address-family*   address-family
+                 +--rw availability
+                 |  +--rw access-priority?   uint32
+                 +--rw vpn-attachment
+                    +--rw (attachment-flavor)
+                       +--:(vpn-policy-id)
+                       |  +--rw vpn-policy-id?   -> ../../../../vpn-policies/vpn-policy/vpn-policy-id
+                       +--:(vpn-id)
+                          +--rw vpn-id?      -> /l3vpn-svc/vpn-services/vpn-service/vpn-id
+                          +--rw site-role?   identityref
diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_network/ComposeNetwork.py b/src/nbi/service/rest_server/nbi_plugins/ietf_network/ComposeNetwork.py
index 6ffc85e387ce4f4691cdc9757d6bd60068bef991..2d3ef29fc1773b8e0c2f762e0978742797e8cce5 100644
--- a/src/nbi/service/rest_server/nbi_plugins/ietf_network/ComposeNetwork.py
+++ b/src/nbi/service/rest_server/nbi_plugins/ietf_network/ComposeNetwork.py
@@ -28,9 +28,11 @@ IGNORE_DEVICE_TYPES = {
     DeviceTypeEnum.DATACENTER.value,
     DeviceTypeEnum.EMULATED_CLIENT.value,
     DeviceTypeEnum.EMULATED_DATACENTER.value,
+    DeviceTypeEnum.EMULATED_IP_SDN_CONTROLLER,
     DeviceTypeEnum.EMULATED_MICROWAVE_RADIO_SYSTEM.value,
     DeviceTypeEnum.EMULATED_OPEN_LINE_SYSTEM.value,
     DeviceTypeEnum.EMULATED_XR_CONSTELLATION.value,
+    DeviceTypeEnum.IP_SDN_CONTROLLER,
     DeviceTypeEnum.MICROWAVE_RADIO_SYSTEM.value,
     DeviceTypeEnum.NETWORK.value,
     DeviceTypeEnum.OPEN_LINE_SYSTEM.value,
@@ -39,10 +41,10 @@ IGNORE_DEVICE_TYPES = {
 
 IGNORE_DEVICE_NAMES = {
     NetworkTypeEnum.TE_OTN_TOPOLOGY: {
-        '128.32.10.1', '128.32.33.5', '128.32.20.5', '128.32.20.1', '128.32.10.5', 'nce-t'
+        'nce-t', '128.32.10.1', '128.32.33.5', '128.32.20.5', '128.32.20.1', '128.32.10.5',
     },
     NetworkTypeEnum.TE_ETH_TRAN_TOPOLOGY: {
-
+        'nce-t',
     },
 }
 
diff --git a/src/nbi/tests/data/ietf_l3vpn_req_svc1.json b/src/nbi/tests/data/ietf_l3vpn_req_svc1.json
index 66e253cb5b99d3b758bba04e1dfa8799e1b13c08..bfeb93fb74c9513ef4d175d5962110127303a2a7 100644
--- a/src/nbi/tests/data/ietf_l3vpn_req_svc1.json
+++ b/src/nbi/tests/data/ietf_l3vpn_req_svc1.json
@@ -39,12 +39,12 @@
                       {
                         "lan": "128.32.10.1/24",
                         "lan-tag": "vlan21",
-                        "next-hop": "128.32.33.5"
+                        "next-hop": "128.32.33.2"
                       },
                       {
                         "lan": "128.32.20.1/24",
                         "lan-tag": "vlan21",
-                        "next-hop": "128.32.33.5"
+                        "next-hop": "128.32.33.2"
                       }
                     ]
                   }
@@ -82,7 +82,7 @@
                             {
                               "lan": "172.1.101.1/24",
                               "lan-tag": "vlan21",
-                              "next-hop": "10.0.10.1"
+                              "next-hop": "128.32.33.254"
                             }
                           ]
                         }
@@ -147,7 +147,7 @@
                       {
                         "lan": "172.1.101.1/24",
                         "lan-tag": "vlan101",
-                        "next-hop": "172.10.33.5"
+                        "next-hop": "172.10.33.2"
                       }
                     ]
                   }
@@ -185,12 +185,12 @@
                             {
                               "lan": "128.32.10.1/24",
                               "lan-tag": "vlan101",
-                              "next-hop": "10.0.30.1"
+                              "next-hop": "172.10.33.254"
                             },
                             {
                               "lan": "128.32.20.1/24",
                               "lan-tag": "vlan101",
-                              "next-hop": "10.0.30.1"
+                              "next-hop": "172.10.33.254"
                             }
                           ]
                         }
diff --git a/src/nbi/tests/data/ietf_l3vpn_req_svc2.json b/src/nbi/tests/data/ietf_l3vpn_req_svc2.json
index 2d2ea2c22e2bb490027b8033bb4fb94a39b35049..2cc512e595c820a8df42ec06af973fefa4601095 100644
--- a/src/nbi/tests/data/ietf_l3vpn_req_svc2.json
+++ b/src/nbi/tests/data/ietf_l3vpn_req_svc2.json
@@ -39,12 +39,12 @@
                       {
                         "lan": "128.32.10.1/24",
                         "lan-tag": "vlan31",
-                        "next-hop": "128.32.33.5"
+                        "next-hop": "128.32.33.2"
                       },
                       {
                         "lan": "128.32.20.1/24",
                         "lan-tag": "vlan31",
-                        "next-hop": "128.32.33.5"
+                        "next-hop": "128.32.33.2"
                       }
                     ]
                   }
@@ -80,9 +80,9 @@
                         "cascaded-lan-prefixes": {
                           "ipv4-lan-prefixes": [
                             {
-                              "lan": "172.1.101.1/24",
+                              "lan": "172.1.201.1/24",
                               "lan-tag": "vlan31",
-                              "next-hop": "10.0.10.1"
+                              "next-hop": "128.32.33.254"
                             }
                           ]
                         }
@@ -145,9 +145,9 @@
                   "cascaded-lan-prefixes": {
                     "ipv4-lan-prefixes": [
                       {
-                        "lan": "172.1.101.1/24",
+                        "lan": "172.1.201.1/24",
                         "lan-tag": "vlan201",
-                        "next-hop": "172.10.33.1"
+                        "next-hop": "172.10.33.2"
                       }
                     ]
                   }
@@ -185,12 +185,12 @@
                             {
                               "lan": "128.32.10.1/24",
                               "lan-tag": "vlan201",
-                              "next-hop": "10.0.30.1"
+                              "next-hop": "172.10.33.254"
                             },
                             {
                               "lan": "128.32.20.1/24",
                               "lan-tag": "vlan201",
-                              "next-hop": "10.0.30.1"
+                              "next-hop": "172.10.33.254"
                             }
                           ]
                         }
diff --git a/src/pathcomp/frontend/service/algorithms/_Algorithm.py b/src/pathcomp/frontend/service/algorithms/_Algorithm.py
index 0a1b62040a81ee964d373132763e964381cbc19e..ca978310842d538efe2f83ed743446067f84c3eb 100644
--- a/src/pathcomp/frontend/service/algorithms/_Algorithm.py
+++ b/src/pathcomp/frontend/service/algorithms/_Algorithm.py
@@ -15,12 +15,16 @@
 import json, logging, requests, uuid
 from typing import Dict, List, Optional, Tuple, Union
 from common.proto.context_pb2 import (
-    Connection, Device, DeviceList, EndPointId, Link, LinkList, Service, ServiceStatusEnum, ServiceTypeEnum)
+    ConfigRule, Connection, Device, DeviceList, EndPointId, Link, LinkList, Service, ServiceStatusEnum, ServiceTypeEnum
+)
 from common.proto.pathcomp_pb2 import PathCompReply, PathCompRequest
+from common.tools.grpc.Tools import grpc_message_list_to_json
 from pathcomp.frontend.Config import BACKEND_URL
 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)
+    compose_device_config_rules, compose_l2nm_config_rules, compose_l3nm_config_rules, compose_tapi_config_rules,
+    generate_neighbor_endpoint_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)
@@ -227,12 +231,25 @@ class _Algorithm:
                 continue
 
             orig_config_rules = grpc_orig_service.service_config.config_rules
+            json_orig_config_rules = grpc_message_list_to_json(orig_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)
+
+                json_generated_config_rules = generate_neighbor_endpoint_config_rules(
+                    json_orig_config_rules, path_hops, self.device_name_mapping, self.endpoint_name_mapping
+                )
+                json_extended_config_rules = list()
+                json_extended_config_rules.extend(json_orig_config_rules)
+                json_extended_config_rules.extend(json_generated_config_rules)
+                extended_config_rules = [
+                    ConfigRule(**json_extended_config_rule)
+                    for json_extended_config_rule in json_extended_config_rules
+                ]
+
                 self.logger.debug('path_hops = {:s}'.format(str(path_hops)))
                 try:
                     _device_dict = {k:v[0] for k,v in self.device_dict.items()}
@@ -256,7 +273,7 @@ class _Algorithm:
                     if service_key in grpc_services: continue
                     grpc_service = self.add_service_to_reply(
                         reply, context_uuid, service_uuid, service_type, path_hops=path_hops,
-                        config_rules=orig_config_rules)
+                        config_rules=extended_config_rules)
                     grpc_services[service_key] = grpc_service
 
                 for connection in connections:
diff --git a/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py b/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py
index 329552a914e478f0e927bd6f04fce6725bef0b5e..2d4ff4fd59187e3581c8426435f80bca958ad655 100644
--- a/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py
+++ b/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py
@@ -12,8 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import itertools, json, logging, re
-from typing import Dict, List, Optional, Tuple
+import copy, itertools, json, logging, re
+from typing import Dict, Iterable, List, Optional, Set, Tuple
 from common.proto.context_pb2 import ConfigRule
 from common.tools.grpc.Tools import grpc_message_to_json_string
 from common.tools.object_factory.ConfigRule import json_config_rule_set
@@ -21,19 +21,26 @@ from common.tools.object_factory.ConfigRule import json_config_rule_set
 LOGGER = logging.getLogger(__name__)
 
 SETTINGS_RULE_NAME = '/settings'
+STATIC_ROUTING_RULE_NAME = '/static_routing'
 
-DEVICE_SETTINGS = re.compile(r'\/device\[([^\]]+)\]\/settings')
-ENDPOINT_SETTINGS = re.compile(r'\/device\[([^\]]+)\]\/endpoint\[([^\]]+)\]\/settings')
+RE_UUID = re.compile(r'([0-9a-fA-F]{8})\-([0-9a-fA-F]{4})\-([0-9a-fA-F]{4})\-([0-9a-fA-F]{4})\-([0-9a-fA-F]{12})')
+
+RE_DEVICE_SETTINGS        = re.compile(r'\/device\[([^\]]+)\]\/settings')
+RE_ENDPOINT_SETTINGS      = re.compile(r'\/device\[([^\]]+)\]\/endpoint\[([^\]]+)\]\/settings')
+RE_ENDPOINT_VLAN_SETTINGS = re.compile(r'\/device\[([^\]]+)\]\/endpoint\[([^\]]+)\]\/vlan\[([^\]]+)\]\/settings')
+
+TMPL_ENDPOINT_SETTINGS      = '/device[{:s}]/endpoint[{:s}]/settings'
+TMPL_ENDPOINT_VLAN_SETTINGS = '/device[{:s}]/endpoint[{:s}]/vlan[{:s}]/settings'
 
 L2NM_SETTINGS_FIELD_DEFAULTS = {
-    'encapsulation_type': 'dot1q',
-    'vlan_id'           : 100,
+    #'encapsulation_type': 'dot1q',
+    #'vlan_id'           : 100,
     'mtu'               : 1450,
 }
 
 L3NM_SETTINGS_FIELD_DEFAULTS = {
-    'encapsulation_type': 'dot1q',
-    'vlan_id'           : 100,
+    #'encapsulation_type': 'dot1q',
+    #'vlan_id'           : 100,
     'mtu'               : 1450,
 }
 
@@ -54,26 +61,48 @@ def find_custom_config_rule(config_rules : List, resource_name : str) -> Optiona
     return resource_value
 
 def compose_config_rules(
-    main_service_config_rules : List, subservice_config_rules : List, field_defaults : Dict
+    main_service_config_rules : List, subservice_config_rules : List, settings_rule_name : str, field_defaults : Dict
 ) -> None:
-    settings = find_custom_config_rule(main_service_config_rules, SETTINGS_RULE_NAME)
+    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))
+    if len(field_defaults) == 0:
+        for field_name,field_value in settings.items():
+            json_settings[field_name] = field_value
+    else:
+        for field_name,default_value in field_defaults.items():
+            field_value = settings.get(field_name, default_value)
+            if field_value is None: continue
+            json_settings[field_name] = field_value
+
+    if len(json_settings) == 0: return
+
+    config_rule = ConfigRule(**json_config_rule_set(settings_rule_name, 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)
+    CONFIG_RULES = [
+        (SETTINGS_RULE_NAME, L2NM_SETTINGS_FIELD_DEFAULTS),
+    ]
+    for rule_name, defaults in CONFIG_RULES:
+        compose_config_rules(main_service_config_rules, subservice_config_rules, rule_name, 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)
+    CONFIG_RULES = [
+        (SETTINGS_RULE_NAME, L3NM_SETTINGS_FIELD_DEFAULTS),
+        (STATIC_ROUTING_RULE_NAME, {}),
+    ]
+    for rule_name, defaults in CONFIG_RULES:
+        compose_config_rules(main_service_config_rules, subservice_config_rules, rule_name, 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)
+    CONFIG_RULES = [
+        (SETTINGS_RULE_NAME, TAPI_SETTINGS_FIELD_DEFAULTS),
+    ]
+    for rule_name, defaults in CONFIG_RULES:
+        compose_config_rules(main_service_config_rules, subservice_config_rules, rule_name, defaults)
 
 def compose_device_config_rules(
     config_rules : List, subservice_config_rules : List, path_hops : List,
@@ -127,25 +156,31 @@ def compose_device_config_rules(
         elif config_rule.WhichOneof('config_rule') == 'custom':
             LOGGER.debug('[compose_device_config_rules]   is custom')
 
-            match = DEVICE_SETTINGS.match(config_rule.custom.resource_key)
+            match = RE_DEVICE_SETTINGS.match(config_rule.custom.resource_key)
             if match is not None:
                 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}
+                device_keys = {device_uuid_or_name}
+                device_name_or_uuid = device_name_mapping.get(device_uuid_or_name)
+                if device_name_or_uuid is not None: device_keys.add(device_name_or_uuid)
 
                 if len(device_keys.intersection(devices_traversed)) == 0: continue
                 subservice_config_rules.append(config_rule)
 
-            match = ENDPOINT_SETTINGS.match(config_rule.custom.resource_key)
+            match = RE_ENDPOINT_SETTINGS.match(config_rule.custom.resource_key)
+            if match is None:
+                match = RE_ENDPOINT_VLAN_SETTINGS.match(config_rule.custom.resource_key)
             if match is not None:
                 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}
+                device_keys = {device_uuid_or_name}
+                device_name_or_uuid = device_name_mapping.get(device_uuid_or_name)
+                if device_name_or_uuid is not None: device_keys.add(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}
+                endpoint_keys = {endpoint_uuid_or_name}
+                endpoint_name_or_uuid_1 = endpoint_name_mapping.get((device_uuid_or_name, endpoint_uuid_or_name))
+                if endpoint_name_or_uuid_1 is not None: endpoint_keys.add(endpoint_name_or_uuid_1)
+                endpoint_name_or_uuid_2 = endpoint_name_mapping.get((device_name_or_uuid, endpoint_uuid_or_name))
+                if endpoint_name_or_uuid_2 is not None: endpoint_keys.add(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
@@ -153,4 +188,146 @@ def compose_device_config_rules(
         else:
             continue
 
+    for config_rule in subservice_config_rules:
+        LOGGER.debug('[compose_device_config_rules] result config_rule: {:s}'.format(
+            grpc_message_to_json_string(config_rule)))
+
     LOGGER.debug('[compose_device_config_rules] end')
+
+def pairwise(iterable : Iterable) -> Tuple[Iterable, Iterable]:
+    # TODO: To be replaced by itertools.pairwise() when we move to Python 3.10
+    # Python 3.10 introduced method itertools.pairwise()
+    # Standalone method extracted from:
+    # - https://docs.python.org/3/library/itertools.html#itertools.pairwise
+    a, b = itertools.tee(iterable, 2)
+    next(b, None)
+    return zip(a, b)
+
+def compute_device_keys(
+    device_uuid_or_name : str, device_name_mapping : Dict[str, str]
+) -> Set[str]:
+    LOGGER.debug('[compute_device_keys] begin')
+    LOGGER.debug('[compute_device_keys] device_uuid_or_name={:s}'.format(str(device_uuid_or_name)))
+    #LOGGER.debug('[compute_device_keys] device_name_mapping={:s}'.format(str(device_name_mapping)))
+
+    device_keys = {device_uuid_or_name}
+    for k,v in device_name_mapping.items():
+        if device_uuid_or_name not in {k, v}: continue
+        device_keys.add(k)
+        device_keys.add(v)
+
+    LOGGER.debug('[compute_device_keys] device_keys={:s}'.format(str(device_keys)))
+    LOGGER.debug('[compute_device_keys] end')
+    return device_keys
+
+def compute_endpoint_keys(
+    device_keys : Set[str], endpoint_uuid_or_name : str, endpoint_name_mapping : Dict[str, str]
+) -> Set[str]:
+    LOGGER.debug('[compute_endpoint_keys] begin')
+    LOGGER.debug('[compute_endpoint_keys] device_keys={:s}'.format(str(device_keys)))
+    LOGGER.debug('[compute_endpoint_keys] endpoint_uuid_or_name={:s}'.format(str(endpoint_uuid_or_name)))
+    #LOGGER.debug('[compute_device_endpoint_keys] endpoint_name_mapping={:s}'.format(str(endpoint_name_mapping)))
+
+    endpoint_keys = {endpoint_uuid_or_name}
+    for k,v in endpoint_name_mapping.items():
+        if (k[0] in device_keys or v in device_keys) and (endpoint_uuid_or_name in {k[1], v}):
+            endpoint_keys.add(k[1])
+            endpoint_keys.add(v)
+
+    LOGGER.debug('[compute_endpoint_keys] endpoint_keys={:s}'.format(str(endpoint_keys)))
+    LOGGER.debug('[compute_endpoint_keys] end')
+    return endpoint_keys
+
+def compute_device_endpoint_keys(
+    device_uuid_or_name : str, endpoint_uuid_or_name : str,
+    device_name_mapping : Dict[str, str], endpoint_name_mapping : Dict[Tuple[str, str], str]
+) -> Set[Tuple[str, str]]:
+    LOGGER.debug('[compute_device_endpoint_keys] begin')
+    LOGGER.debug('[compute_device_endpoint_keys] device_uuid_or_name={:s}'.format(str(device_uuid_or_name)))
+    LOGGER.debug('[compute_device_endpoint_keys] endpoint_uuid_or_name={:s}'.format(str(endpoint_uuid_or_name)))
+    #LOGGER.debug('[compute_device_endpoint_keys] device_name_mapping={:s}'.format(str(device_name_mapping)))
+    #LOGGER.debug('[compute_device_endpoint_keys] endpoint_name_mapping={:s}'.format(str(endpoint_name_mapping)))
+
+    device_keys = compute_device_keys(device_uuid_or_name, device_name_mapping)
+    endpoint_keys = compute_endpoint_keys(device_keys, endpoint_uuid_or_name, endpoint_name_mapping)
+    device_endpoint_keys = set(itertools.product(device_keys, endpoint_keys))
+
+    LOGGER.debug('[compute_device_endpoint_keys] device_endpoint_keys={:s}'.format(str(device_endpoint_keys)))
+    LOGGER.debug('[compute_device_endpoint_keys] end')
+    return device_endpoint_keys
+
+def generate_neighbor_endpoint_config_rules(
+    config_rules : List[Dict], path_hops : List[Dict],
+    device_name_mapping : Dict[str, str], endpoint_name_mapping : Dict[Tuple[str, str], str]
+) -> List[Dict]:
+    LOGGER.debug('[generate_neighbor_endpoint_config_rules] begin')
+    LOGGER.debug('[generate_neighbor_endpoint_config_rules] config_rules={:s}'.format(str(config_rules)))
+    LOGGER.debug('[generate_neighbor_endpoint_config_rules] path_hops={:s}'.format(str(path_hops)))
+    LOGGER.debug('[generate_neighbor_endpoint_config_rules] device_name_mapping={:s}'.format(str(device_name_mapping)))
+    LOGGER.debug('[generate_neighbor_endpoint_config_rules] endpoint_name_mapping={:s}'.format(str(endpoint_name_mapping)))
+
+    generated_config_rules = list()
+    for link_endpoint_a, link_endpoint_b in pairwise(path_hops):
+        LOGGER.debug('[generate_neighbor_endpoint_config_rules] loop begin')
+        LOGGER.debug('[generate_neighbor_endpoint_config_rules] link_endpoint_a={:s}'.format(str(link_endpoint_a)))
+        LOGGER.debug('[generate_neighbor_endpoint_config_rules] link_endpoint_b={:s}'.format(str(link_endpoint_b)))
+
+        device_endpoint_keys_a = compute_device_endpoint_keys(
+            link_endpoint_a['device'], link_endpoint_a['egress_ep'],
+            device_name_mapping, endpoint_name_mapping
+        )
+
+        device_endpoint_keys_b = compute_device_endpoint_keys(
+            link_endpoint_b['device'], link_endpoint_b['ingress_ep'],
+            device_name_mapping, endpoint_name_mapping
+        )
+
+        for config_rule in config_rules:
+            # Only applicable, by now, to Custom Config Rules for endpoint settings
+            if 'custom' not in config_rule: continue
+            match = RE_ENDPOINT_SETTINGS.match(config_rule['custom']['resource_key'])
+            if match is None:
+                match = RE_ENDPOINT_VLAN_SETTINGS.match(config_rule['custom']['resource_key'])
+            if match is None: continue
+
+            resource_key_values = match.groups()
+            if resource_key_values[0:2] in device_endpoint_keys_a:
+                resource_key_values = list(resource_key_values)
+                resource_key_values[0] = link_endpoint_b['device']
+                resource_key_values[1] = link_endpoint_b['ingress_ep']
+            elif resource_key_values[0:2] in device_endpoint_keys_b:
+                resource_key_values = list(resource_key_values)
+                resource_key_values[0] = link_endpoint_a['device']
+                resource_key_values[1] = link_endpoint_a['egress_ep']
+            else:
+                continue
+
+            device_keys = compute_device_keys(resource_key_values[0], device_name_mapping)
+            device_names = {device_key for device_key in device_keys if RE_UUID.match(device_key) is None}
+            if len(device_names) != 1:
+                MSG = 'Unable to identify name for Device({:s}): device_keys({:s})'
+                raise Exception(MSG.format(str(resource_key_values[0]), str(device_keys)))
+            resource_key_values[0] = device_names.pop()
+
+            endpoint_keys = compute_endpoint_keys(device_keys, resource_key_values[1], endpoint_name_mapping)
+            endpoint_names = {endpoint_key for endpoint_key in endpoint_keys if RE_UUID.match(endpoint_key) is None}
+            if len(endpoint_names) != 1:
+                MSG = 'Unable to identify name for Endpoint({:s}): endpoint_keys({:s})'
+                raise Exception(MSG.format(str(resource_key_values[1]), str(endpoint_keys)))
+            resource_key_values[1] = endpoint_names.pop()
+
+            resource_value : Dict = json.loads(config_rule['custom']['resource_value'])
+            if 'neighbor_address' not in resource_value: continue
+            resource_value['ip_address'] = resource_value.pop('neighbor_address')
+
+            # remove neighbor_address also from original rule as it is already consumed
+
+            resource_key_template = TMPL_ENDPOINT_VLAN_SETTINGS if len(match.groups()) == 3 else TMPL_ENDPOINT_SETTINGS
+            generated_config_rule = copy.deepcopy(config_rule)
+            generated_config_rule['custom']['resource_key'] = resource_key_template.format(*resource_key_values)
+            generated_config_rule['custom']['resource_value'] = json.dumps(resource_value)
+            generated_config_rules.append(generated_config_rule)
+
+    LOGGER.debug('[generate_neighbor_endpoint_config_rules] generated_config_rules={:s}'.format(str(generated_config_rules)))
+    LOGGER.debug('[generate_neighbor_endpoint_config_rules] end')
+    return generated_config_rules
diff --git a/src/pathcomp/frontend/service/algorithms/tools/ComputeSubServices.py b/src/pathcomp/frontend/service/algorithms/tools/ComputeSubServices.py
index 06b24031bd82b6b51add6569b9c35b360f0491fe..86a91d00aca2b2b5465bab654d7ab3de0fbe148b 100644
--- a/src/pathcomp/frontend/service/algorithms/tools/ComputeSubServices.py
+++ b/src/pathcomp/frontend/service/algorithms/tools/ComputeSubServices.py
@@ -95,6 +95,36 @@ def convert_explicit_path_hops_to_connections(
             connections.append(connection)
             connection_stack.queue[-1][3].append(connection[0])
             #connection_stack.queue[-1][2].append(path_hop)
+        elif prv_res_class[2] is None and res_class[2] is not None:
+            # entering domain of a device controller, create underlying connection
+            LOGGER.debug('  entering domain of a device controller, create underlying connection')
+            sub_service_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((sub_service_uuid, service_type, [path_hop], []))
+        elif prv_res_class[2] is not None and res_class[2] is None:
+            # leaving domain of a device controller, terminate underlying connection
+            LOGGER.debug('  leaving domain of a device controller, terminate underlying connection')
+            connection = connection_stack.get()
+            connections.append(connection)
+            connection_stack.queue[-1][3].append(connection[0])
+            connection_stack.queue[-1][2].append(path_hop)
+        elif prv_res_class[2] is not None and res_class[2] is not None:
+            if prv_res_class[2] == res_class[2]:
+                # stay in domain of a device controller, connection continues
+                LOGGER.debug('  stay in domain of a device controller, connection continues')
+                connection_stack.queue[-1][2].append(path_hop)
+            else:
+                # switching to different device controller, chain connections
+                LOGGER.debug('  switching to different device controller, chain connections')
+                connection = connection_stack.get()
+                connections.append(connection)
+                connection_stack.queue[-1][3].append(connection[0])
+
+                sub_service_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((sub_service_uuid, service_type, [path_hop], []))
         elif prv_res_class[0] is None:
             # path ingress
             LOGGER.debug('  path ingress')
diff --git a/src/pathcomp/frontend/service/algorithms/tools/ResourceGroups.py b/src/pathcomp/frontend/service/algorithms/tools/ResourceGroups.py
index 843c41803805106e9f7575fb9ff6b1344d036994..7b5221c88fd1baa13ef44dc0a0c06e76cf8dc813 100644
--- a/src/pathcomp/frontend/service/algorithms/tools/ResourceGroups.py
+++ b/src/pathcomp/frontend/service/algorithms/tools/ResourceGroups.py
@@ -24,6 +24,9 @@ DEVICE_TYPE_TO_DEEPNESS = {
     DeviceTypeEnum.DATACENTER.value                      : 90,
 
     DeviceTypeEnum.TERAFLOWSDN_CONTROLLER.value          : 80,
+    DeviceTypeEnum.EMULATED_IP_SDN_CONTROLLER.value      : 80,
+    DeviceTypeEnum.IP_SDN_CONTROLLER.value               : 80,
+
     DeviceTypeEnum.EMULATED_PACKET_ROUTER.value          : 70,
     DeviceTypeEnum.PACKET_ROUTER.value                   : 70,
 
diff --git a/src/pathcomp/frontend/service/algorithms/tools/ServiceTypes.py b/src/pathcomp/frontend/service/algorithms/tools/ServiceTypes.py
index 73a741ae551c5179cb78268f6fb87040c8481c53..094baa1a674fab1a573a97c364a12386ca940cc9 100644
--- a/src/pathcomp/frontend/service/algorithms/tools/ServiceTypes.py
+++ b/src/pathcomp/frontend/service/algorithms/tools/ServiceTypes.py
@@ -22,6 +22,7 @@ NETWORK_DEVICE_TYPES = {
 
 PACKET_DEVICE_TYPES = {
     DeviceTypeEnum.TERAFLOWSDN_CONTROLLER,
+    DeviceTypeEnum.IP_SDN_CONTROLLER, DeviceTypeEnum.EMULATED_IP_SDN_CONTROLLER,
     DeviceTypeEnum.PACKET_ROUTER, DeviceTypeEnum.EMULATED_PACKET_ROUTER,
     DeviceTypeEnum.PACKET_SWITCH, DeviceTypeEnum.EMULATED_PACKET_SWITCH,
 }
diff --git a/src/policy/src/main/java/org/etsi/tfs/policy/PolicyServiceImpl.java b/src/policy/src/main/java/org/etsi/tfs/policy/PolicyServiceImpl.java
index b5f1d85eb4d55668e16c0bdf32212e6ef559b8c9..c94aa37a92bdc5f7d0ad8f47e3b7a7c97e20f313 100644
--- a/src/policy/src/main/java/org/etsi/tfs/policy/PolicyServiceImpl.java
+++ b/src/policy/src/main/java/org/etsi/tfs/policy/PolicyServiceImpl.java
@@ -16,6 +16,8 @@
 
 package org.etsi.tfs.policy;
 
+import static org.etsi.tfs.policy.common.ApplicationProperties.*;
+
 import io.smallrye.mutiny.Multi;
 import io.smallrye.mutiny.Uni;
 import io.smallrye.mutiny.groups.UniJoin;
@@ -38,6 +40,7 @@ import org.etsi.tfs.policy.context.model.Constraint;
 import org.etsi.tfs.policy.context.model.ConstraintCustom;
 import org.etsi.tfs.policy.context.model.ConstraintTypeCustom;
 import org.etsi.tfs.policy.context.model.ServiceConfig;
+import org.etsi.tfs.policy.context.model.ServiceId;
 import org.etsi.tfs.policy.device.DeviceService;
 import org.etsi.tfs.policy.model.BooleanOperator;
 import org.etsi.tfs.policy.model.PolicyRule;
@@ -64,8 +67,7 @@ import org.jboss.logging.Logger;
 public class PolicyServiceImpl implements PolicyService {
 
     private static final Logger LOGGER = Logger.getLogger(PolicyServiceImpl.class);
-    private static final String INVALID_MESSAGE = "%s is invalid.";
-    private static final String VALID_MESSAGE = "%s is valid.";
+
     private static final int POLICY_EVALUATION_TIMEOUT = 5;
     private static final int ACCEPTABLE_NUMBER_OF_ALARMS = 3;
     private static final int MONITORING_WINDOW_IN_SECONDS = 5;
@@ -74,39 +76,6 @@ public class PolicyServiceImpl implements PolicyService {
     // Temporary solution for not calling the same rpc more than it's needed
     private static int noAlarms = 0;
 
-    private static final PolicyRuleState INSERTED_POLICYRULE_STATE =
-            new PolicyRuleState(
-                    PolicyRuleStateEnum.POLICY_INSERTED, "Successfully entered to INSERTED state");
-    private static final PolicyRuleState VALIDATED_POLICYRULE_STATE =
-            new PolicyRuleState(
-                    PolicyRuleStateEnum.POLICY_VALIDATED, "Successfully transitioned to VALIDATED state");
-    private static final PolicyRuleState PROVISIONED_POLICYRULE_STATE =
-            new PolicyRuleState(
-                    PolicyRuleStateEnum.POLICY_PROVISIONED,
-                    "Successfully transitioned from VALIDATED to PROVISIONED state");
-    private static final PolicyRuleState ACTIVE_POLICYRULE_STATE =
-            new PolicyRuleState(
-                    PolicyRuleStateEnum.POLICY_ACTIVE,
-                    "Successfully transitioned from PROVISIONED to ACTIVE state");
-    private static final PolicyRuleState ENFORCED_POLICYRULE_STATE =
-            new PolicyRuleState(
-                    PolicyRuleStateEnum.POLICY_ENFORCED,
-                    "Successfully transitioned from ACTIVE to ENFORCED state");
-    private static final PolicyRuleState INEFFECTIVE_POLICYRULE_STATE =
-            new PolicyRuleState(
-                    PolicyRuleStateEnum.POLICY_INEFFECTIVE,
-                    "Transitioned from ENFORCED to INEFFECTIVE state");
-    private static final PolicyRuleState EFFECTIVE_POLICYRULE_STATE =
-            new PolicyRuleState(
-                    PolicyRuleStateEnum.POLICY_EFFECTIVE,
-                    "Successfully transitioned from ENFORCED to EFFECTIVE state");
-    private static final PolicyRuleState UPDATED_POLICYRULE_STATE =
-            new PolicyRuleState(
-                    PolicyRuleStateEnum.POLICY_UPDATED, "Successfully entered to UPDATED state");
-    private static final PolicyRuleState REMOVED_POLICYRULE_STATE =
-            new PolicyRuleState(
-                    PolicyRuleStateEnum.POLICY_REMOVED, "Successfully entered to REMOVED state");
-
     private final ContextService contextService;
     private final MonitoringService monitoringService;
     private final ServiceService serviceService;
@@ -176,81 +145,122 @@ public class PolicyServiceImpl implements PolicyService {
         return isServiceValid
                 .onItem()
                 .transform(
-                        isService -> {
-                            if (!isService) {
-                                var policyRuleState =
-                                        new PolicyRuleState(
-                                                PolicyRuleStateEnum.POLICY_FAILED,
-                                                String.format(INVALID_MESSAGE, serviceId));
-
-                                return policyRuleState;
-                            }
+                        isService ->
+                                constructPolicyStateBasedOnCriteria(
+                                        isService, serviceId, policyRuleService, policyRuleBasic));
+    }
 
-                            final var policyRuleTypeService = new PolicyRuleTypeService(policyRuleService);
-                            final var policyRule = new PolicyRule(policyRuleTypeService);
-                            final var alarmDescriptorList = createAlarmDescriptorList(policyRule);
-
-                            if (alarmDescriptorList.isEmpty()) {
-                                var policyRuleState =
-                                        new PolicyRuleState(
-                                                PolicyRuleStateEnum.POLICY_FAILED,
-                                                String.format(
-                                                        "Invalid PolicyRuleConditions in PolicyRule with ID: %s",
-                                                        policyRuleBasic.getPolicyRuleId()));
-                                return policyRuleState;
-                            } else {
-                                contextService
-                                        .setPolicyRule(policyRule)
-                                        .subscribe()
-                                        .with(
-                                                policyId -> {
-                                                    setPolicyRuleServiceToContext(
-                                                            policyRuleService, VALIDATED_POLICYRULE_STATE);
-                                                    noAlarms = 0;
-
-                                                    // Create an alarmIds list that contains the promised ids returned from
-                                                    // setKpiAlarm
-                                                    List<Uni<String>> alarmIds = new ArrayList<Uni<String>>();
-                                                    for (AlarmDescriptor alarmDescriptor : alarmDescriptorList) {
-                                                        LOGGER.infof("alarmDescriptor:");
-                                                        LOGGER.infof(alarmDescriptor.toString());
-                                                        alarmIds.add(monitoringService.setKpiAlarm(alarmDescriptor));
-                                                    }
-                                                    // Transform the alarmIds into promised alarms returned from the
-                                                    // getAlarmResponseStream
-                                                    List<Multi<AlarmResponse>> alarmResponseStreamList = new ArrayList<>();
-                                                    for (Uni<String> alarmId : alarmIds) {
-                                                        alarmResponseStreamList.add(
-                                                                alarmId
-                                                                        .onItem()
-                                                                        .transformToMulti(
-                                                                                id -> {
-                                                                                    alarmPolicyRuleServiceMap.put(id, policyRuleService);
-
-                                                                                    // TODO: Create infinite subscription
-                                                                                    var alarmSubscription =
-                                                                                            new AlarmSubscription(id, 259200, 5000);
-                                                                                    return monitoringService.getAlarmResponseStream(
-                                                                                            alarmSubscription);
-                                                                                }));
-                                                    }
-
-                                                    // Merge the promised alarms into one stream (Multi Object)
-                                                    final var multi =
-                                                            Multi.createBy().merging().streams(alarmResponseStreamList);
-                                                    setPolicyRuleServiceToContext(
-                                                            policyRuleService, PROVISIONED_POLICYRULE_STATE);
-
-                                                    subscriptionList.put(policyId, monitorAlarmResponseForService(multi));
-
-                                                    // TODO: Resubscribe to the stream, if it has ended
-
-                                                    // TODO: Redesign evaluation of action
-                                                    // evaluateAction(policyRule, alarmDescriptorList, multi);
-                                                });
-                                return VALIDATED_POLICYRULE_STATE;
-                            }
-                        });
+    private PolicyRuleState constructPolicyStateBasedOnCriteria(
+            Boolean isService,
+            ServiceId serviceId,
+            PolicyRuleService policyRuleService,
+            PolicyRuleBasic policyRuleBasic) {
+
+        if (!isService) {
+            var policyRuleState =
+                    new PolicyRuleState(
+                            PolicyRuleStateEnum.POLICY_FAILED, String.format(INVALID_MESSAGE, serviceId));
+
+            return policyRuleState;
+        }
+
+        final var policyRuleTypeService = new PolicyRuleTypeService(policyRuleService);
+        final var policyRule = new PolicyRule(policyRuleTypeService);
+        final var alarmDescriptorList = createAlarmDescriptorList(policyRule);
+
+        if (alarmDescriptorList.isEmpty()) {
+            var policyRuleState =
+                    new PolicyRuleState(
+                            PolicyRuleStateEnum.POLICY_FAILED,
+                            String.format(
+                                    "Invalid PolicyRuleConditions in PolicyRule with ID: %s",
+                                    policyRuleBasic.getPolicyRuleId()));
+            return policyRuleState;
+        }
+
+        return setPolicyRuleOnContextAndReturnState(policyRule, policyRuleService, alarmDescriptorList);
+    }
+
+    private PolicyRuleState setPolicyRuleOnContextAndReturnState(
+            PolicyRule policyRule,
+            PolicyRuleService policyRuleService,
+            List<AlarmDescriptor> alarmDescriptorList) {
+        contextService
+                .setPolicyRule(policyRule)
+                .subscribe()
+                .with(
+                        policyId ->
+                                startMonitoringBasedOnAlarmDescriptors(
+                                        policyId, policyRuleService, alarmDescriptorList));
+        return VALIDATED_POLICYRULE_STATE;
+    }
+
+    private void startMonitoringBasedOnAlarmDescriptors(
+            String policyId,
+            PolicyRuleService policyRuleService,
+            List<AlarmDescriptor> alarmDescriptorList) {
+        setPolicyRuleServiceToContext(policyRuleService, VALIDATED_POLICYRULE_STATE);
+        noAlarms = 0;
+
+        List<Uni<String>> alarmIds =
+                createAlarmList(alarmDescriptorList); // setAllarmtomonitoring get back alarmid
+
+        List<Multi<AlarmResponse>> alarmResponseStreamList =
+                transformAlarmIds(alarmIds, policyRuleService);
+
+        // Merge the promised alarms into one stream (Multi Object)
+        final var multi = Multi.createBy().merging().streams(alarmResponseStreamList);
+        setPolicyRuleServiceToContext(policyRuleService, PROVISIONED_POLICYRULE_STATE);
+
+        subscriptionList.put(policyId, monitorAlarmResponseForService(multi));
+
+        // TODO: Resubscribe to the stream, if it has ended
+
+        // TODO: Redesign evaluation of action
+        // evaluateAction(policyRule, alarmDescriptorList, multi);
+    }
+
+    /**
+    * Transform the alarmIds into promised alarms returned from the getAlarmResponseStream
+    *
+    * @param alarmIds the list of alarm ids
+    * @param policyRuleService the policy rule service
+    * @return
+    */
+    private List<Multi<AlarmResponse>> transformAlarmIds(
+            List<Uni<String>> alarmIds, PolicyRuleService policyRuleService) {
+        List<Multi<AlarmResponse>> alarmResponseStreamList = new ArrayList<>();
+        for (Uni<String> alarmId : alarmIds) {
+            Multi<AlarmResponse> alarmResponseStream =
+                    alarmId.onItem().transformToMulti(id -> setPolicyMonitor(policyRuleService, id));
+
+            alarmResponseStreamList.add(alarmResponseStream);
+        }
+        return alarmResponseStreamList;
+    }
+
+    private Multi<AlarmResponse> setPolicyMonitor(PolicyRuleService policyRuleService, String id) {
+        alarmPolicyRuleServiceMap.put(id, policyRuleService);
+
+        // TODO: Create infinite subscription
+        var alarmSubscription = new AlarmSubscription(id, 259200, 5000);
+        return monitoringService.getAlarmResponseStream(alarmSubscription);
+    }
+
+    /**
+    * Create an alarmIds list that contains the promised ids returned from setKpiAlarm
+    *
+    * @param alarmDescriptorList the list of alarm descriptors
+    * @return the list of alarm descriptors
+    */
+    public List<Uni<String>> createAlarmList(List<AlarmDescriptor> alarmDescriptorList) {
+        List<Uni<String>> alarmIds = new ArrayList<Uni<String>>();
+        for (AlarmDescriptor alarmDescriptor : alarmDescriptorList) {
+            LOGGER.infof("alarmDescriptor:");
+            LOGGER.infof(alarmDescriptor.toString());
+            alarmIds.add(monitoringService.setKpiAlarm(alarmDescriptor));
+        }
+        return alarmIds;
     }
 
     @Override
@@ -280,74 +290,102 @@ public class PolicyServiceImpl implements PolicyService {
 
         return areDevicesValid
                 .onItem()
-                .transform(
-                        areDevices -> {
-                            if (areDevices.contains(false)) {
-                                var policyRuleState =
-                                        new PolicyRuleState(
-                                                PolicyRuleStateEnum.POLICY_FAILED,
-                                                String.format(
-                                                        INVALID_MESSAGE,
-                                                        policyRuleDevice.getPolicyRuleBasic().getPolicyRuleId()));
-
-                                return policyRuleState;
-                            }
+                .transform(areDevices -> areDeviceOnContext(areDevices, policyRuleDevice, policyRuleBasic));
+    }
 
-                            final var policyRuleTypeDevice = new PolicyRuleTypeDevice(policyRuleDevice);
-                            final var policyRule = new PolicyRule(policyRuleTypeDevice);
-
-                            final var alarmDescriptorList = createAlarmDescriptorList(policyRule);
-                            if (alarmDescriptorList.isEmpty()) {
-                                var policyRuleState =
-                                        new PolicyRuleState(
-                                                PolicyRuleStateEnum.POLICY_FAILED,
-                                                String.format(
-                                                        "Invalid PolicyRuleConditions in PolicyRule with ID: %s",
-                                                        policyRuleBasic.getPolicyRuleId()));
-                                return policyRuleState;
-                            }
+    private PolicyRuleState areDeviceOnContext(
+            List<Boolean> areDevices,
+            PolicyRuleDevice policyRuleDevice,
+            PolicyRuleBasic policyRuleBasic) {
+        if (areDevices.contains(false)) {
+            var policyRuleState =
+                    new PolicyRuleState(
+                            PolicyRuleStateEnum.POLICY_FAILED,
+                            String.format(
+                                    INVALID_MESSAGE, policyRuleDevice.getPolicyRuleBasic().getPolicyRuleId()));
 
-                            contextService.setPolicyRule(policyRule).subscribe().with(x -> {});
-                            setPolicyRuleDeviceToContext(policyRuleDevice, VALIDATED_POLICYRULE_STATE);
-                            noAlarms = 0;
+            return policyRuleState;
+        }
 
-                            List<Uni<String>> alarmIds = new ArrayList<Uni<String>>();
-                            for (AlarmDescriptor alarmDescriptor : alarmDescriptorList) {
-                                LOGGER.infof("alarmDescriptor:");
-                                LOGGER.infof(alarmDescriptor.toString());
-                                alarmIds.add(monitoringService.setKpiAlarm(alarmDescriptor));
-                            }
+        final var policyRuleTypeDevice = new PolicyRuleTypeDevice(policyRuleDevice);
+        final var policyRule = new PolicyRule(policyRuleTypeDevice);
 
-                            // Transform the alarmIds into promised alarms returned from the
-                            // getAlarmResponseStream
-                            List<Multi<AlarmResponse>> alarmResponseStreamList = new ArrayList<>();
-                            for (Uni<String> alarmId : alarmIds) {
-                                alarmResponseStreamList.add(
-                                        alarmId
-                                                .onItem()
-                                                .transformToMulti(
-                                                        id -> {
-                                                            alarmPolicyRuleDeviceMap.put(id, policyRuleDevice);
-
-                                                            // TODO: Create infinite subscription
-                                                            var alarmSubscription = new AlarmSubscription(id, 259200, 5000);
-                                                            return monitoringService.getAlarmResponseStream(alarmSubscription);
-                                                        }));
-                            }
+        final var alarmDescriptorList = createAlarmDescriptorList(policyRule);
+        if (alarmDescriptorList.isEmpty()) {
+            var policyRuleState =
+                    new PolicyRuleState(
+                            PolicyRuleStateEnum.POLICY_FAILED,
+                            String.format(
+                                    "Invalid PolicyRuleConditions in PolicyRule with ID: %s",
+                                    policyRuleBasic.getPolicyRuleId()));
+            return policyRuleState;
+        }
 
-                            // Merge the promised alarms into one stream (Multi Object)
-                            final var multi = Multi.createBy().merging().streams(alarmResponseStreamList);
-                            setPolicyRuleDeviceToContext(policyRuleDevice, PROVISIONED_POLICYRULE_STATE);
+        contextService
+                .setPolicyRule(policyRule)
+                .subscribe()
+                .with(
+                        policyId -> {
+                            startMonitoringBasedOnAlarmDescriptors(
+                                    policyId, policyRuleDevice, alarmDescriptorList);
+                        });
 
-                            monitorAlarmResponseForDevice(multi);
+        return VALIDATED_POLICYRULE_STATE;
+    }
 
-                            // TODO: Resubscribe to the stream, if it has ended
+    private void startMonitoringBasedOnAlarmDescriptors(
+            String policyId,
+            PolicyRuleDevice policyRuleDevice,
+            List<AlarmDescriptor> alarmDescriptorList) {
+        setPolicyRuleDeviceToContext(policyRuleDevice, VALIDATED_POLICYRULE_STATE);
+        noAlarms = 0;
 
-                            // TODO: Redesign evaluation of action
-                            // evaluateAction(policyRule, alarmDescriptorList, multi);
+        List<Uni<String>> alarmIds = getAlarmIds(alarmDescriptorList);
 
-                            return VALIDATED_POLICYRULE_STATE;
-                        });
+        List<Multi<AlarmResponse>> alarmResponseStreamList =
+                getAlarmResponse(alarmIds, policyRuleDevice);
+
+        // Merge the promised alarms into one stream (Multi Object)
+        final var multi = Multi.createBy().merging().streams(alarmResponseStreamList);
+        setPolicyRuleDeviceToContext(policyRuleDevice, PROVISIONED_POLICYRULE_STATE);
+
+        subscriptionList.put(policyId, monitorAlarmResponseForDevice(multi));
+
+        // TODO: Resubscribe to the stream, if it has ended
+
+        // TODO: Redesign evaluation of action
+        // evaluateAction(policyRule, alarmDescriptorList, multi);
+    }
+
+    private List<Multi<AlarmResponse>> getAlarmResponse(
+            List<Uni<String>> alarmIds, PolicyRuleDevice policyRuleDevice) {
+        // Transform the alarmIds into promised alarms returned from the
+        // getAlarmResponseStream
+        List<Multi<AlarmResponse>> alarmResponseStreamList = new ArrayList<>();
+        for (Uni<String> alarmId : alarmIds) {
+            alarmResponseStreamList.add(
+                    alarmId.onItem().transformToMulti(id -> setPolicyMonitoringDevice(policyRuleDevice, id)));
+        }
+        return alarmResponseStreamList;
+    }
+
+    private Multi<AlarmResponse> setPolicyMonitoringDevice(
+            PolicyRuleDevice policyRuleDevice, String id) {
+        alarmPolicyRuleDeviceMap.put(id, policyRuleDevice);
+
+        // TODO: Create infinite subscription
+        var alarmSubscription = new AlarmSubscription(id, 259200, 5000);
+        return monitoringService.getAlarmResponseStream(alarmSubscription);
+    }
+
+    private List<Uni<String>> getAlarmIds(List<AlarmDescriptor> alarmDescriptorList) {
+        List<Uni<String>> alarmIds = new ArrayList<Uni<String>>();
+        for (AlarmDescriptor alarmDescriptor : alarmDescriptorList) {
+            LOGGER.infof("alarmDescriptor:");
+            LOGGER.infof(alarmDescriptor.toString());
+            alarmIds.add(monitoringService.setKpiAlarm(alarmDescriptor));
+        }
+        return alarmIds;
     }
 
     @Override
@@ -435,32 +473,32 @@ public class PolicyServiceImpl implements PolicyService {
 
         final var getPolicyRule = contextService.getPolicyRule(policyRuleId);
 
-        return getPolicyRule
-                .onItem()
-                .transform(
-                        policyRule -> {
-                            var policyRuleBasic = policyRule.getPolicyRuleType().getPolicyRuleBasic();
-                            String policyId = policyRuleBasic.getPolicyRuleId();
+        return getPolicyRule.onItem().transform(policyRule -> removePolicyFromContext(policyRule));
+    }
 
-                            policyRule
-                                    .getPolicyRuleType()
-                                    .getPolicyRuleBasic()
-                                    .setPolicyRuleState(REMOVED_POLICYRULE_STATE);
+    private PolicyRuleState removePolicyFromContext(PolicyRule policyRule) {
+        var policyRuleBasic = policyRule.getPolicyRuleType().getPolicyRuleBasic();
+        String policyId = policyRuleBasic.getPolicyRuleId();
 
-                            contextService
-                                    .setPolicyRule(policyRule)
-                                    .subscribe()
-                                    .with(
-                                            tmp ->
-                                                    LOGGER.infof(
-                                                            "DeletePolicy with id: " + VALID_MESSAGE,
-                                                            policyRuleBasic.getPolicyRuleId()));
+        policyRule
+                .getPolicyRuleType()
+                .getPolicyRuleBasic()
+                .setPolicyRuleState(REMOVED_POLICYRULE_STATE);
+
+        contextService
+                .setPolicyRule(policyRule)
+                .subscribe()
+                .with(
+                        tmp ->
+                                LOGGER.infof(
+                                        "DeletePolicy with id: " + VALID_MESSAGE, policyRuleBasic.getPolicyRuleId()));
 
-                            contextService.removePolicyRule(policyId).subscribe().with(x -> {});
-                            subscriptionList.get(policyId).cancel();
+        contextService.removePolicyRule(policyId).subscribe().with(x -> {});
 
-                            return policyRuleBasic.getPolicyRuleState();
-                        });
+        // TODO: When the Map doesn't contains the policyId we should throw an exception?
+        if (subscriptionList.contains(policyId)) subscriptionList.get(policyId).cancel();
+
+        return policyRuleBasic.getPolicyRuleState();
     }
 
     private Uni<List<Boolean>> returnInvalidDeviceIds(List<String> deviceIds) {
@@ -514,8 +552,8 @@ public class PolicyServiceImpl implements PolicyService {
                         });
     }
 
-    private void monitorAlarmResponseForDevice(Multi<AlarmResponse> multi) {
-        multi
+    private Cancellable monitorAlarmResponseForDevice(Multi<AlarmResponse> multi) {
+        return multi
                 .subscribe()
                 .with(
                         alarmResponse -> {
diff --git a/src/policy/src/main/java/org/etsi/tfs/policy/common/ApplicationProperties.java b/src/policy/src/main/java/org/etsi/tfs/policy/common/ApplicationProperties.java
new file mode 100644
index 0000000000000000000000000000000000000000..f01fcd9cca39350bf103fd1e2fe894334c4a3b80
--- /dev/null
+++ b/src/policy/src/main/java/org/etsi/tfs/policy/common/ApplicationProperties.java
@@ -0,0 +1,59 @@
+/*
+* Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package org.etsi.tfs.policy.common;
+
+import org.etsi.tfs.policy.model.PolicyRuleState;
+import org.etsi.tfs.policy.model.PolicyRuleStateEnum;
+
+public class ApplicationProperties {
+
+    public static final String INVALID_MESSAGE = "%s is invalid.";
+    public static final String VALID_MESSAGE = "%s is valid.";
+
+    public static final PolicyRuleState INSERTED_POLICYRULE_STATE =
+            new PolicyRuleState(
+                    PolicyRuleStateEnum.POLICY_INSERTED, "Successfully entered to INSERTED state");
+    public static final PolicyRuleState VALIDATED_POLICYRULE_STATE =
+            new PolicyRuleState(
+                    PolicyRuleStateEnum.POLICY_VALIDATED, "Successfully transitioned to VALIDATED state");
+    public static final PolicyRuleState PROVISIONED_POLICYRULE_STATE =
+            new PolicyRuleState(
+                    PolicyRuleStateEnum.POLICY_PROVISIONED,
+                    "Successfully transitioned from VALIDATED to PROVISIONED state");
+    public static final PolicyRuleState ACTIVE_POLICYRULE_STATE =
+            new PolicyRuleState(
+                    PolicyRuleStateEnum.POLICY_ACTIVE,
+                    "Successfully transitioned from PROVISIONED to ACTIVE state");
+    public static final PolicyRuleState ENFORCED_POLICYRULE_STATE =
+            new PolicyRuleState(
+                    PolicyRuleStateEnum.POLICY_ENFORCED,
+                    "Successfully transitioned from ACTIVE to ENFORCED state");
+    public static final PolicyRuleState INEFFECTIVE_POLICYRULE_STATE =
+            new PolicyRuleState(
+                    PolicyRuleStateEnum.POLICY_INEFFECTIVE,
+                    "Transitioned from ENFORCED to INEFFECTIVE state");
+    public static final PolicyRuleState EFFECTIVE_POLICYRULE_STATE =
+            new PolicyRuleState(
+                    PolicyRuleStateEnum.POLICY_EFFECTIVE,
+                    "Successfully transitioned from ENFORCED to EFFECTIVE state");
+    public static final PolicyRuleState UPDATED_POLICYRULE_STATE =
+            new PolicyRuleState(
+                    PolicyRuleStateEnum.POLICY_UPDATED, "Successfully entered to UPDATED state");
+    public static final PolicyRuleState REMOVED_POLICYRULE_STATE =
+            new PolicyRuleState(
+                    PolicyRuleStateEnum.POLICY_REMOVED, "Successfully entered to REMOVED state");
+}
diff --git a/src/policy/src/main/java/org/etsi/tfs/policy/model/PolicyRuleBasic.java b/src/policy/src/main/java/org/etsi/tfs/policy/model/PolicyRuleBasic.java
index ea00ea3fc2a2fc8492ef60a860df4e9baf220bfe..7df894abd91f23005e95a5cda8d5df6d196c61f0 100644
--- a/src/policy/src/main/java/org/etsi/tfs/policy/model/PolicyRuleBasic.java
+++ b/src/policy/src/main/java/org/etsi/tfs/policy/model/PolicyRuleBasic.java
@@ -64,7 +64,7 @@ public class PolicyRuleBasic {
             this.booleanOperator = BooleanOperator.POLICYRULE_CONDITION_BOOLEAN_UNDEFINED;
             this.policyRuleActions = new ArrayList<PolicyRuleAction>();
             this.isValid = false;
-            this.exceptionMessage = e.toString();
+            this.exceptionMessage = e.getMessage();
         }
     }
 
diff --git a/src/policy/src/main/java/org/etsi/tfs/policy/model/PolicyRuleDevice.java b/src/policy/src/main/java/org/etsi/tfs/policy/model/PolicyRuleDevice.java
index 9c23692a13827e7701a3877a64fa4d625e25f877..f46635e87bebbcf1dee44987a770b01a2dc3b712 100644
--- a/src/policy/src/main/java/org/etsi/tfs/policy/model/PolicyRuleDevice.java
+++ b/src/policy/src/main/java/org/etsi/tfs/policy/model/PolicyRuleDevice.java
@@ -40,7 +40,7 @@ public class PolicyRuleDevice {
             this.policyRuleBasic = policyRuleBasic;
             this.deviceIds = new ArrayList<String>();
             this.isValid = false;
-            this.exceptionMessage = e.toString();
+            this.exceptionMessage = e.getMessage();
         }
     }
 
diff --git a/src/policy/src/main/java/org/etsi/tfs/policy/model/PolicyRuleService.java b/src/policy/src/main/java/org/etsi/tfs/policy/model/PolicyRuleService.java
index 1f507ebc944ceab6f8018b52c8d534f5b9795930..db25dc9bfb15bc64212ac1f141b73a18eed7ff24 100644
--- a/src/policy/src/main/java/org/etsi/tfs/policy/model/PolicyRuleService.java
+++ b/src/policy/src/main/java/org/etsi/tfs/policy/model/PolicyRuleService.java
@@ -50,7 +50,7 @@ public class PolicyRuleService {
             this.serviceId = new ServiceId("", "");
             this.deviceIds = new ArrayList<String>();
             this.isValid = false;
-            this.exceptionMessage = e.toString();
+            this.exceptionMessage = e.getMessage();
         }
     }
 
diff --git a/src/policy/src/test/java/org/etsi/tfs/policy/PolicyAddDeviceTest.java b/src/policy/src/test/java/org/etsi/tfs/policy/PolicyAddDeviceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..7c7c6b1b5e096ac9422ec5209b213e4f4435410b
--- /dev/null
+++ b/src/policy/src/test/java/org/etsi/tfs/policy/PolicyAddDeviceTest.java
@@ -0,0 +1,204 @@
+/*
+* 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.
+*/
+
+package org.etsi.tfs.policy;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.etsi.tfs.policy.common.ApplicationProperties.INVALID_MESSAGE;
+import static org.etsi.tfs.policy.common.ApplicationProperties.VALIDATED_POLICYRULE_STATE;
+
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.junit.mockito.InjectMock;
+import io.smallrye.mutiny.Uni;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import javax.inject.Inject;
+import org.etsi.tfs.policy.context.ContextService;
+import org.etsi.tfs.policy.model.BooleanOperator;
+import org.etsi.tfs.policy.model.NumericalOperator;
+import org.etsi.tfs.policy.model.PolicyRuleAction;
+import org.etsi.tfs.policy.model.PolicyRuleActionConfig;
+import org.etsi.tfs.policy.model.PolicyRuleActionEnum;
+import org.etsi.tfs.policy.model.PolicyRuleBasic;
+import org.etsi.tfs.policy.model.PolicyRuleCondition;
+import org.etsi.tfs.policy.model.PolicyRuleDevice;
+import org.etsi.tfs.policy.model.PolicyRuleState;
+import org.etsi.tfs.policy.model.PolicyRuleStateEnum;
+import org.etsi.tfs.policy.monitoring.MonitoringService;
+import org.etsi.tfs.policy.monitoring.model.IntegerKpiValue;
+import org.etsi.tfs.policy.monitoring.model.KpiValue;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+@QuarkusTest
+class PolicyAddDeviceTest {
+
+    @Inject PolicyServiceImpl policyService;
+
+    @InjectMock PolicyRuleConditionValidator policyRuleConditionValidator;
+
+    @InjectMock ContextService contextService;
+
+    @InjectMock MonitoringService monitoringService;
+    static PolicyRuleBasic policyRuleBasic;
+    static PolicyRuleDevice policyRuleDevice;
+
+    @BeforeAll
+    static void init() {
+
+        String policyId = "policyRuleId";
+        KpiValue kpiValue = new IntegerKpiValue(100);
+
+        PolicyRuleCondition policyRuleCondition =
+                new PolicyRuleCondition(
+                        "kpiId", NumericalOperator.POLICY_RULE_CONDITION_NUMERICAL_GREATER_THAN, kpiValue);
+
+        PolicyRuleActionConfig policyRuleActionConfig = new PolicyRuleActionConfig("key", "value");
+
+        PolicyRuleAction policyRuleAction =
+                new PolicyRuleAction(
+                        PolicyRuleActionEnum.POLICY_RULE_ACTION_NO_ACTION,
+                        Arrays.asList(policyRuleActionConfig));
+
+        policyRuleBasic =
+                new PolicyRuleBasic(
+                        policyId,
+                        new PolicyRuleState(PolicyRuleStateEnum.POLICY_INSERTED, "Failed due to some errors"),
+                        1,
+                        Arrays.asList(policyRuleCondition),
+                        BooleanOperator.POLICYRULE_CONDITION_BOOLEAN_OR,
+                        Arrays.asList(policyRuleAction));
+
+        List<String> deviceIds = Arrays.asList("device1", "device2");
+
+        policyRuleDevice = new PolicyRuleDevice(policyRuleBasic, deviceIds);
+    }
+
+    @Test
+    void deviceListMustNotBeEmpty()
+            throws ExecutionException, InterruptedException, TimeoutException {
+        CompletableFuture<PolicyRuleState> message = new CompletableFuture<>();
+
+        PolicyRuleDevice policyRuleDevice = new PolicyRuleDevice(policyRuleBasic, new ArrayList<>());
+
+        PolicyRuleState expectedResult =
+                new PolicyRuleState(PolicyRuleStateEnum.POLICY_FAILED, "Device Ids must not be empty.");
+
+        policyService
+                .addPolicyDevice(policyRuleDevice)
+                .subscribe()
+                .with(
+                        item -> {
+                            message.complete(item);
+                        });
+
+        assertThat(message.get(5, TimeUnit.SECONDS).getPolicyRuleStateMessage())
+                .isEqualTo(expectedResult.getPolicyRuleStateMessage().toString());
+    }
+
+    @Test
+    void isPolicyRuleBasicValid() throws ExecutionException, InterruptedException, TimeoutException {
+        CompletableFuture<PolicyRuleState> message = new CompletableFuture<>();
+
+        PolicyRuleBasic policyRuleBasic =
+                new PolicyRuleBasic(
+                        "policyId",
+                        new PolicyRuleState(PolicyRuleStateEnum.POLICY_INSERTED, "Failed due to some errors"),
+                        0,
+                        new ArrayList<>(),
+                        BooleanOperator.POLICYRULE_CONDITION_BOOLEAN_OR,
+                        new ArrayList<>());
+
+        PolicyRuleDevice policyRuleDevice =
+                new PolicyRuleDevice(policyRuleBasic, Arrays.asList("device1", "device2"));
+
+        PolicyRuleState expectedResult =
+                new PolicyRuleState(
+                        PolicyRuleStateEnum.POLICY_FAILED, "Policy Rule conditions cannot be empty.");
+
+        policyService
+                .addPolicyDevice(policyRuleDevice)
+                .subscribe()
+                .with(
+                        item -> {
+                            message.complete(item);
+                        });
+
+        assertThat(message.get(5, TimeUnit.SECONDS).getPolicyRuleStateMessage())
+                .isEqualTo(expectedResult.getPolicyRuleStateMessage().toString());
+    }
+
+    @Test
+    void isPolicyRuleIdValid() throws ExecutionException, InterruptedException, TimeoutException {
+        CompletableFuture<PolicyRuleState> message = new CompletableFuture<>();
+
+        PolicyRuleDevice policyRuleDevice =
+                new PolicyRuleDevice(policyRuleBasic, Arrays.asList("device1", "device2"));
+
+        PolicyRuleState expectedResult =
+                new PolicyRuleState(
+                        PolicyRuleStateEnum.POLICY_FAILED,
+                        String.format(
+                                INVALID_MESSAGE, policyRuleDevice.getPolicyRuleBasic().getPolicyRuleId()));
+
+        Mockito.when(policyRuleConditionValidator.isUpdatedPolicyRuleIdValid(Mockito.anyString()))
+                .thenReturn(Uni.createFrom().item(Boolean.FALSE));
+
+        policyService
+                .addPolicyDevice(policyRuleDevice)
+                .subscribe()
+                .with(
+                        item -> {
+                            message.complete(item);
+                        });
+
+        assertThat(message.get(5, TimeUnit.SECONDS).getPolicyRuleStateMessage())
+                .isEqualTo(expectedResult.getPolicyRuleStateMessage().toString());
+    }
+
+    @Test
+    void successPolicyDevice() throws ExecutionException, InterruptedException, TimeoutException {
+        CompletableFuture<PolicyRuleState> message = new CompletableFuture<>();
+
+        PolicyRuleDevice policyRuleDevice =
+                new PolicyRuleDevice(policyRuleBasic, Arrays.asList("device1", "device2"));
+
+        PolicyRuleState expectedResult =
+                new PolicyRuleState(
+                        PolicyRuleStateEnum.POLICY_VALIDATED,
+                        VALIDATED_POLICYRULE_STATE.getPolicyRuleStateMessage());
+
+        Mockito.when(policyRuleConditionValidator.isDeviceIdValid(Mockito.anyString()))
+                .thenReturn(Uni.createFrom().item(Boolean.TRUE));
+
+        policyService
+                .addPolicyDevice(policyRuleDevice)
+                .subscribe()
+                .with(
+                        item -> {
+                            message.complete(item);
+                        });
+
+        assertThat(message.get(5, TimeUnit.SECONDS).getPolicyRuleStateMessage())
+                .isEqualTo(expectedResult.getPolicyRuleStateMessage().toString());
+    }
+}
diff --git a/src/policy/src/test/java/org/etsi/tfs/policy/PolicyAddServiceTest.java b/src/policy/src/test/java/org/etsi/tfs/policy/PolicyAddServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..773187f0748c6f2d0507e2f78b6ba42cd558e2ae
--- /dev/null
+++ b/src/policy/src/test/java/org/etsi/tfs/policy/PolicyAddServiceTest.java
@@ -0,0 +1,251 @@
+/*
+* 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.
+*/
+
+package org.etsi.tfs.policy;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.etsi.tfs.policy.common.ApplicationProperties.*;
+
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.junit.mockito.InjectMock;
+import io.smallrye.mutiny.Uni;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import javax.inject.Inject;
+import org.etsi.tfs.policy.context.ContextService;
+import org.etsi.tfs.policy.context.model.Service;
+import org.etsi.tfs.policy.context.model.ServiceId;
+import org.etsi.tfs.policy.context.model.ServiceTypeEnum;
+import org.etsi.tfs.policy.model.BooleanOperator;
+import org.etsi.tfs.policy.model.NumericalOperator;
+import org.etsi.tfs.policy.model.PolicyRule;
+import org.etsi.tfs.policy.model.PolicyRuleAction;
+import org.etsi.tfs.policy.model.PolicyRuleActionConfig;
+import org.etsi.tfs.policy.model.PolicyRuleActionEnum;
+import org.etsi.tfs.policy.model.PolicyRuleBasic;
+import org.etsi.tfs.policy.model.PolicyRuleCondition;
+import org.etsi.tfs.policy.model.PolicyRuleService;
+import org.etsi.tfs.policy.model.PolicyRuleState;
+import org.etsi.tfs.policy.model.PolicyRuleStateEnum;
+import org.etsi.tfs.policy.monitoring.MonitoringService;
+import org.etsi.tfs.policy.monitoring.model.IntegerKpiValue;
+import org.etsi.tfs.policy.monitoring.model.KpiValue;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+@QuarkusTest
+public class PolicyAddServiceTest {
+
+    @Inject PolicyServiceImpl policyService;
+
+    @InjectMock PolicyRuleConditionValidator policyRuleConditionValidator;
+
+    @InjectMock ContextService contextService;
+
+    @InjectMock MonitoringService monitoringService;
+
+    static PolicyRuleBasic policyRuleBasic;
+    static PolicyRuleService policyRuleService;
+
+    @BeforeAll
+    static void init() {
+
+        String policyId = "policyRuleId";
+        KpiValue kpiValue = new IntegerKpiValue(100);
+
+        PolicyRuleCondition policyRuleCondition =
+                new PolicyRuleCondition(
+                        "kpiId", NumericalOperator.POLICY_RULE_CONDITION_NUMERICAL_GREATER_THAN, kpiValue);
+
+        PolicyRuleActionConfig policyRuleActionConfig = new PolicyRuleActionConfig("key", "value");
+
+        PolicyRuleAction policyRuleAction =
+                new PolicyRuleAction(
+                        PolicyRuleActionEnum.POLICY_RULE_ACTION_NO_ACTION,
+                        Arrays.asList(policyRuleActionConfig));
+
+        policyRuleBasic =
+                new PolicyRuleBasic(
+                        policyId,
+                        new PolicyRuleState(PolicyRuleStateEnum.POLICY_INSERTED, "Failed due to some errors"),
+                        1,
+                        Arrays.asList(policyRuleCondition),
+                        BooleanOperator.POLICYRULE_CONDITION_BOOLEAN_OR,
+                        Arrays.asList(policyRuleAction));
+
+        ServiceId serviceId = new ServiceId("contextId", "serviceId");
+
+        Service service = new Service(serviceId, ServiceTypeEnum.UNKNOWN, null, null, null, null, 0.0);
+
+        List<String> deviceIds = Arrays.asList("device1", "device2");
+
+        policyRuleService = new PolicyRuleService(policyRuleBasic, serviceId, deviceIds);
+    }
+
+    @Test
+    void contextOrServiceIdMustNotBeEmpty()
+            throws ExecutionException, InterruptedException, TimeoutException {
+        CompletableFuture<PolicyRuleState> message = new CompletableFuture<>();
+
+        ServiceId serviceId = new ServiceId("", "");
+        List<String> deviceIds = Arrays.asList("device1", "device2");
+
+        PolicyRuleService policyRuleService =
+                new PolicyRuleService(policyRuleBasic, serviceId, deviceIds);
+
+        PolicyRuleState expectedResult =
+                new PolicyRuleState(
+                        PolicyRuleStateEnum.POLICY_FAILED, "Context Id of Service Id must not be empty.");
+
+        policyService
+                .addPolicyService(policyRuleService)
+                .subscribe()
+                .with(
+                        item -> {
+                            message.complete(item);
+                        });
+
+        assertThat(message.get(5, TimeUnit.SECONDS).getPolicyRuleStateMessage())
+                .isEqualTo(expectedResult.getPolicyRuleStateMessage().toString());
+    }
+
+    @Test
+    void serviceIdMustNotBeEmpty() throws ExecutionException, InterruptedException, TimeoutException {
+        CompletableFuture<PolicyRuleState> message = new CompletableFuture<>();
+
+        ServiceId serviceId = new ServiceId("sdf", "");
+        List<String> deviceIds = Arrays.asList("device1", "device2");
+
+        PolicyRuleService policyRuleService =
+                new PolicyRuleService(policyRuleBasic, serviceId, deviceIds);
+
+        PolicyRuleState expectedResult =
+                new PolicyRuleState(PolicyRuleStateEnum.POLICY_FAILED, "Service Id must not be empty.");
+
+        policyService
+                .addPolicyService(policyRuleService)
+                .subscribe()
+                .with(item -> message.complete(item));
+
+        assertThat(message.get(5, TimeUnit.SECONDS).getPolicyRuleStateMessage())
+                .isEqualTo(expectedResult.getPolicyRuleStateMessage().toString());
+    }
+
+    @Test
+    void policyRuleIdMustNotBeEmpty()
+            throws ExecutionException, InterruptedException, TimeoutException {
+        CompletableFuture<PolicyRuleState> message = new CompletableFuture<>();
+
+        String policyId = "";
+
+        PolicyRuleBasic policyRuleBasic =
+                new PolicyRuleBasic(
+                        policyId,
+                        new PolicyRuleState(PolicyRuleStateEnum.POLICY_INSERTED, "Failed due to some errors"),
+                        1,
+                        new ArrayList<>(),
+                        null,
+                        new ArrayList<>());
+
+        ServiceId serviceId = new ServiceId("contextId", "serviceId");
+
+        Service service = new Service(serviceId, ServiceTypeEnum.UNKNOWN, null, null, null, null, 0.0);
+
+        PolicyRuleService policyRuleService =
+                new PolicyRuleService(policyRuleBasic, serviceId, new ArrayList<>());
+
+        PolicyRuleState expectedResult =
+                new PolicyRuleState(PolicyRuleStateEnum.POLICY_FAILED, "Policy rule ID must not be empty.");
+
+        policyService
+                .addPolicyService(policyRuleService)
+                .subscribe()
+                .with(
+                        item -> {
+                            message.complete(item);
+                        });
+
+        assertThat(message.get(5, TimeUnit.SECONDS).getPolicyRuleStateMessage())
+                .isEqualTo(expectedResult.getPolicyRuleStateMessage().toString());
+    }
+
+    @Test
+    void checkMessageIfServiceIsNotValid()
+            throws ExecutionException, InterruptedException, TimeoutException {
+        CompletableFuture<PolicyRuleState> message = new CompletableFuture<>();
+
+        ServiceId serviceId = new ServiceId("contextId", "serviceId");
+
+        PolicyRuleService policyRuleService =
+                new PolicyRuleService(policyRuleBasic, serviceId, new ArrayList<>());
+
+        PolicyRuleState expectedResult =
+                new PolicyRuleState(PolicyRuleStateEnum.POLICY_FAILED, serviceId + " is invalid.");
+
+        Mockito.when(
+                        policyRuleConditionValidator.isServiceIdValid(
+                                Mockito.any(ServiceId.class), Mockito.anyList()))
+                .thenReturn(Uni.createFrom().item(Boolean.FALSE));
+
+        policyService
+                .addPolicyService(policyRuleService)
+                .subscribe()
+                .with(
+                        item -> {
+                            message.complete(item);
+                        });
+
+        assertThat(message.get(5, TimeUnit.SECONDS).getPolicyRuleStateMessage())
+                .isEqualTo(expectedResult.getPolicyRuleStateMessage().toString());
+    }
+
+    @Test
+    void policyServiceSuccess()
+            throws ExecutionException, InterruptedException, TimeoutException, IOException {
+        CompletableFuture<PolicyRuleState> message = new CompletableFuture<>();
+
+        PolicyRuleState expectedResult =
+                new PolicyRuleState(
+                        PolicyRuleStateEnum.POLICY_VALIDATED,
+                        VALIDATED_POLICYRULE_STATE.getPolicyRuleStateMessage());
+
+        Mockito.when(
+                        policyRuleConditionValidator.isServiceIdValid(
+                                Mockito.any(ServiceId.class), Mockito.anyList()))
+                .thenReturn(Uni.createFrom().item(Boolean.TRUE));
+
+        Mockito.when(contextService.setPolicyRule(Mockito.any(PolicyRule.class)))
+                .thenReturn(Uni.createFrom().item("policyRuleId"));
+
+        policyService
+                .addPolicyService(policyRuleService)
+                .subscribe()
+                .with(
+                        item -> {
+                            message.complete(item);
+                        });
+
+        assertThat(message.get(5, TimeUnit.SECONDS).getPolicyRuleStateMessage())
+                .isEqualTo(expectedResult.getPolicyRuleStateMessage().toString());
+    }
+}
diff --git a/src/policy/src/test/java/org/etsi/tfs/policy/PolicyDeleteServiceTest.java b/src/policy/src/test/java/org/etsi/tfs/policy/PolicyDeleteServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..56e686bf6822a577439b020ea71d8c7f40b51c94
--- /dev/null
+++ b/src/policy/src/test/java/org/etsi/tfs/policy/PolicyDeleteServiceTest.java
@@ -0,0 +1,131 @@
+/*
+* 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.
+*/
+
+package org.etsi.tfs.policy;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.etsi.tfs.policy.common.ApplicationProperties.REMOVED_POLICYRULE_STATE;
+
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.junit.mockito.InjectMock;
+import io.smallrye.mutiny.Uni;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import javax.inject.Inject;
+import org.etsi.tfs.policy.context.ContextService;
+import org.etsi.tfs.policy.context.model.Service;
+import org.etsi.tfs.policy.context.model.ServiceId;
+import org.etsi.tfs.policy.context.model.ServiceTypeEnum;
+import org.etsi.tfs.policy.model.BooleanOperator;
+import org.etsi.tfs.policy.model.NumericalOperator;
+import org.etsi.tfs.policy.model.PolicyRule;
+import org.etsi.tfs.policy.model.PolicyRuleAction;
+import org.etsi.tfs.policy.model.PolicyRuleActionConfig;
+import org.etsi.tfs.policy.model.PolicyRuleActionEnum;
+import org.etsi.tfs.policy.model.PolicyRuleBasic;
+import org.etsi.tfs.policy.model.PolicyRuleCondition;
+import org.etsi.tfs.policy.model.PolicyRuleService;
+import org.etsi.tfs.policy.model.PolicyRuleState;
+import org.etsi.tfs.policy.model.PolicyRuleStateEnum;
+import org.etsi.tfs.policy.model.PolicyRuleType;
+import org.etsi.tfs.policy.model.PolicyRuleTypeService;
+import org.etsi.tfs.policy.monitoring.MonitoringService;
+import org.etsi.tfs.policy.monitoring.model.IntegerKpiValue;
+import org.etsi.tfs.policy.monitoring.model.KpiValue;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+@QuarkusTest
+class PolicyDeleteServiceTest {
+
+    @Inject PolicyServiceImpl policyService;
+    @InjectMock ContextService contextService;
+
+    @InjectMock MonitoringService monitoringService;
+
+    static PolicyRuleBasic policyRuleBasic;
+    static PolicyRuleService policyRuleService;
+
+    @BeforeAll
+    static void init() {
+
+        String policyId = "policyRuleId";
+        KpiValue kpiValue = new IntegerKpiValue(100);
+
+        PolicyRuleCondition policyRuleCondition =
+                new PolicyRuleCondition(
+                        "kpiId", NumericalOperator.POLICY_RULE_CONDITION_NUMERICAL_GREATER_THAN, kpiValue);
+
+        PolicyRuleActionConfig policyRuleActionConfig = new PolicyRuleActionConfig("key", "value");
+
+        PolicyRuleAction policyRuleAction =
+                new PolicyRuleAction(
+                        PolicyRuleActionEnum.POLICY_RULE_ACTION_NO_ACTION,
+                        Arrays.asList(policyRuleActionConfig));
+
+        policyRuleBasic =
+                new PolicyRuleBasic(
+                        policyId,
+                        new PolicyRuleState(PolicyRuleStateEnum.POLICY_INSERTED, "Failed due to some errors"),
+                        1,
+                        Arrays.asList(policyRuleCondition),
+                        BooleanOperator.POLICYRULE_CONDITION_BOOLEAN_OR,
+                        Arrays.asList(policyRuleAction));
+
+        ServiceId serviceId = new ServiceId("contextId", "serviceId");
+
+        Service service = new Service(serviceId, ServiceTypeEnum.UNKNOWN, null, null, null, null, 0.0);
+
+        List<String> deviceIds = Arrays.asList("device1", "device2");
+
+        policyRuleService = new PolicyRuleService(policyRuleBasic, serviceId, deviceIds);
+    }
+
+    @Test
+    void contextOrServiceIdMustNotBeEmpty()
+            throws ExecutionException, InterruptedException, TimeoutException {
+        CompletableFuture<PolicyRuleState> message = new CompletableFuture<>();
+
+        String policyRuleId = "";
+        PolicyRuleState expectedResult =
+                new PolicyRuleState(
+                        PolicyRuleStateEnum.POLICY_REMOVED,
+                        REMOVED_POLICYRULE_STATE.getPolicyRuleStateMessage());
+
+        PolicyRuleType policyRuleType = new PolicyRuleTypeService(policyRuleService);
+
+        PolicyRule policyRule = new PolicyRule(policyRuleType);
+
+        Mockito.when(contextService.getPolicyRule(Mockito.anyString()))
+                .thenReturn(Uni.createFrom().item(policyRule));
+
+        policyService
+                .deletePolicy(policyRuleId)
+                .subscribe()
+                .with(
+                        item -> {
+                            message.complete(item);
+                        });
+
+        assertThat(message.get(5, TimeUnit.SECONDS).getPolicyRuleStateMessage())
+                .isEqualTo(expectedResult.getPolicyRuleStateMessage().toString());
+    }
+}
diff --git a/src/policy/src/test/java/org/etsi/tfs/policy/PolicyServiceTest.java b/src/policy/src/test/java/org/etsi/tfs/policy/PolicyGrpcServiceTest.java
similarity index 99%
rename from src/policy/src/test/java/org/etsi/tfs/policy/PolicyServiceTest.java
rename to src/policy/src/test/java/org/etsi/tfs/policy/PolicyGrpcServiceTest.java
index 2d1a425a8d4166779d7e18e5deaecb9ff9ec49ca..2461bcee61984656dc99ac75679dae680ab7b20b 100644
--- a/src/policy/src/test/java/org/etsi/tfs/policy/PolicyServiceTest.java
+++ b/src/policy/src/test/java/org/etsi/tfs/policy/PolicyGrpcServiceTest.java
@@ -46,14 +46,14 @@ import policy.PolicyCondition.PolicyRuleCondition;
 import policy.PolicyService;
 
 @QuarkusTest
-class PolicyServiceTest {
-    private static final Logger LOGGER = Logger.getLogger(PolicyServiceTest.class);
+class PolicyGrpcServiceTest {
+    private static final Logger LOGGER = Logger.getLogger(PolicyGrpcServiceTest.class);
 
     @GrpcClient PolicyService client;
     private final Serializer serializer;
 
     @Inject
-    PolicyServiceTest(Serializer serializer) {
+    PolicyGrpcServiceTest(Serializer serializer) {
         this.serializer = serializer;
     }
 
diff --git a/src/policy/src/test/java/org/etsi/tfs/policy/PolicyUpdateDeviceTest.java b/src/policy/src/test/java/org/etsi/tfs/policy/PolicyUpdateDeviceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..ac8757508f2e0ccb5575fe210fe21819ab7e93aa
--- /dev/null
+++ b/src/policy/src/test/java/org/etsi/tfs/policy/PolicyUpdateDeviceTest.java
@@ -0,0 +1,204 @@
+/*
+* 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.
+*/
+
+package org.etsi.tfs.policy;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.etsi.tfs.policy.common.ApplicationProperties.INVALID_MESSAGE;
+import static org.etsi.tfs.policy.common.ApplicationProperties.VALIDATED_POLICYRULE_STATE;
+
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.junit.mockito.InjectMock;
+import io.smallrye.mutiny.Uni;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import javax.inject.Inject;
+import org.etsi.tfs.policy.model.BooleanOperator;
+import org.etsi.tfs.policy.model.NumericalOperator;
+import org.etsi.tfs.policy.model.PolicyRuleAction;
+import org.etsi.tfs.policy.model.PolicyRuleActionConfig;
+import org.etsi.tfs.policy.model.PolicyRuleActionEnum;
+import org.etsi.tfs.policy.model.PolicyRuleBasic;
+import org.etsi.tfs.policy.model.PolicyRuleCondition;
+import org.etsi.tfs.policy.model.PolicyRuleDevice;
+import org.etsi.tfs.policy.model.PolicyRuleState;
+import org.etsi.tfs.policy.model.PolicyRuleStateEnum;
+import org.etsi.tfs.policy.monitoring.MonitoringService;
+import org.etsi.tfs.policy.monitoring.model.IntegerKpiValue;
+import org.etsi.tfs.policy.monitoring.model.KpiValue;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+@QuarkusTest
+class PolicyUpdateDeviceTest {
+
+    @Inject PolicyServiceImpl policyService;
+
+    @InjectMock PolicyRuleConditionValidator policyRuleConditionValidator;
+
+    @InjectMock MonitoringService monitoringService;
+
+    static PolicyRuleBasic policyRuleBasic;
+    static PolicyRuleDevice policyRuleDevice;
+
+    @BeforeAll
+    static void init() {
+
+        String policyId = "policyRuleId";
+        KpiValue kpiValue = new IntegerKpiValue(100);
+
+        PolicyRuleCondition policyRuleCondition =
+                new PolicyRuleCondition(
+                        "kpiId", NumericalOperator.POLICY_RULE_CONDITION_NUMERICAL_GREATER_THAN, kpiValue);
+
+        PolicyRuleActionConfig policyRuleActionConfig = new PolicyRuleActionConfig("key", "value");
+
+        PolicyRuleAction policyRuleAction =
+                new PolicyRuleAction(
+                        PolicyRuleActionEnum.POLICY_RULE_ACTION_NO_ACTION,
+                        Arrays.asList(policyRuleActionConfig));
+
+        policyRuleBasic =
+                new PolicyRuleBasic(
+                        policyId,
+                        new PolicyRuleState(PolicyRuleStateEnum.POLICY_INSERTED, "Failed due to some errors"),
+                        1,
+                        Arrays.asList(policyRuleCondition),
+                        BooleanOperator.POLICYRULE_CONDITION_BOOLEAN_OR,
+                        Arrays.asList(policyRuleAction));
+
+        List<String> deviceIds = Arrays.asList("device1", "device2");
+
+        policyRuleDevice = new PolicyRuleDevice(policyRuleBasic, deviceIds);
+    }
+
+    @Test
+    void deviceListMustNotBeEmpty()
+            throws ExecutionException, InterruptedException, TimeoutException {
+        CompletableFuture<PolicyRuleState> message = new CompletableFuture<>();
+
+        PolicyRuleDevice policyRuleDevice = new PolicyRuleDevice(policyRuleBasic, new ArrayList<>());
+
+        PolicyRuleState expectedResult =
+                new PolicyRuleState(PolicyRuleStateEnum.POLICY_FAILED, "Device Ids must not be empty.");
+
+        policyService
+                .updatePolicyDevice(policyRuleDevice)
+                .subscribe()
+                .with(
+                        item -> {
+                            message.complete(item);
+                        });
+
+        assertThat(message.get(5, TimeUnit.SECONDS).getPolicyRuleStateMessage())
+                .isEqualTo(expectedResult.getPolicyRuleStateMessage().toString());
+    }
+
+    @Test
+    void isPolicyRuleBasicValid() throws ExecutionException, InterruptedException, TimeoutException {
+        CompletableFuture<PolicyRuleState> message = new CompletableFuture<>();
+
+        PolicyRuleBasic policyRuleBasic =
+                new PolicyRuleBasic(
+                        "policyId",
+                        new PolicyRuleState(PolicyRuleStateEnum.POLICY_INSERTED, "Failed due to some errors"),
+                        0,
+                        new ArrayList<>(),
+                        BooleanOperator.POLICYRULE_CONDITION_BOOLEAN_OR,
+                        new ArrayList<>());
+
+        PolicyRuleDevice policyRuleDevice =
+                new PolicyRuleDevice(policyRuleBasic, Arrays.asList("device1", "device2"));
+
+        PolicyRuleState expectedResult =
+                new PolicyRuleState(
+                        PolicyRuleStateEnum.POLICY_FAILED, "Policy Rule conditions cannot be empty.");
+
+        policyService
+                .updatePolicyDevice(policyRuleDevice)
+                .subscribe()
+                .with(
+                        item -> {
+                            message.complete(item);
+                        });
+
+        assertThat(message.get(5, TimeUnit.SECONDS).getPolicyRuleStateMessage())
+                .isEqualTo(expectedResult.getPolicyRuleStateMessage().toString());
+    }
+
+    @Test
+    void isUpdatedPolicyRuleIdValid()
+            throws ExecutionException, InterruptedException, TimeoutException {
+        CompletableFuture<PolicyRuleState> message = new CompletableFuture<>();
+
+        PolicyRuleDevice policyRuleDevice =
+                new PolicyRuleDevice(policyRuleBasic, Arrays.asList("device1", "device2"));
+
+        PolicyRuleState expectedResult =
+                new PolicyRuleState(
+                        PolicyRuleStateEnum.POLICY_FAILED,
+                        String.format(
+                                INVALID_MESSAGE, policyRuleDevice.getPolicyRuleBasic().getPolicyRuleId()));
+
+        Mockito.when(policyRuleConditionValidator.isUpdatedPolicyRuleIdValid(Mockito.anyString()))
+                .thenReturn(Uni.createFrom().item(Boolean.FALSE));
+
+        policyService
+                .updatePolicyDevice(policyRuleDevice)
+                .subscribe()
+                .with(
+                        item -> {
+                            message.complete(item);
+                        });
+
+        assertThat(message.get(5, TimeUnit.SECONDS).getPolicyRuleStateMessage())
+                .isEqualTo(expectedResult.getPolicyRuleStateMessage().toString());
+    }
+
+    @Test
+    void successUpdatePolicyService()
+            throws ExecutionException, InterruptedException, TimeoutException {
+        CompletableFuture<PolicyRuleState> message = new CompletableFuture<>();
+
+        PolicyRuleDevice policyRuleDevice =
+                new PolicyRuleDevice(policyRuleBasic, Arrays.asList("device1"));
+
+        PolicyRuleState expectedResult =
+                new PolicyRuleState(
+                        PolicyRuleStateEnum.POLICY_VALIDATED,
+                        VALIDATED_POLICYRULE_STATE.getPolicyRuleStateMessage());
+
+        Mockito.when(policyRuleConditionValidator.isUpdatedPolicyRuleIdValid(Mockito.anyString()))
+                .thenReturn(Uni.createFrom().item(Boolean.TRUE));
+
+        policyService
+                .updatePolicyDevice(policyRuleDevice)
+                .subscribe()
+                .with(
+                        item -> {
+                            message.complete(item);
+                        });
+
+        assertThat(message.get(5, TimeUnit.SECONDS).getPolicyRuleStateMessage())
+                .isEqualTo(expectedResult.getPolicyRuleStateMessage().toString());
+    }
+}
diff --git a/src/policy/src/test/java/org/etsi/tfs/policy/PolicyUpdateServiceTest.java b/src/policy/src/test/java/org/etsi/tfs/policy/PolicyUpdateServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e65f2d459557c25fe9ca1a61c97d9fd1accc1895
--- /dev/null
+++ b/src/policy/src/test/java/org/etsi/tfs/policy/PolicyUpdateServiceTest.java
@@ -0,0 +1,215 @@
+/*
+* 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.
+*/
+
+package org.etsi.tfs.policy;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.etsi.tfs.policy.common.ApplicationProperties.INVALID_MESSAGE;
+import static org.etsi.tfs.policy.common.ApplicationProperties.VALIDATED_POLICYRULE_STATE;
+
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.junit.mockito.InjectMock;
+import io.smallrye.mutiny.Uni;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import javax.inject.Inject;
+import org.etsi.tfs.policy.context.ContextService;
+import org.etsi.tfs.policy.context.model.Service;
+import org.etsi.tfs.policy.context.model.ServiceId;
+import org.etsi.tfs.policy.context.model.ServiceTypeEnum;
+import org.etsi.tfs.policy.model.BooleanOperator;
+import org.etsi.tfs.policy.model.NumericalOperator;
+import org.etsi.tfs.policy.model.PolicyRuleAction;
+import org.etsi.tfs.policy.model.PolicyRuleActionConfig;
+import org.etsi.tfs.policy.model.PolicyRuleActionEnum;
+import org.etsi.tfs.policy.model.PolicyRuleBasic;
+import org.etsi.tfs.policy.model.PolicyRuleCondition;
+import org.etsi.tfs.policy.model.PolicyRuleService;
+import org.etsi.tfs.policy.model.PolicyRuleState;
+import org.etsi.tfs.policy.model.PolicyRuleStateEnum;
+import org.etsi.tfs.policy.monitoring.MonitoringService;
+import org.etsi.tfs.policy.monitoring.model.IntegerKpiValue;
+import org.etsi.tfs.policy.monitoring.model.KpiValue;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+@QuarkusTest
+class PolicyUpdateServiceTest {
+
+    @Inject PolicyServiceImpl policyService;
+
+    @InjectMock PolicyRuleConditionValidator policyRuleConditionValidator;
+
+    @InjectMock ContextService contextService;
+
+    @InjectMock MonitoringService monitoringService;
+
+    static PolicyRuleBasic policyRuleBasic;
+    static PolicyRuleService policyRuleService;
+
+    @BeforeAll
+    static void init() {
+
+        String policyId = "policyRuleId";
+        KpiValue kpiValue = new IntegerKpiValue(100);
+
+        PolicyRuleCondition policyRuleCondition =
+                new PolicyRuleCondition(
+                        "kpiId", NumericalOperator.POLICY_RULE_CONDITION_NUMERICAL_GREATER_THAN, kpiValue);
+
+        PolicyRuleActionConfig policyRuleActionConfig = new PolicyRuleActionConfig("key", "value");
+
+        PolicyRuleAction policyRuleAction =
+                new PolicyRuleAction(
+                        PolicyRuleActionEnum.POLICY_RULE_ACTION_NO_ACTION,
+                        Arrays.asList(policyRuleActionConfig));
+
+        policyRuleBasic =
+                new PolicyRuleBasic(
+                        policyId,
+                        new PolicyRuleState(PolicyRuleStateEnum.POLICY_INSERTED, "Failed due to some errors"),
+                        1,
+                        Arrays.asList(policyRuleCondition),
+                        BooleanOperator.POLICYRULE_CONDITION_BOOLEAN_OR,
+                        Arrays.asList(policyRuleAction));
+
+        ServiceId serviceId = new ServiceId("contextId", "serviceId");
+
+        Service service = new Service(serviceId, ServiceTypeEnum.UNKNOWN, null, null, null, null, 0.0);
+
+        List<String> deviceIds = Arrays.asList("device1", "device2");
+
+        policyRuleService = new PolicyRuleService(policyRuleBasic, serviceId, deviceIds);
+    }
+
+    @Test
+    void contextOrServiceIdMustNotBeEmpty()
+            throws ExecutionException, InterruptedException, TimeoutException {
+        CompletableFuture<PolicyRuleState> message = new CompletableFuture<>();
+
+        ServiceId serviceId = new ServiceId("", "");
+        List<String> deviceIds = Arrays.asList("device1", "device2");
+
+        PolicyRuleService policyRuleService =
+                new PolicyRuleService(policyRuleBasic, serviceId, deviceIds);
+
+        PolicyRuleState expectedResult =
+                new PolicyRuleState(
+                        PolicyRuleStateEnum.POLICY_FAILED, "Context Id of Service Id must not be empty.");
+
+        policyService
+                .updatePolicyService(policyRuleService)
+                .subscribe()
+                .with(
+                        item -> {
+                            message.complete(item);
+                        });
+
+        assertThat(message.get(5, TimeUnit.SECONDS).getPolicyRuleStateMessage())
+                .isEqualTo(expectedResult.getPolicyRuleStateMessage().toString());
+    }
+
+    @Test
+    void serviceIdMustNotBeEmpty() throws ExecutionException, InterruptedException, TimeoutException {
+        CompletableFuture<PolicyRuleState> message = new CompletableFuture<>();
+
+        ServiceId serviceId = new ServiceId("sdf", "");
+        List<String> deviceIds = Arrays.asList("device1", "device2");
+
+        PolicyRuleService policyRuleService =
+                new PolicyRuleService(policyRuleBasic, serviceId, deviceIds);
+
+        PolicyRuleState expectedResult =
+                new PolicyRuleState(PolicyRuleStateEnum.POLICY_FAILED, "Service Id must not be empty.");
+
+        policyService
+                .updatePolicyService(policyRuleService)
+                .subscribe()
+                .with(item -> message.complete(item));
+
+        assertThat(message.get(5, TimeUnit.SECONDS).getPolicyRuleStateMessage())
+                .isEqualTo(expectedResult.getPolicyRuleStateMessage().toString());
+    }
+
+    @Test
+    void checkMessageIfServiceIsNotValid()
+            throws ExecutionException, InterruptedException, TimeoutException {
+        CompletableFuture<PolicyRuleState> message = new CompletableFuture<>();
+
+        ServiceId serviceId = new ServiceId("contextId", "serviceId");
+
+        PolicyRuleService policyRuleService =
+                new PolicyRuleService(policyRuleBasic, serviceId, new ArrayList<>());
+
+        PolicyRuleState expectedResult =
+                new PolicyRuleState(
+                        PolicyRuleStateEnum.POLICY_FAILED, String.format(INVALID_MESSAGE, serviceId));
+
+        Mockito.when(
+                        policyRuleConditionValidator.isPolicyRuleServiceValid(
+                                Mockito.anyString(), Mockito.any(ServiceId.class)))
+                .thenReturn(Uni.createFrom().item(Boolean.FALSE));
+
+        policyService
+                .updatePolicyService(policyRuleService)
+                .subscribe()
+                .with(
+                        item -> {
+                            message.complete(item);
+                        });
+
+        assertThat(message.get(5, TimeUnit.SECONDS).getPolicyRuleStateMessage())
+                .isEqualTo(expectedResult.getPolicyRuleStateMessage().toString());
+    }
+
+    @Test
+    void successUpdatePolicyService()
+            throws ExecutionException, InterruptedException, TimeoutException {
+        CompletableFuture<PolicyRuleState> message = new CompletableFuture<>();
+
+        ServiceId serviceId = new ServiceId("contextId", "serviceId");
+
+        PolicyRuleService policyRuleService =
+                new PolicyRuleService(policyRuleBasic, serviceId, new ArrayList<>());
+
+        PolicyRuleState expectedResult =
+                new PolicyRuleState(
+                        PolicyRuleStateEnum.POLICY_VALIDATED,
+                        VALIDATED_POLICYRULE_STATE.getPolicyRuleStateMessage());
+
+        Mockito.when(
+                        policyRuleConditionValidator.isPolicyRuleServiceValid(
+                                Mockito.anyString(), Mockito.any(ServiceId.class)))
+                .thenReturn(Uni.createFrom().item(Boolean.TRUE));
+
+        policyService
+                .updatePolicyService(policyRuleService)
+                .subscribe()
+                .with(
+                        item -> {
+                            message.complete(item);
+                        });
+
+        assertThat(message.get(5, TimeUnit.SECONDS).getPolicyRuleStateMessage())
+                .isEqualTo(expectedResult.getPolicyRuleStateMessage().toString());
+    }
+}
diff --git a/src/policy/target/kubernetes/kubernetes.yml b/src/policy/target/kubernetes/kubernetes.yml
index 55847f89e7c031de854d1e54336342f7a24e320a..2737f8546570e5d483c62024018137d96fe32a4d 100644
--- a/src/policy/target/kubernetes/kubernetes.yml
+++ b/src/policy/target/kubernetes/kubernetes.yml
@@ -3,8 +3,8 @@ apiVersion: v1
 kind: Service
 metadata:
   annotations:
-    app.quarkus.io/commit-id: 5f8866be9cb91871607627819258b0b375410467
-    app.quarkus.io/build-timestamp: 2024-01-26 - 16:40:15 +0000
+    app.quarkus.io/commit-id: 47e6691312515be37e2d9ffa85a1ee165a66c9db
+    app.quarkus.io/build-timestamp: 2024-02-09 - 14:52:23 +0000
     prometheus.io/scrape: "true"
     prometheus.io/path: /q/metrics
     prometheus.io/port: "8080"
@@ -29,8 +29,8 @@ apiVersion: apps/v1
 kind: Deployment
 metadata:
   annotations:
-    app.quarkus.io/commit-id: 5f8866be9cb91871607627819258b0b375410467
-    app.quarkus.io/build-timestamp: 2024-01-26 - 16:40:15 +0000
+    app.quarkus.io/commit-id: 47e6691312515be37e2d9ffa85a1ee165a66c9db
+    app.quarkus.io/build-timestamp: 2024-02-09 - 14:52:23 +0000
     prometheus.io/scrape: "true"
     prometheus.io/path: /q/metrics
     prometheus.io/port: "8080"
@@ -47,8 +47,8 @@ spec:
   template:
     metadata:
       annotations:
-        app.quarkus.io/commit-id: 5f8866be9cb91871607627819258b0b375410467
-        app.quarkus.io/build-timestamp: 2024-01-26 - 16:40:15 +0000
+        app.quarkus.io/commit-id: 47e6691312515be37e2d9ffa85a1ee165a66c9db
+        app.quarkus.io/build-timestamp: 2024-02-09 - 14:52:23 +0000
         prometheus.io/scrape: "true"
         prometheus.io/path: /q/metrics
         prometheus.io/port: "8080"
@@ -63,12 +63,12 @@ spec:
               valueFrom:
                 fieldRef:
                   fieldPath: metadata.namespace
+            - name: CONTEXT_SERVICE_HOST
+              value: contextservice
             - name: SERVICE_SERVICE_HOST
               value: serviceservice
             - name: MONITORING_SERVICE_HOST
               value: monitoringservice
-            - name: CONTEXT_SERVICE_HOST
-              value: contextservice
           image: labs.etsi.org:5050/tfs/controller/policy:0.1.0
           imagePullPolicy: Always
           livenessProbe:
diff --git a/src/service/requirements.in b/src/service/requirements.in
index 48fd76485d6bbaf53c3867147882614fc0cf1b04..a10f7da7aba3027a89bdd8e9b62bc4ab90fb5e02 100644
--- a/src/service/requirements.in
+++ b/src/service/requirements.in
@@ -15,6 +15,7 @@
 
 anytree==2.8.0
 geopy==2.3.0
+netaddr==0.9.0
 networkx==2.6.3
 pydot==1.4.2
 redis==4.1.2
diff --git a/src/service/service/service_handlers/__init__.py b/src/service/service/service_handlers/__init__.py
index 551d35c7ba8f5386831871fe70fdc962633e1a18..eaf8f715aeff435b67bce77928e726daeb4729e2 100644
--- a/src/service/service/service_handlers/__init__.py
+++ b/src/service/service/service_handlers/__init__.py
@@ -20,6 +20,7 @@ from .l2nm_openconfig.L2NMOpenConfigServiceHandler import L2NMOpenConfigServiceH
 from .l3nm_emulated.L3NMEmulatedServiceHandler import L3NMEmulatedServiceHandler
 from .l3nm_openconfig.L3NMOpenConfigServiceHandler import L3NMOpenConfigServiceHandler
 from .l3nm_gnmi_openconfig.L3NMGnmiOpenConfigServiceHandler import L3NMGnmiOpenConfigServiceHandler
+from .l3nm_ietf_actn.L3NMIetfActnServiceHandler import L3NMIetfActnServiceHandler
 from .microwave.MicrowaveServiceHandler import MicrowaveServiceHandler
 from .p4.p4_service_handler import P4ServiceHandler
 from .tapi_tapi.TapiServiceHandler import TapiServiceHandler
@@ -57,6 +58,12 @@ SERVICE_HANDLERS = [
             FilterFieldEnum.DEVICE_DRIVER : DeviceDriverEnum.DEVICEDRIVER_GNMI_OPENCONFIG,
         }
     ]),
+    (L3NMIetfActnServiceHandler, [
+        {
+            FilterFieldEnum.SERVICE_TYPE  : ServiceTypeEnum.SERVICETYPE_L3NM,
+            FilterFieldEnum.DEVICE_DRIVER : DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN,
+        }
+    ]),
     (TapiServiceHandler, [
         {
             FilterFieldEnum.SERVICE_TYPE  : ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE,
diff --git a/src/service/service/service_handlers/l3nm_ietf_actn/Constants.py b/src/service/service/service_handlers/l3nm_ietf_actn/Constants.py
new file mode 100644
index 0000000000000000000000000000000000000000..62babd7c20491a2673a6dc3d10db33d33bb0a47a
--- /dev/null
+++ b/src/service/service/service_handlers/l3nm_ietf_actn/Constants.py
@@ -0,0 +1,52 @@
+# 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.
+
+# These hardcoded values will be updated with proper logic in second phase of the PoC
+
+VPN_VLAN_TAGS_TO_SERVICE_NAME = {
+    (21, 101): ('osu_tunnel_1', 'etht_service_1'),
+    (31, 201): ('osu_tunnel_2', 'etht_service_2'),
+}
+
+OSU_TUNNEL_SETTINGS = {
+    'osu_tunnel_1': {
+        'odu_type': 'osuflex',
+        'osuflex_number': 40,
+        'bidirectional': True,
+        'delay': 20,
+        'ttp_channel_names': {
+            ('10.0.10.1', '200'): 'och:1-odu2:1-oduflex:1-osuflex:2',
+            ('10.0.30.1', '200'): 'och:1-odu2:1-oduflex:3-osuflex:1',
+        }
+    },
+    'osu_tunnel_2': {
+        'odu_type': 'osuflex',
+        'osuflex_number': 40,
+        'bidirectional': True,
+        'delay': 20,
+        'ttp_channel_names': {
+            ('10.0.10.1', '200'): 'och:1-odu2:1-oduflex:1-osuflex:2',
+            ('10.0.30.1', '200'): 'och:1-odu2:1-oduflex:3-osuflex:1',
+        }
+    },
+}
+
+ETHT_SERVICE_SETTINGS = {
+    'etht_service_1': {
+        'service_type': 'op-mp2mp-svc',
+    },
+    'etht_service_2': {
+        'service_type': 'op-mp2mp-svc',
+    },
+}
diff --git a/src/service/service/service_handlers/l3nm_ietf_actn/L3NMIetfActnServiceHandler.py b/src/service/service/service_handlers/l3nm_ietf_actn/L3NMIetfActnServiceHandler.py
new file mode 100644
index 0000000000000000000000000000000000000000..0c20fdf96d30d0c12ef3f8c0a29189befd791a15
--- /dev/null
+++ b/src/service/service/service_handlers/l3nm_ietf_actn/L3NMIetfActnServiceHandler.py
@@ -0,0 +1,318 @@
+# 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, netaddr
+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, Device, DeviceId, EndPoint, Service
+from common.tools.grpc.Tools import grpc_message_to_json_string
+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
+from .Constants import ETHT_SERVICE_SETTINGS, OSU_TUNNEL_SETTINGS, VPN_VLAN_TAGS_TO_SERVICE_NAME
+
+LOGGER = logging.getLogger(__name__)
+
+METRICS_POOL = MetricsPool('Service', 'Handler', labels={'handler': 'l3nm_ietf_actn'})
+
+class L3NMIetfActnServiceHandler(_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)
+
+    def _get_endpoint_details(
+        self, endpoint : Tuple[str, str, Optional[str]]
+    ) -> Tuple[Device, EndPoint, Dict]:
+        device_uuid, endpoint_uuid = get_device_endpoint_uuids(endpoint)
+        device_obj = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid)))
+        endpoint_obj = get_endpoint_matching(device_obj, endpoint_uuid)
+        endpoint_settings = self.__settings_handler.get_endpoint_settings(device_obj, endpoint_obj)
+        device_name = device_obj.name
+        endpoint_name = endpoint_obj.name
+        if endpoint_settings is None:
+            MSG = 'Settings not found for Endpoint(device=[uuid={:s}, name={:s}], endpoint=[uuid={:s}, name={:s}])'
+            raise Exception(MSG.format(device_uuid, device_name, endpoint_uuid, endpoint_name))
+        endpoint_settings_dict : Dict = endpoint_settings.value
+        return device_obj, endpoint_obj, endpoint_settings_dict
+
+    def _get_service_names(
+        self,
+        src_endpoint_details : Tuple[Device, EndPoint, Dict],
+        dst_endpoint_details : Tuple[Device, EndPoint, Dict]
+    ) -> Tuple[str, str]:
+        _, _, src_endpoint_settings_dict = src_endpoint_details
+        src_vlan_tag = src_endpoint_settings_dict['vlan_tag']
+
+        _, _, dst_endpoint_settings_dict = dst_endpoint_details
+        dst_vlan_tag = dst_endpoint_settings_dict['vlan_tag']
+
+        service_names = VPN_VLAN_TAGS_TO_SERVICE_NAME.get((src_vlan_tag, dst_vlan_tag))
+        if service_names is None:
+            MSG = 'Unable to find service names from VLAN tags(src={:s}, dst={:s})'
+            raise Exception(MSG.format(str(src_vlan_tag), str(dst_vlan_tag)))
+        return service_names
+
+    def _compose_osu_tunnel(
+        self, osu_tunnel_name : str,
+        src_endpoint_details : Tuple[Device, EndPoint, Dict],
+        dst_endpoint_details : Tuple[Device, EndPoint, Dict],
+        is_delete : bool = False
+    ) -> ConfigRule:
+        osu_tunnel_resource_key = '/osu_tunnels/osu_tunnel[{:s}]'.format(osu_tunnel_name)
+        osu_tunnel_resource_value = {'name' : osu_tunnel_name}
+        if is_delete:
+            osu_tunnel_config_rule = json_config_rule_delete(osu_tunnel_resource_key, osu_tunnel_resource_value)
+        else:
+            src_device_obj, src_endpoint_obj, _ = src_endpoint_details
+            dst_device_obj, dst_endpoint_obj, _ = dst_endpoint_details
+
+            osu_tunnel_settings = OSU_TUNNEL_SETTINGS[osu_tunnel_name]
+            ttp_channel_names = osu_tunnel_settings['ttp_channel_names']
+            src_ttp_channel_name = ttp_channel_names[(src_device_obj.name, src_endpoint_obj.name)]
+            dst_ttp_channel_name = ttp_channel_names[(dst_device_obj.name, dst_endpoint_obj.name)]
+
+            osu_tunnel_resource_value.update({
+                'odu_type'            : osu_tunnel_settings['odu_type'],
+                'osuflex_number'      : osu_tunnel_settings['osuflex_number'],
+                'bidirectional'       : osu_tunnel_settings['bidirectional'],
+                'delay'               : osu_tunnel_settings['delay'],
+                'src_node_id'         : src_device_obj.name,
+                'src_tp_id'           : src_endpoint_obj.name,
+                'src_ttp_channel_name': src_ttp_channel_name,
+                'dst_node_id'         : dst_device_obj.name,
+                'dst_tp_id'           : dst_endpoint_obj.name,
+                'dst_ttp_channel_name': dst_ttp_channel_name,
+            })
+            osu_tunnel_config_rule = json_config_rule_set(osu_tunnel_resource_key, osu_tunnel_resource_value)
+        LOGGER.debug('osu_tunnel_config_rule = {:s}'.format(str(osu_tunnel_config_rule)))
+        return ConfigRule(**osu_tunnel_config_rule)
+
+    def _compose_static_routing(
+        self, src_vlan_tag : int, dst_vlan_tag : int
+    ) -> Tuple[List[Dict], List[Dict]]:
+        static_routing = self.__settings_handler.get('/static_routing')
+        if static_routing is None: raise Exception('static_routing not found')
+        static_routing_dict : Dict = static_routing.value
+        src_static_routes = list()
+        dst_static_routes = list()
+        for _, static_route in static_routing_dict.items():
+            vlan_id     = static_route['vlan-id']
+            ipn_cidr    = netaddr.IPNetwork(static_route['ip-network'])
+            ipn_network = str(ipn_cidr.network)
+            ipn_preflen = int(ipn_cidr.prefixlen)
+            next_hop = static_route['next-hop']
+            if vlan_id == src_vlan_tag:
+                src_static_routes.append([ipn_network, ipn_preflen, next_hop])
+            elif vlan_id == dst_vlan_tag:
+                dst_static_routes.append([ipn_network, ipn_preflen, next_hop])
+        return src_static_routes, dst_static_routes
+
+    def _compose_etht_service(
+        self, etht_service_name : str, osu_tunnel_name : str,
+        src_endpoint_details : Tuple[Device, EndPoint, Dict],
+        dst_endpoint_details : Tuple[Device, EndPoint, Dict],
+        is_delete : bool = False
+    ) -> ConfigRule:
+        etht_service_resource_key = '/etht_services/etht_service[{:s}]'.format(etht_service_name)
+        etht_service_resource_value = {'name' : etht_service_name}
+        if is_delete:
+            etht_service_config_rule = json_config_rule_delete(etht_service_resource_key, etht_service_resource_value)
+        else:
+            src_device_obj, src_endpoint_obj, src_endpoint_details = src_endpoint_details
+            src_vlan_tag = src_endpoint_details['vlan_tag']
+            dst_device_obj, dst_endpoint_obj, dst_endpoint_details = dst_endpoint_details
+            dst_vlan_tag = dst_endpoint_details['vlan_tag']
+            src_static_routes, dst_static_routes = self._compose_static_routing(src_vlan_tag, dst_vlan_tag)
+            etht_service_resource_value.update({
+                'osu_tunnel_name'  : osu_tunnel_name,
+                'service_type'     : ETHT_SERVICE_SETTINGS[etht_service_name]['service_type'],
+                'src_node_id'      : src_device_obj.name,
+                'src_tp_id'        : src_endpoint_obj.name,
+                'src_vlan_tag'     : src_vlan_tag,
+                'src_static_routes': src_static_routes,
+                'dst_node_id'      : dst_device_obj.name,
+                'dst_tp_id'        : dst_endpoint_obj.name,
+                'dst_vlan_tag'     : dst_vlan_tag,
+                'dst_static_routes': dst_static_routes,
+            })
+            etht_service_config_rule = json_config_rule_set(etht_service_resource_key, etht_service_resource_value)
+        LOGGER.debug('etht_service_config_rule = {:s}'.format(str(etht_service_config_rule)))
+        return ConfigRule(**etht_service_config_rule)
+
+    @metered_subclass_method(METRICS_POOL)
+    def SetEndpoint(
+        self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None
+    ) -> List[Union[bool, Exception]]:
+        LOGGER.debug('endpoints = {:s}'.format(str(endpoints)))
+        chk_type('endpoints', endpoints, list)
+        if len(endpoints) < 2:
+            LOGGER.warning('nothing done: not enough endpoints')
+            return []
+        service_uuid = self.__service.service_id.service_uuid.uuid
+        LOGGER.debug('service_uuid = {:s}'.format(str(service_uuid)))
+        LOGGER.debug('self.__settings_handler = {:s}'.format(str(self.__settings_handler.dump_config_rules())))
+
+        results = []
+        try:
+            src_endpoint_details = self._get_endpoint_details(endpoints[0])
+            src_device_obj, _, _ = src_endpoint_details
+            src_controller = self.__task_executor.get_device_controller(src_device_obj)
+            if src_controller is None: src_controller = src_device_obj
+
+            dst_endpoint_details = self._get_endpoint_details(endpoints[-1])
+            dst_device_obj, _, _ = dst_endpoint_details
+            dst_controller = self.__task_executor.get_device_controller(dst_device_obj)
+            if dst_controller is None: dst_controller = dst_device_obj
+
+            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
+
+            osu_tunnel_name, etht_service_name = self._get_service_names(
+                src_endpoint_details, dst_endpoint_details
+            )
+
+            osu_tunnel_config_rule = self._compose_osu_tunnel(
+                osu_tunnel_name, src_endpoint_details, dst_endpoint_details,
+                is_delete=False
+            )
+
+            etht_service_config_rule = self._compose_etht_service(
+                etht_service_name, osu_tunnel_name, src_endpoint_details,
+                dst_endpoint_details, is_delete=False
+            )
+
+            del controller.device_config.config_rules[:]
+            controller.device_config.config_rules.append(osu_tunnel_config_rule)
+            controller.device_config.config_rules.append(etht_service_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)
+
+        LOGGER.debug('results = {:s}'.format(str(results)))
+        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]]:
+        LOGGER.debug('endpoints = {:s}'.format(str(endpoints)))
+        chk_type('endpoints', endpoints, list)
+        if len(endpoints) < 2:
+            LOGGER.warning('nothing done: not enough endpoints')
+            return []
+        service_uuid = self.__service.service_id.service_uuid.uuid
+        LOGGER.debug('service_uuid = {:s}'.format(str(service_uuid)))
+        LOGGER.debug('self.__settings_handler = {:s}'.format(str(self.__settings_handler.dump_config_rules())))
+
+        results = []
+        try:
+            src_endpoint_details = self._get_endpoint_details(endpoints[0])
+            src_device_obj, _, _ = src_endpoint_details
+            src_controller = self.__task_executor.get_device_controller(src_device_obj)
+            if src_controller is None: src_controller = src_device_obj
+
+            dst_endpoint_details = self._get_endpoint_details(endpoints[-1])
+            dst_device_obj, _, _ = dst_endpoint_details
+            dst_controller = self.__task_executor.get_device_controller(dst_device_obj)
+            if dst_controller is None: dst_controller = dst_device_obj
+
+            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
+
+            osu_tunnel_name, etht_service_name = self._get_service_names(
+                src_endpoint_details, dst_endpoint_details
+            )
+
+            osu_tunnel_config_rule = self._compose_osu_tunnel(
+                osu_tunnel_name, src_endpoint_details, dst_endpoint_details,
+                is_delete=True
+            )
+
+            etht_service_config_rule = self._compose_etht_service(
+                etht_service_name, osu_tunnel_name, src_endpoint_details,
+                dst_endpoint_details, is_delete=True
+            )
+
+            del controller.device_config.config_rules[:]
+            controller.device_config.config_rules.append(osu_tunnel_config_rule)
+            controller.device_config.config_rules.append(etht_service_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)
+
+        LOGGER.debug('results = {:s}'.format(str(results)))
+        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/l3nm_ietf_actn/__init__.py b/src/service/service/service_handlers/l3nm_ietf_actn/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..1549d9811aa5d1c193a44ad45d0d7773236c0612
--- /dev/null
+++ b/src/service/service/service_handlers/l3nm_ietf_actn/__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/f5g-poc-camara/POC-CAMARA-Guide.md b/src/tests/f5g-poc-camara/POC-CAMARA-Guide.md
new file mode 100644
index 0000000000000000000000000000000000000000..85ec44cb6df77089194b9c8d7a7f5309a8513677
--- /dev/null
+++ b/src/tests/f5g-poc-camara/POC-CAMARA-Guide.md
@@ -0,0 +1,162 @@
+# TeraFlowSDN - ETSI F5G PoC CAMARA Guide
+
+This guide describes how to:
+1. Configure and Deploy TeraFlowSDN for the ETSI F5G PoC CAMARA
+2. Start Mock IETF ACTN SDN Controller (for testing and debugging)
+3. Onboard the network topology descriptor
+4. Expose the topology through the RESTConf IETF Network endpoint
+5. Create Services through RESTConf IETF L3VPN endpoint
+6. Get State of Services through RESTConf IETF L3VPN endpoint
+7. Check configurations done in the Mock IETF ACTN SDN Controller (for testing and debugging)
+8. Destroy Services through RESTConf IETF L3VPN endpoint
+
+
+## 1. Configure and Deploy TeraFlowSDN for the ETSI F5G PoC CAMARA
+
+This guide assumes the user pre-configured a physical/virtual machine based on the steps described in
+the official
+[ETSI TeraFlowSDN - Deployment Guide](https://labs.etsi.org/rep/tfs/controller/-/wikis/1.-Deployment-Guide).
+
+__NOTE__: When you perform step _1.3. Deploy TeraFlowSDN_, configure the `my_deploy.sh` script modifying
+the following settings:
+```bash
+# ...
+export TFS_COMPONENTS="context device pathcomp service slice nbi webui"
+# ...
+export CRDB_DROP_DATABASE_IF_EXISTS="YES"
+# ...
+export QDB_DROP_TABLES_IF_EXIST="YES"
+# ...
+```
+
+After modifying the file, deploy the TeraFlowSDN using the regular script `./deploy/all.sh`.
+The script might take a while to run, especially the first time, since it needs to build the TeraFlowSDN
+microservices.
+
+
+## 2. Start Mock IETF ACTN SDN Controller (for testing and debugging)
+
+__NOTE__: This step is not needed when using the real NCE-T controller.
+
+Start the Mock IETF ACTN SDN Controller. This controller is a simple Python script that accepts requests
+based on agreed F5G PoC CAMARA and stores it in memory, mimicking the NCE-T controller.
+
+Run the Mock IETF ACTN SDN Controller as follows:
+```bash
+python src/tests/tools/mock_ietf_actn_sdn_ctrl/MockIetfActnSdnCtrl.py
+```
+
+
+## 3. Onboard the network topology descriptor
+
+The network topology descriptor is a TeraFlowSDN configuration file describing the different elements to be
+managed by the SDN controller, such as devices, links, networks, etc. A preliminary descriptor has been
+prepared for the PoC CAMARA. The file is named as `topology-real.json`.
+
+**NOTE**: Before onboarding file `topology-real.json`, update settings of device `nce-t` to match the IP
+address, port, username, password, HTTP scheme, etc. of the real NCE-T.
+
+To onboard the descriptor file, navigate to the [TeraFlowSDN WebUI](http://127.0.0.1/webui) > Home.
+Browse the file through the _Descriptors_ field, and click the _Submit_ button that is next to the field.
+The onboarding should take few seconds and the WebUI should report that 1 context, 1 topology, 1 controller,
+10 devices, and 24 links were added. Also, it should report that 1 topology was updated.
+
+Next, select in the field _Ctx/Topo_ the entry named as `Context(admin):Topology(admin)`, and click the
+_Submit_ button that is next to the field. The topology should be displayed just below.
+
+Then, navigate to the WebUI > Devices and WebUI > Links sections to familiarize with the details provided
+for each entity. You can check the details of each entity by clicking the eye-shaped icon on the right
+side of each row.
+
+The underlying devices are configured as EMULATED entities while the NCE-T controller is configured as an
+IP SDN controller. Auto-discovery of devices is not implemented as this will fall in PoC phase two.
+
+
+## 4. Expose the topology through the RESTConf IETF Network endpoint
+
+The TeraFlowSDN controller features an NBI component that exposes RESTConf-based endpoints. To retrieve the
+topology following the IETF Network data model, use the following `curl` (or similar) command:
+
+```bash
+curl -u admin:admin http://127.0.0.1/restconf/data/ietf-network:networks/
+```
+
+__NOTE__: The command requires to interrogate the complete database and might take few seconds to complete.
+
+
+## 5. Create Services through RESTConf IETF L3VPN endpoint
+
+The TeraFlowSDN controller's NBI component also exposes the IETF L3VPN endpoints to
+create/check_status/delete services. To try them, use the following `curl` (or similar) commands:
+
+```bash
+curl -u admin:admin -X POST -H "Content-Type: application/json" -d @src/nbi/tests/data/ietf_l3vpn_req_svc1.json http://127.0.0.1/restconf/data/ietf-l3vpn-svc:l3vpn-svc/vpn-services
+curl -u admin:admin -X POST -H "Content-Type: application/json" -d @src/nbi/tests/data/ietf_l3vpn_req_svc2.json http://127.0.0.1/restconf/data/ietf-l3vpn-svc:l3vpn-svc/vpn-services
+```
+
+__NOTE 1__: This command uses the provided descriptors for creating the VPN services with some adaptations
+to adjust to the official data model.
+
+__NOTE 2__: This command retrieves no data if everything succeeds, in case of error, it will be reported.
+
+This step will create the services in TeraFlowSDN and create the appropriate configuration rules in the
+NCE-T controller through the appropriate service handlers and SBI drivers.
+
+When the services are created, navigate to the WebUI > Services section to familiarize with the details
+provided for each service. You can check the details of the service by clicking the eye-shaped icon on
+the right side of each row.
+
+Note that two services are created per requested VPN. The reason for that is because those services named
+as "vpnX" (the name provided in the request) correspond to end-to-end services, while the others with a UUID
+as a name are generated by TeraFlowSDN to represent the transport segment managed through the NCE-T.
+TeraFlowSDN gathers the settings from the upper-layer end-to-end service and contructs the NCE-T-bound
+services.
+
+Also, you can navigate to the WebUI > Devices section, click on the eye-shaped icon next to the `nce-t`
+device and check the configuration rules (defined using an internal data model, not IETF ACTN) that are
+later converted into the IETF ACTN configuration instructions sent to the NCE-T.
+
+You should see in configuration rules of the `nce-t` device rules with a resource key formatted as
+`/osu_tunnels/osu_tunnel[{osu-tunnel-name}]` for each OSU tunnel, and others with resource key like
+`/etht_services/etht_service[{etht-service-name}]` for each EthT service.
+
+
+## 6. Get State of Services through RESTConf IETF L3VPN endpoint
+
+To get the status of the services, use the following `curl` (or similar) commands:
+
+```bash
+curl -u admin:admin http://127.0.0.1/restconf/data/ietf-l3vpn-svc:l3vpn-svc/vpn-services/vpn-service=vpn1
+curl -u admin:admin http://127.0.0.1/restconf/data/ietf-l3vpn-svc:l3vpn-svc/vpn-services/vpn-service=vpn2
+```
+
+__NOTE__: This command retrieves an empty dictionary with no error if the service is ready and ACTIVE.
+
+
+## 7. Check configurations done in the Mock IETF ACTN SDN Controller (for testing and debugging)
+
+__NOTE__: This step is not needed when using the real NCE-T controller.
+
+While running the Mock IETF ACTN SDN Controller, you can interrogate the OSU tunnels and EthT Services
+created using the following commands:
+
+```bash
+curl --insecure https://127.0.0.1:8443/restconf/v2/data/ietf-te:te/tunnels
+curl --insecure https://127.0.0.1:8443/restconf/v2/data/ietf-eth-tran-service:etht-svc
+```
+
+
+## 8. Destroy Services through RESTConf IETF L3VPN endpoint
+
+To destroy the services, use the following `curl` (or similar) commands:
+
+```bash
+curl -u admin:admin -X DELETE http://127.0.0.1/restconf/data/ietf-l3vpn-svc:l3vpn-svc/vpn-services/vpn-service=vpn1
+curl -u admin:admin -X DELETE http://127.0.0.1/restconf/data/ietf-l3vpn-svc:l3vpn-svc/vpn-services/vpn-service=vpn2
+```
+
+__NOTE__: This command retrieves no data when it succeeds.
+
+When the services are deleted, navigate to the WebUI > Services section verify that no service is present.
+Besides, navigate to the WebUI > Devices section, and inspect the NCE-T device to verify that the OSU
+tunnel and ETHT service configuration rules disapeared.
diff --git a/src/tests/f5g-poc-camara/data/topology-real.json b/src/tests/f5g-poc-camara/data/topology-real.json
new file mode 100644
index 0000000000000000000000000000000000000000..c8c146ce28101cb727ceb526ef8fcd3aeefb4e53
--- /dev/null
+++ b/src/tests/f5g-poc-camara/data/topology-real.json
@@ -0,0 +1,260 @@
+{
+    "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": "nce-t"}}, "name": "nce-t", "device_type": "ip-sdn-controller",
+         "device_operational_status": 1, "device_drivers": [10], "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": {
+                "scheme": "https", "username": "admin", "password": "admin", "base_url": "/restconf/v2/data",
+                "timeout": 120, "verify": false
+            }}}
+        ]}},
+
+        {"device_id": {"device_uuid": {"uuid": "10.0.10.1"}}, "name": "10.0.10.1", "device_type": "emu-packet-router",
+         "controller_id": {"device_uuid": {"uuid": "nce-t"}},
+         "device_operational_status": 1, "device_drivers": [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": [
+                {"uuid": "mgmt", "name": "mgmt", "type": "mgmt"   },
+                {"uuid": "200",  "name": "200",  "type": "copper" },
+                {"uuid": "500",  "name": "500",  "type": "optical"},
+                {"uuid": "501",  "name": "501",  "type": "optical"}
+            ]}}}
+        ]}},
+
+        {"device_id": {"device_uuid": {"uuid": "10.0.20.1"}}, "name": "10.0.20.1", "device_type": "emu-packet-router",
+         "controller_id": {"device_uuid": {"uuid": "nce-t"}},
+         "device_operational_status": 1, "device_drivers": [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": [
+                {"uuid": "mgmt", "name": "mgmt", "type": "mgmt"   },
+                {"uuid": "500",  "name": "500",  "type": "optical"},
+                {"uuid": "501",  "name": "501",  "type": "optical"}
+            ]}}}
+        ]}},
+
+        {"device_id": {"device_uuid": {"uuid": "10.0.30.1"}}, "name": "10.0.30.1", "device_type": "emu-packet-router",
+         "controller_id": {"device_uuid": {"uuid": "nce-t"}},
+         "device_operational_status": 1, "device_drivers": [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": [
+                {"uuid": "mgmt", "name": "mgmt", "type": "mgmt"   },
+                {"uuid": "200",  "name": "200",  "type": "copper" },
+                {"uuid": "500",  "name": "500",  "type": "optical"},
+                {"uuid": "501",  "name": "501",  "type": "optical"}
+            ]}}}
+        ]}},
+
+        {"device_id": {"device_uuid": {"uuid": "10.0.40.1"}}, "name": "10.0.40.1", "device_type": "emu-packet-router",
+         "controller_id": {"device_uuid": {"uuid": "nce-t"}},
+         "device_operational_status": 1, "device_drivers": [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": [
+                {"uuid": "mgmt", "name": "mgmt", "type": "mgmt"   },
+                {"uuid": "500",  "name": "500",  "type": "optical"},
+                {"uuid": "501",  "name": "501",  "type": "optical"}
+            ]}}}
+        ]}},
+
+        {"device_id": {"device_uuid": {"uuid": "128.32.10.5"}}, "name": "128.32.10.5", "device_type": "emu-client",
+         "device_operational_status": 1, "device_drivers": [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": [
+                {"uuid": "eth1", "name": "eth1", "type": "copper"}
+            ]}}}
+        ]}},
+
+        {"device_id": {"device_uuid": {"uuid": "128.32.10.1"}}, "name": "128.32.10.1", "device_type": "emu-packet-router",
+         "device_operational_status": 1, "device_drivers": [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": [
+                {"uuid": "200", "name": "200", "type": "copper"},
+                {"uuid": "500", "name": "500", "type": "copper"}
+            ]}}}
+        ]}},
+
+        {"device_id": {"device_uuid": {"uuid": "128.32.20.5"}}, "name": "128.32.20.5", "device_type": "emu-client",
+         "device_operational_status": 1, "device_drivers": [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": [
+                {"uuid": "eth1", "name": "eth1", "type": "copper"}
+            ]}}}
+        ]}},
+
+        {"device_id": {"device_uuid": {"uuid": "128.32.20.1"}}, "name": "128.32.20.1", "device_type": "emu-packet-router",
+         "device_operational_status": 1, "device_drivers": [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": [
+                {"uuid": "200", "name": "200", "type": "copper"},
+                {"uuid": "500", "name": "500", "type": "copper"}
+            ]}}}
+        ]}},
+
+        {"device_id": {"device_uuid": {"uuid": "128.32.33.5"}}, "name": "128.32.33.5", "device_type": "emu-packet-router",
+         "device_operational_status": 1, "device_drivers": [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": [
+                {"uuid": "200", "name": "200", "type": "copper"},
+                {"uuid": "201", "name": "201", "type": "copper"},
+                {"uuid": "500", "name": "500", "type": "copper"}
+            ]}}}
+        ]}},
+
+        {"device_id": {"device_uuid": {"uuid": "172.10.33.5"}}, "name": "172.10.33.5", "device_type": "emu-datacenter",
+         "device_operational_status": 1, "device_drivers": [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": [
+                {"uuid": "200", "name": "200", "type": "copper"},
+                {"uuid": "201", "name": "201", "type": "copper"},
+                {"uuid": "500", "name": "500", "type": "copper"}
+            ]}}}
+        ]}}
+    ],
+
+    "links": [
+        {"link_id": {"link_uuid": {"uuid": "nce-t/mgmt==10.0.10.1/mgmt"}}, "name": "nce-t/mgmt==10.0.10.1/mgmt", "link_endpoint_ids": [
+            {"device_id": {"device_uuid": {"uuid": "nce-t"    }}, "endpoint_uuid": {"uuid": "mgmt"}},
+            {"device_id": {"device_uuid": {"uuid": "10.0.10.1"}}, "endpoint_uuid": {"uuid": "mgmt"}}
+        ]},
+        {"link_id": {"link_uuid": {"uuid": "nce-t/mgmt==10.0.20.1/mgmt"}}, "name": "nce-t/mgmt==10.0.20.1/mgmt", "link_endpoint_ids": [
+            {"device_id": {"device_uuid": {"uuid": "nce-t"    }}, "endpoint_uuid": {"uuid": "mgmt"}},
+            {"device_id": {"device_uuid": {"uuid": "10.0.20.1"}}, "endpoint_uuid": {"uuid": "mgmt"}}
+        ]},
+        {"link_id": {"link_uuid": {"uuid": "nce-t/mgmt==10.0.30.1/mgmt"}}, "name": "nce-t/mgmt==10.0.30.1/mgmt", "link_endpoint_ids": [
+            {"device_id": {"device_uuid": {"uuid": "nce-t"    }}, "endpoint_uuid": {"uuid": "mgmt"}},
+            {"device_id": {"device_uuid": {"uuid": "10.0.30.1"}}, "endpoint_uuid": {"uuid": "mgmt"}}
+        ]},
+        {"link_id": {"link_uuid": {"uuid": "nce-t/mgmt==10.0.40.1/mgmt"}}, "name": "nce-t/mgmt==10.0.40.1/mgmt", "link_endpoint_ids": [
+            {"device_id": {"device_uuid": {"uuid": "nce-t"    }}, "endpoint_uuid": {"uuid": "mgmt"}},
+            {"device_id": {"device_uuid": {"uuid": "10.0.40.1"}}, "endpoint_uuid": {"uuid": "mgmt"}}
+        ]},
+
+        {"link_id": {"link_uuid": {"uuid": "10.0.10.1-501"}}, "name": "10.0.10.1-501",
+         "attributes": {"total_capacity_gbps": 10, "used_capacity_gbps": 0}, "link_endpoint_ids": [
+            {"device_id": {"device_uuid": {"uuid": "10.0.10.1"}}, "endpoint_uuid": {"uuid": "501"}},
+            {"device_id": {"device_uuid": {"uuid": "10.0.20.1"}}, "endpoint_uuid": {"uuid": "501"}}
+        ]},
+        {"link_id": {"link_uuid": {"uuid": "10.0.20.1-501"}}, "name": "10.0.20.1-501",
+         "attributes": {"total_capacity_gbps": 10, "used_capacity_gbps": 0}, "link_endpoint_ids": [
+            {"device_id": {"device_uuid": {"uuid": "10.0.20.1"}}, "endpoint_uuid": {"uuid": "501"}},
+            {"device_id": {"device_uuid": {"uuid": "10.0.10.1"}}, "endpoint_uuid": {"uuid": "501"}}
+        ]},
+
+        {"link_id": {"link_uuid": {"uuid": "10.0.10.1-500"}}, "name": "10.0.10.1-500",
+         "attributes": {"total_capacity_gbps": 10, "used_capacity_gbps": 0}, "link_endpoint_ids": [
+            {"device_id": {"device_uuid": {"uuid": "10.0.10.1"}}, "endpoint_uuid": {"uuid": "500"}},
+            {"device_id": {"device_uuid": {"uuid": "10.0.40.1"}}, "endpoint_uuid": {"uuid": "500"}}
+        ]},
+        {"link_id": {"link_uuid": {"uuid": "10.0.40.1-500"}}, "name": "10.0.40.1-500",
+         "attributes": {"total_capacity_gbps": 10, "used_capacity_gbps": 0}, "link_endpoint_ids": [
+            {"device_id": {"device_uuid": {"uuid": "10.0.40.1"}}, "endpoint_uuid": {"uuid": "500"}},
+            {"device_id": {"device_uuid": {"uuid": "10.0.10.1"}}, "endpoint_uuid": {"uuid": "500"}}
+        ]},
+
+        {"link_id": {"link_uuid": {"uuid": "10.0.20.1-500"}}, "name": "10.0.20.1-500",
+         "attributes": {"total_capacity_gbps": 10, "used_capacity_gbps": 0}, "link_endpoint_ids": [
+            {"device_id": {"device_uuid": {"uuid": "10.0.20.1"}}, "endpoint_uuid": {"uuid": "500"}},
+            {"device_id": {"device_uuid": {"uuid": "10.0.30.1"}}, "endpoint_uuid": {"uuid": "500"}}
+        ]},
+        {"link_id": {"link_uuid": {"uuid": "10.0.30.1-500"}}, "name": "10.0.30.1-500",
+         "attributes": {"total_capacity_gbps": 10, "used_capacity_gbps": 0}, "link_endpoint_ids": [
+            {"device_id": {"device_uuid": {"uuid": "10.0.30.1"}}, "endpoint_uuid": {"uuid": "500"}},
+            {"device_id": {"device_uuid": {"uuid": "10.0.20.1"}}, "endpoint_uuid": {"uuid": "500"}}
+        ]},
+
+        {"link_id": {"link_uuid": {"uuid": "10.0.40.1-501"}}, "name": "10.0.40.1-501",
+         "attributes": {"total_capacity_gbps": 10, "used_capacity_gbps": 0}, "link_endpoint_ids": [
+            {"device_id": {"device_uuid": {"uuid": "10.0.40.1"}}, "endpoint_uuid": {"uuid": "501"}},
+            {"device_id": {"device_uuid": {"uuid": "10.0.30.1"}}, "endpoint_uuid": {"uuid": "501"}}
+        ]},
+        {"link_id": {"link_uuid": {"uuid": "10.0.30.1-501"}}, "name": "10.0.30.1-501",
+         "attributes": {"total_capacity_gbps": 10, "used_capacity_gbps": 0}, "link_endpoint_ids": [
+            {"device_id": {"device_uuid": {"uuid": "10.0.30.1"}}, "endpoint_uuid": {"uuid": "501"}},
+            {"device_id": {"device_uuid": {"uuid": "10.0.40.1"}}, "endpoint_uuid": {"uuid": "501"}}
+        ]},
+
+        {"link_id": {"link_uuid": {"uuid": "128.32.10.5-eth1"}}, "name": "128.32.10.5-eth1",
+         "attributes": {"total_capacity_gbps": 10, "used_capacity_gbps": 0}, "link_endpoint_ids": [
+            {"device_id": {"device_uuid": {"uuid": "128.32.10.5"}}, "endpoint_uuid": {"uuid": "eth1"}},
+            {"device_id": {"device_uuid": {"uuid": "128.32.10.1"}}, "endpoint_uuid": {"uuid": "200" }}
+        ]},
+        {"link_id": {"link_uuid": {"uuid": "128.32.10.1-200"}}, "name": "128.32.10.1-200",
+         "attributes": {"total_capacity_gbps": 10, "used_capacity_gbps": 0}, "link_endpoint_ids": [
+            {"device_id": {"device_uuid": {"uuid": "128.32.10.1"}}, "endpoint_uuid": {"uuid": "200" }},
+            {"device_id": {"device_uuid": {"uuid": "128.32.10.5"}}, "endpoint_uuid": {"uuid": "eth1"}}
+        ]},
+
+        {"link_id": {"link_uuid": {"uuid": "128.32.10.1-500"}}, "name": "128.32.10.1-500",
+         "attributes": {"total_capacity_gbps": 10, "used_capacity_gbps": 0}, "link_endpoint_ids": [
+            {"device_id": {"device_uuid": {"uuid": "128.32.10.1"}}, "endpoint_uuid": {"uuid": "500"}},
+            {"device_id": {"device_uuid": {"uuid": "128.32.33.5"}}, "endpoint_uuid": {"uuid": "200"}}
+        ]},
+        {"link_id": {"link_uuid": {"uuid": "128.32.33.5-200"}}, "name": "128.32.33.5-200",
+         "attributes": {"total_capacity_gbps": 10, "used_capacity_gbps": 0}, "link_endpoint_ids": [
+            {"device_id": {"device_uuid": {"uuid": "128.32.33.5"}}, "endpoint_uuid": {"uuid": "200"}},
+            {"device_id": {"device_uuid": {"uuid": "128.32.10.1"}}, "endpoint_uuid": {"uuid": "500"}}
+        ]},
+
+        {"link_id": {"link_uuid": {"uuid": "128.32.20.5-eth1"}}, "name": "128.32.20.5-eth1",
+         "attributes": {"total_capacity_gbps": 10, "used_capacity_gbps": 0}, "link_endpoint_ids": [
+            {"device_id": {"device_uuid": {"uuid": "128.32.20.5"}}, "endpoint_uuid": {"uuid": "eth1"}},
+            {"device_id": {"device_uuid": {"uuid": "128.32.20.1"}}, "endpoint_uuid": {"uuid": "200" }}
+        ]},
+        {"link_id": {"link_uuid": {"uuid": "128.32.20.1-200"}}, "name": "128.32.20.1-200",
+         "attributes": {"total_capacity_gbps": 10, "used_capacity_gbps": 0}, "link_endpoint_ids": [
+            {"device_id": {"device_uuid": {"uuid": "128.32.20.1"}}, "endpoint_uuid": {"uuid": "200" }},
+            {"device_id": {"device_uuid": {"uuid": "128.32.20.5"}}, "endpoint_uuid": {"uuid": "eth1"}}
+        ]},
+
+        {"link_id": {"link_uuid": {"uuid": "128.32.20.1-500"}}, "name": "128.32.20.1-500",
+         "attributes": {"total_capacity_gbps": 10, "used_capacity_gbps": 0}, "link_endpoint_ids": [
+            {"device_id": {"device_uuid": {"uuid": "128.32.20.1"}}, "endpoint_uuid": {"uuid": "500"}},
+            {"device_id": {"device_uuid": {"uuid": "128.32.33.5"}}, "endpoint_uuid": {"uuid": "201"}}
+        ]},
+        {"link_id": {"link_uuid": {"uuid": "128.32.33.5-201"}}, "name": "128.32.33.5-201",
+         "attributes": {"total_capacity_gbps": 10, "used_capacity_gbps": 0}, "link_endpoint_ids": [
+            {"device_id": {"device_uuid": {"uuid": "128.32.33.5"}}, "endpoint_uuid": {"uuid": "201"}},
+            {"device_id": {"device_uuid": {"uuid": "128.32.20.1"}}, "endpoint_uuid": {"uuid": "500"}}
+        ]},
+
+        {"link_id": {"link_uuid": {"uuid": "128.32.33.5-500"}}, "name": "128.32.33.5-500",
+         "attributes": {"total_capacity_gbps": 10, "used_capacity_gbps": 0}, "link_endpoint_ids": [
+            {"device_id": {"device_uuid": {"uuid": "128.32.33.5"}}, "endpoint_uuid": {"uuid": "500"}},
+            {"device_id": {"device_uuid": {"uuid": "10.0.10.1"}}, "endpoint_uuid": {"uuid": "200"}}
+        ]},
+        {"link_id": {"link_uuid": {"uuid": "10.0.10.1-200"}}, "name": "10.0.10.1-200",
+         "attributes": {"total_capacity_gbps": 10, "used_capacity_gbps": 0}, "link_endpoint_ids": [
+            {"device_id": {"device_uuid": {"uuid": "10.0.10.1"}}, "endpoint_uuid": {"uuid": "200"}},
+            {"device_id": {"device_uuid": {"uuid": "128.32.33.5"}}, "endpoint_uuid": {"uuid": "500"}}
+        ]},
+
+        {"link_id": {"link_uuid": {"uuid": "172.10.33.5-500"}}, "name": "172.10.33.5-500",
+         "attributes": {"total_capacity_gbps": 10, "used_capacity_gbps": 0}, "link_endpoint_ids": [
+            {"device_id": {"device_uuid": {"uuid": "172.10.33.5"}}, "endpoint_uuid": {"uuid": "500"}},
+            {"device_id": {"device_uuid": {"uuid": "10.0.30.1"}}, "endpoint_uuid": {"uuid": "200"}}
+        ]},
+        {"link_id": {"link_uuid": {"uuid": "10.0.30.1-200"}}, "name": "10.0.30.1-200",
+         "attributes": {"total_capacity_gbps": 10, "used_capacity_gbps": 0}, "link_endpoint_ids": [
+            {"device_id": {"device_uuid": {"uuid": "10.0.30.1"}}, "endpoint_uuid": {"uuid": "200"}},
+            {"device_id": {"device_uuid": {"uuid": "172.10.33.5"}}, "endpoint_uuid": {"uuid": "500"}}
+        ]}
+    ]
+}
diff --git a/src/tests/tools/mock_ietf_actn_sdn_ctrl/MockIetfActnSdnCtrl.py b/src/tests/tools/mock_ietf_actn_sdn_ctrl/MockIetfActnSdnCtrl.py
index c459c294ccb1c457c258a4e80018a90244702b8b..26243e2b6bd8e8d8bd2b64e9552be7d9d36c853e 100644
--- a/src/tests/tools/mock_ietf_actn_sdn_ctrl/MockIetfActnSdnCtrl.py
+++ b/src/tests/tools/mock_ietf_actn_sdn_ctrl/MockIetfActnSdnCtrl.py
@@ -29,7 +29,7 @@ from ResourceOsuTunnels import OsuTunnel, OsuTunnels
 
 BIND_ADDRESS = '0.0.0.0'
 BIND_PORT    = 8443
-BASE_URL     = '/restconf/data'
+BASE_URL     = '/restconf/v2/data'
 STR_ENDPOINT = 'https://{:s}:{:s}{:s}'.format(str(BIND_ADDRESS), str(BIND_PORT), str(BASE_URL))
 LOG_LEVEL    = logging.DEBUG
 
diff --git a/src/tests/tools/mock_ietf_actn_sdn_ctrl/README.md b/src/tests/tools/mock_ietf_actn_sdn_ctrl/README.md
index a12ae907e792f98413903b6637738c392506be4a..52aa2922d0699b1d286319e11cc25f8707fda686 100644
--- a/src/tests/tools/mock_ietf_actn_sdn_ctrl/README.md
+++ b/src/tests/tools/mock_ietf_actn_sdn_ctrl/README.md
@@ -7,22 +7,17 @@ This REST server implements very basic support for the following YANG data model
   - Ref: https://datatracker.ietf.org/doc/draft-ietf-teas-yang-te/
 
 The aim of this server is to enable testing the IetfActnDeviceDriver and the IetfActnServiceHandler.
-Follow the steps below to perform the test:
 
-## 1. Deploy TeraFlowSDN controller and the scenario
-Deploy the test scenario "ietf_actn_deploy.sh":
-```bash
-source src/tests/tools/mock_ietf_actn_sdn_ctrl/scenario/ietf_actn_deploy.sh
-./deploy/all.sh
-```
 
-## 2. Install requirements and run the Mock IETF ACTN SDN controller
-__NOTE__: if you run the Mock IETF ACTN SDN controller from the PyEnv used for developping on the TeraFlowSDN framework,
+## 1. Install requirements for the Mock IETF ACTN SDN controller
+__NOTE__: if you run the Mock IETF ACTN SDN controller from the PyEnv used for developing on the TeraFlowSDN
+framework and you followed the official steps in
+[Development Guide > Configure Environment > Python](https://labs.etsi.org/rep/tfs/controller/-/wikis/2.-Development-Guide/2.1.-Configure-Environment/2.1.1.-Python),
 all the requirements are already in place. Install them only if you execute it in a separate/standalone environment.
 
 Install the required dependencies as follows:
 ```bash
-pip install Flask==2.1.3 Flask-RESTful==0.3.9
+pip install -r src/tests/tools/mock_ietf_actn_sdn_ctrl/requirements.in
 ```
 
 Run the Mock IETF ACTN SDN Controller as follows:
@@ -30,24 +25,9 @@ Run the Mock IETF ACTN SDN Controller as follows:
 python src/tests/tools/mock_ietf_actn_sdn_ctrl/MockIetfActnSdnCtrl.py
 ```
 
-## 3. Deploy the test descriptors
-Edit the descriptors to meet your environment specifications.
-Edit "network_descriptors.json" and change IP address and port of the IETF ACTN SDN controller of the "ACTN" device.
-- Set value of config rule "_connect/address" to the address of the host where the Mock IETF ACTN SDN controller is
-  running (default="192.168.1.1").
-- Set value of config rule "_connect/port" to the port where your Mock IETF ACTN SDN controller is listening on
-  (default="8443").
-
-Upload the "network_descriptors.json" through the TeraFlowSDN WebUI.
-- If not already selected, select Context(admin)/Topology(admin).
-- Check that a network topology with 4 routers + 1 IETF ACTN radio system are loaded. They should form 2 rings.
-
-Upload the "service_descriptor.json" through the TeraFlowSDN WebUI.
-- Check that 2 services have been created.
-- The "actn-svc" should have a connection and be supported by a sub-service.
-- The sub-service should also have a connection.
-- The R1, R3, and MW devices should have configuration rules established.
-
-# 4. Delete the IETF ACTN service
-Find the "mw-svc" on the WebUI, navigate to its details, and delete the service pressing the "Delete Service" button.
-The service, sub-service, and device configuration rules should be removed.
+
+## 2. Run the Mock IETF ACTN SDN controller
+Run the Mock IETF ACTN SDN Controller as follows:
+```bash
+python src/tests/tools/mock_ietf_actn_sdn_ctrl/MockIetfActnSdnCtrl.py
+```
diff --git a/src/tests/tools/mock_ietf_actn_sdn_ctrl/build.sh b/src/tests/tools/mock_ietf_actn_sdn_ctrl/build.sh
index d9db334cbf1d361cc9cbce42f95fb10bafc609ed..fe958995ac303ca003aee9557b7fc07905933fce 100755
--- a/src/tests/tools/mock_ietf_actn_sdn_ctrl/build.sh
+++ b/src/tests/tools/mock_ietf_actn_sdn_ctrl/build.sh
@@ -13,6 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+# Make folder containing the script the root folder for its execution
+cd $(dirname $0)
+
 docker build -t mock-ietf-actn-sdn-ctrl:test -f Dockerfile .
 docker tag mock-ietf-actn-sdn-ctrl:test localhost:32000/tfs/mock-ietf-actn-sdn-ctrl:test
 docker push localhost:32000/tfs/mock-ietf-actn-sdn-ctrl:test
diff --git a/src/tests/tools/mock_ietf_actn_sdn_ctrl/run.sh b/src/tests/tools/mock_ietf_actn_sdn_ctrl/run.sh
index 2697e538ec69a99da4c0fae898748ff496b5d28f..48a23f2e41d6d30d244ef01c72ac9700b588b140 100755
--- a/src/tests/tools/mock_ietf_actn_sdn_ctrl/run.sh
+++ b/src/tests/tools/mock_ietf_actn_sdn_ctrl/run.sh
@@ -13,4 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+# Make folder containing the script the root folder for its execution
+cd $(dirname $0)
+
 python MockIetfActnSdnCtrl.py
diff --git a/src/webui/service/static/topology_icons/Acknowledgements.txt b/src/webui/service/static/topology_icons/Acknowledgements.txt
index 43ecee79806682317bd5d2a8ebff16bac8767220..08e9ed27ce3e87e05d8d1d1176704b53b93c8ef3 100644
--- a/src/webui/service/static/topology_icons/Acknowledgements.txt
+++ b/src/webui/service/static/topology_icons/Acknowledgements.txt
@@ -31,5 +31,9 @@ https://symbols.getvecta.com/stencil_241/158_local-director.6b38eab9e4.png => em
 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
 
-https://symbols.getvecta.com/stencil_240/124_laptop.be264ceb77.png => laptop.png
-https://symbols.getvecta.com/stencil_241/154_laptop.c01910b6c8.png => emu-laptop.png
+https://symbols.getvecta.com/stencil_240/124_laptop.be264ceb77.png => client.png
+https://symbols.getvecta.com/stencil_241/154_laptop.c01910b6c8.png => emu-client.png
+
+https://symbols.getvecta.com/stencil_240/16_atm-tag-switch-router.3149d7e933.png => ip-sdn-controller.png
+https://symbols.getvecta.com/stencil_241/46_atm-tag-sw-rtr.776719c0b0.png => emu-ip-sdn-controller.png
+
diff --git a/src/webui/service/static/topology_icons/emu-ip-sdn-controller.png b/src/webui/service/static/topology_icons/emu-ip-sdn-controller.png
new file mode 100644
index 0000000000000000000000000000000000000000..ff4c69120e28b434df3d5a8db46fe304dbdd03e7
Binary files /dev/null and b/src/webui/service/static/topology_icons/emu-ip-sdn-controller.png differ
diff --git a/src/webui/service/static/topology_icons/ip-sdn-controller.png b/src/webui/service/static/topology_icons/ip-sdn-controller.png
new file mode 100644
index 0000000000000000000000000000000000000000..d0b1abe8738b7e34faaa34d286157a778069fdbb
Binary files /dev/null and b/src/webui/service/static/topology_icons/ip-sdn-controller.png differ