DEVICEDRIVER_XR = 6;
*/
DEVICEDRIVER_XR(6),
+ /**
+ * DEVICEDRIVER_IETF_L2VPN = 7;
+ */
+ DEVICEDRIVER_IETF_L2VPN(7),
UNRECOGNIZED(-1),
;
@@ -212,6 +216,10 @@ public final class ContextOuterClass {
* DEVICEDRIVER_XR = 6;
*/
public static final int DEVICEDRIVER_XR_VALUE = 6;
+ /**
+ * DEVICEDRIVER_IETF_L2VPN = 7;
+ */
+ public static final int DEVICEDRIVER_IETF_L2VPN_VALUE = 7;
public final int getNumber() {
@@ -245,6 +253,7 @@ public final class ContextOuterClass {
case 4: return DEVICEDRIVER_IETF_NETWORK_TOPOLOGY;
case 5: return DEVICEDRIVER_ONF_TR_352;
case 6: return DEVICEDRIVER_XR;
+ case 7: return DEVICEDRIVER_IETF_L2VPN;
default: return null;
}
}
diff --git a/src/common/DeviceTypes.py b/src/common/DeviceTypes.py
index 99255defdb6b5ee155607536a2e13d23b97b2d3a..bb8948585f163aeb84ee758b8581bc6509d29799 100644
--- a/src/common/DeviceTypes.py
+++ b/src/common/DeviceTypes.py
@@ -25,9 +25,12 @@ class DeviceTypeEnum(Enum):
EMULATED_OPEN_LINE_SYSTEM = 'emu-open-line-system'
EMULATED_OPTICAL_ROADM = 'emu-optical-roadm'
EMULATED_OPTICAL_TRANSPONDER = 'emu-optical-transponder'
+ EMULATED_OPTICAL_SPLITTER = 'emu-optical-splitter' # passive component required for XR Constellation
EMULATED_P4_SWITCH = 'emu-p4-switch'
+ EMULATED_PACKET_RADIO_ROUTER = 'emu-packet-radio-router'
EMULATED_PACKET_ROUTER = 'emu-packet-router'
EMULATED_PACKET_SWITCH = 'emu-packet-switch'
+ EMULATED_XR_CONSTELLATION = 'emu-xr-constellation'
# Real device types
DATACENTER = 'datacenter'
@@ -36,6 +39,10 @@ class DeviceTypeEnum(Enum):
OPTICAL_ROADM = 'optical-roadm'
OPTICAL_TRANSPONDER = 'optical-transponder'
P4_SWITCH = 'p4-switch'
+ PACKET_RADIO_ROUTER = 'packet-radio-router'
PACKET_ROUTER = 'packet-router'
PACKET_SWITCH = 'packet-switch'
- XR_CONSTELLATION = 'xr-constellation'
\ No newline at end of file
+ XR_CONSTELLATION = 'xr-constellation'
+
+ # ETSI TeraFlowSDN controller
+ TERAFLOWSDN_CONTROLLER = 'teraflowsdn'
diff --git a/src/common/tools/object_factory/Device.py b/src/common/tools/object_factory/Device.py
index 0cc4555d455bf28ac2143a5d58b87e084a8360c7..66c87b14dd866d44b5d48addf93d172aea962f8e 100644
--- a/src/common/tools/object_factory/Device.py
+++ b/src/common/tools/object_factory/Device.py
@@ -43,6 +43,9 @@ DEVICE_MICROWAVE_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY]
DEVICE_P4_TYPE = DeviceTypeEnum.P4_SWITCH.value
DEVICE_P4_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_P4]
+DEVICE_TFS_TYPE = DeviceTypeEnum.TERAFLOWSDN_CONTROLLER.value
+DEVICE_TFS_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN]
+
def json_device_id(device_uuid : str):
return {'device_uuid': {'uuid': device_uuid}}
@@ -120,6 +123,13 @@ def json_device_p4_disabled(
return json_device(
device_uuid, DEVICE_P4_TYPE, DEVICE_DISABLED, endpoints=endpoints, config_rules=config_rules, drivers=drivers)
+def json_device_tfs_disabled(
+ device_uuid : str, endpoints : List[Dict] = [], config_rules : List[Dict] = [],
+ drivers : List[Dict] = DEVICE_TFS_DRIVERS
+ ):
+ return json_device(
+ device_uuid, DEVICE_TFS_TYPE, DEVICE_DISABLED, endpoints=endpoints, config_rules=config_rules, drivers=drivers)
+
def json_device_connect_rules(address : str, port : int, settings : Dict = {}):
return [
json_config_rule_set('_connect/address', address),
diff --git a/src/common/type_checkers/Assertions.py b/src/common/type_checkers/Assertions.py
index c0442d8770c682ac1eea032980b58e7028be90c4..ba82e535ec958104bd14abf625eb6cd38c2a08ee 100644
--- a/src/common/type_checkers/Assertions.py
+++ b/src/common/type_checkers/Assertions.py
@@ -33,6 +33,7 @@ def validate_device_driver_enum(message):
'DEVICEDRIVER_IETF_NETWORK_TOPOLOGY',
'DEVICEDRIVER_ONF_TR_352',
'DEVICEDRIVER_XR',
+ 'DEVICEDRIVER_IETF_L2VPN',
]
def validate_device_operational_status_enum(message):
diff --git a/src/compute/service/rest_server/nbi_plugins/ietf_l2vpn/Constants.py b/src/compute/service/rest_server/nbi_plugins/ietf_l2vpn/Constants.py
index f95b532af4ba01968d17bc3958e1cffbf84a5e7f..ed25dbab3cd6b07ef73d64c5d37ad64e85353c02 100644
--- a/src/compute/service/rest_server/nbi_plugins/ietf_l2vpn/Constants.py
+++ b/src/compute/service/rest_server/nbi_plugins/ietf_l2vpn/Constants.py
@@ -74,4 +74,18 @@ BEARER_MAPPINGS = {
'R3:1/3': ('R3', '1/3', '5.3.1.3', None, 0, None, None, None, None),
'R4:1/2': ('R4', '1/2', '5.4.1.2', None, 0, None, None, None, None),
'R4:1/3': ('R4', '1/3', '5.4.1.3', None, 0, None, None, None, None),
+
+ # OFC'23
+ 'PE1:1/1': ('PE1', '1/1', '10.1.1.1', None, 0, None, None, None, None),
+ 'PE1:1/2': ('PE1', '1/2', '10.1.1.2', None, 0, None, None, None, None),
+ 'PE2:1/1': ('PE2', '1/1', '10.2.1.1', None, 0, None, None, None, None),
+ 'PE2:1/2': ('PE2', '1/2', '10.2.1.2', None, 0, None, None, None, None),
+ 'PE3:1/1': ('PE3', '1/1', '10.3.1.1', None, 0, None, None, None, None),
+ 'PE3:1/2': ('PE3', '1/2', '10.3.1.2', None, 0, None, None, None, None),
+ 'PE4:1/1': ('PE4', '1/1', '10.4.1.1', None, 0, None, None, None, None),
+ 'PE4:1/2': ('PE4', '1/2', '10.4.1.2', None, 0, None, None, None, None),
+
+ 'R149:eth-1/0/22': ('R149', 'eth-1/0/22', '5.5.5.5', None, 0, None, None, '5.5.5.1', '100'),
+ 'R155:eth-1/0/22': ('R155', 'eth-1/0/22', '5.5.5.1', None, 0, None, None, '5.5.5.5', '100'),
+ 'R199:eth-1/0/21': ('R199', 'eth-1/0/21', '5.5.5.6', None, 0, None, None, '5.5.5.5', '100'),
}
diff --git a/src/context/service/database/models/enums/DeviceDriver.py b/src/context/service/database/models/enums/DeviceDriver.py
index 6997e7dfbff6bc1d4b6452a28f11cdac9aae412f..a612803e235de2c6d2d8c91052416a675a3a3085 100644
--- a/src/context/service/database/models/enums/DeviceDriver.py
+++ b/src/context/service/database/models/enums/DeviceDriver.py
@@ -24,6 +24,7 @@ class ORM_DeviceDriverEnum(enum.Enum):
IETF_NETWORK_TOPOLOGY = DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY
ONF_TR_352 = DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352
XR = DeviceDriverEnum.DEVICEDRIVER_XR
+ IETF_L2VPN = DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN
grpc_to_enum__device_driver = functools.partial(
grpc_to_enum, DeviceDriverEnum, ORM_DeviceDriverEnum)
diff --git a/src/device/service/DeviceServiceServicerImpl.py b/src/device/service/DeviceServiceServicerImpl.py
index be40e64ecd25a5c46c23d5ec0a73a2484b65691d..2b08b6c7e03cfd50557f25f99ffea3032dbb811e 100644
--- a/src/device/service/DeviceServiceServicerImpl.py
+++ b/src/device/service/DeviceServiceServicerImpl.py
@@ -12,11 +12,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from typing import Dict
import grpc, logging
from common.method_wrappers.Decorator import MetricsPool, safe_and_metered_rpc_method
from common.method_wrappers.ServiceExceptions import NotFoundException, OperationFailedException
from common.proto.context_pb2 import (
- Device, DeviceConfig, DeviceDriverEnum, DeviceId, DeviceOperationalStatusEnum, Empty)
+ Device, DeviceConfig, DeviceDriverEnum, DeviceId, DeviceOperationalStatusEnum, Empty, Link)
from common.proto.device_pb2 import MonitoringSettings
from common.proto.device_pb2_grpc import DeviceServiceServicer
from common.tools.context_queries.Device import get_device
@@ -28,8 +29,8 @@ from .monitoring.MonitoringLoops import MonitoringLoops
from .ErrorMessages import ERROR_MISSING_DRIVER, ERROR_MISSING_KPI
from .Tools import (
check_connect_rules, check_no_endpoints, compute_rules_to_add_delete, configure_rules, deconfigure_rules,
- populate_config_rules, populate_endpoint_monitoring_resources, populate_endpoints, populate_initial_config_rules,
- subscribe_kpi, unsubscribe_kpi, update_endpoints)
+ get_device_controller_uuid, populate_config_rules, populate_endpoint_monitoring_resources, populate_endpoints,
+ populate_initial_config_rules, subscribe_kpi, unsubscribe_kpi, update_endpoints)
LOGGER = logging.getLogger(__name__)
@@ -73,9 +74,16 @@ class DeviceServiceServicerImpl(DeviceServiceServicer):
errors = []
+ # Sub-devices and sub-links are exposed by intermediate controllers or represent mgmt links.
+ # They are used to assist in path computation algorithms, and/or to identify dependencies
+ # (which controller is in charge of which sub-device).
+ new_sub_devices : Dict[str, Device] = dict()
+ new_sub_links : Dict[str, Link] = dict()
+
if len(device.device_endpoints) == 0:
# created from request, populate endpoints using driver
- errors.extend(populate_endpoints(device, driver, self.monitoring_loops))
+ errors.extend(populate_endpoints(
+ device, driver, self.monitoring_loops, new_sub_devices, new_sub_links))
if len(device.device_config.config_rules) == len(connection_config_rules):
# created from request, populate config rules using driver
@@ -87,12 +95,20 @@ class DeviceServiceServicerImpl(DeviceServiceServicer):
for error in errors: LOGGER.error(error)
raise OperationFailedException('AddDevice', extra_details=errors)
+ device.device_operational_status = DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_DISABLED
device_id = context_client.SetDevice(device)
+ for sub_device in new_sub_devices.values():
+ context_client.SetDevice(sub_device)
+
+ for sub_links in new_sub_links.values():
+ context_client.SetLink(sub_links)
+
# Update endpoint monitoring resources with UUIDs
device_with_uuids = context_client.GetDevice(device_id)
populate_endpoint_monitoring_resources(device_with_uuids, self.monitoring_loops)
+ context_client.close()
return device_id
finally:
self.mutex_queues.signal_done(device_uuid)
@@ -109,6 +125,13 @@ class DeviceServiceServicerImpl(DeviceServiceServicer):
if device is None:
raise NotFoundException('Device', device_uuid, extra_details='loading in ConfigureDevice')
+ device_controller_uuid = get_device_controller_uuid(device)
+ if device_controller_uuid is not None:
+ device = get_device(context_client, device_controller_uuid, rw_copy=True)
+ if device is None:
+ raise NotFoundException(
+ 'Device', device_controller_uuid, extra_details='loading in ConfigureDevice')
+
driver : _Driver = get_driver(self.driver_instance_cache, device)
if driver is None:
msg = ERROR_MISSING_DRIVER.format(device_uuid=str(device_uuid))
diff --git a/src/device/service/ErrorMessages.py b/src/device/service/ErrorMessages.py
index 1fbea721fdc52bdf759581c0525b30b1206ae844..bb7702e4e629bad43df4870d923f0a1829378e2e 100644
--- a/src/device/service/ErrorMessages.py
+++ b/src/device/service/ErrorMessages.py
@@ -14,9 +14,9 @@
_DEVICE_ID = 'DeviceId({device_uuid:s})'
_ENDPOINT_ID = 'EndpointId({endpoint_uuid:s})'
-_ENDPOINT_DATA = 'EndpointId({endpoint_data:s})'
_KPI = 'Kpi({kpi_uuid:s})'
_DEVICE_ENDPOINT_ID = _DEVICE_ID + '/' + _ENDPOINT_ID
+_RESOURCE = 'Resource({resource_data:s})'
_RESOURCE_KEY = 'Resource(key={resource_key:s})'
_RESOURCE_KEY_VALUE = 'Resource(key={resource_key:s}, value={resource_value:s})'
_SUBSCRIPTION = 'Subscription(key={subscr_key:s}, duration={subscr_duration:s}, interval={subscr_interval:s})'
@@ -26,7 +26,8 @@ _ERROR = 'Error({error:s})'
ERROR_MISSING_DRIVER = _DEVICE_ID + ' has not been added to this Device instance'
ERROR_MISSING_KPI = _KPI + ' not found'
-ERROR_BAD_ENDPOINT = _DEVICE_ID + ': GetConfig retrieved malformed ' + _ENDPOINT_DATA
+ERROR_BAD_RESOURCE = _DEVICE_ID + ': GetConfig retrieved malformed ' + _RESOURCE
+ERROR_UNSUP_RESOURCE = _DEVICE_ID + ': GetConfig retrieved unsupported ' + _RESOURCE
ERROR_GET = _DEVICE_ID + ': Unable to Get ' + _RESOURCE_KEY + '; ' + _ERROR
ERROR_GET_INIT = _DEVICE_ID + ': Unable to Get Initial ' + _RESOURCE_KEY + '; ' + _ERROR
diff --git a/src/device/service/Tools.py b/src/device/service/Tools.py
index 571e8acdab7fc243c22923a69202c89db88c8ce3..cd3af07e3324e50ff43eb5e653c4c46771a5507e 100644
--- a/src/device/service/Tools.py
+++ b/src/device/service/Tools.py
@@ -13,19 +13,20 @@
# limitations under the License.
import json, logging
-from typing import Any, Dict, List, Tuple, Union
+from typing import Any, Dict, List, Optional, Tuple, Union
from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME
from common.method_wrappers.ServiceExceptions import InvalidArgumentException
-from common.proto.context_pb2 import ConfigActionEnum, Device, DeviceConfig
+from common.proto.context_pb2 import ConfigActionEnum, Device, DeviceConfig, Link
from common.proto.device_pb2 import MonitoringSettings
from common.proto.kpi_sample_types_pb2 import KpiSampleType
from common.tools.grpc.ConfigRules import update_config_rule_custom
from common.tools.grpc.Tools import grpc_message_to_json
+from context.client.ContextClient import ContextClient
from .driver_api._Driver import _Driver, RESOURCE_ENDPOINTS
from .monitoring.MonitoringLoops import MonitoringLoops
from .ErrorMessages import (
- ERROR_BAD_ENDPOINT, ERROR_DELETE, ERROR_GET, ERROR_GET_INIT, ERROR_MISSING_KPI, ERROR_SAMPLETYPE, ERROR_SET,
- ERROR_SUBSCRIBE, ERROR_UNSUBSCRIBE
+ ERROR_BAD_RESOURCE, ERROR_DELETE, ERROR_GET, ERROR_GET_INIT, ERROR_MISSING_KPI, ERROR_SAMPLETYPE, ERROR_SET,
+ ERROR_SUBSCRIBE, ERROR_UNSUBSCRIBE, ERROR_UNSUP_RESOURCE
)
LOGGER = logging.getLogger(__name__)
@@ -77,19 +78,51 @@ def check_no_endpoints(device_endpoints) -> None:
extra_details='RPC method AddDevice does not accept Endpoints. Endpoints are discovered through '\
'interrogation of the physical device.')
-def populate_endpoints(device : Device, driver : _Driver, monitoring_loops : MonitoringLoops) -> List[str]:
+def get_device_controller_uuid(device : Device) -> Optional[str]:
+ for config_rule in device.device_config.config_rules:
+ if config_rule.WhichOneof('config_rule') != 'custom': continue
+ if config_rule.custom.resource_key != '_controller': continue
+ device_controller_id = json.loads(config_rule.custom.resource_value)
+ return device_controller_id['uuid']
+ return None
+
+def populate_endpoints(
+ device : Device, driver : _Driver, monitoring_loops : MonitoringLoops,
+ new_sub_devices : Dict[str, Device], new_sub_links : Dict[str, Link]
+) -> List[str]:
device_uuid = device.device_id.device_uuid.uuid
+ device_name = device.name
resources_to_get = [RESOURCE_ENDPOINTS]
results_getconfig = driver.GetConfig(resources_to_get)
+ LOGGER.debug('results_getconfig = {:s}'.format(str(results_getconfig)))
+
+ # first quick pass to identify need of mgmt endpoints and links
+ add_mgmt_port = False
+ for resource_data in results_getconfig:
+ if len(resource_data) != 2: continue
+ resource_key, _ = resource_data
+ if resource_key.startswith('/devices/device'):
+ add_mgmt_port = True
+ break
+
+ if add_mgmt_port:
+ # add mgmt port to main device
+ device_mgmt_endpoint = device.device_endpoints.add()
+ device_mgmt_endpoint.endpoint_id.topology_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME
+ device_mgmt_endpoint.endpoint_id.topology_id.topology_uuid.uuid = DEFAULT_TOPOLOGY_NAME
+ device_mgmt_endpoint.endpoint_id.device_id.device_uuid.uuid = device_uuid
+ device_mgmt_endpoint.endpoint_id.endpoint_uuid.uuid = 'mgmt'
+ device_mgmt_endpoint.name = 'mgmt'
+ device_mgmt_endpoint.endpoint_type = 'mgmt'
errors : List[str] = list()
- for endpoint in results_getconfig:
- if len(endpoint) != 2:
- errors.append(ERROR_BAD_ENDPOINT.format(device_uuid=device_uuid, endpoint_data=str(endpoint)))
+ for resource_data in results_getconfig:
+ if len(resource_data) != 2:
+ errors.append(ERROR_BAD_RESOURCE.format(device_uuid=device_uuid, resource_data=str(resource_data)))
continue
- resource_key, resource_value = endpoint
+ resource_key, resource_value = resource_data
if isinstance(resource_value, Exception):
errors.append(ERROR_GET.format(
device_uuid=device_uuid, resource_key=str(resource_key), error=str(resource_value)))
@@ -97,19 +130,88 @@ def populate_endpoints(device : Device, driver : _Driver, monitoring_loops : Mon
if resource_value is None:
continue
- endpoint_uuid = resource_value.get('uuid')
-
- device_endpoint = device.device_endpoints.add()
- device_endpoint.endpoint_id.topology_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME
- device_endpoint.endpoint_id.topology_id.topology_uuid.uuid = DEFAULT_TOPOLOGY_NAME
- device_endpoint.endpoint_id.device_id.device_uuid.uuid = device_uuid
- device_endpoint.endpoint_id.endpoint_uuid.uuid = endpoint_uuid
- device_endpoint.endpoint_type = resource_value.get('type')
+ if resource_key.startswith('/devices/device'):
+ # create sub-device
+ _sub_device_uuid = resource_value['uuid']
+ _sub_device = Device()
+ _sub_device.device_id.device_uuid.uuid = _sub_device_uuid # pylint: disable=no-member
+ _sub_device.name = resource_value['name']
+ _sub_device.device_type = resource_value['type']
+ _sub_device.device_operational_status = resource_value['status']
+
+ # Sub-devices should not have a driver assigned. Instead, they should have
+ # a config rule specifying their controller.
+ #_sub_device.device_drivers.extend(resource_value['drivers']) # pylint: disable=no-member
+ controller_config_rule = _sub_device.device_config.config_rules.add()
+ controller_config_rule.action = ConfigActionEnum.CONFIGACTION_SET
+ controller_config_rule.custom.resource_key = '_controller'
+ controller = {'uuid': device_uuid, 'name': device_name}
+ controller_config_rule.custom.resource_value = json.dumps(controller, indent=0, sort_keys=True)
+
+ new_sub_devices[_sub_device_uuid] = _sub_device
+
+ # add mgmt port to sub-device
+ _sub_device_mgmt_endpoint = _sub_device.device_endpoints.add() # pylint: disable=no-member
+ _sub_device_mgmt_endpoint.endpoint_id.topology_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME
+ _sub_device_mgmt_endpoint.endpoint_id.topology_id.topology_uuid.uuid = DEFAULT_TOPOLOGY_NAME
+ _sub_device_mgmt_endpoint.endpoint_id.device_id.device_uuid.uuid = _sub_device_uuid
+ _sub_device_mgmt_endpoint.endpoint_id.endpoint_uuid.uuid = 'mgmt'
+ _sub_device_mgmt_endpoint.name = 'mgmt'
+ _sub_device_mgmt_endpoint.endpoint_type = 'mgmt'
+
+ # add mgmt link
+ _mgmt_link_uuid = '{:s}/{:s}=={:s}/{:s}'.format(device_name, 'mgmt', _sub_device.name, 'mgmt')
+ _mgmt_link = Link()
+ _mgmt_link.link_id.link_uuid.uuid = _mgmt_link_uuid # pylint: disable=no-member
+ _mgmt_link.name = _mgmt_link_uuid
+ _mgmt_link.link_endpoint_ids.append(device_mgmt_endpoint.endpoint_id) # pylint: disable=no-member
+ _mgmt_link.link_endpoint_ids.append(_sub_device_mgmt_endpoint.endpoint_id) # pylint: disable=no-member
+ new_sub_links[_mgmt_link_uuid] = _mgmt_link
+
+ elif resource_key.startswith('/endpoints/endpoint'):
+ endpoint_uuid = resource_value['uuid']
+ _device_uuid = resource_value.get('device_uuid')
+ endpoint_name = resource_value.get('name')
+
+ if _device_uuid is None:
+ # add endpoint to current device
+ device_endpoint = device.device_endpoints.add()
+ device_endpoint.endpoint_id.device_id.device_uuid.uuid = device_uuid
+ else:
+ # add endpoint to specified device
+ device_endpoint = new_sub_devices[_device_uuid].device_endpoints.add()
+ device_endpoint.endpoint_id.device_id.device_uuid.uuid = _device_uuid
+
+ device_endpoint.endpoint_id.topology_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME
+ device_endpoint.endpoint_id.topology_id.topology_uuid.uuid = DEFAULT_TOPOLOGY_NAME
+
+ device_endpoint.endpoint_id.endpoint_uuid.uuid = endpoint_uuid
+ if endpoint_name is not None: device_endpoint.name = endpoint_name
+ device_endpoint.endpoint_type = resource_value.get('type', '-')
+
+ sample_types : Dict[int, str] = resource_value.get('sample_types', {})
+ for kpi_sample_type, monitor_resource_key in sample_types.items():
+ device_endpoint.kpi_sample_types.append(kpi_sample_type)
+ monitoring_loops.add_resource_key(device_uuid, endpoint_uuid, kpi_sample_type, monitor_resource_key)
+
+ elif resource_key.startswith('/links/link'):
+ # create sub-link
+ _sub_link_uuid = resource_value['uuid']
+ _sub_link = Link()
+ _sub_link.link_id.link_uuid.uuid = _sub_link_uuid # pylint: disable=no-member
+ _sub_link.name = resource_value['name']
+ new_sub_links[_sub_link_uuid] = _sub_link
+
+ for device_uuid,endpoint_uuid in resource_value['endpoints']:
+ _sub_link_endpoint_id = _sub_link.link_endpoint_ids.add() # pylint: disable=no-member
+ _sub_link_endpoint_id.topology_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME
+ _sub_link_endpoint_id.topology_id.topology_uuid.uuid = DEFAULT_TOPOLOGY_NAME
+ _sub_link_endpoint_id.device_id.device_uuid.uuid = device_uuid
+ _sub_link_endpoint_id.endpoint_uuid.uuid = endpoint_uuid
- sample_types : Dict[int, str] = resource_value.get('sample_types', {})
- for kpi_sample_type, monitor_resource_key in sample_types.items():
- device_endpoint.kpi_sample_types.append(kpi_sample_type)
- monitoring_loops.add_resource_key(device_uuid, endpoint_uuid, kpi_sample_type, monitor_resource_key)
+ else:
+ errors.append(ERROR_UNSUP_RESOURCE.format(device_uuid=device_uuid, resource_data=str(resource_data)))
+ continue
return errors
diff --git a/src/device/service/driver_api/ImportTopologyEnum.py b/src/device/service/driver_api/ImportTopologyEnum.py
new file mode 100644
index 0000000000000000000000000000000000000000..06f0ff9c2db1f1baccc4b46c5babc4458ca6ffb6
--- /dev/null
+++ b/src/device/service/driver_api/ImportTopologyEnum.py
@@ -0,0 +1,37 @@
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from enum import Enum
+from typing import Dict
+
+class ImportTopologyEnum(Enum):
+ # While importing underlying resources, the driver just imports endpoints and exposes them directly.
+ DISABLED = 'disabled'
+
+ # While importing underlying resources, the driver just imports imports sub-devices but not links
+ # connecting them. The endpoints are exposed in virtual nodes representing the sub-devices.
+ # (a remotely-controlled transport domain might exist between nodes)
+ DEVICES = 'devices'
+
+ # While importing underlying resources, the driver just imports imports sub-devices and links
+ # connecting them. The endpoints are exposed in virtual nodes representing the sub-devices.
+ # (enables to define constrained connectivity between the sub-devices)
+ TOPOLOGY = 'topology'
+
+def get_import_topology(settings : Dict, default : ImportTopologyEnum = ImportTopologyEnum.DISABLED):
+ str_import_topology = settings.get('import_topology')
+ if str_import_topology is None: return default
+ import_topology = ImportTopologyEnum._value2member_map_.get(str_import_topology) # pylint: disable=no-member
+ if import_topology is None: raise Exception('Unexpected setting value')
+ return import_topology
diff --git a/src/device/service/driver_api/_Driver.py b/src/device/service/driver_api/_Driver.py
index cc9f7a2c63f2f841b864cbe4fa596464a6783cec..947bc8570a941f8f666c87647d89c315b1bd202a 100644
--- a/src/device/service/driver_api/_Driver.py
+++ b/src/device/service/driver_api/_Driver.py
@@ -22,6 +22,7 @@ RESOURCE_ENDPOINTS = '__endpoints__'
RESOURCE_INTERFACES = '__interfaces__'
RESOURCE_NETWORK_INSTANCES = '__network_instances__'
RESOURCE_ROUTING_POLICIES = '__routing_policies__'
+RESOURCE_SERVICES = '__services__'
RESOURCE_ACL = '__acl__'
diff --git a/src/device/service/drivers/__init__.py b/src/device/service/drivers/__init__.py
index 469abcad387dc055ba17770e4f405db1d1ceaa3b..b3b485a471899dd96a4985fedb4bb6ede2432921 100644
--- a/src/device/service/drivers/__init__.py
+++ b/src/device/service/drivers/__init__.py
@@ -74,6 +74,15 @@ DRIVERS.append(
#}
]))
+from .ietf_l2vpn.IetfL2VpnDriver import IetfL2VpnDriver # pylint: disable=wrong-import-position
+DRIVERS.append(
+ (IetfL2VpnDriver, [
+ {
+ FilterFieldEnum.DEVICE_TYPE: DeviceTypeEnum.TERAFLOWSDN_CONTROLLER,
+ FilterFieldEnum.DRIVER: DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN,
+ }
+ ]))
+
if LOAD_ALL_DEVICE_DRIVERS:
from .openconfig.OpenConfigDriver import OpenConfigDriver # pylint: disable=wrong-import-position
DRIVERS.append(
diff --git a/src/device/service/drivers/ietf_l2vpn/IetfL2VpnDriver.py b/src/device/service/drivers/ietf_l2vpn/IetfL2VpnDriver.py
new file mode 100644
index 0000000000000000000000000000000000000000..96dfd2c15f6b359e254a6d6a24dfe42a546833ce
--- /dev/null
+++ b/src/device/service/drivers/ietf_l2vpn/IetfL2VpnDriver.py
@@ -0,0 +1,188 @@
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import json, logging, threading
+from typing import Any, Iterator, List, Optional, Tuple, Union
+from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method
+from common.tools.object_factory.Device import json_device_id
+from common.tools.object_factory.EndPoint import json_endpoint_id
+from common.type_checkers.Checkers import chk_string, chk_type
+from device.service.driver_api._Driver import _Driver, RESOURCE_ENDPOINTS, RESOURCE_SERVICES
+from device.service.drivers.ietf_l2vpn.TfsDebugApiClient import TfsDebugApiClient
+from .Tools import connection_point, wim_mapping
+from .WimconnectorIETFL2VPN import WimconnectorIETFL2VPN
+
+LOGGER = logging.getLogger(__name__)
+
+def service_exists(wim : WimconnectorIETFL2VPN, service_uuid : str) -> bool:
+ try:
+ wim.get_connectivity_service_status(service_uuid)
+ return True
+ except: # pylint: disable=bare-except
+ return False
+
+ALL_RESOURCE_KEYS = [
+ RESOURCE_ENDPOINTS,
+ RESOURCE_SERVICES,
+]
+
+SERVICE_TYPE = 'ELINE'
+
+METRICS_POOL = MetricsPool('Device', 'Driver', labels={'driver': 'ietf_l2vpn'})
+
+class IetfL2VpnDriver(_Driver):
+ def __init__(self, address: str, port: int, **settings) -> None: # pylint: disable=super-init-not-called
+ self.__lock = threading.Lock()
+ self.__started = threading.Event()
+ self.__terminate = threading.Event()
+ username = settings.get('username')
+ password = settings.get('password')
+ scheme = settings.get('scheme', 'http')
+ wim = {'wim_url': '{:s}://{:s}:{:d}'.format(scheme, address, int(port))}
+ wim_account = {'user': username, 'password': password}
+ # Mapping updated dynamically with each request
+ config = {'mapping_not_needed': False, 'service_endpoint_mapping': []}
+ self.dac = TfsDebugApiClient(address, int(port), scheme=scheme, username=username, password=password)
+ self.wim = WimconnectorIETFL2VPN(wim, wim_account, config=config)
+ self.conn_info = {} # internal database emulating OSM storage provided to WIM Connectors
+
+ def Connect(self) -> bool:
+ with self.__lock:
+ try:
+ self.wim.check_credentials()
+ except Exception: # pylint: disable=broad-except
+ LOGGER.exception('Exception checking credentials')
+ return False
+ else:
+ self.__started.set()
+ return True
+
+ def Disconnect(self) -> bool:
+ with self.__lock:
+ self.__terminate.set()
+ return True
+
+ @metered_subclass_method(METRICS_POOL)
+ def GetInitialConfig(self) -> List[Tuple[str, Any]]:
+ with self.__lock:
+ return []
+
+ @metered_subclass_method(METRICS_POOL)
+ def GetConfig(self, resource_keys : List[str] = []) -> List[Tuple[str, Union[Any, None, Exception]]]:
+ chk_type('resources', resource_keys, list)
+ results = []
+ with self.__lock:
+ self.wim.check_credentials()
+ if len(resource_keys) == 0: resource_keys = ALL_RESOURCE_KEYS
+ for i, resource_key in enumerate(resource_keys):
+ str_resource_name = 'resource_key[#{:d}]'.format(i)
+ try:
+ chk_string(str_resource_name, resource_key, allow_empty=False)
+ if resource_key == RESOURCE_ENDPOINTS:
+ # return endpoints through debug-api and list-devices method
+ results.extend(self.dac.get_devices_endpoints())
+ elif resource_key == RESOURCE_SERVICES:
+ # return all services through
+ reply = self.wim.get_all_active_connectivity_services()
+ results.extend(reply.json())
+ else:
+ # assume single-service retrieval
+ reply = self.wim.get_connectivity_service(resource_key)
+ results.append(reply.json())
+ except Exception as e: # pylint: disable=broad-except
+ LOGGER.exception('Unhandled error processing resource_key({:s})'.format(str(resource_key)))
+ results.append((resource_key, e))
+ return results
+
+ @metered_subclass_method(METRICS_POOL)
+ def SetConfig(self, resources: List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+ results = []
+ if len(resources) == 0: return results
+ with self.__lock:
+ self.wim.check_credentials()
+ for resource in resources:
+ LOGGER.info('resource = {:s}'.format(str(resource)))
+ resource_key,resource_value = resource
+ try:
+ resource_value = json.loads(resource_value)
+ service_uuid = resource_value['uuid']
+
+ if service_exists(self.wim, service_uuid):
+ exc = NotImplementedError('IETF L2VPN Service Update is still not supported')
+ results.append((resource[0], exc))
+ continue
+
+ src_device_name = resource_value['src_device_name']
+ src_endpoint_name = resource_value['src_endpoint_name']
+ dst_device_name = resource_value['dst_device_name']
+ dst_endpoint_name = resource_value['dst_endpoint_name']
+ encap_type = resource_value['encapsulation_type']
+ vlan_id = resource_value['vlan_id']
+
+ src_endpoint_id = json_endpoint_id(json_device_id(src_device_name), src_endpoint_name)
+ src_service_endpoint_id, src_mapping = wim_mapping('1', src_endpoint_id)
+ self.wim.mappings[src_service_endpoint_id] = src_mapping
+
+ dst_endpoint_id = json_endpoint_id(json_device_id(dst_device_name), dst_endpoint_name)
+ dst_service_endpoint_id, dst_mapping = wim_mapping('2', dst_endpoint_id)
+ self.wim.mappings[dst_service_endpoint_id] = dst_mapping
+
+ connection_points = [
+ connection_point(src_service_endpoint_id, encap_type, vlan_id),
+ connection_point(dst_service_endpoint_id, encap_type, vlan_id),
+ ]
+
+ self.wim.create_connectivity_service(service_uuid, SERVICE_TYPE, connection_points)
+ results.append((resource_key, True))
+ except Exception as e: # pylint: disable=broad-except
+ LOGGER.exception('Unhandled error processing resource_key({:s})'.format(str(resource_key)))
+ results.append((resource_key, e))
+ return results
+
+ @metered_subclass_method(METRICS_POOL)
+ def DeleteConfig(self, resources: List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+ results = []
+ if len(resources) == 0: return results
+ with self.__lock:
+ self.wim.check_credentials()
+ for resource in resources:
+ LOGGER.info('resource = {:s}'.format(str(resource)))
+ resource_key,resource_value = resource
+ try:
+ resource_value = json.loads(resource_value)
+ service_uuid = resource_value['uuid']
+
+ if service_exists(self.wim, service_uuid):
+ self.wim.delete_connectivity_service(service_uuid)
+ results.append((resource_key, True))
+ except Exception as e: # pylint: disable=broad-except
+ LOGGER.exception('Unhandled error processing resource_key({:s})'.format(str(resource_key)))
+ results.append((resource_key, e))
+ return results
+
+ @metered_subclass_method(METRICS_POOL)
+ def SubscribeState(self, subscriptions : List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]:
+ # TODO: IETF L2VPN does not support monitoring by now
+ return [False for _ in subscriptions]
+
+ @metered_subclass_method(METRICS_POOL)
+ def UnsubscribeState(self, subscriptions : List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]:
+ # TODO: IETF L2VPN does not support monitoring by now
+ return [False for _ in subscriptions]
+
+ def GetState(
+ self, blocking=False, terminate : Optional[threading.Event] = None
+ ) -> Iterator[Tuple[float, str, Any]]:
+ # TODO: IETF L2VPN does not support monitoring by now
+ return []
diff --git a/src/device/service/drivers/ietf_l2vpn/TfsDebugApiClient.py b/src/device/service/drivers/ietf_l2vpn/TfsDebugApiClient.py
new file mode 100644
index 0000000000000000000000000000000000000000..4bf40af030fda990f96efe0ff8ab2ce54f82c312
--- /dev/null
+++ b/src/device/service/drivers/ietf_l2vpn/TfsDebugApiClient.py
@@ -0,0 +1,92 @@
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging, requests
+from requests.auth import HTTPBasicAuth
+from typing import Dict, List, Optional
+
+GET_DEVICES_URL = '{:s}://{:s}:{:d}/restconf/debug-api/devices'
+TIMEOUT = 30
+
+HTTP_OK_CODES = {
+ 200, # OK
+ 201, # Created
+ 202, # Accepted
+ 204, # No Content
+}
+
+MAPPING_STATUS = {
+ 'DEVICEOPERATIONALSTATUS_UNDEFINED': 0,
+ 'DEVICEOPERATIONALSTATUS_DISABLED' : 1,
+ 'DEVICEOPERATIONALSTATUS_ENABLED' : 2,
+}
+
+MAPPING_DRIVER = {
+ 'DEVICEDRIVER_UNDEFINED' : 0,
+ 'DEVICEDRIVER_OPENCONFIG' : 1,
+ 'DEVICEDRIVER_TRANSPORT_API' : 2,
+ 'DEVICEDRIVER_P4' : 3,
+ 'DEVICEDRIVER_IETF_NETWORK_TOPOLOGY': 4,
+ 'DEVICEDRIVER_ONF_TR_352' : 5,
+ 'DEVICEDRIVER_XR' : 6,
+ 'DEVICEDRIVER_IETF_L2VPN' : 7,
+}
+
+MSG_ERROR = 'Could not retrieve devices in remote TeraFlowSDN instance({:s}). status_code={:s} reply={:s}'
+
+LOGGER = logging.getLogger(__name__)
+
+class TfsDebugApiClient:
+ def __init__(
+ self, address : str, port : int, scheme : str = 'http',
+ username : Optional[str] = None, password : Optional[str] = None
+ ) -> None:
+ self._url = GET_DEVICES_URL.format(scheme, address, port)
+ self._auth = HTTPBasicAuth(username, password) if username is not None and password is not None else None
+
+ def get_devices_endpoints(self) -> List[Dict]:
+ reply = requests.get(self._url, timeout=TIMEOUT, verify=False, auth=self._auth)
+ if reply.status_code not in HTTP_OK_CODES:
+ msg = MSG_ERROR.format(str(self._url), str(reply.status_code), str(reply))
+ LOGGER.error(msg)
+ raise Exception(msg)
+
+ result = list()
+ for json_device in reply.json()['devices']:
+ device_uuid : str = json_device['device_id']['device_uuid']['uuid']
+ device_type : str = json_device['device_type']
+ #if not device_type.startswith('emu-'): device_type = 'emu-' + device_type
+ device_status = json_device['device_operational_status']
+ device_url = '/devices/device[{:s}]'.format(device_uuid)
+ device_data = {
+ 'uuid': json_device['device_id']['device_uuid']['uuid'],
+ 'name': json_device['name'],
+ 'type': device_type,
+ 'status': MAPPING_STATUS[device_status],
+ 'drivers': [MAPPING_DRIVER[driver] for driver in json_device['device_drivers']],
+ }
+ result.append((device_url, device_data))
+
+ for json_endpoint in json_device['device_endpoints']:
+ endpoint_uuid = json_endpoint['endpoint_id']['endpoint_uuid']['uuid']
+ endpoint_url = '/endpoints/endpoint[{:s}]'.format(endpoint_uuid)
+ endpoint_data = {
+ 'device_uuid': device_uuid,
+ 'uuid': endpoint_uuid,
+ 'name': json_endpoint['name'],
+ 'type': json_endpoint['endpoint_type'],
+ }
+ result.append((endpoint_url, endpoint_data))
+
+ return result
diff --git a/src/device/service/drivers/ietf_l2vpn/Tools.py b/src/device/service/drivers/ietf_l2vpn/Tools.py
new file mode 100644
index 0000000000000000000000000000000000000000..45dfa23c984e175c01efa77371e94454b98ea94e
--- /dev/null
+++ b/src/device/service/drivers/ietf_l2vpn/Tools.py
@@ -0,0 +1,48 @@
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from typing import Dict, Optional
+
+def compose_service_endpoint_id(site_id : str, endpoint_id : Dict):
+ device_uuid = endpoint_id['device_id']['device_uuid']['uuid']
+ endpoint_uuid = endpoint_id['endpoint_uuid']['uuid']
+ return ':'.join([site_id, device_uuid, endpoint_uuid])
+
+def wim_mapping(site_id, ce_endpoint_id, pe_device_id : Optional[Dict] = None, priority=None, redundant=[]):
+ ce_device_uuid = ce_endpoint_id['device_id']['device_uuid']['uuid']
+ ce_endpoint_uuid = ce_endpoint_id['endpoint_uuid']['uuid']
+ service_endpoint_id = compose_service_endpoint_id(site_id, ce_endpoint_id)
+ if pe_device_id is None:
+ bearer = '{:s}:{:s}'.format(ce_device_uuid, ce_endpoint_uuid)
+ else:
+ pe_device_uuid = pe_device_id['device_uuid']['uuid']
+ bearer = '{:s}:{:s}'.format(ce_device_uuid, pe_device_uuid)
+ mapping = {
+ 'service_endpoint_id': service_endpoint_id,
+ 'datacenter_id': site_id, 'device_id': ce_device_uuid, 'device_interface_id': ce_endpoint_uuid,
+ 'service_mapping_info': {
+ 'site-id': site_id,
+ 'bearer': {'bearer-reference': bearer},
+ }
+ }
+ if priority is not None: mapping['service_mapping_info']['priority'] = priority
+ if len(redundant) > 0: mapping['service_mapping_info']['redundant'] = redundant
+ return service_endpoint_id, mapping
+
+def connection_point(service_endpoint_id : str, encapsulation_type : str, vlan_id : int):
+ return {
+ 'service_endpoint_id': service_endpoint_id,
+ 'service_endpoint_encapsulation_type': encapsulation_type,
+ 'service_endpoint_encapsulation_info': {'vlan': vlan_id}
+ }
diff --git a/src/device/service/drivers/ietf_l2vpn/WimconnectorIETFL2VPN.py b/src/device/service/drivers/ietf_l2vpn/WimconnectorIETFL2VPN.py
new file mode 100644
index 0000000000000000000000000000000000000000..34ff184c022f379e7420de237bd08fc1dc6282a6
--- /dev/null
+++ b/src/device/service/drivers/ietf_l2vpn/WimconnectorIETFL2VPN.py
@@ -0,0 +1,565 @@
+# -*- coding: utf-8 -*-
+##
+# Copyright 2018 Telefonica
+# All Rights Reserved.
+#
+# Contributors: Oscar Gonzalez de Dios, Manuel Lopez Bravo, Guillermo Pajares Martin
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# This work has been performed in the context of the Metro-Haul project -
+# funded by the European Commission under Grant number 761727 through the
+# Horizon 2020 program.
+##
+"""The SDN/WIM connector is responsible for establishing wide area network
+connectivity.
+
+This SDN/WIM connector implements the standard IETF RFC 8466 "A YANG Data
+ Model for Layer 2 Virtual Private Network (L2VPN) Service Delivery"
+
+It receives the endpoints and the necessary details to request
+the Layer 2 service.
+"""
+import requests
+import uuid
+import logging
+import copy
+#from osm_ro_plugin.sdnconn import SdnConnectorBase, SdnConnectorError
+from .sdnconn import SdnConnectorBase, SdnConnectorError
+
+"""Check layer where we move it"""
+
+
+class WimconnectorIETFL2VPN(SdnConnectorBase):
+ def __init__(self, wim, wim_account, config=None, logger=None):
+ """IETF L2VPN WIM connector
+
+ Arguments: (To be completed)
+ wim (dict): WIM record, as stored in the database
+ wim_account (dict): WIM account record, as stored in the database
+ """
+ self.logger = logging.getLogger("ro.sdn.ietfl2vpn")
+ super().__init__(wim, wim_account, config, logger)
+ self.headers = {"Content-Type": "application/json"}
+ self.mappings = {
+ m["service_endpoint_id"]: m for m in self.service_endpoint_mapping
+ }
+ self.user = wim_account.get("user")
+ self.passwd = wim_account.get("password")
+
+ if self.user is not None and self.passwd is not None:
+ self.auth = (self.user, self.passwd)
+ else:
+ self.auth = None
+
+ self.logger.info("IETFL2VPN Connector Initialized.")
+
+ def check_credentials(self):
+ endpoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
+ self.wim["wim_url"]
+ )
+
+ try:
+ response = requests.get(endpoint, auth=self.auth)
+ http_code = response.status_code
+ except requests.exceptions.RequestException as e:
+ raise SdnConnectorError(e.response, http_code=503)
+
+ if http_code != 200:
+ raise SdnConnectorError("Failed while authenticating", http_code=http_code)
+
+ self.logger.info("Credentials checked")
+
+ def get_connectivity_service_status(self, service_uuid, conn_info=None):
+ """Monitor the status of the connectivity service stablished
+
+ Arguments:
+ service_uuid: Connectivity service unique identifier
+
+ Returns:
+ Examples::
+ {'sdn_status': 'ACTIVE'}
+ {'sdn_status': 'INACTIVE'}
+ {'sdn_status': 'DOWN'}
+ {'sdn_status': 'ERROR'}
+ """
+ try:
+ self.logger.info("Sending get connectivity service stuatus")
+ servicepoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services/vpn-service={}/".format(
+ self.wim["wim_url"], service_uuid
+ )
+ response = requests.get(servicepoint, auth=self.auth)
+ self.logger.warning('response.status_code={:s}'.format(str(response.status_code)))
+ if response.status_code != requests.codes.ok:
+ raise SdnConnectorError(
+ "Unable to obtain connectivity servcice status",
+ http_code=response.status_code,
+ )
+
+ service_status = {"sdn_status": "ACTIVE"}
+
+ return service_status
+ except requests.exceptions.ConnectionError:
+ raise SdnConnectorError("Request Timeout", http_code=408)
+
+ def search_mapp(self, connection_point):
+ id = connection_point["service_endpoint_id"]
+ if id not in self.mappings:
+ raise SdnConnectorError("Endpoint {} not located".format(str(id)))
+ else:
+ return self.mappings[id]
+
+ def create_connectivity_service(self, service_uuid, service_type, connection_points, **kwargs):
+ """Stablish WAN connectivity between the endpoints
+
+ Arguments:
+ service_type (str): ``ELINE`` (L2), ``ELAN`` (L2), ``ETREE`` (L2),
+ ``L3``.
+ connection_points (list): each point corresponds to
+ an entry point from the DC to the transport network. One
+ connection point serves to identify the specific access and
+ some other service parameters, such as encapsulation type.
+ Represented by a dict as follows::
+
+ {
+ "service_endpoint_id": ..., (str[uuid])
+ "service_endpoint_encapsulation_type": ...,
+ (enum: none, dot1q, ...)
+ "service_endpoint_encapsulation_info": {
+ ... (dict)
+ "vlan": ..., (int, present if encapsulation is dot1q)
+ "vni": ... (int, present if encapsulation is vxlan),
+ "peers": [(ipv4_1), (ipv4_2)]
+ (present if encapsulation is vxlan)
+ }
+ }
+
+ The service endpoint ID should be previously informed to the WIM
+ engine in the RO when the WIM port mapping is registered.
+
+ Keyword Arguments:
+ bandwidth (int): value in kilobytes
+ latency (int): value in milliseconds
+
+ Other QoS might be passed as keyword arguments.
+
+ Returns:
+ tuple: ``conn_info``:
+ - *conn_info* (dict or None): Information to be stored at the
+ database (or ``None``). This information will be provided to
+ the :meth:`~.edit_connectivity_service` and :obj:`~.delete`.
+ **MUST** be JSON/YAML-serializable (plain data structures).
+
+ Raises:
+ SdnConnectorException: In case of error.
+ """
+ SETTINGS = { # min_endpoints, max_endpoints, vpn_service_type
+ 'ELINE': (2, 2, 'vpws'), # Virtual Private Wire Service
+ 'ELAN' : (2, None, 'vpls'), # Virtual Private LAN Service
+ }
+ settings = SETTINGS.get(service_type)
+ if settings is None: raise NotImplementedError('Unsupported service_type({:s})'.format(str(service_type)))
+ min_endpoints, max_endpoints, vpn_service_type = settings
+
+ if max_endpoints is not None and len(connection_points) > max_endpoints:
+ msg = "Connections between more than {:d} endpoints are not supported for service_type {:s}"
+ raise SdnConnectorError(msg.format(max_endpoints, service_type))
+
+ if min_endpoints is not None and len(connection_points) < min_endpoints:
+ msg = "Connections must be of at least {:d} endpoints for service_type {:s}"
+ raise SdnConnectorError(msg.format(min_endpoints, service_type))
+
+ """First step, create the vpn service"""
+ vpn_service = {}
+ vpn_service["vpn-id"] = service_uuid
+ vpn_service["vpn-svc-type"] = vpn_service_type
+ vpn_service["svc-topo"] = "any-to-any"
+ vpn_service["customer-name"] = "osm"
+ vpn_service_list = []
+ vpn_service_list.append(vpn_service)
+ vpn_service_l = {"ietf-l2vpn-svc:vpn-service": vpn_service_list}
+ response_service_creation = None
+ conn_info = []
+ self.logger.info("Sending vpn-service :{}".format(vpn_service_l))
+
+ try:
+ endpoint_service_creation = (
+ "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
+ self.wim["wim_url"]
+ )
+ )
+ response_service_creation = requests.post(
+ endpoint_service_creation,
+ headers=self.headers,
+ json=vpn_service_l,
+ auth=self.auth,
+ )
+ except requests.exceptions.ConnectionError:
+ raise SdnConnectorError(
+ "Request to create service Timeout", http_code=408
+ )
+
+ if response_service_creation.status_code == 409:
+ raise SdnConnectorError(
+ "Service already exists",
+ http_code=response_service_creation.status_code,
+ )
+ elif response_service_creation.status_code != requests.codes.created:
+ raise SdnConnectorError(
+ "Request to create service not accepted",
+ http_code=response_service_creation.status_code,
+ )
+
+ self.logger.info('connection_points = {:s}'.format(str(connection_points)))
+
+ # Check if protected paths are requested
+ extended_connection_points = []
+ for connection_point in connection_points:
+ extended_connection_points.append(connection_point)
+
+ connection_point_wan_info = self.search_mapp(connection_point)
+ service_mapping_info = connection_point_wan_info.get('service_mapping_info', {})
+ redundant_service_endpoint_ids = service_mapping_info.get('redundant')
+
+ if redundant_service_endpoint_ids is None: continue
+ if len(redundant_service_endpoint_ids) == 0: continue
+
+ for redundant_service_endpoint_id in redundant_service_endpoint_ids:
+ redundant_connection_point = copy.deepcopy(connection_point)
+ redundant_connection_point['service_endpoint_id'] = redundant_service_endpoint_id
+ extended_connection_points.append(redundant_connection_point)
+
+ self.logger.info('extended_connection_points = {:s}'.format(str(extended_connection_points)))
+
+ """Second step, create the connections and vpn attachments"""
+ for connection_point in extended_connection_points:
+ connection_point_wan_info = self.search_mapp(connection_point)
+ site_network_access = {}
+ connection = {}
+
+ if connection_point["service_endpoint_encapsulation_type"] != "none":
+ if (
+ connection_point["service_endpoint_encapsulation_type"]
+ == "dot1q"
+ ):
+ """The connection is a VLAN"""
+ connection["encapsulation-type"] = "dot1q-vlan-tagged"
+ tagged = {}
+ tagged_interf = {}
+ service_endpoint_encapsulation_info = connection_point[
+ "service_endpoint_encapsulation_info"
+ ]
+
+ if service_endpoint_encapsulation_info["vlan"] is None:
+ raise SdnConnectorError("VLAN must be provided")
+
+ tagged_interf["cvlan-id"] = service_endpoint_encapsulation_info[
+ "vlan"
+ ]
+ tagged["dot1q-vlan-tagged"] = tagged_interf
+ connection["tagged-interface"] = tagged
+ else:
+ raise NotImplementedError("Encapsulation type not implemented")
+
+ site_network_access["connection"] = connection
+ self.logger.info("Sending connection:{}".format(connection))
+ vpn_attach = {}
+ vpn_attach["vpn-id"] = service_uuid
+ vpn_attach["site-role"] = vpn_service["svc-topo"] + "-role"
+ site_network_access["vpn-attachment"] = vpn_attach
+ self.logger.info("Sending vpn-attachement :{}".format(vpn_attach))
+ uuid_sna = str(uuid.uuid4())
+ site_network_access["network-access-id"] = uuid_sna
+ site_network_access["bearer"] = connection_point_wan_info[
+ "service_mapping_info"
+ ]["bearer"]
+
+ access_priority = connection_point_wan_info["service_mapping_info"].get("priority")
+ if access_priority is not None:
+ availability = {}
+ availability["access-priority"] = access_priority
+ availability["single-active"] = [None]
+ site_network_access["availability"] = availability
+
+ constraint = {}
+ constraint['constraint-type'] = 'end-to-end-diverse'
+ constraint['target'] = {'all-other-accesses': [None]}
+
+ access_diversity = {}
+ access_diversity['constraints'] = {'constraint': []}
+ access_diversity['constraints']['constraint'].append(constraint)
+ site_network_access["access-diversity"] = access_diversity
+
+ site_network_accesses = {}
+ site_network_access_list = []
+ site_network_access_list.append(site_network_access)
+ site_network_accesses[
+ "ietf-l2vpn-svc:site-network-access"
+ ] = site_network_access_list
+ conn_info_d = {}
+ conn_info_d["site"] = connection_point_wan_info["service_mapping_info"][
+ "site-id"
+ ]
+ conn_info_d["site-network-access-id"] = site_network_access[
+ "network-access-id"
+ ]
+ conn_info_d["mapping"] = None
+ conn_info.append(conn_info_d)
+
+ try:
+ endpoint_site_network_access_creation = (
+ "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/"
+ "sites/site={}/site-network-accesses/".format(
+ self.wim["wim_url"],
+ connection_point_wan_info["service_mapping_info"][
+ "site-id"
+ ],
+ )
+ )
+ response_endpoint_site_network_access_creation = requests.post(
+ endpoint_site_network_access_creation,
+ headers=self.headers,
+ json=site_network_accesses,
+ auth=self.auth,
+ )
+
+ if (
+ response_endpoint_site_network_access_creation.status_code
+ == 409
+ ):
+ self.delete_connectivity_service(vpn_service["vpn-id"])
+
+ raise SdnConnectorError(
+ "Site_Network_Access with ID '{}' already exists".format(
+ site_network_access["network-access-id"]
+ ),
+ http_code=response_endpoint_site_network_access_creation.status_code,
+ )
+ elif (
+ response_endpoint_site_network_access_creation.status_code
+ == 400
+ ):
+ self.delete_connectivity_service(vpn_service["vpn-id"])
+
+ raise SdnConnectorError(
+ "Site {} does not exist".format(
+ connection_point_wan_info["service_mapping_info"][
+ "site-id"
+ ]
+ ),
+ http_code=response_endpoint_site_network_access_creation.status_code,
+ )
+ elif (
+ response_endpoint_site_network_access_creation.status_code
+ != requests.codes.created
+ and response_endpoint_site_network_access_creation.status_code
+ != requests.codes.no_content
+ ):
+ self.delete_connectivity_service(vpn_service["vpn-id"])
+
+ raise SdnConnectorError(
+ "Request not accepted",
+ http_code=response_endpoint_site_network_access_creation.status_code,
+ )
+ except requests.exceptions.ConnectionError:
+ self.delete_connectivity_service(vpn_service["vpn-id"])
+
+ raise SdnConnectorError("Request Timeout", http_code=408)
+
+ return conn_info
+
+ def delete_connectivity_service(self, service_uuid, conn_info=None):
+ """Disconnect multi-site endpoints previously connected
+
+ This method should receive as the first argument the UUID generated by
+ the ``create_connectivity_service``
+ """
+ try:
+ self.logger.info("Sending delete")
+ servicepoint = "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services/vpn-service={}/".format(
+ self.wim["wim_url"], service_uuid
+ )
+ response = requests.delete(servicepoint, auth=self.auth)
+
+ if response.status_code != requests.codes.no_content:
+ raise SdnConnectorError(
+ "Error in the request", http_code=response.status_code
+ )
+ except requests.exceptions.ConnectionError:
+ raise SdnConnectorError("Request Timeout", http_code=408)
+
+ def edit_connectivity_service(
+ self, service_uuid, conn_info=None, connection_points=None, **kwargs
+ ):
+ """Change an existing connectivity service, see
+ ``create_connectivity_service``"""
+ # sites = {"sites": {}}
+ # site_list = []
+ vpn_service = {}
+ vpn_service["svc-topo"] = "any-to-any"
+ counter = 0
+
+ for connection_point in connection_points:
+ site_network_access = {}
+ connection_point_wan_info = self.search_mapp(connection_point)
+ params_site = {}
+ params_site["site-id"] = connection_point_wan_info["service_mapping_info"][
+ "site-id"
+ ]
+ params_site["site-vpn-flavor"] = "site-vpn-flavor-single"
+ device_site = {}
+ device_site["device-id"] = connection_point_wan_info["device-id"]
+ params_site["devices"] = device_site
+ # network_access = {}
+ connection = {}
+
+ if connection_point["service_endpoint_encapsulation_type"] != "none":
+ if connection_point["service_endpoint_encapsulation_type"] == "dot1q":
+ """The connection is a VLAN"""
+ connection["encapsulation-type"] = "dot1q-vlan-tagged"
+ tagged = {}
+ tagged_interf = {}
+ service_endpoint_encapsulation_info = connection_point[
+ "service_endpoint_encapsulation_info"
+ ]
+
+ if service_endpoint_encapsulation_info["vlan"] is None:
+ raise SdnConnectorError("VLAN must be provided")
+
+ tagged_interf["cvlan-id"] = service_endpoint_encapsulation_info[
+ "vlan"
+ ]
+ tagged["dot1q-vlan-tagged"] = tagged_interf
+ connection["tagged-interface"] = tagged
+ else:
+ raise NotImplementedError("Encapsulation type not implemented")
+
+ site_network_access["connection"] = connection
+ vpn_attach = {}
+ vpn_attach["vpn-id"] = service_uuid
+ vpn_attach["site-role"] = vpn_service["svc-topo"] + "-role"
+ site_network_access["vpn-attachment"] = vpn_attach
+ uuid_sna = conn_info[counter]["site-network-access-id"]
+ site_network_access["network-access-id"] = uuid_sna
+ site_network_access["bearer"] = connection_point_wan_info[
+ "service_mapping_info"
+ ]["bearer"]
+ site_network_accesses = {}
+ site_network_access_list = []
+ site_network_access_list.append(site_network_access)
+ site_network_accesses[
+ "ietf-l2vpn-svc:site-network-access"
+ ] = site_network_access_list
+
+ try:
+ endpoint_site_network_access_edit = (
+ "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/"
+ "sites/site={}/site-network-accesses/".format(
+ self.wim["wim_url"],
+ connection_point_wan_info["service_mapping_info"]["site-id"],
+ )
+ )
+ response_endpoint_site_network_access_creation = requests.put(
+ endpoint_site_network_access_edit,
+ headers=self.headers,
+ json=site_network_accesses,
+ auth=self.auth,
+ )
+
+ if response_endpoint_site_network_access_creation.status_code == 400:
+ raise SdnConnectorError(
+ "Service does not exist",
+ http_code=response_endpoint_site_network_access_creation.status_code,
+ )
+ elif (
+ response_endpoint_site_network_access_creation.status_code != 201
+ and response_endpoint_site_network_access_creation.status_code
+ != 204
+ ):
+ raise SdnConnectorError(
+ "Request no accepted",
+ http_code=response_endpoint_site_network_access_creation.status_code,
+ )
+ except requests.exceptions.ConnectionError:
+ raise SdnConnectorError("Request Timeout", http_code=408)
+
+ counter += 1
+
+ return None
+
+ def clear_all_connectivity_services(self):
+ """Delete all WAN Links corresponding to a WIM"""
+ try:
+ self.logger.info("Sending clear all connectivity services")
+ servicepoint = (
+ "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
+ self.wim["wim_url"]
+ )
+ )
+ response = requests.delete(servicepoint, auth=self.auth)
+
+ if response.status_code != requests.codes.no_content:
+ raise SdnConnectorError(
+ "Unable to clear all connectivity services",
+ http_code=response.status_code,
+ )
+ except requests.exceptions.ConnectionError:
+ raise SdnConnectorError("Request Timeout", http_code=408)
+
+ def get_all_active_connectivity_services(self):
+ """Provide information about all active connections provisioned by a
+ WIM
+ """
+ try:
+ self.logger.info("Sending get all connectivity services")
+ servicepoint = (
+ "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services".format(
+ self.wim["wim_url"]
+ )
+ )
+ response = requests.get(servicepoint, auth=self.auth)
+
+ if response.status_code != requests.codes.ok:
+ raise SdnConnectorError(
+ "Unable to get all connectivity services",
+ http_code=response.status_code,
+ )
+
+ return response
+ except requests.exceptions.ConnectionError:
+ raise SdnConnectorError("Request Timeout", http_code=408)
+
+ def get_connectivity_service(self, service_uuid, conn_info=None):
+ """Provide information about a specific connection provisioned by a WIM.
+
+ This method should receive as the first argument the UUID generated by
+ the ``create_connectivity_service``
+ """
+ try:
+ self.logger.info("Sending get connectivity service")
+ servicepoint = (
+ "{}/restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services/vpn-service={}/".format(
+ self.wim["wim_url"], service_uuid
+ )
+ )
+ response = requests.get(servicepoint, auth=self.auth)
+
+ if response.status_code != requests.codes.ok:
+ raise SdnConnectorError(
+ "Unable to get connectivity service {:s}".format(str(service_uuid)),
+ http_code=response.status_code,
+ )
+
+ return response
+ except requests.exceptions.ConnectionError:
+ raise SdnConnectorError("Request Timeout", http_code=408)
diff --git a/src/device/service/drivers/ietf_l2vpn/__init__.py b/src/device/service/drivers/ietf_l2vpn/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..38d04994fb0fa1951fb465bc127eb72659dc2eaf
--- /dev/null
+++ b/src/device/service/drivers/ietf_l2vpn/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
diff --git a/src/device/service/drivers/ietf_l2vpn/acknowledgements.txt b/src/device/service/drivers/ietf_l2vpn/acknowledgements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3a7ed47ad6626ad13f4176bd696ec7e7dbab20ee
--- /dev/null
+++ b/src/device/service/drivers/ietf_l2vpn/acknowledgements.txt
@@ -0,0 +1,3 @@
+IETF L2VPN Driver is based on source code taken from:
+https://osm.etsi.org/gitlab/osm/ro/-/blob/master/RO-plugin/osm_ro_plugin/sdnconn.py
+https://osm.etsi.org/gitlab/osm/ro/-/blob/master/RO-SDN-ietfl2vpn/osm_rosdn_ietfl2vpn/wimconn_ietfl2vpn.py
diff --git a/src/device/service/drivers/ietf_l2vpn/sdnconn.py b/src/device/service/drivers/ietf_l2vpn/sdnconn.py
new file mode 100644
index 0000000000000000000000000000000000000000..a1849c9ef3e1a1260ff42bbadabc99f91a6435d7
--- /dev/null
+++ b/src/device/service/drivers/ietf_l2vpn/sdnconn.py
@@ -0,0 +1,242 @@
+# -*- coding: utf-8 -*-
+##
+# Copyright 2018 University of Bristol - High Performance Networks Research
+# Group
+# All Rights Reserved.
+#
+# Contributors: Anderson Bravalheri, Dimitrios Gkounis, Abubakar Siddique
+# Muqaddas, Navdeep Uniyal, Reza Nejabati and Dimitra Simeonidou
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: DEVICEDRIVER_XR = 6;
*/
DEVICEDRIVER_XR(6),
+ /**
+ * DEVICEDRIVER_IETF_L2VPN = 7;
+ */
+ DEVICEDRIVER_IETF_L2VPN(7),
UNRECOGNIZED(-1),
;
@@ -212,6 +216,10 @@ public final class ContextOuterClass {
* DEVICEDRIVER_XR = 6;
*/
public static final int DEVICEDRIVER_XR_VALUE = 6;
+ /**
+ * DEVICEDRIVER_IETF_L2VPN = 7;
+ */
+ public static final int DEVICEDRIVER_IETF_L2VPN_VALUE = 7;
public final int getNumber() {
@@ -245,6 +253,7 @@ public final class ContextOuterClass {
case 4: return DEVICEDRIVER_IETF_NETWORK_TOPOLOGY;
case 5: return DEVICEDRIVER_ONF_TR_352;
case 6: return DEVICEDRIVER_XR;
+ case 7: return DEVICEDRIVER_IETF_L2VPN;
default: return null;
}
}
diff --git a/src/service/service/service_handler_api/FilterFields.py b/src/service/service/service_handler_api/FilterFields.py
index a73ec53f37d68e0414eeb1df146373c6906273c5..3ec71dc64536e28457c4f1adbf3679186285786d 100644
--- a/src/service/service/service_handler_api/FilterFields.py
+++ b/src/service/service/service_handler_api/FilterFields.py
@@ -33,7 +33,8 @@ DEVICE_DRIVER_VALUES = {
DeviceDriverEnum.DEVICEDRIVER_P4,
DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY,
DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352,
- DeviceDriverEnum.DEVICEDRIVER_XR
+ DeviceDriverEnum.DEVICEDRIVER_XR,
+ DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN,
}
# Map allowed filter fields to allowed values per Filter field. If no restriction (free text) None is specified
diff --git a/src/service/service/service_handler_api/ServiceHandlerFactory.py b/src/service/service/service_handler_api/ServiceHandlerFactory.py
index 6aa21b49920254383fad5f28aa234b6ec0cad5a3..64ea204a2600a71b08c8c373a15640f5e2134787 100644
--- a/src/service/service/service_handler_api/ServiceHandlerFactory.py
+++ b/src/service/service/service_handler_api/ServiceHandlerFactory.py
@@ -73,6 +73,9 @@ class ServiceHandlerFactory:
if field_indice is None: continue
if not isinstance(field_values, Iterable) or isinstance(field_values, str):
field_values = [field_values]
+ if len(field_values) == 0:
+ # do not allow empty fields; might cause wrong selection
+ raise UnsatisfiedFilterException(filter_fields)
field_enum_values = FILTER_FIELD_ALLOWED_VALUES.get(field_name)
diff --git a/src/service/service/service_handlers/__init__.py b/src/service/service/service_handlers/__init__.py
index 4c9059779d6b7031685e1de76b0a7ed651af6c5f..257bc138fe932e7e5abee00981848248039d0b3f 100644
--- a/src/service/service/service_handlers/__init__.py
+++ b/src/service/service/service_handlers/__init__.py
@@ -15,12 +15,14 @@
from common.proto.context_pb2 import DeviceDriverEnum, ServiceTypeEnum
from ..service_handler_api.FilterFields import FilterFieldEnum
from .l2nm_emulated.L2NMEmulatedServiceHandler import L2NMEmulatedServiceHandler
+from .l2nm_ietfl2vpn.L2NM_IETFL2VPN_ServiceHandler import L2NM_IETFL2VPN_ServiceHandler
from .l2nm_openconfig.L2NMOpenConfigServiceHandler import L2NMOpenConfigServiceHandler
from .l3nm_emulated.L3NMEmulatedServiceHandler import L3NMEmulatedServiceHandler
from .l3nm_openconfig.L3NMOpenConfigServiceHandler import L3NMOpenConfigServiceHandler
+from .microwave.MicrowaveServiceHandler import MicrowaveServiceHandler
from .p4.p4_service_handler import P4ServiceHandler
from .tapi_tapi.TapiServiceHandler import TapiServiceHandler
-from .microwave.MicrowaveServiceHandler import MicrowaveServiceHandler
+from .tapi_xr.TapiXrServiceHandler import TapiXrServiceHandler
SERVICE_HANDLERS = [
(L2NMEmulatedServiceHandler, [
@@ -50,13 +52,19 @@ SERVICE_HANDLERS = [
(TapiServiceHandler, [
{
FilterFieldEnum.SERVICE_TYPE : ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE,
- FilterFieldEnum.DEVICE_DRIVER : [DeviceDriverEnum.DEVICEDRIVER_TRANSPORT_API, DeviceDriverEnum.DEVICEDRIVER_XR],
+ FilterFieldEnum.DEVICE_DRIVER : [DeviceDriverEnum.DEVICEDRIVER_TRANSPORT_API],
+ }
+ ]),
+ (TapiXrServiceHandler, [
+ {
+ FilterFieldEnum.SERVICE_TYPE : ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE,
+ FilterFieldEnum.DEVICE_DRIVER : [DeviceDriverEnum.DEVICEDRIVER_XR],
}
]),
(MicrowaveServiceHandler, [
{
FilterFieldEnum.SERVICE_TYPE : ServiceTypeEnum.SERVICETYPE_L2NM,
- FilterFieldEnum.DEVICE_DRIVER : DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY,
+ FilterFieldEnum.DEVICE_DRIVER : [DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY, DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352],
}
]),
(P4ServiceHandler, [
@@ -65,4 +73,10 @@ SERVICE_HANDLERS = [
FilterFieldEnum.DEVICE_DRIVER: DeviceDriverEnum.DEVICEDRIVER_P4,
}
]),
+ (L2NM_IETFL2VPN_ServiceHandler, [
+ {
+ FilterFieldEnum.SERVICE_TYPE : ServiceTypeEnum.SERVICETYPE_L2NM,
+ FilterFieldEnum.DEVICE_DRIVER : [DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN],
+ }
+ ]),
]
diff --git a/src/service/service/service_handlers/l2nm_emulated/ConfigRules.py b/src/service/service/service_handlers/l2nm_emulated/ConfigRules.py
index c2ea6e213ee8d18b4507089fb2762c913e03039a..363983b8653e1cfa553279d2df74d6ac893a4fec 100644
--- a/src/service/service/service_handlers/l2nm_emulated/ConfigRules.py
+++ b/src/service/service/service_handlers/l2nm_emulated/ConfigRules.py
@@ -21,15 +21,18 @@ def setup_config_rules(
service_settings : TreeNode, endpoint_settings : TreeNode
) -> List[Dict]:
- json_settings : Dict = {} if service_settings is None else service_settings.value
- json_endpoint_settings : Dict = {} if endpoint_settings is None else endpoint_settings.value
+ if service_settings is None: return []
+ if endpoint_settings is None: return []
- mtu = json_settings.get('mtu', 1450 ) # 1512
+ #json_settings : Dict = service_settings.value
+ json_endpoint_settings : Dict = endpoint_settings.value
+
+ #mtu = json_settings.get('mtu', 1450 ) # 1512
#address_families = json_settings.get('address_families', [] ) # ['IPV4']
#bgp_as = json_settings.get('bgp_as', 0 ) # 65000
#bgp_route_target = json_settings.get('bgp_route_target', '0:0') # 65000:333
- router_id = json_endpoint_settings.get('router_id', '0.0.0.0') # '10.95.0.10'
+ #router_id = json_endpoint_settings.get('router_id', '0.0.0.0') # '10.95.0.10'
#route_distinguisher = json_endpoint_settings.get('route_distinguisher', '0:0' ) # '60001:801'
sub_interface_index = json_endpoint_settings.get('sub_interface_index', 0 ) # 1
vlan_id = json_endpoint_settings.get('vlan_id', 1 ) # 400
@@ -43,17 +46,17 @@ def setup_config_rules(
connection_point_id = 'VC-1'
json_config_rules = [
- json_config_rule_set(
- '/network_instance[default]',
- {'name': 'default', 'type': 'DEFAULT_INSTANCE', 'router_id': router_id}),
+ #json_config_rule_set(
+ # '/network_instance[default]',
+ # {'name': 'default', 'type': 'DEFAULT_INSTANCE', 'router_id': router_id}),
- json_config_rule_set(
- '/network_instance[default]/protocols[OSPF]',
- {'name': 'default', 'identifier': 'OSPF', 'protocol_name': 'OSPF'}),
+ #json_config_rule_set(
+ # '/network_instance[default]/protocols[OSPF]',
+ # {'name': 'default', 'identifier': 'OSPF', 'protocol_name': 'OSPF'}),
- json_config_rule_set(
- '/network_instance[default]/protocols[STATIC]',
- {'name': 'default', 'identifier': 'STATIC', 'protocol_name': 'STATIC'}),
+ #json_config_rule_set(
+ # '/network_instance[default]/protocols[STATIC]',
+ # {'name': 'default', 'identifier': 'STATIC', 'protocol_name': 'STATIC'}),
json_config_rule_set(
'/network_instance[{:s}]'.format(network_instance_name),
@@ -66,7 +69,7 @@ def setup_config_rules(
json_config_rule_set(
'/network_instance[{:s}]/interface[{:s}]'.format(network_instance_name, if_cirid_name),
{'name': network_instance_name, 'id': if_cirid_name, 'interface': if_cirid_name,
- 'subinterface': sub_interface_index}),
+ 'subinterface': sub_interface_index}),
json_config_rule_set(
'/network_instance[{:s}]/connection_point[{:s}]'.format(network_instance_name, connection_point_id),
@@ -80,15 +83,18 @@ def teardown_config_rules(
service_settings : TreeNode, endpoint_settings : TreeNode
) -> List[Dict]:
- #json_settings : Dict = {} if service_settings is None else service_settings.value
- json_endpoint_settings : Dict = {} if endpoint_settings is None else endpoint_settings.value
+ if service_settings is None: return []
+ if endpoint_settings is None: return []
+
+ #json_settings : Dict = service_settings.value
+ json_endpoint_settings : Dict = endpoint_settings.value
#mtu = json_settings.get('mtu', 1450 ) # 1512
#address_families = json_settings.get('address_families', [] ) # ['IPV4']
#bgp_as = json_settings.get('bgp_as', 0 ) # 65000
#bgp_route_target = json_settings.get('bgp_route_target', '0:0') # 65000:333
- router_id = json_endpoint_settings.get('router_id', '0.0.0.0') # '10.95.0.10'
+ #router_id = json_endpoint_settings.get('router_id', '0.0.0.0') # '10.95.0.10'
#route_distinguisher = json_endpoint_settings.get('route_distinguisher', '0:0' ) # '60001:801'
sub_interface_index = json_endpoint_settings.get('sub_interface_index', 0 ) # 1
#vlan_id = json_endpoint_settings.get('vlan_id', 1 ) # 400
@@ -99,36 +105,36 @@ def teardown_config_rules(
if_cirid_name = '{:s}.{:s}'.format(endpoint_name, str(circuit_id))
network_instance_name = 'ELAN-AC:{:s}'.format(str(circuit_id))
- connection_point_id = 'VC-1'
+ #connection_point_id = 'VC-1'
json_config_rules = [
- json_config_rule_delete(
- '/network_instance[{:s}]/connection_point[{:s}]'.format(network_instance_name, connection_point_id),
- {'name': network_instance_name, 'connection_point': connection_point_id}),
+ #json_config_rule_delete(
+ # '/network_instance[{:s}]/connection_point[{:s}]'.format(network_instance_name, connection_point_id),
+ # {'name': network_instance_name, 'connection_point': connection_point_id}),
- json_config_rule_delete(
- '/network_instance[{:s}]/interface[{:s}]'.format(network_instance_name, if_cirid_name),
- {'name': network_instance_name, 'id': if_cirid_name, 'interface': if_cirid_name,
- 'subinterface': sub_interface_index}),
-
- json_config_rule_delete(
- '/interface[{:s}]/subinterface[{:d}]'.format(if_cirid_name, sub_interface_index),
- {'name': if_cirid_name, 'index': sub_interface_index}),
+ #json_config_rule_delete(
+ # '/network_instance[{:s}]/interface[{:s}]'.format(network_instance_name, if_cirid_name),
+ # {'name': network_instance_name, 'id': if_cirid_name, 'interface': if_cirid_name,
+ # 'subinterface': sub_interface_index}),
json_config_rule_delete(
'/network_instance[{:s}]'.format(network_instance_name),
{'name': network_instance_name}),
json_config_rule_delete(
- '/network_instance[default]/protocols[STATIC]',
- {'name': 'default', 'identifier': 'STATIC', 'protocol_name': 'STATIC'}),
+ '/interface[{:s}]/subinterface[{:d}]'.format(if_cirid_name, sub_interface_index),
+ {'name': if_cirid_name, 'index': sub_interface_index}),
- json_config_rule_delete(
- '/network_instance[default]/protocols[OSPF]',
- {'name': 'default', 'identifier': 'OSPF', 'protocol_name': 'OSPF'}),
+ #json_config_rule_delete(
+ # '/network_instance[default]/protocols[STATIC]',
+ # {'name': 'default', 'identifier': 'STATIC', 'protocol_name': 'STATIC'}),
- json_config_rule_delete(
- '/network_instance[default]',
- {'name': 'default', 'type': 'DEFAULT_INSTANCE', 'router_id': router_id}),
+ #json_config_rule_delete(
+ # '/network_instance[default]/protocols[OSPF]',
+ # {'name': 'default', 'identifier': 'OSPF', 'protocol_name': 'OSPF'}),
+
+ #json_config_rule_delete(
+ # '/network_instance[default]',
+ # {'name': 'default', 'type': 'DEFAULT_INSTANCE', 'router_id': router_id}),
]
return json_config_rules
diff --git a/src/service/service/service_handlers/l2nm_emulated/L2NMEmulatedServiceHandler.py b/src/service/service/service_handlers/l2nm_emulated/L2NMEmulatedServiceHandler.py
index 9de6c607b1336a4b3fb43867efc16d30048177e0..0a2261ceb4e1a63c984cf833121e3a87e13c0e9f 100644
--- a/src/service/service/service_handlers/l2nm_emulated/L2NMEmulatedServiceHandler.py
+++ b/src/service/service/service_handlers/l2nm_emulated/L2NMEmulatedServiceHandler.py
@@ -75,10 +75,12 @@ class L2NMEmulatedServiceHandler(_ServiceHandler):
service_uuid, connection_uuid, device_uuid, endpoint_uuid, endpoint_name,
settings, endpoint_settings)
- del device_obj.device_config.config_rules[:]
- for json_config_rule in json_config_rules:
- device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
- self.__task_executor.configure_device(device_obj)
+ if len(json_config_rules) > 0:
+ del device_obj.device_config.config_rules[:]
+ for json_config_rule in json_config_rules:
+ device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
+ self.__task_executor.configure_device(device_obj)
+
results.append(True)
except Exception as e: # pylint: disable=broad-except
LOGGER.exception('Unable to SetEndpoint({:s})'.format(str(endpoint)))
@@ -110,10 +112,12 @@ class L2NMEmulatedServiceHandler(_ServiceHandler):
service_uuid, connection_uuid, device_uuid, endpoint_uuid, endpoint_name,
settings, endpoint_settings)
- del device_obj.device_config.config_rules[:]
- for json_config_rule in json_config_rules:
- device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
- self.__task_executor.configure_device(device_obj)
+ if len(json_config_rules) > 0:
+ del device_obj.device_config.config_rules[:]
+ for json_config_rule in json_config_rules:
+ device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
+ self.__task_executor.configure_device(device_obj)
+
results.append(True)
except Exception as e: # pylint: disable=broad-except
LOGGER.exception('Unable to DeleteEndpoint({:s})'.format(str(endpoint)))
diff --git a/src/service/service/service_handlers/l2nm_ietfl2vpn/L2NM_IETFL2VPN_ServiceHandler.py b/src/service/service/service_handlers/l2nm_ietfl2vpn/L2NM_IETFL2VPN_ServiceHandler.py
new file mode 100644
index 0000000000000000000000000000000000000000..880a6c5a20d618c9d9f21701b7ef3886dbb9fc21
--- /dev/null
+++ b/src/service/service/service_handlers/l2nm_ietfl2vpn/L2NM_IETFL2VPN_ServiceHandler.py
@@ -0,0 +1,173 @@
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import json, logging
+from typing import Any, Dict, List, Optional, Tuple, Union
+from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method
+from common.proto.context_pb2 import ConfigRule, DeviceId, Service
+from common.tools.object_factory.ConfigRule import json_config_rule_delete, json_config_rule_set
+from common.tools.object_factory.Device import json_device_id
+from common.type_checkers.Checkers import chk_type
+from service.service.service_handler_api.Tools import get_device_endpoint_uuids, get_endpoint_matching
+from service.service.service_handler_api._ServiceHandler import _ServiceHandler
+from service.service.service_handler_api.SettingsHandler import SettingsHandler
+from service.service.task_scheduler.TaskExecutor import TaskExecutor
+
+LOGGER = logging.getLogger(__name__)
+
+METRICS_POOL = MetricsPool('Service', 'Handler', labels={'handler': 'tapi_tapi'})
+
+class L2NM_IETFL2VPN_ServiceHandler(_ServiceHandler):
+ def __init__( # pylint: disable=super-init-not-called
+ self, service : Service, task_executor : TaskExecutor, **settings
+ ) -> None:
+ self.__service = service
+ self.__task_executor = task_executor
+ self.__settings_handler = SettingsHandler(service.service_config, **settings)
+
+ @metered_subclass_method(METRICS_POOL)
+ def SetEndpoint(
+ self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None
+ ) -> List[Union[bool, Exception]]:
+
+ chk_type('endpoints', endpoints, list)
+ if len(endpoints) < 2: return []
+
+ service_uuid = self.__service.service_id.service_uuid.uuid
+ settings = self.__settings_handler.get('/settings')
+ json_settings : Dict = {} if settings is None else settings.value
+ encap_type = json_settings.get('encapsulation_type', '')
+ vlan_id = json_settings.get('vlan_id', 100)
+
+ results = []
+ try:
+ src_device_uuid, src_endpoint_uuid = get_device_endpoint_uuids(endpoints[0])
+ src_device = self.__task_executor.get_device(DeviceId(**json_device_id(src_device_uuid)))
+ src_endpoint = get_endpoint_matching(src_device, src_endpoint_uuid)
+ src_controller = self.__task_executor.get_device_controller(src_device)
+
+ dst_device_uuid, dst_endpoint_uuid = get_device_endpoint_uuids(endpoints[-1])
+ dst_device = self.__task_executor.get_device(DeviceId(**json_device_id(dst_device_uuid)))
+ dst_endpoint = get_endpoint_matching(dst_device, dst_endpoint_uuid)
+ dst_controller = self.__task_executor.get_device_controller(dst_device)
+
+ if src_controller.device_id.device_uuid.uuid != dst_controller.device_id.device_uuid.uuid:
+ raise Exception('Different Src-Dst devices not supported by now')
+ controller = src_controller
+
+ json_config_rule = json_config_rule_set('/services/service[{:s}]'.format(service_uuid), {
+ 'uuid' : service_uuid,
+ 'src_device_name' : src_device.name,
+ 'src_endpoint_name' : src_endpoint.name,
+ 'dst_device_name' : dst_device.name,
+ 'dst_endpoint_name' : dst_endpoint.name,
+ 'encapsulation_type': encap_type,
+ 'vlan_id' : vlan_id,
+ })
+ del controller.device_config.config_rules[:]
+ controller.device_config.config_rules.append(ConfigRule(**json_config_rule))
+ self.__task_executor.configure_device(controller)
+ results.append(True)
+ except Exception as e: # pylint: disable=broad-except
+ LOGGER.exception('Unable to SetEndpoint for Service({:s})'.format(str(service_uuid)))
+ results.append(e)
+
+ return results
+
+ @metered_subclass_method(METRICS_POOL)
+ def DeleteEndpoint(
+ self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None
+ ) -> List[Union[bool, Exception]]:
+
+ chk_type('endpoints', endpoints, list)
+ if len(endpoints) < 2: return []
+
+ service_uuid = self.__service.service_id.service_uuid.uuid
+
+ results = []
+ try:
+ src_device_uuid, _ = get_device_endpoint_uuids(endpoints[0])
+ src_device = self.__task_executor.get_device(DeviceId(**json_device_id(src_device_uuid)))
+ src_controller = self.__task_executor.get_device_controller(src_device)
+
+ dst_device_uuid, _ = get_device_endpoint_uuids(endpoints[1])
+ dst_device = self.__task_executor.get_device(DeviceId(**json_device_id(dst_device_uuid)))
+ dst_controller = self.__task_executor.get_device_controller(dst_device)
+
+ if src_controller.device_id.device_uuid.uuid != dst_controller.device_id.device_uuid.uuid:
+ raise Exception('Different Src-Dst devices not supported by now')
+ controller = src_controller
+
+ json_config_rule = json_config_rule_delete('/services/service[{:s}]'.format(service_uuid), {
+ 'uuid': service_uuid
+ })
+ del controller.device_config.config_rules[:]
+ controller.device_config.config_rules.append(ConfigRule(**json_config_rule))
+ self.__task_executor.configure_device(controller)
+ results.append(True)
+ except Exception as e: # pylint: disable=broad-except
+ LOGGER.exception('Unable to DeleteEndpoint for Service({:s})'.format(str(service_uuid)))
+ results.append(e)
+
+ return results
+
+ @metered_subclass_method(METRICS_POOL)
+ def SetConstraint(self, constraints : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+ chk_type('constraints', constraints, list)
+ if len(constraints) == 0: return []
+
+ msg = '[SetConstraint] Method not implemented. Constraints({:s}) are being ignored.'
+ LOGGER.warning(msg.format(str(constraints)))
+ return [True for _ in range(len(constraints))]
+
+ @metered_subclass_method(METRICS_POOL)
+ def DeleteConstraint(self, constraints : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+ chk_type('constraints', constraints, list)
+ if len(constraints) == 0: return []
+
+ msg = '[DeleteConstraint] Method not implemented. Constraints({:s}) are being ignored.'
+ LOGGER.warning(msg.format(str(constraints)))
+ return [True for _ in range(len(constraints))]
+
+ @metered_subclass_method(METRICS_POOL)
+ def SetConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+ chk_type('resources', resources, list)
+ if len(resources) == 0: return []
+
+ results = []
+ for resource in resources:
+ try:
+ resource_value = json.loads(resource[1])
+ self.__settings_handler.set(resource[0], resource_value)
+ results.append(True)
+ except Exception as e: # pylint: disable=broad-except
+ LOGGER.exception('Unable to SetConfig({:s})'.format(str(resource)))
+ results.append(e)
+
+ return results
+
+ @metered_subclass_method(METRICS_POOL)
+ def DeleteConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+ chk_type('resources', resources, list)
+ if len(resources) == 0: return []
+
+ results = []
+ for resource in resources:
+ try:
+ self.__settings_handler.delete(resource[0])
+ except Exception as e: # pylint: disable=broad-except
+ LOGGER.exception('Unable to DeleteConfig({:s})'.format(str(resource)))
+ results.append(e)
+
+ return results
diff --git a/src/service/service/service_handlers/l2nm_ietfl2vpn/__init__.py b/src/service/service/service_handlers/l2nm_ietfl2vpn/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..1549d9811aa5d1c193a44ad45d0d7773236c0612
--- /dev/null
+++ b/src/service/service/service_handlers/l2nm_ietfl2vpn/__init__.py
@@ -0,0 +1,14 @@
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
diff --git a/src/service/service/service_handlers/l2nm_openconfig/ConfigRules.py b/src/service/service/service_handlers/l2nm_openconfig/ConfigRules.py
index 07e78d73631342d101d77697098e83961c7dcf26..63e818a8303f9939abe8f776cd34b3066cb84ec1 100644
--- a/src/service/service/service_handlers/l2nm_openconfig/ConfigRules.py
+++ b/src/service/service/service_handlers/l2nm_openconfig/ConfigRules.py
@@ -20,16 +20,13 @@ def setup_config_rules(
service_uuid : str, connection_uuid : str, device_uuid : str, endpoint_uuid : str, endpoint_name : str,
service_settings : TreeNode, endpoint_settings : TreeNode
) -> List[Dict]:
-
+
if service_settings is None: return []
if endpoint_settings is None: return []
- json_settings : Dict = service_settings.value
+ #json_settings : Dict = service_settings.value
json_endpoint_settings : Dict = endpoint_settings.value
- json_settings : Dict = {} if service_settings is None else service_settings.value
- json_endpoint_settings : Dict = {} if endpoint_settings is None else endpoint_settings.value
-
#mtu = json_settings.get('mtu', 1450 ) # 1512
#address_families = json_settings.get('address_families', [] ) # ['IPV4']
#bgp_as = json_settings.get('bgp_as', 0 ) # 65000
@@ -41,43 +38,32 @@ def setup_config_rules(
vlan_id = json_endpoint_settings.get('vlan_id', 1 ) # 400
#address_ip = json_endpoint_settings.get('address_ip', '0.0.0.0') # '2.2.2.1'
#address_prefix = json_endpoint_settings.get('address_prefix', 24 ) # 30
- remote_router = json_endpoint_settings.get('remote_router', '5.5.5.5') # '5.5.5.5'
- circuit_id = json_endpoint_settings.get('circuit_id', '111' ) # '111'
-
+ remote_router = json_endpoint_settings.get('remote_router', '0.0.0.0') # '5.5.5.5'
+ circuit_id = json_endpoint_settings.get('circuit_id', '000' ) # '111'
if_cirid_name = '{:s}.{:s}'.format(endpoint_name, str(circuit_id))
network_instance_name = 'ELAN-AC:{:s}'.format(str(circuit_id))
connection_point_id = 'VC-1'
json_config_rules = [
-
+
json_config_rule_set(
'/network_instance[{:s}]'.format(network_instance_name),
- {'name': network_instance_name,
- 'type': 'L2VSI'}),
+ {'name': network_instance_name, 'type': 'L2VSI'}),
json_config_rule_set(
- '/interface[{:s}]/subinterface[0]'.format(if_cirid_name),
- {'name': if_cirid_name,
- 'type': 'l2vlan',
- 'index': sub_interface_index,
- 'vlan_id': vlan_id}),
+ '/interface[{:s}]/subinterface[{:d}]'.format(if_cirid_name, sub_interface_index),
+ {'name': if_cirid_name, 'type': 'l2vlan', 'index': sub_interface_index, 'vlan_id': vlan_id}),
json_config_rule_set(
'/network_instance[{:s}]/interface[{:s}]'.format(network_instance_name, if_cirid_name),
- {'name': network_instance_name,
- 'id': if_cirid_name,
- 'interface': if_cirid_name,
- 'subinterface': 0
- }),
+ {'name': network_instance_name, 'id': if_cirid_name, 'interface': if_cirid_name,
+ 'subinterface': sub_interface_index}),
json_config_rule_set(
'/network_instance[{:s}]/connection_point[{:s}]'.format(network_instance_name, connection_point_id),
- {'name': network_instance_name,
- 'connection_point': connection_point_id,
- 'VC_ID': circuit_id,
- 'remote_system': remote_router
- }),
+ {'name': network_instance_name, 'connection_point': connection_point_id, 'VC_ID': circuit_id,
+ 'remote_system': remote_router}),
]
return json_config_rules
@@ -86,8 +72,11 @@ def teardown_config_rules(
service_settings : TreeNode, endpoint_settings : TreeNode
) -> List[Dict]:
- #json_settings : Dict = {} if service_settings is None else service_settings.value
- json_endpoint_settings : Dict = {} if endpoint_settings is None else endpoint_settings.value
+ if service_settings is None: return []
+ if endpoint_settings is None: return []
+
+ #json_settings : Dict = service_settings.value
+ json_endpoint_settings : Dict = endpoint_settings.value
#mtu = json_settings.get('mtu', 1450 ) # 1512
#address_families = json_settings.get('address_families', [] ) # ['IPV4']
@@ -96,7 +85,7 @@ def teardown_config_rules(
#router_id = json_endpoint_settings.get('router_id', '0.0.0.0') # '10.95.0.10'
#route_distinguisher = json_endpoint_settings.get('route_distinguisher', '0:0' ) # '60001:801'
- #sub_interface_index = json_endpoint_settings.get('sub_interface_index', 0 ) # 1
+ sub_interface_index = json_endpoint_settings.get('sub_interface_index', 0 ) # 1
#vlan_id = json_endpoint_settings.get('vlan_id', 1 ) # 400
#address_ip = json_endpoint_settings.get('address_ip', '0.0.0.0') # '2.2.2.1'
#address_prefix = json_endpoint_settings.get('address_prefix', 24 ) # 30
@@ -105,17 +94,26 @@ def teardown_config_rules(
if_cirid_name = '{:s}.{:s}'.format(endpoint_name, str(circuit_id))
network_instance_name = 'ELAN-AC:{:s}'.format(str(circuit_id))
- #connection_point_id = 'VC-1'
+ connection_point_id = 'VC-1'
json_config_rules = [
+
+ json_config_rule_delete(
+ '/network_instance[{:s}]/connection_point[{:s}]'.format(network_instance_name, connection_point_id),
+ {'name': network_instance_name, 'connection_point': connection_point_id, 'VC_ID': circuit_id}),
+
+ json_config_rule_delete(
+ '/network_instance[{:s}]/interface[{:s}]'.format(network_instance_name, if_cirid_name),
+ {'name': network_instance_name, 'id': if_cirid_name, 'interface': if_cirid_name,
+ 'subinterface': sub_interface_index}),
+
+ json_config_rule_delete(
+ '/interface[{:s}]/subinterface[{:d}]'.format(if_cirid_name, sub_interface_index),
+ {'name': if_cirid_name, 'index': sub_interface_index}),
+
json_config_rule_delete(
'/network_instance[{:s}]'.format(network_instance_name),
{'name': network_instance_name}),
-
- json_config_rule_delete(
- '/interface[{:s}]/subinterface[0]'.format(if_cirid_name),{
- 'name': if_cirid_name,
- }),
-
+
]
return json_config_rules
diff --git a/src/service/service/service_handlers/l3nm_emulated/ConfigRules.py b/src/service/service/service_handlers/l3nm_emulated/ConfigRules.py
index 903ad8cd5ae442a03d54fb49083f3837a3c8187c..f4a46112e778bd01aa76322384d8adee942aaa5b 100644
--- a/src/service/service/service_handlers/l3nm_emulated/ConfigRules.py
+++ b/src/service/service/service_handlers/l3nm_emulated/ConfigRules.py
@@ -21,8 +21,11 @@ def setup_config_rules(
service_settings : TreeNode, endpoint_settings : TreeNode
) -> List[Dict]:
- json_settings : Dict = {} if service_settings is None else service_settings.value
- json_endpoint_settings : Dict = {} if endpoint_settings is None else endpoint_settings.value
+ if service_settings is None: return []
+ if endpoint_settings is None: return []
+
+ json_settings : Dict = service_settings.value
+ json_endpoint_settings : Dict = endpoint_settings.value
service_short_uuid = service_uuid.split('-')[-1]
network_instance_name = '{:s}-NetInst'.format(service_short_uuid)
@@ -142,8 +145,11 @@ def teardown_config_rules(
service_settings : TreeNode, endpoint_settings : TreeNode
) -> List[Dict]:
- json_settings : Dict = {} if service_settings is None else service_settings.value
- json_endpoint_settings : Dict = {} if endpoint_settings is None else endpoint_settings.value
+ if service_settings is None: return []
+ if endpoint_settings is None: return []
+
+ json_settings : Dict = service_settings.value
+ json_endpoint_settings : Dict = endpoint_settings.value
#mtu = json_settings.get('mtu', 1450 ) # 1512
#address_families = json_settings.get('address_families', [] ) # ['IPV4']
diff --git a/src/service/service/service_handlers/l3nm_emulated/L3NMEmulatedServiceHandler.py b/src/service/service/service_handlers/l3nm_emulated/L3NMEmulatedServiceHandler.py
index 47de9c94fbb8a5ddac848336c2ed7936d0126b45..18da03b08c0ea490b60c41df3894392022ddf228 100644
--- a/src/service/service/service_handlers/l3nm_emulated/L3NMEmulatedServiceHandler.py
+++ b/src/service/service/service_handlers/l3nm_emulated/L3NMEmulatedServiceHandler.py
@@ -75,10 +75,12 @@ class L3NMEmulatedServiceHandler(_ServiceHandler):
service_uuid, connection_uuid, device_uuid, endpoint_uuid, endpoint_name,
settings, endpoint_settings)
- del device_obj.device_config.config_rules[:]
- for json_config_rule in json_config_rules:
- device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
- self.__task_executor.configure_device(device_obj)
+ if len(json_config_rules) > 0:
+ del device_obj.device_config.config_rules[:]
+ for json_config_rule in json_config_rules:
+ device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
+ self.__task_executor.configure_device(device_obj)
+
results.append(True)
except Exception as e: # pylint: disable=broad-except
LOGGER.exception('Unable to SetEndpoint({:s})'.format(str(endpoint)))
@@ -110,10 +112,12 @@ class L3NMEmulatedServiceHandler(_ServiceHandler):
service_uuid, connection_uuid, device_uuid, endpoint_uuid, endpoint_name,
settings, endpoint_settings)
- del device_obj.device_config.config_rules[:]
- for json_config_rule in json_config_rules:
- device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
- self.__task_executor.configure_device(device_obj)
+ if len(json_config_rules) > 0:
+ del device_obj.device_config.config_rules[:]
+ for json_config_rule in json_config_rules:
+ device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
+ self.__task_executor.configure_device(device_obj)
+
results.append(True)
except Exception as e: # pylint: disable=broad-except
LOGGER.exception('Unable to DeleteEndpoint({:s})'.format(str(endpoint)))
diff --git a/src/service/service/service_handlers/l3nm_openconfig/ConfigRules.py b/src/service/service/service_handlers/l3nm_openconfig/ConfigRules.py
index ef93dcdda8145cab15ff21c24b6318e9eb00e098..5d260bf86b82c66be8eb2f0caa683a72d8bd0ba5 100644
--- a/src/service/service/service_handlers/l3nm_openconfig/ConfigRules.py
+++ b/src/service/service/service_handlers/l3nm_openconfig/ConfigRules.py
@@ -187,8 +187,8 @@ def teardown_config_rules(
if service_settings is None: return []
if endpoint_settings is None: return []
- json_settings : Dict = {} if service_settings is None else service_settings.value
- json_endpoint_settings : Dict = {} if endpoint_settings is None else endpoint_settings.value
+ json_settings : Dict = service_settings.value
+ json_endpoint_settings : Dict = endpoint_settings.value
service_short_uuid = service_uuid.split('-')[-1]
network_instance_name = '{:s}-NetInst'.format(service_short_uuid)
diff --git a/src/service/service/service_handlers/microwave/MicrowaveServiceHandler.py b/src/service/service/service_handlers/microwave/MicrowaveServiceHandler.py
index ee64d2fa4ff0110aea9ee4beee97fa83915ab57d..40c87eeee2c8dd1ddd5a39162f8ff7f117344e3b 100644
--- a/src/service/service/service_handlers/microwave/MicrowaveServiceHandler.py
+++ b/src/service/service/service_handlers/microwave/MicrowaveServiceHandler.py
@@ -61,7 +61,7 @@ class MicrowaveServiceHandler(_ServiceHandler):
device_uuid_dst, endpoint_uuid_dst = get_device_endpoint_uuids(endpoints[1])
if device_uuid_src != device_uuid_dst:
- raise Exception('Diferent Src-Dst devices not supported by now')
+ raise Exception('Different Src-Dst devices not supported by now')
device_uuid = device_uuid_src
device_obj = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid)))
@@ -106,7 +106,7 @@ class MicrowaveServiceHandler(_ServiceHandler):
device_uuid_dst, _ = get_device_endpoint_uuids(endpoints[1])
if device_uuid_src != device_uuid_dst:
- raise Exception('Diferent Src-Dst devices not supported by now')
+ raise Exception('Different Src-Dst devices not supported by now')
device_uuid = device_uuid_src
device_obj = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid)))
diff --git a/src/service/service/service_handlers/tapi_tapi/TapiServiceHandler.py b/src/service/service/service_handlers/tapi_tapi/TapiServiceHandler.py
index 8abd12b2a24c49a6c5e50cebe7a2d65dc7ce4eb1..af7d4bc949fc98f057ade66b58d8b9b38e0707ed 100644
--- a/src/service/service/service_handlers/tapi_tapi/TapiServiceHandler.py
+++ b/src/service/service/service_handlers/tapi_tapi/TapiServiceHandler.py
@@ -19,7 +19,7 @@ from common.proto.context_pb2 import ConfigRule, DeviceId, Service
from common.tools.object_factory.ConfigRule import json_config_rule_delete, json_config_rule_set
from common.tools.object_factory.Device import json_device_id
from common.type_checkers.Checkers import chk_type
-from service.service.service_handler_api.Tools import get_device_endpoint_uuids, get_endpoint_matching
+from service.service.service_handler_api.Tools import get_device_endpoint_uuids
from service.service.service_handler_api._ServiceHandler import _ServiceHandler
from service.service.service_handler_api.SettingsHandler import SettingsHandler
from service.service.task_scheduler.TaskExecutor import TaskExecutor
@@ -42,7 +42,7 @@ class TapiServiceHandler(_ServiceHandler):
) -> List[Union[bool, Exception]]:
chk_type('endpoints', endpoints, list)
- if len(endpoints) != 2: return []
+ if len(endpoints) < 2: return []
service_uuid = self.__service.service_id.service_uuid.uuid
settings = self.__settings_handler.get('/settings')
@@ -55,30 +55,33 @@ class TapiServiceHandler(_ServiceHandler):
results = []
try:
- device_uuid_src, endpoint_uuid_src = get_device_endpoint_uuids(endpoints[0])
- device_uuid_dst, endpoint_uuid_dst = get_device_endpoint_uuids(endpoints[1])
+ src_device_uuid, src_endpoint_uuid = get_device_endpoint_uuids(endpoints[0])
+ src_device = self.__task_executor.get_device(DeviceId(**json_device_id(src_device_uuid)))
+ src_controller = self.__task_executor.get_device_controller(src_device)
+ if src_controller is None: src_controller = src_device
- if device_uuid_src != device_uuid_dst:
- raise Exception('Diferent Src-Dst devices not supported by now')
- device_uuid = device_uuid_src
+ dst_device_uuid, dst_endpoint_uuid = get_device_endpoint_uuids(endpoints[-1])
+ dst_device = self.__task_executor.get_device(DeviceId(**json_device_id(dst_device_uuid)))
+ dst_controller = self.__task_executor.get_device_controller(dst_device)
+ if dst_controller is None: dst_controller = dst_device
- device_obj = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid)))
- endpoint_name_src = get_endpoint_matching(device_obj, endpoint_uuid_src).name
- endpoint_name_dst = get_endpoint_matching(device_obj, endpoint_uuid_dst).name
+ if src_controller.device_id.device_uuid.uuid != dst_controller.device_id.device_uuid.uuid:
+ raise Exception('Different Src-Dst devices not supported by now')
+ controller = src_controller
json_config_rule = json_config_rule_set('/services/service[{:s}]'.format(service_uuid), {
'uuid' : service_uuid,
- 'input_sip' : endpoint_name_src,
- 'output_sip' : endpoint_name_dst,
+ 'input_sip_uuid' : src_endpoint_uuid,
+ 'output_sip_uuid' : dst_endpoint_uuid,
'capacity_unit' : capacity_unit,
'capacity_value' : capacity_value,
'layer_protocol_name' : layer_proto_name,
'layer_protocol_qualifier': layer_proto_qual,
'direction' : direction,
})
- del device_obj.device_config.config_rules[:]
- device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
- self.__task_executor.configure_device(device_obj)
+ del controller.device_config.config_rules[:]
+ controller.device_config.config_rules.append(ConfigRule(**json_config_rule))
+ self.__task_executor.configure_device(controller)
results.append(True)
except Exception as e: # pylint: disable=broad-except
LOGGER.exception('Unable to SetEndpoint for Service({:s})'.format(str(service_uuid)))
@@ -92,27 +95,32 @@ class TapiServiceHandler(_ServiceHandler):
) -> List[Union[bool, Exception]]:
chk_type('endpoints', endpoints, list)
- if len(endpoints) != 2: return []
+ if len(endpoints) < 2: return []
service_uuid = self.__service.service_id.service_uuid.uuid
results = []
try:
- device_uuid_src, _ = get_device_endpoint_uuids(endpoints[0])
- device_uuid_dst, _ = get_device_endpoint_uuids(endpoints[1])
+ src_device_uuid, _ = get_device_endpoint_uuids(endpoints[0])
+ src_device = self.__task_executor.get_device(DeviceId(**json_device_id(src_device_uuid)))
+ src_controller = self.__task_executor.get_device_controller(src_device)
+ if src_controller is None: src_controller = src_device
- if device_uuid_src != device_uuid_dst:
- raise Exception('Diferent Src-Dst devices not supported by now')
- device_uuid = device_uuid_src
+ dst_device_uuid, _ = get_device_endpoint_uuids(endpoints[1])
+ dst_device = self.__task_executor.get_device(DeviceId(**json_device_id(dst_device_uuid)))
+ dst_controller = self.__task_executor.get_device_controller(dst_device)
+ if dst_controller is None: dst_controller = dst_device
- device_obj = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid)))
+ if src_controller.device_id.device_uuid.uuid != dst_controller.device_id.device_uuid.uuid:
+ raise Exception('Different Src-Dst devices not supported by now')
+ controller = src_controller
json_config_rule = json_config_rule_delete('/services/service[{:s}]'.format(service_uuid), {
'uuid': service_uuid
})
- del device_obj.device_config.config_rules[:]
- device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule))
- self.__task_executor.configure_device(device_obj)
+ del controller.device_config.config_rules[:]
+ controller.device_config.config_rules.append(ConfigRule(**json_config_rule))
+ self.__task_executor.configure_device(controller)
results.append(True)
except Exception as e: # pylint: disable=broad-except
LOGGER.exception('Unable to DeleteEndpoint for Service({:s})'.format(str(service_uuid)))
diff --git a/src/service/service/service_handlers/tapi_xr/TapiXrServiceHandler.py b/src/service/service/service_handlers/tapi_xr/TapiXrServiceHandler.py
new file mode 100644
index 0000000000000000000000000000000000000000..a1e1b8a6fff9436d6cdff13b95b0ecd43f6fa661
--- /dev/null
+++ b/src/service/service/service_handlers/tapi_xr/TapiXrServiceHandler.py
@@ -0,0 +1,176 @@
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import json, logging
+from typing import Any, Dict, List, Optional, Tuple, Union
+from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method
+from common.proto.context_pb2 import ConfigRule, DeviceId, Service
+from common.tools.object_factory.ConfigRule import json_config_rule_delete, json_config_rule_set
+from common.tools.object_factory.Device import json_device_id
+from common.type_checkers.Checkers import chk_type
+from service.service.service_handler_api.Tools import get_device_endpoint_uuids, get_endpoint_matching
+from service.service.service_handler_api._ServiceHandler import _ServiceHandler
+from service.service.service_handler_api.SettingsHandler import SettingsHandler
+from service.service.task_scheduler.TaskExecutor import TaskExecutor
+
+LOGGER = logging.getLogger(__name__)
+
+METRICS_POOL = MetricsPool('Service', 'Handler', labels={'handler': 'tapi_xr'})
+
+class TapiXrServiceHandler(_ServiceHandler):
+ def __init__( # pylint: disable=super-init-not-called
+ self, service : Service, task_executor : TaskExecutor, **settings
+ ) -> None:
+ self.__service = service
+ self.__task_executor = task_executor
+ self.__settings_handler = SettingsHandler(service.service_config, **settings)
+
+ @metered_subclass_method(METRICS_POOL)
+ def SetEndpoint(
+ self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None
+ ) -> List[Union[bool, Exception]]:
+
+ chk_type('endpoints', endpoints, list)
+ if len(endpoints) != 4: return []
+
+ service_uuid = self.__service.service_id.service_uuid.uuid
+ settings = self.__settings_handler.get('/settings')
+ json_settings : Dict = {} if settings is None else settings.value
+ capacity_value = json_settings.get('capacity_value', 50.0)
+ capacity_unit = json_settings.get('capacity_unit', 'GHz')
+
+ results = []
+ try:
+ src_device_uuid, src_endpoint_uuid = get_device_endpoint_uuids(endpoints[1])
+ src_device = self.__task_executor.get_device(DeviceId(**json_device_id(src_device_uuid)))
+ src_endpoint = get_endpoint_matching(src_device, src_endpoint_uuid)
+ src_controller = self.__task_executor.get_device_controller(src_device)
+ if src_controller is None: src_controller = src_device
+
+ dst_device_uuid, dst_endpoint_uuid = get_device_endpoint_uuids(endpoints[2])
+ dst_device = self.__task_executor.get_device(DeviceId(**json_device_id(dst_device_uuid)))
+ dst_endpoint = get_endpoint_matching(dst_device, dst_endpoint_uuid)
+ dst_controller = self.__task_executor.get_device_controller(dst_device)
+ if dst_controller is None: dst_controller = dst_device
+
+ if src_controller.device_id.device_uuid.uuid != dst_controller.device_id.device_uuid.uuid:
+ raise Exception('Different Src-Dst devices not supported by now')
+ controller = src_controller
+
+ json_config_rule = json_config_rule_set('/services/service[{:s}]'.format(service_uuid), {
+ 'uuid' : service_uuid,
+ 'input_sip_name' : '|'.join([src_device.name, src_endpoint.name]),
+ 'output_sip_name': '|'.join([dst_device.name, dst_endpoint.name]),
+ 'capacity_unit' : capacity_unit,
+ 'capacity_value' : capacity_value,
+ })
+
+ del controller.device_config.config_rules[:]
+ controller.device_config.config_rules.append(ConfigRule(**json_config_rule))
+ self.__task_executor.configure_device(controller)
+ results.append(True)
+ except Exception as e: # pylint: disable=broad-except
+ LOGGER.exception('Unable to SetEndpoint for Service({:s})'.format(str(service_uuid)))
+ results.append(e)
+
+ return results
+
+ @metered_subclass_method(METRICS_POOL)
+ def DeleteEndpoint(
+ self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None
+ ) -> List[Union[bool, Exception]]:
+
+ chk_type('endpoints', endpoints, list)
+ if len(endpoints) < 2: return []
+
+ service_uuid = self.__service.service_id.service_uuid.uuid
+
+ results = []
+ try:
+ src_device_uuid, _ = get_device_endpoint_uuids(endpoints[0])
+ src_device = self.__task_executor.get_device(DeviceId(**json_device_id(src_device_uuid)))
+ src_controller = self.__task_executor.get_device_controller(src_device)
+ if src_controller is None: src_controller = src_device
+
+ dst_device_uuid, _ = get_device_endpoint_uuids(endpoints[1])
+ dst_device = self.__task_executor.get_device(DeviceId(**json_device_id(dst_device_uuid)))
+ dst_controller = self.__task_executor.get_device_controller(dst_device)
+ if dst_controller is None: dst_controller = dst_device
+
+ if src_controller.device_id.device_uuid.uuid != dst_controller.device_id.device_uuid.uuid:
+ raise Exception('Different Src-Dst devices not supported by now')
+ controller = src_controller
+
+ json_config_rule = json_config_rule_delete('/services/service[{:s}]'.format(service_uuid), {
+ 'uuid': service_uuid
+ })
+ del controller.device_config.config_rules[:]
+ controller.device_config.config_rules.append(ConfigRule(**json_config_rule))
+ self.__task_executor.configure_device(controller)
+ results.append(True)
+ except Exception as e: # pylint: disable=broad-except
+ LOGGER.exception('Unable to DeleteEndpoint for Service({:s})'.format(str(service_uuid)))
+ results.append(e)
+
+ return results
+
+ @metered_subclass_method(METRICS_POOL)
+ def SetConstraint(self, constraints : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+ chk_type('constraints', constraints, list)
+ if len(constraints) == 0: return []
+
+ msg = '[SetConstraint] Method not implemented. Constraints({:s}) are being ignored.'
+ LOGGER.warning(msg.format(str(constraints)))
+ return [True for _ in range(len(constraints))]
+
+ @metered_subclass_method(METRICS_POOL)
+ def DeleteConstraint(self, constraints : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+ chk_type('constraints', constraints, list)
+ if len(constraints) == 0: return []
+
+ msg = '[DeleteConstraint] Method not implemented. Constraints({:s}) are being ignored.'
+ LOGGER.warning(msg.format(str(constraints)))
+ return [True for _ in range(len(constraints))]
+
+ @metered_subclass_method(METRICS_POOL)
+ def SetConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+ chk_type('resources', resources, list)
+ if len(resources) == 0: return []
+
+ results = []
+ for resource in resources:
+ try:
+ resource_value = json.loads(resource[1])
+ self.__settings_handler.set(resource[0], resource_value)
+ results.append(True)
+ except Exception as e: # pylint: disable=broad-except
+ LOGGER.exception('Unable to SetConfig({:s})'.format(str(resource)))
+ results.append(e)
+
+ return results
+
+ @metered_subclass_method(METRICS_POOL)
+ def DeleteConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]:
+ chk_type('resources', resources, list)
+ if len(resources) == 0: return []
+
+ results = []
+ for resource in resources:
+ try:
+ self.__settings_handler.delete(resource[0])
+ except Exception as e: # pylint: disable=broad-except
+ LOGGER.exception('Unable to DeleteConfig({:s})'.format(str(resource)))
+ results.append(e)
+
+ return results
diff --git a/src/service/service/service_handlers/tapi_xr/__init__.py b/src/service/service/service_handlers/tapi_xr/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..1549d9811aa5d1c193a44ad45d0d7773236c0612
--- /dev/null
+++ b/src/service/service/service_handlers/tapi_xr/__init__.py
@@ -0,0 +1,14 @@
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
diff --git a/src/service/service/task_scheduler/TaskExecutor.py b/src/service/service/task_scheduler/TaskExecutor.py
index 932c56e2b1934e12e7849a60c22d3ca1be7f8093..3d157e3145d4b195c0251a4ab79f710a38726569 100644
--- a/src/service/service/task_scheduler/TaskExecutor.py
+++ b/src/service/service/task_scheduler/TaskExecutor.py
@@ -12,10 +12,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import json
from enum import Enum
from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from common.method_wrappers.ServiceExceptions import NotFoundException
from common.proto.context_pb2 import Connection, ConnectionId, Device, DeviceId, Service, ServiceId
+from common.tools.object_factory.Device import json_device_id
from context.client.ContextClient import ContextClient
from device.client.DeviceClient import DeviceClient
from service.service.service_handler_api.ServiceHandlerFactory import ServiceHandlerFactory, get_service_handler_class
@@ -103,13 +105,38 @@ class TaskExecutor:
self._device_client.ConfigureDevice(device)
self._store_grpc_object(CacheableObjectType.DEVICE, device_key, device)
- def get_devices_from_connection(self, connection : Connection) -> Dict[str, Device]:
+ def get_device_controller(self, device : Device) -> Optional[Device]:
+ json_controller = None
+ for config_rule in device.device_config.config_rules:
+ if config_rule.WhichOneof('config_rule') != 'custom': continue
+ if config_rule.custom.resource_key != '_controller': continue
+ json_controller = json.loads(config_rule.custom.resource_value)
+ break
+
+ if json_controller is None: return None
+
+ controller_uuid = json_controller['uuid']
+ controller = self.get_device(DeviceId(**json_device_id(controller_uuid)))
+ controller_uuid = controller.device_id.device_uuid.uuid
+ if controller is None: raise Exception('Device({:s}) not found'.format(str(controller_uuid)))
+ return controller
+
+ def get_devices_from_connection(
+ self, connection : Connection, exclude_managed_by_controller : bool = False
+ ) -> Dict[str, Device]:
devices = dict()
for endpoint_id in connection.path_hops_endpoint_ids:
device = self.get_device(endpoint_id.device_id)
device_uuid = endpoint_id.device_id.device_uuid.uuid
if device is None: raise Exception('Device({:s}) not found'.format(str(device_uuid)))
- devices[device_uuid] = device
+
+ controller = self.get_device_controller(device)
+ if controller is None:
+ devices[device_uuid] = device
+ else:
+ if not exclude_managed_by_controller:
+ devices[device_uuid] = device
+ devices[controller.device_id.device_uuid.uuid] = controller
return devices
# ----- Service-related methods ------------------------------------------------------------------------------------
@@ -139,6 +166,6 @@ class TaskExecutor:
def get_service_handler(
self, connection : Connection, service : Service, **service_handler_settings
) -> '_ServiceHandler':
- connection_devices = self.get_devices_from_connection(connection)
+ connection_devices = self.get_devices_from_connection(connection, exclude_managed_by_controller=True)
service_handler_class = get_service_handler_class(self._service_handler_factory, service, connection_devices)
return service_handler_class(service, self, **service_handler_settings)
diff --git a/src/service/service/task_scheduler/tasks/Task_ConnectionConfigure.py b/src/service/service/task_scheduler/tasks/Task_ConnectionConfigure.py
index 5a47005b304836050dd8c0882214dd9cebd5d8b5..4367ffdee4d6d5b9edfc9fd30d0d6b6f48da8a75 100644
--- a/src/service/service/task_scheduler/tasks/Task_ConnectionConfigure.py
+++ b/src/service/service/task_scheduler/tasks/Task_ConnectionConfigure.py
@@ -32,7 +32,7 @@ class Task_ConnectionConfigure(_Task):
def connection_id(self) -> ConnectionId: return self._connection_id
@staticmethod
- def build_key(connection_id : ConnectionId) -> str:
+ def build_key(connection_id : ConnectionId) -> str: # pylint: disable=arguments-differ
str_connection_id = get_connection_key(connection_id)
return KEY_TEMPLATE.format(connection_id=str_connection_id)
diff --git a/src/service/service/task_scheduler/tasks/Task_ConnectionDeconfigure.py b/src/service/service/task_scheduler/tasks/Task_ConnectionDeconfigure.py
index 5736054febd2fb9e8a36b5a2235ca3f412e0e174..70f41566ef5e69605a527cc0392b77acb866ec2c 100644
--- a/src/service/service/task_scheduler/tasks/Task_ConnectionDeconfigure.py
+++ b/src/service/service/task_scheduler/tasks/Task_ConnectionDeconfigure.py
@@ -32,7 +32,7 @@ class Task_ConnectionDeconfigure(_Task):
def connection_id(self) -> ConnectionId: return self._connection_id
@staticmethod
- def build_key(connection_id : ConnectionId) -> str:
+ def build_key(connection_id : ConnectionId) -> str: # pylint: disable=arguments-differ
str_connection_id = get_connection_key(connection_id)
return KEY_TEMPLATE.format(connection_id=str_connection_id)
diff --git a/src/service/service/task_scheduler/tasks/Task_ServiceDelete.py b/src/service/service/task_scheduler/tasks/Task_ServiceDelete.py
index 6a4e11b540cd9b85028d92cf86899ee098056c36..0f021b6ca65da1c6b5e44d8577bf9dd6875eb17a 100644
--- a/src/service/service/task_scheduler/tasks/Task_ServiceDelete.py
+++ b/src/service/service/task_scheduler/tasks/Task_ServiceDelete.py
@@ -28,7 +28,7 @@ class Task_ServiceDelete(_Task):
def service_id(self) -> ServiceId: return self._service_id
@staticmethod
- def build_key(service_id : ServiceId) -> str:
+ def build_key(service_id : ServiceId) -> str: # pylint: disable=arguments-differ
str_service_id = get_service_key(service_id)
return KEY_TEMPLATE.format(service_id=str_service_id)
diff --git a/src/service/service/task_scheduler/tasks/Task_ServiceSetStatus.py b/src/service/service/task_scheduler/tasks/Task_ServiceSetStatus.py
index 815cb33c3d540755704153b661e889fc2660d268..d5360fe85eae68085298406fc0ed19dd105f187e 100644
--- a/src/service/service/task_scheduler/tasks/Task_ServiceSetStatus.py
+++ b/src/service/service/task_scheduler/tasks/Task_ServiceSetStatus.py
@@ -32,7 +32,7 @@ class Task_ServiceSetStatus(_Task):
def new_status(self) -> ServiceStatusEnum: return self._new_status
@staticmethod
- def build_key(service_id : ServiceId, new_status : ServiceStatusEnum) -> str:
+ def build_key(service_id : ServiceId, new_status : ServiceStatusEnum) -> str: # pylint: disable=arguments-differ
str_service_id = get_service_key(service_id)
str_new_status = ServiceStatusEnum.Name(new_status)
return KEY_TEMPLATE.format(service_id=str_service_id, new_status=str_new_status)
diff --git a/src/slice/service/slice_grouper/SliceGrouper.py b/src/slice/service/slice_grouper/SliceGrouper.py
index 735d028993eb11e83138caebde1e32ebc830093f..2f1a791819f6a8d0951e9e93ca22d071ea66c1f7 100644
--- a/src/slice/service/slice_grouper/SliceGrouper.py
+++ b/src/slice/service/slice_grouper/SliceGrouper.py
@@ -29,6 +29,7 @@ class SliceGrouper:
def __init__(self) -> None:
self._lock = threading.Lock()
self._is_enabled = is_slice_grouping_enabled()
+ LOGGER.info('Slice Grouping: {:s}'.format('ENABLED' if self._is_enabled else 'DISABLED'))
if not self._is_enabled: return
metrics_exporter = MetricsExporter()
diff --git a/src/tests/ofc23/.gitignore b/src/tests/ofc23/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..0a3f4400d5c88b1af32c7667d69d2fdc12d5424e
--- /dev/null
+++ b/src/tests/ofc23/.gitignore
@@ -0,0 +1,2 @@
+# Add here your files containing confidential testbed details such as IP addresses, ports, usernames, passwords, etc.
+descriptors_real.json
diff --git a/src/tests/ofc23/MultiIngressController.txt b/src/tests/ofc23/MultiIngressController.txt
new file mode 100644
index 0000000000000000000000000000000000000000..190e6df7425983db43d8b1888f29861ec9056ed6
--- /dev/null
+++ b/src/tests/ofc23/MultiIngressController.txt
@@ -0,0 +1,23 @@
+# Ref: https://kubernetes.github.io/ingress-nginx/user-guide/multiple-ingress/
+# Ref: https://fabianlee.org/2021/07/29/kubernetes-microk8s-with-multiple-metallb-endpoints-and-nginx-ingress-controllers/
+
+# Check node limits
+kubectl describe nodes
+
+# Create secondary ingress controllers
+kubectl apply -f ofc23/nginx-ingress-controller-parent.yaml
+kubectl apply -f ofc23/nginx-ingress-controller-child.yaml
+
+# Delete secondary ingress controllers
+kubectl delete -f ofc23/nginx-ingress-controller-parent.yaml
+kubectl delete -f ofc23/nginx-ingress-controller-child.yaml
+
+source ofc23/deploy_specs_parent.sh
+./deploy/all.sh
+
+source ofc23/deploy_specs_child.sh
+./deploy/all.sh
+
+# Manually deploy ingresses for instances
+kubectl --namespace tfs-parent apply -f ofc23/tfs-ingress-parent.yaml
+kubectl --namespace tfs-child apply -f ofc23/tfs-ingress-child.yaml
diff --git a/src/tests/ofc23/__init__.py b/src/tests/ofc23/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..1549d9811aa5d1c193a44ad45d0d7773236c0612
--- /dev/null
+++ b/src/tests/ofc23/__init__.py
@@ -0,0 +1,14 @@
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
diff --git a/src/tests/ofc23/delete_hierar.sh b/src/tests/ofc23/delete_hierar.sh
new file mode 100755
index 0000000000000000000000000000000000000000..4a03dad1cc29cff72347f68bc7b1a082924a9211
--- /dev/null
+++ b/src/tests/ofc23/delete_hierar.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+# Delete old namespaces
+kubectl delete namespace tfs-parent tfs-child
+
+# Delete secondary ingress controllers
+kubectl delete -f ofc23/nginx-ingress-controller-parent.yaml
+kubectl delete -f ofc23/nginx-ingress-controller-child.yaml
diff --git a/src/tests/ofc23/delete_sligrp.sh b/src/tests/ofc23/delete_sligrp.sh
new file mode 100755
index 0000000000000000000000000000000000000000..cce0bd53febc4765f9d455619f49ea4de8dfe870
--- /dev/null
+++ b/src/tests/ofc23/delete_sligrp.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+# Delete old namespaces
+kubectl delete namespace tfs
diff --git a/src/tests/ofc23/deploy_child.sh b/src/tests/ofc23/deploy_child.sh
new file mode 100755
index 0000000000000000000000000000000000000000..9b05ed88739114bf9029d8afaf491d7fec726bff
--- /dev/null
+++ b/src/tests/ofc23/deploy_child.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+# Delete old namespaces
+kubectl delete namespace tfs-child
+
+# Delete secondary ingress controllers
+kubectl delete -f ofc23/nginx-ingress-controller-child.yaml
+
+# Create secondary ingress controllers
+kubectl apply -f ofc23/nginx-ingress-controller-child.yaml
+
+# Deploy TFS for Child
+source ofc23/deploy_specs_child.sh
+./deploy/all.sh
+mv tfs_runtime_env_vars.sh tfs_runtime_env_vars_child.sh
diff --git a/src/tests/ofc23/deploy_hierar.sh b/src/tests/ofc23/deploy_hierar.sh
new file mode 100755
index 0000000000000000000000000000000000000000..4874688ad7561156b1b5fc4c80b72a9745feb6a0
--- /dev/null
+++ b/src/tests/ofc23/deploy_hierar.sh
@@ -0,0 +1,36 @@
+#!/bin/bash
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+# Delete old namespaces
+kubectl delete namespace tfs-parent tfs-child
+
+# Delete secondary ingress controllers
+kubectl delete -f ofc23/nginx-ingress-controller-parent.yaml
+kubectl delete -f ofc23/nginx-ingress-controller-child.yaml
+
+# Create secondary ingress controllers
+kubectl apply -f ofc23/nginx-ingress-controller-parent.yaml
+kubectl apply -f ofc23/nginx-ingress-controller-child.yaml
+
+# Deploy TFS for Parent
+source ofc23/deploy_specs_parent.sh
+./deploy/all.sh
+mv tfs_runtime_env_vars.sh tfs_runtime_env_vars_parent.sh
+
+# Deploy TFS for Child
+source ofc23/deploy_specs_child.sh
+./deploy/all.sh
+mv tfs_runtime_env_vars.sh tfs_runtime_env_vars_child.sh
diff --git a/src/tests/ofc23/deploy_parent.sh b/src/tests/ofc23/deploy_parent.sh
new file mode 100755
index 0000000000000000000000000000000000000000..ac4a2954213cf577efe1b1a8e499635d80ea3548
--- /dev/null
+++ b/src/tests/ofc23/deploy_parent.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+# Delete old namespaces
+kubectl delete namespace tfs-parent
+
+# Delete secondary ingress controllers
+kubectl delete -f ofc23/nginx-ingress-controller-parent.yaml
+
+# Create secondary ingress controllers
+kubectl apply -f ofc23/nginx-ingress-controller-parent.yaml
+
+# Deploy TFS for Parent
+source ofc23/deploy_specs_parent.sh
+./deploy/all.sh
+mv tfs_runtime_env_vars.sh tfs_runtime_env_vars_parent.sh
diff --git a/src/tests/ofc23/deploy_sligrp.sh b/src/tests/ofc23/deploy_sligrp.sh
new file mode 100755
index 0000000000000000000000000000000000000000..62a9df5cf006af856f168add4058d63eaa905784
--- /dev/null
+++ b/src/tests/ofc23/deploy_sligrp.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+# Delete old namespaces
+kubectl delete namespace tfs-sligrp
+
+# Deploy TFS for Slice Goruping
+source ofc23/deploy_specs_sligrp.sh
+./deploy/all.sh
+mv tfs_runtime_env_vars.sh tfs_runtime_env_vars_sligrp.sh
diff --git a/src/tests/ofc23/deploy_specs_child.sh b/src/tests/ofc23/deploy_specs_child.sh
new file mode 100755
index 0000000000000000000000000000000000000000..4d2b3502294925d82f675263fd6bddea62ec181a
--- /dev/null
+++ b/src/tests/ofc23/deploy_specs_child.sh
@@ -0,0 +1,118 @@
+#!/bin/bash
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+# ----- TeraFlowSDN ------------------------------------------------------------
+
+# Set the URL of the internal MicroK8s Docker registry where the images will be uploaded to.
+export TFS_REGISTRY_IMAGES="http://localhost:32000/tfs/"
+
+# Set the list of components, separated by spaces, you want to build images for, and deploy.
+#automation monitoring load_generator
+export TFS_COMPONENTS="context device pathcomp service slice compute webui"
+
+# Set the tag you want to use for your images.
+export TFS_IMAGE_TAG="dev"
+
+# Set the name of the Kubernetes namespace to deploy TFS to.
+export TFS_K8S_NAMESPACE="tfs-child"
+
+# Set additional manifest files to be applied after the deployment
+export TFS_EXTRA_MANIFESTS="ofc23/tfs-ingress-child.yaml"
+
+# Set the new Grafana admin password
+export TFS_GRAFANA_PASSWORD="admin123+"
+
+# Disable skip-build flag to rebuild the Docker images.
+export TFS_SKIP_BUILD="YES"
+
+
+# ----- CockroachDB ------------------------------------------------------------
+
+# Set the namespace where CockroackDB will be deployed.
+export CRDB_NAMESPACE="crdb"
+
+# Set the external port CockroackDB Postgre SQL interface will be exposed to.
+export CRDB_EXT_PORT_SQL="26257"
+
+# Set the external port CockroackDB HTTP Mgmt GUI interface will be exposed to.
+export CRDB_EXT_PORT_HTTP="8081"
+
+# Set the database username to be used by Context.
+export CRDB_USERNAME="tfs"
+
+# Set the database user's password to be used by Context.
+export CRDB_PASSWORD="tfs123"
+
+# Set the database name to be used by Context.
+export CRDB_DATABASE="tfs_child"
+
+# Set CockroachDB installation mode to 'single'. This option is convenient for development and testing.
+# See ./deploy/all.sh or ./deploy/crdb.sh for additional details
+export CRDB_DEPLOY_MODE="single"
+
+# Disable flag for dropping database, if it exists.
+export CRDB_DROP_DATABASE_IF_EXISTS="YES"
+
+# Disable flag for re-deploying CockroachDB from scratch.
+export CRDB_REDEPLOY=""
+
+
+# ----- NATS -------------------------------------------------------------------
+
+# Set the namespace where NATS will be deployed.
+export NATS_NAMESPACE="nats-child"
+
+# Set the external port NATS Client interface will be exposed to.
+export NATS_EXT_PORT_CLIENT="4224"
+
+# Set the external port NATS HTTP Mgmt GUI interface will be exposed to.
+export NATS_EXT_PORT_HTTP="8224"
+
+# Disable flag for re-deploying NATS from scratch.
+export NATS_REDEPLOY=""
+
+
+# ----- QuestDB ----------------------------------------------------------------
+
+# Set the namespace where QuestDB will be deployed.
+export QDB_NAMESPACE="qdb-child"
+
+# Set the external port QuestDB Postgre SQL interface will be exposed to.
+export QDB_EXT_PORT_SQL="8814"
+
+# Set the external port QuestDB Influx Line Protocol interface will be exposed to.
+export QDB_EXT_PORT_ILP="9012"
+
+# Set the external port QuestDB HTTP Mgmt GUI interface will be exposed to.
+export QDB_EXT_PORT_HTTP="9002"
+
+# Set the database username to be used for QuestDB.
+export QDB_USERNAME="admin"
+
+# Set the database user's password to be used for QuestDB.
+export QDB_PASSWORD="quest"
+
+# Set the table name to be used by Monitoring for KPIs.
+export QDB_TABLE_MONITORING_KPIS="tfs_monitoring_kpis"
+
+# Set the table name to be used by Slice for plotting groups.
+export QDB_TABLE_SLICE_GROUPS="tfs_slice_groups"
+
+# Disable flag for dropping tables if they exist.
+export QDB_DROP_TABLES_IF_EXIST="YES"
+
+# Disable flag for re-deploying QuestDB from scratch.
+export QDB_REDEPLOY=""
diff --git a/src/tests/ofc23/deploy_specs_parent.sh b/src/tests/ofc23/deploy_specs_parent.sh
new file mode 100755
index 0000000000000000000000000000000000000000..808f4e28734be71e6eb7fb2aced39211fd8e7f24
--- /dev/null
+++ b/src/tests/ofc23/deploy_specs_parent.sh
@@ -0,0 +1,118 @@
+#!/bin/bash
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+# ----- TeraFlowSDN ------------------------------------------------------------
+
+# Set the URL of the internal MicroK8s Docker registry where the images will be uploaded to.
+export TFS_REGISTRY_IMAGES="http://localhost:32000/tfs/"
+
+# Set the list of components, separated by spaces, you want to build images for, and deploy.
+#automation monitoring load_generator
+export TFS_COMPONENTS="context device pathcomp service slice compute webui"
+
+# Set the tag you want to use for your images.
+export TFS_IMAGE_TAG="dev"
+
+# Set the name of the Kubernetes namespace to deploy TFS to.
+export TFS_K8S_NAMESPACE="tfs-parent"
+
+# Set additional manifest files to be applied after the deployment
+export TFS_EXTRA_MANIFESTS="ofc23/tfs-ingress-parent.yaml"
+
+# Set the new Grafana admin password
+export TFS_GRAFANA_PASSWORD="admin123+"
+
+# Disable skip-build flag to rebuild the Docker images.
+export TFS_SKIP_BUILD=""
+
+
+# ----- CockroachDB ------------------------------------------------------------
+
+# Set the namespace where CockroackDB will be deployed.
+export CRDB_NAMESPACE="crdb"
+
+# Set the external port CockroackDB Postgre SQL interface will be exposed to.
+export CRDB_EXT_PORT_SQL="26257"
+
+# Set the external port CockroackDB HTTP Mgmt GUI interface will be exposed to.
+export CRDB_EXT_PORT_HTTP="8081"
+
+# Set the database username to be used by Context.
+export CRDB_USERNAME="tfs"
+
+# Set the database user's password to be used by Context.
+export CRDB_PASSWORD="tfs123"
+
+# Set the database name to be used by Context.
+export CRDB_DATABASE="tfs_parent"
+
+# Set CockroachDB installation mode to 'single'. This option is convenient for development and testing.
+# See ./deploy/all.sh or ./deploy/crdb.sh for additional details
+export CRDB_DEPLOY_MODE="single"
+
+# Disable flag for dropping database, if it exists.
+export CRDB_DROP_DATABASE_IF_EXISTS="YES"
+
+# Disable flag for re-deploying CockroachDB from scratch.
+export CRDB_REDEPLOY=""
+
+
+# ----- NATS -------------------------------------------------------------------
+
+# Set the namespace where NATS will be deployed.
+export NATS_NAMESPACE="nats-parent"
+
+# Set the external port NATS Client interface will be exposed to.
+export NATS_EXT_PORT_CLIENT="4223"
+
+# Set the external port NATS HTTP Mgmt GUI interface will be exposed to.
+export NATS_EXT_PORT_HTTP="8223"
+
+# Disable flag for re-deploying NATS from scratch.
+export NATS_REDEPLOY=""
+
+
+# ----- QuestDB ----------------------------------------------------------------
+
+# Set the namespace where QuestDB will be deployed.
+export QDB_NAMESPACE="qdb-parent"
+
+# Set the external port QuestDB Postgre SQL interface will be exposed to.
+export QDB_EXT_PORT_SQL="8813"
+
+# Set the external port QuestDB Influx Line Protocol interface will be exposed to.
+export QDB_EXT_PORT_ILP="9011"
+
+# Set the external port QuestDB HTTP Mgmt GUI interface will be exposed to.
+export QDB_EXT_PORT_HTTP="9001"
+
+# Set the database username to be used for QuestDB.
+export QDB_USERNAME="admin"
+
+# Set the database user's password to be used for QuestDB.
+export QDB_PASSWORD="quest"
+
+# Set the table name to be used by Monitoring for KPIs.
+export QDB_TABLE_MONITORING_KPIS="tfs_monitoring_kpis"
+
+# Set the table name to be used by Slice for plotting groups.
+export QDB_TABLE_SLICE_GROUPS="tfs_slice_groups"
+
+# Disable flag for dropping tables if they exist.
+export QDB_DROP_TABLES_IF_EXIST="YES"
+
+# Disable flag for re-deploying QuestDB from scratch.
+export QDB_REDEPLOY=""
diff --git a/src/tests/ofc23/deploy_specs_sligrp.sh b/src/tests/ofc23/deploy_specs_sligrp.sh
new file mode 100755
index 0000000000000000000000000000000000000000..90bea4567bd35d845abf943670f8aa33070dff57
--- /dev/null
+++ b/src/tests/ofc23/deploy_specs_sligrp.sh
@@ -0,0 +1,118 @@
+#!/bin/bash
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+# ----- TeraFlowSDN ------------------------------------------------------------
+
+# Set the URL of the internal MicroK8s Docker registry where the images will be uploaded to.
+export TFS_REGISTRY_IMAGES="http://localhost:32000/tfs/"
+
+# Set the list of components, separated by spaces, you want to build images for, and deploy.
+#automation monitoring load_generator
+export TFS_COMPONENTS="context device pathcomp service slice webui load_generator"
+
+# Set the tag you want to use for your images.
+export TFS_IMAGE_TAG="dev"
+
+# Set the name of the Kubernetes namespace to deploy TFS to.
+export TFS_K8S_NAMESPACE="tfs-sligrp"
+
+# Set additional manifest files to be applied after the deployment
+export TFS_EXTRA_MANIFESTS="manifests/nginx_ingress_http.yaml"
+
+# Set the new Grafana admin password
+export TFS_GRAFANA_PASSWORD="admin123+"
+
+# Disable skip-build flag to rebuild the Docker images.
+export TFS_SKIP_BUILD=""
+
+
+# ----- CockroachDB ------------------------------------------------------------
+
+# Set the namespace where CockroackDB will be deployed.
+export CRDB_NAMESPACE="crdb"
+
+# Set the external port CockroackDB Postgre SQL interface will be exposed to.
+export CRDB_EXT_PORT_SQL="26257"
+
+# Set the external port CockroackDB HTTP Mgmt GUI interface will be exposed to.
+export CRDB_EXT_PORT_HTTP="8081"
+
+# Set the database username to be used by Context.
+export CRDB_USERNAME="tfs"
+
+# Set the database user's password to be used by Context.
+export CRDB_PASSWORD="tfs123"
+
+# Set the database name to be used by Context.
+export CRDB_DATABASE="tfs_sligrp"
+
+# Set CockroachDB installation mode to 'single'. This option is convenient for development and testing.
+# See ./deploy/all.sh or ./deploy/crdb.sh for additional details
+export CRDB_DEPLOY_MODE="single"
+
+# Disable flag for dropping database, if it exists.
+export CRDB_DROP_DATABASE_IF_EXISTS="YES"
+
+# Disable flag for re-deploying CockroachDB from scratch.
+export CRDB_REDEPLOY=""
+
+
+# ----- NATS -------------------------------------------------------------------
+
+# Set the namespace where NATS will be deployed.
+export NATS_NAMESPACE="nats-sligrp"
+
+# Set the external port NATS Client interface will be exposed to.
+export NATS_EXT_PORT_CLIENT="4222"
+
+# Set the external port NATS HTTP Mgmt GUI interface will be exposed to.
+export NATS_EXT_PORT_HTTP="8222"
+
+# Disable flag for re-deploying NATS from scratch.
+export NATS_REDEPLOY=""
+
+
+# ----- QuestDB ----------------------------------------------------------------
+
+# Set the namespace where QuestDB will be deployed.
+export QDB_NAMESPACE="qdb-sligrp"
+
+# Set the external port QuestDB Postgre SQL interface will be exposed to.
+export QDB_EXT_PORT_SQL="8812"
+
+# Set the external port QuestDB Influx Line Protocol interface will be exposed to.
+export QDB_EXT_PORT_ILP="9010"
+
+# Set the external port QuestDB HTTP Mgmt GUI interface will be exposed to.
+export QDB_EXT_PORT_HTTP="9000"
+
+# Set the database username to be used for QuestDB.
+export QDB_USERNAME="admin"
+
+# Set the database user's password to be used for QuestDB.
+export QDB_PASSWORD="quest"
+
+# Set the table name to be used by Monitoring for KPIs.
+export QDB_TABLE_MONITORING_KPIS="tfs_monitoring_kpis"
+
+# Set the table name to be used by Slice for plotting groups.
+export QDB_TABLE_SLICE_GROUPS="tfs_slice_groups"
+
+# Disable flag for dropping tables if they exist.
+export QDB_DROP_TABLES_IF_EXIST="YES"
+
+# Disable flag for re-deploying QuestDB from scratch.
+export QDB_REDEPLOY=""
diff --git a/src/tests/ofc23/descriptors/adva-interfaces.txt b/src/tests/ofc23/descriptors/adva-interfaces.txt
new file mode 100644
index 0000000000000000000000000000000000000000..a634735058aa9490ddbd43e8e9a0752fbd4a6ee6
--- /dev/null
+++ b/src/tests/ofc23/descriptors/adva-interfaces.txt
@@ -0,0 +1,89 @@
+R199
+eth-1/0/1
+eth-1/0/10
+eth-1/0/11
+eth-1/0/12
+eth-1/0/13
+eth-1/0/14
+eth-1/0/15
+eth-1/0/16
+eth-1/0/17
+eth-1/0/18
+eth-1/0/19
+eth-1/0/2
+eth-1/0/20
+eth-1/0/21
+eth-1/0/22
+eth-1/0/23
+eth-1/0/24
+eth-1/0/25
+eth-1/0/26
+eth-1/0/27
+eth-1/0/28
+eth-1/0/29
+eth-1/0/3
+eth-1/0/30
+eth-1/0/4
+eth-1/0/5
+eth-1/0/6
+eth-1/0/7
+eth-1/0/8
+eth-1/0/9
+
+R155
+eth-1/0/1
+eth-1/0/10
+eth-1/0/11
+eth-1/0/12
+eth-1/0/13
+eth-1/0/14
+eth-1/0/15
+eth-1/0/16
+eth-1/0/17
+eth-1/0/18
+eth-1/0/19
+eth-1/0/2
+eth-1/0/20
+eth-1/0/21
+eth-1/0/22
+eth-1/0/23
+eth-1/0/24
+eth-1/0/25
+eth-1/0/26
+eth-1/0/27
+eth-1/0/3
+eth-1/0/4
+eth-1/0/5
+eth-1/0/6
+eth-1/0/7
+eth-1/0/8
+eth-1/0/9
+
+R149
+eth-1/0/1
+eth-1/0/10
+eth-1/0/11
+eth-1/0/12
+eth-1/0/13
+eth-1/0/14
+eth-1/0/15
+eth-1/0/16
+eth-1/0/17
+eth-1/0/18
+eth-1/0/19
+eth-1/0/2
+eth-1/0/20
+eth-1/0/21
+eth-1/0/22
+eth-1/0/23
+eth-1/0/24
+eth-1/0/25
+eth-1/0/26
+eth-1/0/27
+eth-1/0/3
+eth-1/0/4
+eth-1/0/5
+eth-1/0/6
+eth-1/0/7
+eth-1/0/8
+eth-1/0/9
diff --git a/src/tests/ofc23/descriptors/backup/dc-2-dc-service.json b/src/tests/ofc23/descriptors/backup/dc-2-dc-service.json
new file mode 100644
index 0000000000000000000000000000000000000000..3a83afa6de81f137204aecc5f0eca476aad71e61
--- /dev/null
+++ b/src/tests/ofc23/descriptors/backup/dc-2-dc-service.json
@@ -0,0 +1,37 @@
+{
+ "services": [
+ {
+ "service_id": {
+ "context_id": {"context_uuid": {"uuid": "admin"}}, "service_uuid": {"uuid": "dc-2-dc-svc"}
+ },
+ "service_type": 2,
+ "service_status": {"service_status": 1},
+ "service_endpoint_ids": [
+ {"device_id":{"device_uuid":{"uuid":"DC1"}},"endpoint_uuid":{"uuid":"int"}},
+ {"device_id":{"device_uuid":{"uuid":"DC2"}},"endpoint_uuid":{"uuid":"int"}}
+ ],
+ "service_constraints": [
+ {"sla_capacity": {"capacity_gbps": 10.0}},
+ {"sla_latency": {"e2e_latency_ms": 15.2}}
+ ],
+ "service_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "/settings", "resource_value": {
+ "address_families": ["IPV4"], "bgp_as": 65000, "bgp_route_target": "65000:123",
+ "mtu": 1512, "vlan_id": 111
+ }}},
+ {"action": 1, "custom": {"resource_key": "/device[R149]/endpoint[eth-1/0/22]/settings", "resource_value": {
+ "route_distinguisher": "65000:123", "router_id": "5.5.5.5",
+ "address_ip": "172.16.4.1", "address_prefix": 24, "sub_interface_index": 0, "vlan_id": 111
+ }}},
+ {"action": 1, "custom": {"resource_key": "/device[R155]/endpoint[eth-1/0/22]/settings", "resource_value": {
+ "route_distinguisher": "65000:123", "router_id": "5.5.5.1",
+ "address_ip": "172.16.2.1", "address_prefix": 24, "sub_interface_index": 0, "vlan_id": 111
+ }}},
+ {"action": 1, "custom": {"resource_key": "/device[R199]/endpoint[eth-1/0/21]/settings", "resource_value": {
+ "route_distinguisher": "65000:123", "router_id": "5.5.5.6",
+ "address_ip": "172.16.1.1", "address_prefix": 24, "sub_interface_index": 0, "vlan_id": 111
+ }}}
+ ]}
+ }
+ ]
+}
diff --git a/src/tests/ofc23/descriptors/backup/descriptor_child.json b/src/tests/ofc23/descriptors/backup/descriptor_child.json
new file mode 100644
index 0000000000000000000000000000000000000000..eea9571531cfbebfcc53dba0679d1bd1b6900b2f
--- /dev/null
+++ b/src/tests/ofc23/descriptors/backup/descriptor_child.json
@@ -0,0 +1,183 @@
+{
+ "contexts": [
+ {"context_id": {"context_uuid": {"uuid": "admin"}}}
+ ],
+ "topologies": [
+ {"topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}}
+ ],
+ "devices": [
+ {
+ "device_id": {"device_uuid": {"uuid": "R199"}}, "device_type": "packet-router", "device_drivers": [1],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.95.86.199"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "830"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "admin", "password": "admin",
+ "force_running": false, "hostkey_verify": false, "look_for_keys": false,
+ "allow_agent": false, "commit_per_rule": true, "device_params": {"name": "huaweiyang"},
+ "manager_params": {"timeout" : 120},
+ "endpoints": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/3"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/4"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/5"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/6"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/7"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/8"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/9"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/10"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/11"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/12"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/13"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/14"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/15"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/16"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/17"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/18"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/19"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/20"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/21"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/22"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/23"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/24"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/25"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/26"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/27"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/28"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/29"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/30"}
+ ]
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "R155"}}, "device_type": "packet-router", "device_drivers": [1],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.95.86.155"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "830"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "admin", "password": "admin",
+ "force_running": false, "hostkey_verify": false, "look_for_keys": false,
+ "allow_agent": false, "commit_per_rule": true, "device_params": {"name": "huaweiyang"},
+ "manager_params": {"timeout" : 120},
+ "endpoints": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/3"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/4"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/5"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/6"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/7"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/8"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/9"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/10"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/11"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/12"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/13"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/14"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/15"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/16"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/17"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/18"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/19"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/20"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/21"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/22"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/23"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/24"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/25"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/26"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/27"}
+ ]
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "R149"}}, "device_type": "packet-router", "device_drivers": [1],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.95.86.149"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "830"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "admin", "password": "admin",
+ "force_running": false, "hostkey_verify": false, "look_for_keys": false,
+ "allow_agent": false, "commit_per_rule": true, "device_params": {"name": "huaweiyang"},
+ "manager_params": {"timeout" : 120},
+ "endpoints": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/3"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/4"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/5"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/6"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/7"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/8"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/9"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/10"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/11"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/12"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/13"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/14"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/15"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/16"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/17"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/18"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/19"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/20"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/21"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/22"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/23"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/24"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/25"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/26"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth-1/0/27"}
+ ]
+ }}}
+ ]}
+ }
+ ],
+ "links": [
+ {
+ "link_id": {"link_uuid": {"uuid": "R199/eth-1/0/19==R155/eth-1/0/19"}},
+ "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/19"}},
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/19"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R155/eth-1/0/19==R199/eth-1/0/19"}},
+ "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/19"}},
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/19"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R199/eth-1/0/20==R149/eth-1/0/20"}},
+ "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}},
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R149/eth-1/0/20==R199/eth-1/0/20"}},
+ "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}},
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R149/eth-1/0/25==R155/eth-1/0/25"}},
+ "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}},
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R155/eth-1/0/25==R149/eth-1/0/25"}},
+ "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}},
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}}
+ ]
+ }
+ ]
+}
diff --git a/src/tests/ofc23/descriptors/backup/descriptor_parent.json b/src/tests/ofc23/descriptors/backup/descriptor_parent.json
new file mode 100644
index 0000000000000000000000000000000000000000..42b60e3cf09285955fbfbc567d977e60f78956be
--- /dev/null
+++ b/src/tests/ofc23/descriptors/backup/descriptor_parent.json
@@ -0,0 +1,258 @@
+{
+ "contexts": [
+ {"context_id": {"context_uuid": {"uuid": "admin"}}}
+ ],
+ "topologies": [
+ {"topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}}
+ ],
+ "devices": [
+ {
+ "device_id": {"device_uuid": {"uuid": "TFS-IP"}}, "device_type": "teraflowsdn", "device_drivers": [7],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8002"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "scheme": "http", "username": "admin", "password": "admin"
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "MW"}}, "device_type": "microwave-radio-system", "device_drivers": [4, 5],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8443"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "nms5ux", "password": "nms5ux", "timeout": 120, "scheme": "https",
+ "node_ids": ["192.168.27.139", "192.168.27.140"]
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "OLS"}}, "device_type": "open-line-system", "device_drivers": [2],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "cttc-ols.cttc-ols.svc.cluster.local"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "4900"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"timeout": 120}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "IPM"}}, "device_type": "xr-constellation", "device_drivers": [6],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8444"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "xr-user-1", "password": "xr-user-1", "hub_module_name": "OFC HUB 1",
+ "consistency-mode": "lifecycle", "import_topology": "devices"
+ }}}
+ ]}
+ },
+
+
+ {
+ "device_id": {"device_uuid": {"uuid": "DC1"}}, "device_type": "emu-datacenter", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "int"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "device_type": "emu-optical-splitter", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
+ {"sample_types": [], "type": "optical/internal", "uuid": "common"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf1"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf2"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf3"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf4"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "DC2"}}, "device_type": "emu-datacenter", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "int"}
+ ]}}}
+ ]}
+ }
+ ],
+ "links": [
+ {
+ "link_id": {"link_uuid": {"uuid": "DC1/eth1==R149/eth-1/0/22"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}},
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R149/eth-1/0/22==DC1/eth1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}},
+ {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "R149/eth-1/0/9==MW/192.168.27.140:5"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/9"}},
+ {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.140:5"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "MW/192.168.27.140:5==R149/eth-1/0/9"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.140:5"}},
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/9"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "MW/192.168.27.139:5==OFC HUB 1/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.139:5"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC HUB 1/1/1==MW/192.168.27.139:5"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.139:5"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC HUB 1/XR-T1==Optical-Splitter/common"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "common"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "Optical-Splitter/common==OFC HUB 1/XR-T1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "common"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "Optical-Splitter/leaf1==OLS/aade6001-f00b-5e2f-a357-6a0a9d3de870"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf1"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "aade6001-f00b-5e2f-a357-6a0a9d3de870"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/79516f5e-55a0-5671-977a-1f5cc934e700==Optical-Splitter/leaf1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "79516f5e-55a0-5671-977a-1f5cc934e700"}},
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "Optical-Splitter/leaf2==OLS/eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf2"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/30d9323e-b916-51ce-a9a8-cf88f62eb77f==Optical-Splitter/leaf2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "30d9323e-b916-51ce-a9a8-cf88f62eb77f"}},
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf2"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 1/XR-T1==OLS/0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/68ac012e-54d4-5846-b5dc-6ec356404f90==OFC LEAF 1/XR-T1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "68ac012e-54d4-5846-b5dc-6ec356404f90"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 2/XR-T1==OLS/50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/367b19b1-3172-54d8-bdd4-12d3ac5604f6==OFC LEAF 2/XR-T1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "367b19b1-3172-54d8-bdd4-12d3ac5604f6"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 1/1/1==R155/eth-1/0/25"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R155/eth-1/0/25==OFC LEAF 1/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 2/1/1==R199/eth-1/0/20"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R199/eth-1/0/20==OFC LEAF 2/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "R155/eth-1/0/22==DC2/eth1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}},
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "DC2/eth1==R155/eth-1/0/22"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}},
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "R199/eth-1/0/21==DC2/eth2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/21"}},
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth2"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "DC2/eth2==R199/eth-1/0/21"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth2"}},
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/21"}}
+ ]
+ }
+ ]
+}
diff --git a/src/tests/ofc23/descriptors/emulated/dc-2-dc-service.json b/src/tests/ofc23/descriptors/emulated/dc-2-dc-service.json
new file mode 100644
index 0000000000000000000000000000000000000000..7c3be015d0965d4bdaed8e225e79da072a7de6f3
--- /dev/null
+++ b/src/tests/ofc23/descriptors/emulated/dc-2-dc-service.json
@@ -0,0 +1,41 @@
+{
+ "services": [
+ {
+ "service_id": {
+ "context_id": {"context_uuid": {"uuid": "admin"}}, "service_uuid": {"uuid": "dc-2-dc-svc"}
+ },
+ "service_type": 2,
+ "service_status": {"service_status": 1},
+ "service_endpoint_ids": [
+ {"device_id":{"device_uuid":{"uuid":"DC1"}},"endpoint_uuid":{"uuid":"int"}},
+ {"device_id":{"device_uuid":{"uuid":"DC2"}},"endpoint_uuid":{"uuid":"int"}}
+ ],
+ "service_constraints": [
+ {"sla_capacity": {"capacity_gbps": 10.0}},
+ {"sla_latency": {"e2e_latency_ms": 15.2}}
+ ],
+ "service_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "/settings", "resource_value": {
+ "address_families": ["IPV4"], "bgp_as": 65000, "bgp_route_target": "65000:123",
+ "mtu": 1512, "vlan_id": 300
+ }}},
+ {"action": 1, "custom": {"resource_key": "/device[PE1]/endpoint[1/1]/settings", "resource_value": {
+ "route_distinguisher": "65000:123", "router_id": "10.0.0.1",
+ "address_ip": "3.3.1.1", "address_prefix": 24, "sub_interface_index": 1, "vlan_id": 300
+ }}},
+ {"action": 1, "custom": {"resource_key": "/device[PE2]/endpoint[1/1]/settings", "resource_value": {
+ "route_distinguisher": "65000:123", "router_id": "10.0.0.2",
+ "address_ip": "3.3.2.1", "address_prefix": 24, "sub_interface_index": 1, "vlan_id": 300
+ }}},
+ {"action": 1, "custom": {"resource_key": "/device[PE3]/endpoint[1/1]/settings", "resource_value": {
+ "route_distinguisher": "65000:123", "router_id": "10.0.0.3",
+ "address_ip": "3.3.3.1", "address_prefix": 24, "sub_interface_index": 1, "vlan_id": 300
+ }}},
+ {"action": 1, "custom": {"resource_key": "/device[PE4]/endpoint[1/1]/settings", "resource_value": {
+ "route_distinguisher": "65000:123", "router_id": "10.0.0.4",
+ "address_ip": "3.3.4.1", "address_prefix": 24, "sub_interface_index": 1, "vlan_id": 300
+ }}}
+ ]}
+ }
+ ]
+}
diff --git a/src/tests/ofc23/descriptors/emulated/descriptor_child.json b/src/tests/ofc23/descriptors/emulated/descriptor_child.json
new file mode 100644
index 0000000000000000000000000000000000000000..1dc6fd35531db1989b9b85c846b6fc8d0524f08f
--- /dev/null
+++ b/src/tests/ofc23/descriptors/emulated/descriptor_child.json
@@ -0,0 +1,149 @@
+{
+ "contexts": [
+ {"context_id": {"context_uuid": {"uuid": "admin"}}}
+ ],
+ "topologies": [
+ {"topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}}
+ ],
+ "devices": [
+ {
+ "device_id": {"device_uuid": {"uuid": "PE1"}}, "device_type": "emu-packet-router", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "1/1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "1/2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/3"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/4"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "PE2"}}, "device_type": "emu-packet-router", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "1/1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "1/2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/3"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/4"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "PE3"}}, "device_type": "emu-packet-router", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "1/1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "1/2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/3"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/4"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "PE4"}}, "device_type": "emu-packet-router", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "1/1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "1/2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/3"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "2/4"}
+ ]}}}
+ ]}
+ }
+ ],
+ "links": [
+
+ {
+ "link_id": {"link_uuid": {"uuid": "PE1/2/2==PE2/2/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "2/2"}},
+ {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "2/1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE1/2/3==PE3/2/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "2/3"}},
+ {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "2/1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE1/2/4==PE4/2/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "2/4"}},
+ {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "2/1"}}
+ ]
+ },
+
+ {
+ "link_id": {"link_uuid": {"uuid": "PE2/2/1==PE1/2/2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "2/1"}},
+ {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "2/2"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE2/2/3==PE3/2/2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "2/3"}},
+ {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "2/2"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE2/2/4==PE4/2/2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "2/4"}},
+ {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "2/2"}}
+ ]
+ },
+
+ {
+ "link_id": {"link_uuid": {"uuid": "PE3/2/1==PE1/2/3"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "2/1"}},
+ {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "2/3"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE3/2/2==PE2/2/3"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "2/2"}},
+ {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "2/3"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE4/2/2==PE2/2/4"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "2/2"}},
+ {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "2/4"}}
+ ]
+ },
+
+ {
+ "link_id": {"link_uuid": {"uuid": "PE4/2/1==PE1/2/4"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "2/1"}},
+ {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "2/4"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE4/2/2==PE2/2/4"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "2/2"}},
+ {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "2/4"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE4/2/3==PE3/2/4"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "2/3"}},
+ {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "2/4"}}
+ ]
+ }
+
+ ]
+}
diff --git a/src/tests/ofc23/descriptors/emulated/descriptor_parent.json b/src/tests/ofc23/descriptors/emulated/descriptor_parent.json
new file mode 100644
index 0000000000000000000000000000000000000000..1b1f5dbfd57b2e1543e86ba8d2633a0e944fced5
--- /dev/null
+++ b/src/tests/ofc23/descriptors/emulated/descriptor_parent.json
@@ -0,0 +1,258 @@
+{
+ "contexts": [
+ {"context_id": {"context_uuid": {"uuid": "admin"}}}
+ ],
+ "topologies": [
+ {"topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}}
+ ],
+ "devices": [
+ {
+ "device_id": {"device_uuid": {"uuid": "TFS-IP"}}, "device_type": "teraflowsdn", "device_drivers": [7],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8002"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "scheme": "http", "username": "admin", "password": "admin"
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "MW"}}, "device_type": "microwave-radio-system", "device_drivers": [4, 5],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8443"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "admin", "password": "admin", "timeout": 120, "scheme": "https",
+ "node_ids": ["192.168.27.139", "192.168.27.140"]
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "OLS"}}, "device_type": "open-line-system", "device_drivers": [2],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "cttc-ols.cttc-ols.svc.cluster.local"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "4900"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"timeout": 120}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "IPM"}}, "device_type": "xr-constellation", "device_drivers": [6],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8444"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "xr-user-1", "password": "xr-user-1", "hub_module_name": "OFC HUB 1",
+ "consistency-mode": "lifecycle", "import_topology": "devices"
+ }}}
+ ]}
+ },
+
+
+ {
+ "device_id": {"device_uuid": {"uuid": "DC1"}}, "device_type": "emu-datacenter", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "int"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "device_type": "emu-optical-splitter", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
+ {"sample_types": [], "type": "optical/internal", "uuid": "common"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf1"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf2"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf3"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf4"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "DC2"}}, "device_type": "emu-datacenter", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "int"}
+ ]}}}
+ ]}
+ }
+ ],
+ "links": [
+ {
+ "link_id": {"link_uuid": {"uuid": "DC1/eth1==R149/eth-1/0/22"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}},
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R149/eth-1/0/22==DC1/eth1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}},
+ {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "R149/eth-1/0/9==MW/192.168.27.140:5"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/9"}},
+ {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.140:5"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "MW/192.168.27.140:5==R149/eth-1/0/9"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.140:5"}},
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/9"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "MW/192.168.27.139:5==OFC HUB 1/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.139:5"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC HUB 1/1/1==MW/192.168.27.139:5"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.139:5"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC HUB 1/XR-T1==Optical-Splitter/common"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "common"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "Optical-Splitter/common==OFC HUB 1/XR-T1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "common"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "Optical-Splitter/leaf1==OLS/aade6001-f00b-5e2f-a357-6a0a9d3de870"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf1"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "aade6001-f00b-5e2f-a357-6a0a9d3de870"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/79516f5e-55a0-5671-977a-1f5cc934e700==Optical-Splitter/leaf1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "79516f5e-55a0-5671-977a-1f5cc934e700"}},
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "Optical-Splitter/leaf2==OLS/eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf2"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/30d9323e-b916-51ce-a9a8-cf88f62eb77f==Optical-Splitter/leaf2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "30d9323e-b916-51ce-a9a8-cf88f62eb77f"}},
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf2"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 1/XR-T1==OLS/0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/68ac012e-54d4-5846-b5dc-6ec356404f90==OFC LEAF 1/XR-T1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "68ac012e-54d4-5846-b5dc-6ec356404f90"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 2/XR-T1==OLS/50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/367b19b1-3172-54d8-bdd4-12d3ac5604f6==OFC LEAF 2/XR-T1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "367b19b1-3172-54d8-bdd4-12d3ac5604f6"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 1/1/1==R155/eth-1/0/25"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R155/eth-1/0/25==OFC LEAF 1/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 2/1/1==R199/eth-1/0/20"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R199/eth-1/0/20==OFC LEAF 2/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "R155/eth-1/0/22==DC2/eth1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}},
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "DC2/eth1==R155/eth-1/0/22"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}},
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "R199/eth-1/0/21==DC2/eth2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/21"}},
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth2"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "DC2/eth2==R199/eth-1/0/21"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth2"}},
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/21"}}
+ ]
+ }
+ ]
+}
diff --git a/src/tests/ofc23/descriptors/emulated/descriptor_parent_noxr.json b/src/tests/ofc23/descriptors/emulated/descriptor_parent_noxr.json
new file mode 100644
index 0000000000000000000000000000000000000000..c4a6646ede081fc2f6ee449d7771de3dbcbd77ec
--- /dev/null
+++ b/src/tests/ofc23/descriptors/emulated/descriptor_parent_noxr.json
@@ -0,0 +1,332 @@
+{
+ "contexts": [
+ {"context_id": {"context_uuid": {"uuid": "admin"}}}
+ ],
+ "topologies": [
+ {"topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}}
+ ],
+ "devices": [
+ {
+ "device_id": {"device_uuid": {"uuid": "TFS-IP"}}, "device_type": "teraflowsdn", "device_drivers": [7],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8002"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "scheme": "http", "username": "admin", "password": "admin"
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "MW1-2"}}, "device_type": "microwave-radio-system", "device_drivers": [5],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8443"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "admin", "password": "admin", "timeout": 120, "scheme": "https",
+ "node_ids": ["172.18.0.1", "172.18.0.2"]
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "MW3-4"}}, "device_type": "microwave-radio-system", "device_drivers": [5],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8443"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "admin", "password": "admin", "timeout": 120, "scheme": "https",
+ "node_ids": ["172.18.0.3", "172.18.0.4"]
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "OLS"}}, "device_type": "open-line-system", "device_drivers": [2],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "cttc-ols.cttc-ols.svc.cluster.local"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "4900"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"timeout": 120}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "DC1"}}, "device_type": "emu-datacenter", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "int"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "R1"}}, "device_type": "emu-packet-router", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "1/1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "1/2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "1/3"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "device_type": "emu-optical-splitter", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
+ {"sample_types": [], "type": "optical/internal", "uuid": "common"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf1"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf2"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf3"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf4"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "R2"}}, "device_type": "emu-packet-router", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "1/1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "1/2"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "R3"}}, "device_type": "emu-packet-router", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "1/1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "1/2"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "DC2"}}, "device_type": "emu-datacenter", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "int"}
+ ]}}}
+ ]}
+ }
+ ],
+ "links": [
+ {
+ "link_id": {"link_uuid": {"uuid": "DC1/eth1==PE1/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}},
+ {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE1/1/1==DC1/eth1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "DC1/eth2==PE2/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth2"}},
+ {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE2/1/1==DC1/eth2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth2"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "PE1/1/2==MW1-2/172.18.0.1:1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "1/2"}},
+ {"device_id": {"device_uuid": {"uuid": "MW1-2"}}, "endpoint_uuid": {"uuid": "172.18.0.1:1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "MW1-2/172.18.0.1:1==PE1/1/2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "MW1-2"}}, "endpoint_uuid": {"uuid": "172.18.0.1:1"}},
+ {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "1/2"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "MW1-2/172.18.0.2:1==R1/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "MW1-2"}}, "endpoint_uuid": {"uuid": "172.18.0.2:1"}},
+ {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R1/1/1==MW1-2/172.18.0.2:1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "MW1-2"}}, "endpoint_uuid": {"uuid": "172.18.0.2:1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "PE2/1/2==MW3-4/172.18.0.3:1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "1/2"}},
+ {"device_id": {"device_uuid": {"uuid": "MW3-4"}}, "endpoint_uuid": {"uuid": "172.18.0.3:1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "MW3-4/172.18.0.3:1==PE2/1/2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "MW3-4"}}, "endpoint_uuid": {"uuid": "172.18.0.3:1"}},
+ {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "1/2"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "MW3-4/172.18.0.4:1==R1/1/2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "MW3-4"}}, "endpoint_uuid": {"uuid": "172.18.0.4:1"}},
+ {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "1/2"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R1/1/2==MW3-4/172.18.0.4:1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "1/2"}},
+ {"device_id": {"device_uuid": {"uuid": "MW3-4"}}, "endpoint_uuid": {"uuid": "172.18.0.4:1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "R1/1/3==Optical-Splitter/common"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "1/3"}},
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "common"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "Optical-Splitter/common==R1/1/3"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "common"}},
+ {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "1/3"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "Optical-Splitter/leaf1==OLS/aade6001-f00b-5e2f-a357-6a0a9d3de870"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf1"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "aade6001-f00b-5e2f-a357-6a0a9d3de870"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/79516f5e-55a0-5671-977a-1f5cc934e700==Optical-Splitter/leaf1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "79516f5e-55a0-5671-977a-1f5cc934e700"}},
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "Optical-Splitter/leaf2==OLS/eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf2"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/30d9323e-b916-51ce-a9a8-cf88f62eb77f==Optical-Splitter/leaf2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "30d9323e-b916-51ce-a9a8-cf88f62eb77f"}},
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf2"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "R2/1/1==OLS/0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/68ac012e-54d4-5846-b5dc-6ec356404f90==R2/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "68ac012e-54d4-5846-b5dc-6ec356404f90"}},
+ {"device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "R3/1/1==OLS/50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/367b19b1-3172-54d8-bdd4-12d3ac5604f6==R3/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "367b19b1-3172-54d8-bdd4-12d3ac5604f6"}},
+ {"device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "R2/1/2==PE3/1/2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "1/2"}},
+ {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "1/2"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE3/1/2==R2/1/2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "1/2"}},
+ {"device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "1/2"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "R3/1/2==PE4/1/2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "1/2"}},
+ {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "1/2"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE4/1/2==R3/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "1/2"}},
+ {"device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "PE3/1/1==DC2/eth1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "DC2/eth1==PE3/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}},
+ {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "PE4/1/1==DC2/eth2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth2"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "DC2/eth2==PE4/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth2"}},
+ {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ }
+ ]
+}
diff --git a/src/tests/ofc23/descriptors/emulated/ipm-ctrl.json b/src/tests/ofc23/descriptors/emulated/ipm-ctrl.json
new file mode 100644
index 0000000000000000000000000000000000000000..91e9de611dac2627525bb11f81755ea651887e74
--- /dev/null
+++ b/src/tests/ofc23/descriptors/emulated/ipm-ctrl.json
@@ -0,0 +1,25 @@
+{
+ "contexts": [
+ {"context_id": {"context_uuid": {"uuid": "admin"}}}
+ ],
+ "topologies": [
+ {"topology_id": {"topology_uuid": {"uuid": "admin"}, "context_id": {"context_uuid": {"uuid": "admin"}}}}
+ ],
+ "devices": [
+ {
+ "device_id": {"device_uuid": {"uuid": "XR-CONSTELLATION"}},
+ "device_type": "xr-constellation",
+ "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8444"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "xr-user-1", "password": "xr-user-1", "hub_module_name": "OFC HUB 1",
+ "consistency-mode": "lifecycle"
+ }}}
+ ]},
+ "device_operational_status": 1,
+ "device_drivers": [6],
+ "device_endpoints": []
+ }
+ ]
+}
diff --git a/src/tests/ofc23/descriptors/emulated/old/descriptor_parent.json b/src/tests/ofc23/descriptors/emulated/old/descriptor_parent.json
new file mode 100644
index 0000000000000000000000000000000000000000..413b7566292d7841777547aeb665c7eb3b8ca293
--- /dev/null
+++ b/src/tests/ofc23/descriptors/emulated/old/descriptor_parent.json
@@ -0,0 +1,311 @@
+{
+ "contexts": [
+ {"context_id": {"context_uuid": {"uuid": "admin"}}}
+ ],
+ "topologies": [
+ {"topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}}
+ ],
+ "devices": [
+ {
+ "device_id": {"device_uuid": {"uuid": "TFS-IP"}}, "device_type": "teraflowsdn", "device_drivers": [7],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8002"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "scheme": "http", "username": "admin", "password": "admin"
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "MW1-2"}}, "device_type": "microwave-radio-system", "device_drivers": [5],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8443"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "admin", "password": "admin", "timeout": 120, "scheme": "https",
+ "node_ids": ["172.18.0.1", "172.18.0.2"]
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "MW3-4"}}, "device_type": "microwave-radio-system", "device_drivers": [5],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8443"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "admin", "password": "admin", "timeout": 120, "scheme": "https",
+ "node_ids": ["172.18.0.3", "172.18.0.4"]
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "OLS"}}, "device_type": "open-line-system", "device_drivers": [2],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "cttc-ols.cttc-ols.svc.cluster.local"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "4900"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"timeout": 120}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "IPM"}}, "device_type": "xr-constellation", "device_drivers": [6],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8444"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "xr-user-1", "password": "xr-user-1", "hub_module_name": "OFC HUB 1",
+ "consistency-mode": "lifecycle", "import_topology": "devices"
+ }}}
+ ]}
+ },
+
+
+ {
+ "device_id": {"device_uuid": {"uuid": "DC1"}}, "device_type": "emu-datacenter", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "int"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "device_type": "emu-optical-splitter", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
+ {"sample_types": [], "type": "optical/internal", "uuid": "common"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf1"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf2"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf3"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf4"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "DC2"}}, "device_type": "emu-datacenter", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "int"}
+ ]}}}
+ ]}
+ }
+ ],
+ "links": [
+ {
+ "link_id": {"link_uuid": {"uuid": "DC1/eth1==PE1/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}},
+ {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE1/1/1==DC1/eth1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "DC1/eth2==PE2/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth2"}},
+ {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE2/1/1==DC1/eth2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth2"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "PE1/1/2==MW1-2/172.18.0.1:1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "1/2"}},
+ {"device_id": {"device_uuid": {"uuid": "MW1-2"}}, "endpoint_uuid": {"uuid": "172.18.0.1:1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "MW1-2/172.18.0.1:1==PE1/1/2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "MW1-2"}}, "endpoint_uuid": {"uuid": "172.18.0.1:1"}},
+ {"device_id": {"device_uuid": {"uuid": "PE1"}}, "endpoint_uuid": {"uuid": "1/2"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "MW1-2/172.18.0.2:1==OFC HUB 1/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "MW1-2"}}, "endpoint_uuid": {"uuid": "172.18.0.2:1"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC HUB 1/1/1==MW1-2/172.18.0.2:1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "MW1-2"}}, "endpoint_uuid": {"uuid": "172.18.0.2:1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "PE2/1/2==MW3-4/172.18.0.3:1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "1/2"}},
+ {"device_id": {"device_uuid": {"uuid": "MW3-4"}}, "endpoint_uuid": {"uuid": "172.18.0.3:1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "MW3-4/172.18.0.3:1==PE2/1/2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "MW3-4"}}, "endpoint_uuid": {"uuid": "172.18.0.3:1"}},
+ {"device_id": {"device_uuid": {"uuid": "PE2"}}, "endpoint_uuid": {"uuid": "1/2"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "MW3-4/172.18.0.4:1==OFC HUB 1/1/2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "MW3-4"}}, "endpoint_uuid": {"uuid": "172.18.0.4:1"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "1/2"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC HUB 1/1/2==MW3-4/172.18.0.4:1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "1/2"}},
+ {"device_id": {"device_uuid": {"uuid": "MW3-4"}}, "endpoint_uuid": {"uuid": "172.18.0.4:1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC HUB 1/XR-T1==Optical-Splitter/common"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "common"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "Optical-Splitter/common==OFC HUB 1/XR-T1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "common"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "Optical-Splitter/leaf1==OLS/aade6001-f00b-5e2f-a357-6a0a9d3de870"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf1"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "aade6001-f00b-5e2f-a357-6a0a9d3de870"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/79516f5e-55a0-5671-977a-1f5cc934e700==Optical-Splitter/leaf1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "79516f5e-55a0-5671-977a-1f5cc934e700"}},
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "Optical-Splitter/leaf2==OLS/eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf2"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/30d9323e-b916-51ce-a9a8-cf88f62eb77f==Optical-Splitter/leaf2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "30d9323e-b916-51ce-a9a8-cf88f62eb77f"}},
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf2"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 1/XR-T1==OLS/0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/68ac012e-54d4-5846-b5dc-6ec356404f90==OFC LEAF 1/XR-T1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "68ac012e-54d4-5846-b5dc-6ec356404f90"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 2/XR-T1==OLS/50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/367b19b1-3172-54d8-bdd4-12d3ac5604f6==OFC LEAF 2/XR-T1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "367b19b1-3172-54d8-bdd4-12d3ac5604f6"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 1/1/1==PE3/1/2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "1/2"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE3/1/2==OFC LEAF 1/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "1/2"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 2/1/1==PE4/1/2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "1/2"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "PE4/1/2==OFC LEAF 2/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "1/2"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "PE3/1/1==DC2/eth1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "DC2/eth1==PE3/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}},
+ {"device_id": {"device_uuid": {"uuid": "PE3"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "PE4/1/1==DC2/eth2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth2"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "DC2/eth2==PE4/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth2"}},
+ {"device_id": {"device_uuid": {"uuid": "PE4"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ }
+ ]
+}
diff --git a/src/tests/ofc23/descriptors/real/dc-2-dc-service.json b/src/tests/ofc23/descriptors/real/dc-2-dc-service.json
new file mode 100644
index 0000000000000000000000000000000000000000..3a83afa6de81f137204aecc5f0eca476aad71e61
--- /dev/null
+++ b/src/tests/ofc23/descriptors/real/dc-2-dc-service.json
@@ -0,0 +1,37 @@
+{
+ "services": [
+ {
+ "service_id": {
+ "context_id": {"context_uuid": {"uuid": "admin"}}, "service_uuid": {"uuid": "dc-2-dc-svc"}
+ },
+ "service_type": 2,
+ "service_status": {"service_status": 1},
+ "service_endpoint_ids": [
+ {"device_id":{"device_uuid":{"uuid":"DC1"}},"endpoint_uuid":{"uuid":"int"}},
+ {"device_id":{"device_uuid":{"uuid":"DC2"}},"endpoint_uuid":{"uuid":"int"}}
+ ],
+ "service_constraints": [
+ {"sla_capacity": {"capacity_gbps": 10.0}},
+ {"sla_latency": {"e2e_latency_ms": 15.2}}
+ ],
+ "service_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "/settings", "resource_value": {
+ "address_families": ["IPV4"], "bgp_as": 65000, "bgp_route_target": "65000:123",
+ "mtu": 1512, "vlan_id": 111
+ }}},
+ {"action": 1, "custom": {"resource_key": "/device[R149]/endpoint[eth-1/0/22]/settings", "resource_value": {
+ "route_distinguisher": "65000:123", "router_id": "5.5.5.5",
+ "address_ip": "172.16.4.1", "address_prefix": 24, "sub_interface_index": 0, "vlan_id": 111
+ }}},
+ {"action": 1, "custom": {"resource_key": "/device[R155]/endpoint[eth-1/0/22]/settings", "resource_value": {
+ "route_distinguisher": "65000:123", "router_id": "5.5.5.1",
+ "address_ip": "172.16.2.1", "address_prefix": 24, "sub_interface_index": 0, "vlan_id": 111
+ }}},
+ {"action": 1, "custom": {"resource_key": "/device[R199]/endpoint[eth-1/0/21]/settings", "resource_value": {
+ "route_distinguisher": "65000:123", "router_id": "5.5.5.6",
+ "address_ip": "172.16.1.1", "address_prefix": 24, "sub_interface_index": 0, "vlan_id": 111
+ }}}
+ ]}
+ }
+ ]
+}
diff --git a/src/tests/ofc23/descriptors/real/descriptor_child.json b/src/tests/ofc23/descriptors/real/descriptor_child.json
new file mode 100644
index 0000000000000000000000000000000000000000..8d695cfd2d419f263b554d0f2bf648b92cdde672
--- /dev/null
+++ b/src/tests/ofc23/descriptors/real/descriptor_child.json
@@ -0,0 +1,93 @@
+{
+ "contexts": [
+ {"context_id": {"context_uuid": {"uuid": "admin"}}}
+ ],
+ "topologies": [
+ {"topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}}
+ ],
+ "devices": [
+ {
+ "device_id": {"device_uuid": {"uuid": "R199"}}, "device_type": "packet-router", "device_drivers": [1],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.95.86.199"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "830"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "admin", "password": "admin",
+ "force_running": false, "hostkey_verify": false, "look_for_keys": false,
+ "allow_agent": false, "commit_per_rule": true, "device_params": {"name": "huaweiyang"},
+ "manager_params": {"timeout" : 86400}
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "R149"}}, "device_type": "packet-router", "device_drivers": [1],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.95.86.149"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "830"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "admin", "password": "admin",
+ "force_running": false, "hostkey_verify": false, "look_for_keys": false,
+ "allow_agent": false, "commit_per_rule": true, "device_params": {"name": "huaweiyang"},
+ "manager_params": {"timeout" : 86400}
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "R155"}}, "device_type": "packet-router", "device_drivers": [1],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.95.86.155"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "830"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "admin", "password": "admin",
+ "force_running": false, "hostkey_verify": false, "look_for_keys": false,
+ "allow_agent": false, "commit_per_rule": true, "device_params": {"name": "huaweiyang"},
+ "manager_params": {"timeout" : 86400}
+ }}}
+ ]}
+ }
+ ],
+ "links": [
+ {
+ "link_id": {"link_uuid": {"uuid": "R199/eth-1/0/19==R155/eth-1/0/19"}},
+ "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/19"}},
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/19"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R155/eth-1/0/19==R199/eth-1/0/19"}},
+ "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/19"}},
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/19"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R199/eth-1/0/20==R149/eth-1/0/20"}},
+ "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}},
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R149/eth-1/0/20==R199/eth-1/0/20"}},
+ "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}},
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R149/eth-1/0/25==R155/eth-1/0/25"}},
+ "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}},
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R155/eth-1/0/25==R149/eth-1/0/25"}},
+ "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}},
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}}
+ ]
+ }
+ ]
+}
diff --git a/src/tests/ofc23/descriptors/real/descriptor_parent.json b/src/tests/ofc23/descriptors/real/descriptor_parent.json
new file mode 100644
index 0000000000000000000000000000000000000000..3317d46edaf6d270125d8b094c3b6384a3dd52fd
--- /dev/null
+++ b/src/tests/ofc23/descriptors/real/descriptor_parent.json
@@ -0,0 +1,258 @@
+{
+ "contexts": [
+ {"context_id": {"context_uuid": {"uuid": "admin"}}}
+ ],
+ "topologies": [
+ {"topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}}
+ ],
+ "devices": [
+ {
+ "device_id": {"device_uuid": {"uuid": "TFS-IP"}}, "device_type": "teraflowsdn", "device_drivers": [7],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.0.2.10"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8002"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "scheme": "http", "username": "admin", "password": "admin"
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "MW"}}, "device_type": "microwave-radio-system", "device_drivers": [4, 5],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "192.168.27.136"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8443"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "nms5ux", "password": "nms5ux", "timeout": 120, "scheme": "https",
+ "node_ids": ["192.168.27.139", "192.168.27.140"]
+ }}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "OLS"}}, "device_type": "open-line-system", "device_drivers": [2],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "cttc-ols.cttc-ols.svc.cluster.local"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "4900"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"timeout": 120}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "IPM"}}, "device_type": "xr-constellation", "device_drivers": [6],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "10.95.86.126"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "443"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {
+ "username": "xr-user-1", "password": "xr-user-1", "hub_module_name": "OFC HUB 1",
+ "consistency-mode": "lifecycle", "import_topology": "devices"
+ }}}
+ ]}
+ },
+
+
+ {
+ "device_id": {"device_uuid": {"uuid": "DC1"}}, "device_type": "emu-datacenter", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "int"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "device_type": "emu-optical-splitter", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
+ {"sample_types": [], "type": "optical/internal", "uuid": "common"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf1"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf2"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf3"},
+ {"sample_types": [], "type": "optical/internal", "uuid": "leaf4"}
+ ]}}}
+ ]}
+ },
+ {
+ "device_id": {"device_uuid": {"uuid": "DC2"}}, "device_type": "emu-datacenter", "device_drivers": [0],
+ "device_endpoints": [], "device_operational_status": 0, "device_config": {"config_rules": [
+ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}},
+ {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}},
+ {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth1"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "eth2"},
+ {"sample_types": [], "type": "copper/internal", "uuid": "int"}
+ ]}}}
+ ]}
+ }
+ ],
+ "links": [
+ {
+ "link_id": {"link_uuid": {"uuid": "DC1/eth1==R149/eth-1/0/22"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}},
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R149/eth-1/0/22==DC1/eth1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}},
+ {"device_id": {"device_uuid": {"uuid": "DC1"}}, "endpoint_uuid": {"uuid": "eth1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "R149/eth-1/0/9==MW/192.168.27.140:5"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/9"}},
+ {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.140:5"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "MW/192.168.27.140:5==R149/eth-1/0/9"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.140:5"}},
+ {"device_id": {"device_uuid": {"uuid": "R149"}}, "endpoint_uuid": {"uuid": "eth-1/0/9"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "MW/192.168.27.139:5==OFC HUB 1/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.139:5"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC HUB 1/1/1==MW/192.168.27.139:5"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "192.168.27.139:5"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC HUB 1/XR-T1==Optical-Splitter/common"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "common"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "Optical-Splitter/common==OFC HUB 1/XR-T1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "common"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC HUB 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "Optical-Splitter/leaf1==OLS/aade6001-f00b-5e2f-a357-6a0a9d3de870"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf1"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "aade6001-f00b-5e2f-a357-6a0a9d3de870"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/79516f5e-55a0-5671-977a-1f5cc934e700==Optical-Splitter/leaf1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "79516f5e-55a0-5671-977a-1f5cc934e700"}},
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "Optical-Splitter/leaf2==OLS/eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf2"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/30d9323e-b916-51ce-a9a8-cf88f62eb77f==Optical-Splitter/leaf2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "30d9323e-b916-51ce-a9a8-cf88f62eb77f"}},
+ {"device_id": {"device_uuid": {"uuid": "Optical-Splitter"}}, "endpoint_uuid": {"uuid": "leaf2"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 1/XR-T1==OLS/0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/68ac012e-54d4-5846-b5dc-6ec356404f90==OFC LEAF 1/XR-T1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "68ac012e-54d4-5846-b5dc-6ec356404f90"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 2/XR-T1==OLS/50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "XR-T1"}},
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "OLS/367b19b1-3172-54d8-bdd4-12d3ac5604f6==OFC LEAF 2/XR-T1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OLS"}}, "endpoint_uuid": {"uuid": "367b19b1-3172-54d8-bdd4-12d3ac5604f6"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "XR-T1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 1/1/1==R155/eth-1/0/25"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R155/eth-1/0/25==OFC LEAF 1/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/25"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 1"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "OFC LEAF 2/1/1==R199/eth-1/0/20"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "1/1"}},
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "R199/eth-1/0/20==OFC LEAF 2/1/1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/20"}},
+ {"device_id": {"device_uuid": {"uuid": "OFC LEAF 2"}}, "endpoint_uuid": {"uuid": "1/1"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "R155/eth-1/0/22==DC2/eth1"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}},
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "DC2/eth1==R155/eth-1/0/22"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth1"}},
+ {"device_id": {"device_uuid": {"uuid": "R155"}}, "endpoint_uuid": {"uuid": "eth-1/0/22"}}
+ ]
+ },
+
+
+ {
+ "link_id": {"link_uuid": {"uuid": "R199/eth-1/0/21==DC2/eth2"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/21"}},
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth2"}}
+ ]
+ },
+ {
+ "link_id": {"link_uuid": {"uuid": "DC2/eth2==R199/eth-1/0/21"}}, "link_endpoint_ids": [
+ {"device_id": {"device_uuid": {"uuid": "DC2"}}, "endpoint_uuid": {"uuid": "eth2"}},
+ {"device_id": {"device_uuid": {"uuid": "R199"}}, "endpoint_uuid": {"uuid": "eth-1/0/21"}}
+ ]
+ }
+ ]
+}
diff --git a/src/tests/ofc23/dump_logs.sh b/src/tests/ofc23/dump_logs.sh
new file mode 100755
index 0000000000000000000000000000000000000000..cc3162b337c1b35e9ff158b400a8d5c47931bdca
--- /dev/null
+++ b/src/tests/ofc23/dump_logs.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+rm -rf tmp/exec
+
+echo "Collecting logs for Parent..."
+mkdir -p tmp/exec/parent
+kubectl --namespace tfs-parent logs deployments/contextservice server > tmp/exec/parent/context.log
+kubectl --namespace tfs-parent logs deployments/deviceservice server > tmp/exec/parent/device.log
+kubectl --namespace tfs-parent logs deployments/serviceservice server > tmp/exec/parent/service.log
+kubectl --namespace tfs-parent logs deployments/pathcompservice frontend > tmp/exec/parent/pathcomp-frontend.log
+kubectl --namespace tfs-parent logs deployments/pathcompservice backend > tmp/exec/parent/pathcomp-backend.log
+kubectl --namespace tfs-parent logs deployments/sliceservice server > tmp/exec/parent/slice.log
+printf "\n"
+
+echo "Collecting logs for Child..."
+mkdir -p tmp/exec/child
+kubectl --namespace tfs-child logs deployments/contextservice server > tmp/exec/child/context.log
+kubectl --namespace tfs-child logs deployments/deviceservice server > tmp/exec/child/device.log
+kubectl --namespace tfs-child logs deployments/serviceservice server > tmp/exec/child/service.log
+kubectl --namespace tfs-child logs deployments/pathcompservice frontend > tmp/exec/child/pathcomp-frontend.log
+kubectl --namespace tfs-child logs deployments/pathcompservice backend > tmp/exec/child/pathcomp-backend.log
+kubectl --namespace tfs-child logs deployments/sliceservice server > tmp/exec/child/slice.log
+printf "\n"
+
+echo "Done!"
diff --git a/src/tests/ofc23/fast_redeploy.sh b/src/tests/ofc23/fast_redeploy.sh
new file mode 100755
index 0000000000000000000000000000000000000000..58d1193ded582d4fdff3222d5bcdc0fe510a7034
--- /dev/null
+++ b/src/tests/ofc23/fast_redeploy.sh
@@ -0,0 +1,63 @@
+#!/bin/bash
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+kubectl delete namespace tfs-parent tfs-child
+
+echo "Deploying tfs-parent ..."
+kubectl delete -f ofc23/nginx-ingress-controller-parent.yaml > ./tmp/logs/deploy-tfs-parent.log
+kubectl create namespace tfs-parent > ./tmp/logs/deploy-tfs-parent.log
+kubectl apply -f ofc23/nginx-ingress-controller-parent.yaml > ./tmp/logs/deploy-tfs-parent.log
+kubectl --namespace tfs-parent apply -f ./tmp/manifests/contextservice.yaml > ./tmp/logs/deploy-tfs-parent.log
+kubectl --namespace tfs-parent apply -f ./tmp/manifests/deviceservice.yaml > ./tmp/logs/deploy-tfs-parent.log
+kubectl --namespace tfs-parent apply -f ./tmp/manifests/pathcompservice.yaml > ./tmp/logs/deploy-tfs-parent.log
+kubectl --namespace tfs-parent apply -f ./tmp/manifests/serviceservice.yaml > ./tmp/logs/deploy-tfs-parent.log
+kubectl --namespace tfs-parent apply -f ./tmp/manifests/sliceservice.yaml > ./tmp/logs/deploy-tfs-parent.log
+kubectl --namespace tfs-parent apply -f ./tmp/manifests/webuiservice.yaml > ./tmp/logs/deploy-tfs-parent.log
+kubectl --namespace tfs-parent apply -f ofc23/tfs-ingress-parent.yaml > ./tmp/logs/deploy-tfs-parent.log
+printf "\n"
+
+echo "Deploying tfs-child ..."
+kubectl delete -f ofc23/nginx-ingress-controller-child.yaml > ./tmp/logs/deploy-tfs-child.log
+kubectl create namespace tfs-child > ./tmp/logs/deploy-tfs-child.log
+kubectl apply -f ofc23/nginx-ingress-controller-child.yaml > ./tmp/logs/deploy-tfs-child.log
+kubectl --namespace tfs-child apply -f ./tmp/manifests/contextservice.yaml > ./tmp/logs/deploy-tfs-child.log
+kubectl --namespace tfs-child apply -f ./tmp/manifests/deviceservice.yaml > ./tmp/logs/deploy-tfs-child.log
+kubectl --namespace tfs-child apply -f ./tmp/manifests/pathcompservice.yaml > ./tmp/logs/deploy-tfs-child.log
+kubectl --namespace tfs-child apply -f ./tmp/manifests/serviceservice.yaml > ./tmp/logs/deploy-tfs-child.log
+kubectl --namespace tfs-child apply -f ./tmp/manifests/sliceservice.yaml > ./tmp/logs/deploy-tfs-child.log
+kubectl --namespace tfs-child apply -f ./tmp/manifests/webuiservice.yaml > ./tmp/logs/deploy-tfs-child.log
+kubectl --namespace tfs-child apply -f ofc23/tfs-ingress-child.yaml > ./tmp/logs/deploy-tfs-child.log
+printf "\n"
+
+echo "Waiting tfs-parent ..."
+kubectl wait --namespace tfs-parent --for='condition=available' --timeout=300s deployment/contextservice
+kubectl wait --namespace tfs-parent --for='condition=available' --timeout=300s deployment/deviceservice
+kubectl wait --namespace tfs-parent --for='condition=available' --timeout=300s deployment/pathcompservice
+kubectl wait --namespace tfs-parent --for='condition=available' --timeout=300s deployment/serviceservice
+kubectl wait --namespace tfs-parent --for='condition=available' --timeout=300s deployment/sliceservice
+kubectl wait --namespace tfs-parent --for='condition=available' --timeout=300s deployment/webuiservice
+printf "\n"
+
+echo "Waiting tfs-child ..."
+kubectl wait --namespace tfs-child --for='condition=available' --timeout=300s deployment/contextservice
+kubectl wait --namespace tfs-child --for='condition=available' --timeout=300s deployment/deviceservice
+kubectl wait --namespace tfs-child --for='condition=available' --timeout=300s deployment/pathcompservice
+kubectl wait --namespace tfs-child --for='condition=available' --timeout=300s deployment/serviceservice
+kubectl wait --namespace tfs-child --for='condition=available' --timeout=300s deployment/sliceservice
+kubectl wait --namespace tfs-child --for='condition=available' --timeout=300s deployment/webuiservice
+printf "\n"
+
+echo "Done!"
diff --git a/src/tests/ofc23/nginx-ingress-controller-child.yaml b/src/tests/ofc23/nginx-ingress-controller-child.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..00a64d75e9a9a2cb93cdbd6d89790e26b0730eb6
--- /dev/null
+++ b/src/tests/ofc23/nginx-ingress-controller-child.yaml
@@ -0,0 +1,134 @@
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: nginx-load-balancer-microk8s-conf-child
+ namespace: ingress
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: nginx-ingress-udp-microk8s-conf-child
+ namespace: ingress
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: nginx-ingress-tcp-microk8s-conf-child
+ namespace: ingress
+---
+apiVersion: networking.k8s.io/v1
+kind: IngressClass
+metadata:
+ name: tfs-ingress-class-child
+ annotations:
+ ingressclass.kubernetes.io/is-default-class: "false"
+spec:
+ controller: tfs.etsi.org/controller-class-child
+---
+apiVersion: apps/v1
+kind: DaemonSet
+metadata:
+ name: nginx-ingress-microk8s-controller-child
+ namespace: ingress
+ labels:
+ microk8s-application: nginx-ingress-microk8s-child
+spec:
+ selector:
+ matchLabels:
+ name: nginx-ingress-microk8s-child
+ updateStrategy:
+ rollingUpdate:
+ maxSurge: 0
+ maxUnavailable: 1
+ type: RollingUpdate
+ template:
+ metadata:
+ labels:
+ name: nginx-ingress-microk8s-child
+ spec:
+ terminationGracePeriodSeconds: 60
+ restartPolicy: Always
+ serviceAccountName: nginx-ingress-microk8s-serviceaccount
+ containers:
+ - image: k8s.gcr.io/ingress-nginx/controller:v1.2.0
+ imagePullPolicy: IfNotPresent
+ name: nginx-ingress-microk8s
+ livenessProbe:
+ httpGet:
+ path: /healthz
+ port: 10254
+ scheme: HTTP
+ initialDelaySeconds: 10
+ periodSeconds: 10
+ successThreshold: 1
+ failureThreshold: 3
+ timeoutSeconds: 5
+ readinessProbe:
+ httpGet:
+ path: /healthz
+ port: 10254
+ scheme: HTTP
+ periodSeconds: 10
+ successThreshold: 1
+ failureThreshold: 3
+ timeoutSeconds: 5
+ lifecycle:
+ preStop:
+ exec:
+ command:
+ - /wait-shutdown
+ securityContext:
+ capabilities:
+ add:
+ - NET_BIND_SERVICE
+ drop:
+ - ALL
+ runAsUser: 101 # www-data
+ env:
+ - name: POD_NAME
+ valueFrom:
+ fieldRef:
+ apiVersion: v1
+ fieldPath: metadata.name
+ - name: POD_NAMESPACE
+ valueFrom:
+ fieldRef:
+ apiVersion: v1
+ fieldPath: metadata.namespace
+ ports:
+ - name: http
+ containerPort: 80
+ hostPort: 8002
+ protocol: TCP
+ - name: https
+ containerPort: 443
+ hostPort: 4432
+ protocol: TCP
+ - name: health
+ containerPort: 10254
+ hostPort: 12542
+ protocol: TCP
+ args:
+ - /nginx-ingress-controller
+ - --configmap=$(POD_NAMESPACE)/nginx-load-balancer-microk8s-conf-child
+ - --tcp-services-configmap=$(POD_NAMESPACE)/nginx-ingress-tcp-microk8s-conf-child
+ - --udp-services-configmap=$(POD_NAMESPACE)/nginx-ingress-udp-microk8s-conf-child
+ - --election-id=ingress-controller-leader-child
+ - --controller-class=tfs.etsi.org/controller-class-child
+ - --ingress-class=tfs-ingress-class-child
+ - ' '
+ - --publish-status-address=127.0.0.1
diff --git a/src/tests/ofc23/nginx-ingress-controller-parent.yaml b/src/tests/ofc23/nginx-ingress-controller-parent.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..c504c2e6766c1ad5b81a2479b4d05a09ba46d906
--- /dev/null
+++ b/src/tests/ofc23/nginx-ingress-controller-parent.yaml
@@ -0,0 +1,134 @@
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: nginx-load-balancer-microk8s-conf-parent
+ namespace: ingress
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: nginx-ingress-udp-microk8s-conf-parent
+ namespace: ingress
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: nginx-ingress-tcp-microk8s-conf-parent
+ namespace: ingress
+---
+apiVersion: networking.k8s.io/v1
+kind: IngressClass
+metadata:
+ name: tfs-ingress-class-parent
+ annotations:
+ ingressclass.kubernetes.io/is-default-class: "false"
+spec:
+ controller: tfs.etsi.org/controller-class-parent
+---
+apiVersion: apps/v1
+kind: DaemonSet
+metadata:
+ name: nginx-ingress-microk8s-controller-parent
+ namespace: ingress
+ labels:
+ microk8s-application: nginx-ingress-microk8s-parent
+spec:
+ selector:
+ matchLabels:
+ name: nginx-ingress-microk8s-parent
+ updateStrategy:
+ rollingUpdate:
+ maxSurge: 0
+ maxUnavailable: 1
+ type: RollingUpdate
+ template:
+ metadata:
+ labels:
+ name: nginx-ingress-microk8s-parent
+ spec:
+ terminationGracePeriodSeconds: 60
+ restartPolicy: Always
+ serviceAccountName: nginx-ingress-microk8s-serviceaccount
+ containers:
+ - image: k8s.gcr.io/ingress-nginx/controller:v1.2.0
+ imagePullPolicy: IfNotPresent
+ name: nginx-ingress-microk8s
+ livenessProbe:
+ httpGet:
+ path: /healthz
+ port: 10254
+ scheme: HTTP
+ initialDelaySeconds: 10
+ periodSeconds: 10
+ successThreshold: 1
+ failureThreshold: 3
+ timeoutSeconds: 5
+ readinessProbe:
+ httpGet:
+ path: /healthz
+ port: 10254
+ scheme: HTTP
+ periodSeconds: 10
+ successThreshold: 1
+ failureThreshold: 3
+ timeoutSeconds: 5
+ lifecycle:
+ preStop:
+ exec:
+ command:
+ - /wait-shutdown
+ securityContext:
+ capabilities:
+ add:
+ - NET_BIND_SERVICE
+ drop:
+ - ALL
+ runAsUser: 101 # www-data
+ env:
+ - name: POD_NAME
+ valueFrom:
+ fieldRef:
+ apiVersion: v1
+ fieldPath: metadata.name
+ - name: POD_NAMESPACE
+ valueFrom:
+ fieldRef:
+ apiVersion: v1
+ fieldPath: metadata.namespace
+ ports:
+ - name: http
+ containerPort: 80
+ hostPort: 8001
+ protocol: TCP
+ - name: https
+ containerPort: 443
+ hostPort: 4431
+ protocol: TCP
+ - name: health
+ containerPort: 10254
+ hostPort: 12541
+ protocol: TCP
+ args:
+ - /nginx-ingress-controller
+ - --configmap=$(POD_NAMESPACE)/nginx-load-balancer-microk8s-conf-parent
+ - --tcp-services-configmap=$(POD_NAMESPACE)/nginx-ingress-tcp-microk8s-conf-parent
+ - --udp-services-configmap=$(POD_NAMESPACE)/nginx-ingress-udp-microk8s-conf-parent
+ - --election-id=ingress-controller-leader-parent
+ - --controller-class=tfs.etsi.org/controller-class-parent
+ - --ingress-class=tfs-ingress-class-parent
+ - ' '
+ - --publish-status-address=127.0.0.1
diff --git a/src/tests/ofc23/show_deploy.sh b/src/tests/ofc23/show_deploy.sh
new file mode 100755
index 0000000000000000000000000000000000000000..d4e112b0f494e4a6dba964eda4e31652b8548043
--- /dev/null
+++ b/src/tests/ofc23/show_deploy.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+########################################################################################################################
+# Automated steps start here
+########################################################################################################################
+
+echo "Deployment Resources:"
+kubectl --namespace tfs-parent get all
+printf "\n"
+
+echo "Deployment Ingress:"
+kubectl --namespace tfs-parent get ingress
+printf "\n"
+
+echo "Deployment Resources:"
+kubectl --namespace tfs-child get all
+printf "\n"
+
+echo "Deployment Ingress:"
+kubectl --namespace tfs-child get ingress
+printf "\n"
diff --git a/src/tests/ofc23/show_deploy_sligrp.sh b/src/tests/ofc23/show_deploy_sligrp.sh
new file mode 100755
index 0000000000000000000000000000000000000000..b5e3600ba99ec2e4de4a953f99c44ed2d88bba57
--- /dev/null
+++ b/src/tests/ofc23/show_deploy_sligrp.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+########################################################################################################################
+# Automated steps start here
+########################################################################################################################
+
+echo "Deployment Resources:"
+kubectl --namespace tfs get all
+printf "\n"
+
+echo "Deployment Ingress:"
+kubectl --namespace tfs get ingress
+printf "\n"
diff --git a/src/tests/ofc23/tfs-ingress-child.yaml b/src/tests/ofc23/tfs-ingress-child.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..a93b9321c9f25c78e8423413c4225f78c7aee719
--- /dev/null
+++ b/src/tests/ofc23/tfs-ingress-child.yaml
@@ -0,0 +1,53 @@
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: tfs-ingress-child
+ annotations:
+ nginx.ingress.kubernetes.io/rewrite-target: /$2
+spec:
+ ingressClassName: tfs-ingress-class-child
+ rules:
+ - http:
+ paths:
+ - path: /webui(/|$)(.*)
+ pathType: Prefix
+ backend:
+ service:
+ name: webuiservice
+ port:
+ number: 8004
+ - path: /grafana(/|$)(.*)
+ pathType: Prefix
+ backend:
+ service:
+ name: webuiservice
+ port:
+ number: 3000
+ - path: /context(/|$)(.*)
+ pathType: Prefix
+ backend:
+ service:
+ name: contextservice
+ port:
+ number: 8080
+ - path: /()(restconf/.*)
+ pathType: Prefix
+ backend:
+ service:
+ name: computeservice
+ port:
+ number: 8080
diff --git a/src/tests/ofc23/tfs-ingress-parent.yaml b/src/tests/ofc23/tfs-ingress-parent.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..baf506dd90f320a1913beb8becd39164daa21370
--- /dev/null
+++ b/src/tests/ofc23/tfs-ingress-parent.yaml
@@ -0,0 +1,53 @@
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: tfs-ingress-parent
+ annotations:
+ nginx.ingress.kubernetes.io/rewrite-target: /$2
+spec:
+ ingressClassName: tfs-ingress-class-parent
+ rules:
+ - http:
+ paths:
+ - path: /webui(/|$)(.*)
+ pathType: Prefix
+ backend:
+ service:
+ name: webuiservice
+ port:
+ number: 8004
+ - path: /grafana(/|$)(.*)
+ pathType: Prefix
+ backend:
+ service:
+ name: webuiservice
+ port:
+ number: 3000
+ - path: /context(/|$)(.*)
+ pathType: Prefix
+ backend:
+ service:
+ name: contextservice
+ port:
+ number: 8080
+ - path: /()(restconf/.*)
+ pathType: Prefix
+ backend:
+ service:
+ name: computeservice
+ port:
+ number: 8080
diff --git a/src/tests/tools/mock_ipm_sdn_ctrl/MockIPMSdnCtrl.py b/src/tests/tools/mock_ipm_sdn_ctrl/MockIPMSdnCtrl.py
new file mode 100644
index 0000000000000000000000000000000000000000..ecac81be7e3bdff1dcbac458142ea15bf367a1a1
--- /dev/null
+++ b/src/tests/tools/mock_ipm_sdn_ctrl/MockIPMSdnCtrl.py
@@ -0,0 +1,192 @@
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Mock IPM controller (implements minimal support)
+
+import functools, json, logging, sys, time, uuid
+from typing import Any, Dict, Optional, Tuple
+from flask import Flask, jsonify, make_response, request
+from flask_restful import Api, Resource
+
+BIND_ADDRESS = '0.0.0.0'
+BIND_PORT = 8444
+IPM_USERNAME = 'xr-user-1'
+IPM_PASSWORD = 'xr-user-1'
+STR_ENDPOINT = 'https://{:s}:{:s}'.format(str(BIND_ADDRESS), str(BIND_PORT))
+LOG_LEVEL = logging.DEBUG
+
+CONSTELLATION = {
+ 'id': 'ofc-constellation',
+ 'hubModule': {'state': {
+ 'module': {'moduleName': 'OFC HUB 1', 'trafficMode': 'L1Mode', 'capacity': 100},
+ 'endpoints': [{'moduleIf': {'clientIfAid': 'XR-T1'}}, {'moduleIf': {'clientIfAid': 'XR-T4'}}]
+ }},
+ 'leafModules': [
+ {'state': {
+ 'module': {'moduleName': 'OFC LEAF 1', 'trafficMode': 'L1Mode', 'capacity': 100},
+ 'endpoints': [{'moduleIf': {'clientIfAid': 'XR-T1'}}]
+ }},
+ {'state': {
+ 'module': {'moduleName': 'OFC LEAF 2', 'trafficMode': 'L1Mode', 'capacity': 100},
+ 'endpoints': [{'moduleIf': {'clientIfAid': 'XR-T1'}}]
+ }}
+ ]
+}
+
+CONNECTIONS : Dict[str, Any] = dict()
+STATE_NAME_TO_CONNECTION : Dict[str, str] = dict()
+
+def select_module_state(module_name : str) -> Optional[Dict]:
+ hub_module_state = CONSTELLATION.get('hubModule', {}).get('state', {})
+ if module_name == hub_module_state.get('module', {}).get('moduleName'): return hub_module_state
+ for leaf_module in CONSTELLATION.get('leafModules', []):
+ leaf_module_state = leaf_module.get('state', {})
+ if module_name == leaf_module_state.get('module', {}).get('moduleName'): return leaf_module_state
+ return None
+
+def select_endpoint(module_state : Dict, module_if : str) -> Optional[Dict]:
+ for endpoint in module_state.get('endpoints', []):
+ if module_if == endpoint.get('moduleIf', {}).get('clientIfAid'): return endpoint
+ return None
+
+def select_module_endpoint(selector : Dict) -> Optional[Tuple[Dict, Dict]]:
+ selected_module_name = selector['moduleIfSelectorByModuleName']['moduleName']
+ selected_module_if = selector['moduleIfSelectorByModuleName']['moduleClientIfAid']
+ module_state = select_module_state(selected_module_name)
+ if module_state is None: return None
+ return module_state, select_endpoint(module_state, selected_module_if)
+
+def compose_endpoint(endpoint_selector : Dict) -> Dict:
+ module, endpoint = select_module_endpoint(endpoint_selector['selector'])
+ return {
+ 'href': '/' + str(uuid.uuid4()),
+ 'state': {
+ 'moduleIf': {
+ 'moduleName': module['module']['moduleName'],
+ 'clientIfAid': endpoint['moduleIf']['clientIfAid'],
+ },
+ 'capacity': module['module']['capacity'],
+ }
+ }
+
+logging.basicConfig(level=LOG_LEVEL, format="[%(asctime)s] %(levelname)s:%(name)s:%(message)s")
+LOGGER = logging.getLogger(__name__)
+
+logging.getLogger('werkzeug').setLevel(logging.WARNING)
+
+def log_request(logger : logging.Logger, response):
+ timestamp = time.strftime('[%Y-%b-%d %H:%M]')
+ logger.info('%s %s %s %s %s', timestamp, request.remote_addr, request.method, request.full_path, response.status)
+ return response
+
+class OpenIdConnect(Resource):
+ ACCESS_TOKENS = {}
+
+ def post(self):
+ if request.content_type != 'application/x-www-form-urlencoded': return make_response('bad content type', 400)
+ if request.content_length == 0: return make_response('bad content length', 400)
+ request_form = request.form
+ if request_form.get('client_id') != 'xr-web-client': return make_response('bad client_id', 403)
+ if request_form.get('client_secret') != 'xr-web-client': return make_response('bad client_secret', 403)
+ if request_form.get('grant_type') != 'password': return make_response('bad grant_type', 403)
+ if request_form.get('username') != IPM_USERNAME: return make_response('bad username', 403)
+ if request_form.get('password') != IPM_PASSWORD: return make_response('bad password', 403)
+ access_token = OpenIdConnect.ACCESS_TOKENS.setdefault(IPM_USERNAME, uuid.uuid4())
+ reply = {'access_token': access_token, 'expires_in': 86400}
+ return make_response(jsonify(reply), 200)
+
+class XrNetworks(Resource):
+ def get(self):
+ query = json.loads(request.args.get('q'))
+ hub_module_name = query.get('hubModule.state.module.moduleName')
+ if hub_module_name != 'OFC HUB 1': return make_response('unexpected hub module', 404)
+ return make_response(jsonify([CONSTELLATION]), 200)
+
+class XrNetworkConnections(Resource):
+ def get(self):
+ query = json.loads(request.args.get('q'))
+ state_name = query.get('state.name')
+ if state_name is None:
+ connections = [connection for connection in CONNECTIONS.values()]
+ else:
+ connection_uuid = STATE_NAME_TO_CONNECTION.get(state_name)
+ if connection_uuid is None: return make_response('state name not found', 404)
+ connection = CONNECTIONS.get(connection_uuid)
+ if connection is None: return make_response('connection for state name not found', 404)
+ connections = [connection]
+ return make_response(jsonify(connections), 200)
+
+ def post(self):
+ if request.content_type != 'application/json': return make_response('bad content type', 400)
+ if request.content_length == 0: return make_response('bad content length', 400)
+ request_json = request.json
+ if not isinstance(request_json, list): return make_response('content is not list', 400)
+ reply = []
+ for connection in request_json:
+ connection_uuid = str(uuid.uuid4())
+ state_name = connection['name']
+
+ if state_name is not None: STATE_NAME_TO_CONNECTION[state_name] = connection_uuid
+ CONNECTIONS[connection_uuid] = {
+ 'href': '/network-connections/{:s}'.format(str(connection_uuid)),
+ 'config': {
+ 'implicitTransportCapacity': connection['implicitTransportCapacity']
+ # 'mc': ??
+ },
+ 'state': {
+ 'name': state_name,
+ 'serviceMode': connection['serviceMode']
+ # 'outerVID' : ??
+ },
+ 'endpoints': [
+ compose_endpoint(endpoint)
+ for endpoint in connection['endpoints']
+ ]
+ }
+ reply.append(CONNECTIONS[connection_uuid])
+ return make_response(jsonify(reply), 202)
+
+class XrNetworkConnection(Resource):
+ def get(self, connection_uuid : str):
+ connection = CONNECTIONS.get(connection_uuid)
+ if connection is None: return make_response('unexpected connection id', 404)
+ return make_response(jsonify(connection), 200)
+
+ def delete(self, connection_uuid : str):
+ connection = CONNECTIONS.pop(connection_uuid, None)
+ if connection is None: return make_response('unexpected connection id', 404)
+ state_name = connection['state']['name']
+ STATE_NAME_TO_CONNECTION.pop(state_name, None)
+ return make_response(jsonify({}), 202)
+
+def main():
+ LOGGER.info('Starting...')
+
+ app = Flask(__name__)
+ app.after_request(functools.partial(log_request, LOGGER))
+
+ api = Api(app)
+ api.add_resource(OpenIdConnect, '/realms/xr-cm/protocol/openid-connect/token')
+ api.add_resource(XrNetworks, '/api/v1/xr-networks')
+ api.add_resource(XrNetworkConnections, '/api/v1/network-connections')
+ api.add_resource(XrNetworkConnection, '/api/v1/network-connections/