diff --git a/ofc23 b/ofc23 new file mode 120000 index 0000000000000000000000000000000000000000..a1135d4c59a81997350864319a6c267eaaf9ed93 --- /dev/null +++ b/ofc23 @@ -0,0 +1 @@ +src/tests/ofc23/ \ No newline at end of file diff --git a/proto/context.proto b/proto/context.proto index 49d16229cdac5de84f25cfaa7d196d25184f46f0..2dfbb7805eb444ee94e27bb00ca05d9a1c83b8ec 100644 --- a/proto/context.proto +++ b/proto/context.proto @@ -191,6 +191,7 @@ enum DeviceDriverEnum { DEVICEDRIVER_IETF_NETWORK_TOPOLOGY = 4; DEVICEDRIVER_ONF_TR_352 = 5; DEVICEDRIVER_XR = 6; + DEVICEDRIVER_IETF_L2VPN = 7; } enum DeviceOperationalStatusEnum { diff --git a/scripts/show_logs_compute.sh b/scripts/show_logs_compute.sh index fc992eb43e5872b4522db6f5c8ce39207f12d559..f0c24b63aa7b7e5c6678659c34dee34e8ce5b49e 100755 --- a/scripts/show_logs_compute.sh +++ b/scripts/show_logs_compute.sh @@ -24,4 +24,4 @@ export TFS_K8S_NAMESPACE=${TFS_K8S_NAMESPACE:-"tfs"} # Automated steps start here ######################################################################################################################## -kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/computeservice +kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/computeservice -c server diff --git a/scripts/show_logs_device.sh b/scripts/show_logs_device.sh index 6a77c38152716f1e6fbf320671dda25d974431c8..e643f563a6b8ba250985b013cecc9340c53c9411 100755 --- a/scripts/show_logs_device.sh +++ b/scripts/show_logs_device.sh @@ -24,4 +24,4 @@ export TFS_K8S_NAMESPACE=${TFS_K8S_NAMESPACE:-"tfs"} # Automated steps start here ######################################################################################################################## -kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/deviceservice +kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/deviceservice -c server diff --git a/scripts/show_logs_load_generator.sh b/scripts/show_logs_load_generator.sh index d0f2527d74840d48a10e0ec7ba018f513eea2c52..51438f181f5492a1c9c9bc8dd0b5a76f6db1046c 100755 --- a/scripts/show_logs_load_generator.sh +++ b/scripts/show_logs_load_generator.sh @@ -24,4 +24,4 @@ export TFS_K8S_NAMESPACE=${TFS_K8S_NAMESPACE:-"tfs"} # Automated steps start here ######################################################################################################################## -kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/load-generatorservice +kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/load-generatorservice -c server diff --git a/scripts/show_logs_monitoring.sh b/scripts/show_logs_monitoring.sh index 1a152a32216545f53607880c3908266f4ac41e95..61b0b5cc024f89daeffc0745c2689d85500f4115 100755 --- a/scripts/show_logs_monitoring.sh +++ b/scripts/show_logs_monitoring.sh @@ -24,4 +24,4 @@ export TFS_K8S_NAMESPACE=${TFS_K8S_NAMESPACE:-"tfs"} # Automated steps start here ######################################################################################################################## -kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/monitoringservice server +kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/monitoringservice -c server diff --git a/scripts/show_logs_service.sh b/scripts/show_logs_service.sh index 7ca1c1c2f4286a5fc46f7d36197d376472b447ed..cc75e19c64935f99c7919f9371717b91b0e6b3cb 100755 --- a/scripts/show_logs_service.sh +++ b/scripts/show_logs_service.sh @@ -24,4 +24,4 @@ export TFS_K8S_NAMESPACE=${TFS_K8S_NAMESPACE:-"tfs"} # Automated steps start here ######################################################################################################################## -kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/serviceservice +kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/serviceservice -c server diff --git a/scripts/show_logs_slice.sh b/scripts/show_logs_slice.sh index c71bc92eaa4a8d411372fc0ad4194881a5a2a9c8..7fa8091cce081ff7cef152f465bea8e426b40124 100755 --- a/scripts/show_logs_slice.sh +++ b/scripts/show_logs_slice.sh @@ -24,4 +24,4 @@ export TFS_K8S_NAMESPACE=${TFS_K8S_NAMESPACE:-"tfs"} # Automated steps start here ######################################################################################################################## -kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/sliceservice +kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/sliceservice -c server diff --git a/src/automation/src/main/java/eu/teraflow/automation/Serializer.java b/src/automation/src/main/java/eu/teraflow/automation/Serializer.java index 08691b5266b8172a2bd0449df870033bd2664dd0..b0729aa55b25da030f9722330e22a0976a3d007f 100644 --- a/src/automation/src/main/java/eu/teraflow/automation/Serializer.java +++ b/src/automation/src/main/java/eu/teraflow/automation/Serializer.java @@ -853,6 +853,8 @@ public class Serializer { return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352; case XR: return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_XR; + case IETF_L2VPN: + return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN; case UNDEFINED: default: return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED; @@ -874,6 +876,8 @@ public class Serializer { return DeviceDriverEnum.ONF_TR_352; case DEVICEDRIVER_XR: return DeviceDriverEnum.XR; + case DEVICEDRIVER_IETF_L2VPN: + return DeviceDriverEnum.IETF_L2VPN; case DEVICEDRIVER_UNDEFINED: case UNRECOGNIZED: default: diff --git a/src/automation/src/main/java/eu/teraflow/automation/context/model/DeviceDriverEnum.java b/src/automation/src/main/java/eu/teraflow/automation/context/model/DeviceDriverEnum.java index a364bb0e3cb821d20061d574428984acacb0cc46..3a26937e79d0df2cfead305a10ccadf3c54eae89 100644 --- a/src/automation/src/main/java/eu/teraflow/automation/context/model/DeviceDriverEnum.java +++ b/src/automation/src/main/java/eu/teraflow/automation/context/model/DeviceDriverEnum.java @@ -23,5 +23,6 @@ public enum DeviceDriverEnum { P4, IETF_NETWORK_TOPOLOGY, ONF_TR_352, - XR + XR, + IETF_L2VPN } diff --git a/src/automation/src/test/java/eu/teraflow/automation/SerializerTest.java b/src/automation/src/test/java/eu/teraflow/automation/SerializerTest.java index 494e608a105897fe005a18b7041b34fe95b40f8b..0931054c682dede502fb9f22bf911439e52c2140 100644 --- a/src/automation/src/test/java/eu/teraflow/automation/SerializerTest.java +++ b/src/automation/src/test/java/eu/teraflow/automation/SerializerTest.java @@ -1215,6 +1215,8 @@ class SerializerTest { DeviceDriverEnum.ONF_TR_352, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352), Arguments.of(DeviceDriverEnum.XR, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_XR), + Arguments.of( + DeviceDriverEnum.IETF_L2VPN, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN), Arguments.of( DeviceDriverEnum.UNDEFINED, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED)); } diff --git a/src/automation/target/generated-sources/grpc/context/ContextOuterClass.java b/src/automation/target/generated-sources/grpc/context/ContextOuterClass.java index 060e81a556893b1ca3c60928569983506bff3672..b1bccdeccf564b0d8d7bd2a8606f614b00ede972 100644 --- a/src/automation/target/generated-sources/grpc/context/ContextOuterClass.java +++ b/src/automation/target/generated-sources/grpc/context/ContextOuterClass.java @@ -177,6 +177,10 @@ public final class ContextOuterClass { * 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: +# +# Neither the name of the University of Bristol nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# This work has been performed in the context of DCMS UK 5G Testbeds +# & Trials Programme and in the framework of the Metro-Haul project - +# funded by the European Commission under Grant number 761727 through the +# Horizon 2020 and 5G-PPP programmes. +## +"""The SDN connector is responsible for establishing both wide area network connectivity (WIM) +and intranet SDN connectivity. + +It receives information from ports to be connected . +""" + +import logging +from http import HTTPStatus + + +class SdnConnectorError(Exception): + """Base Exception for all connector related errors + provide the parameter 'http_code' (int) with the error code: + Bad_Request = 400 + Unauthorized = 401 (e.g. credentials are not valid) + Not_Found = 404 (e.g. try to edit or delete a non existing connectivity service) + Forbidden = 403 + Method_Not_Allowed = 405 + Not_Acceptable = 406 + Request_Timeout = 408 (e.g timeout reaching server, or cannot reach the server) + Conflict = 409 + Service_Unavailable = 503 + Internal_Server_Error = 500 + """ + + def __init__(self, message, http_code=HTTPStatus.INTERNAL_SERVER_ERROR.value): + Exception.__init__(self, message) + self.http_code = http_code + + +class SdnConnectorBase(object): + """Abstract base class for all the SDN connectors + + Arguments: + wim (dict): WIM record, as stored in the database + wim_account (dict): WIM account record, as stored in the database + config + The arguments of the constructor are converted to object attributes. + An extra property, ``service_endpoint_mapping`` is created from ``config``. + """ + + def __init__(self, wim, wim_account, config=None, logger=None): + """ + :param wim: (dict). Contains among others 'wim_url' + :param wim_account: (dict). Contains among others 'uuid' (internal id), 'name', + 'sdn' (True if is intended for SDN-assist or False if intended for WIM), 'user', 'password'. + :param config: (dict or None): Particular information of plugin. These keys if present have a common meaning: + 'mapping_not_needed': (bool) False by default or if missing, indicates that mapping is not needed. + 'service_endpoint_mapping': (list) provides the internal endpoint mapping. The meaning is: + KEY meaning for WIM meaning for SDN assist + -------- -------- -------- + device_id pop_switch_dpid compute_id + device_interface_id pop_switch_port compute_pci_address + service_endpoint_id wan_service_endpoint_id SDN_service_endpoint_id + service_mapping_info wan_service_mapping_info SDN_service_mapping_info + contains extra information if needed. Text in Yaml format + switch_dpid wan_switch_dpid SDN_switch_dpid + switch_port wan_switch_port SDN_switch_port + datacenter_id vim_account vim_account + id: (internal, do not use) + wim_id: (internal, do not use) + :param logger (logging.Logger): optional logger object. If none is passed 'openmano.sdn.sdnconn' is used. + """ + self.logger = logger or logging.getLogger("ro.sdn") + self.wim = wim + self.wim_account = wim_account + self.config = config or {} + self.service_endpoint_mapping = self.config.get("service_endpoint_mapping", []) + + def check_credentials(self): + """Check if the connector itself can access the SDN/WIM with the provided url (wim.wim_url), + user (wim_account.user), and password (wim_account.password) + + Raises: + SdnConnectorError: Issues regarding authorization, access to + external URLs, etc are detected. + """ + raise NotImplementedError + + def get_connectivity_service_status(self, service_uuid, conn_info=None): + """Monitor the status of the connectivity service established + + Arguments: + service_uuid (str): UUID of the connectivity service + conn_info (dict or None): Information returned by the connector + during the service creation/edition and subsequently stored in + the database. + + Returns: + dict: JSON/YAML-serializable dict that contains a mandatory key + ``sdn_status`` associated with one of the following values:: + + {'sdn_status': 'ACTIVE'} + # The service is up and running. + + {'sdn_status': 'INACTIVE'} + # The service was created, but the connector + # cannot determine yet if connectivity exists + # (ideally, the caller needs to wait and check again). + + {'sdn_status': 'DOWN'} + # Connection was previously established, + # but an error/failure was detected. + + {'sdn_status': 'ERROR'} + # An error occurred when trying to create the service/ + # establish the connectivity. + + {'sdn_status': 'BUILD'} + # Still trying to create the service, the caller + # needs to wait and check again. + + Additionally ``error_msg``(**str**) and ``sdn_info``(**dict**) + keys can be used to provide additional status explanation or + new information available for the connectivity service. + """ + raise NotImplementedError + + def create_connectivity_service(self, service_type, connection_points, **kwargs): + """ + Establish SDN/WAN connectivity between the endpoints + :param service_type: (str): ``ELINE`` (L2), ``ELAN`` (L2), ``ETREE`` (L2), ``L3``. + :param connection_points: (list): each point corresponds to + an entry point to be connected. For WIM: from the DC to the transport network. + For SDN: Compute/PCI to the transport network. One + connection point serves to identify the specific access and + some other service parameters, such as encapsulation type. + Each item of the list is a dict with: + "service_endpoint_id": (str)(uuid) Same meaning that for 'service_endpoint_mapping' (see __init__) + In case the config attribute mapping_not_needed is True, this value is not relevant. In this case + it will contain the string "device_id:device_interface_id" + "service_endpoint_encapsulation_type": None, "dot1q", ... + "service_endpoint_encapsulation_info": (dict) with: + "vlan": ..., (int, present if encapsulation is dot1q) + "vni": ... (int, present if encapsulation is vxlan), + "peers": [(ipv4_1), (ipv4_2)] (present if encapsulation is vxlan) + "mac": ... + "device_id": ..., same meaning that for 'service_endpoint_mapping' (see __init__) + "device_interface_id": same meaning that for 'service_endpoint_mapping' (see __init__) + "switch_dpid": ..., present if mapping has been found for this device_id,device_interface_id + "swith_port": ... present if mapping has been found for this device_id,device_interface_id + "service_mapping_info": present if mapping has been found for this device_id,device_interface_id + :param kwargs: For future versions: + bandwidth (int): value in kilobytes + latency (int): value in milliseconds + Other QoS might be passed as keyword arguments. + :return: tuple: ``(service_id, conn_info)`` containing: + - *service_uuid* (str): UUID of the established connectivity service + - *conn_info* (dict or None): Information to be stored at the database (or ``None``). + This information will be provided to the :meth:`~.edit_connectivity_service` and :obj:`~.delete`. + **MUST** be JSON/YAML-serializable (plain data structures). + :raises: SdnConnectorException: In case of error. Nothing should be created in this case. + Provide the parameter http_code + """ + raise NotImplementedError + + def delete_connectivity_service(self, service_uuid, conn_info=None): + """ + Disconnect multi-site endpoints previously connected + + :param service_uuid: The one returned by create_connectivity_service + :param conn_info: The one returned by last call to 'create_connectivity_service' or 'edit_connectivity_service' + if they do not return None + :return: None + :raises: SdnConnectorException: In case of error. The parameter http_code must be filled + """ + raise NotImplementedError + + def edit_connectivity_service( + self, service_uuid, conn_info=None, connection_points=None, **kwargs + ): + """Change an existing connectivity service. + + This method's arguments and return value follow the same convention as + :meth:`~.create_connectivity_service`. + + :param service_uuid: UUID of the connectivity service. + :param conn_info: (dict or None): Information previously returned by last call to create_connectivity_service + or edit_connectivity_service + :param connection_points: (list): If provided, the old list of connection points will be replaced. + :param kwargs: Same meaning that create_connectivity_service + :return: dict or None: Information to be updated and stored at the database. + When ``None`` is returned, no information should be changed. + When an empty dict is returned, the database record will be deleted. + **MUST** be JSON/YAML-serializable (plain data structures). + Raises: + SdnConnectorException: In case of error. + """ + + def clear_all_connectivity_services(self): + """Delete all WAN Links in a WIM. + + This method is intended for debugging only, and should delete all the + connections controlled by the WIM/SDN, not only the connections that + a specific RO is aware of. + + Raises: + SdnConnectorException: In case of error. + """ + raise NotImplementedError + + def get_all_active_connectivity_services(self): + """Provide information about all active connections provisioned by a + WIM. + + Raises: + SdnConnectorException: In case of error. + """ + raise NotImplementedError diff --git a/src/device/service/drivers/microwave/Tools.py b/src/device/service/drivers/microwave/Tools.py index 711fb55fd4bd9e1bcb16e851aa73f3a61f4bf4bd..4490c0f63fe6a517e5f31a5acd62208013bbaad0 100644 --- a/src/device/service/drivers/microwave/Tools.py +++ b/src/device/service/drivers/microwave/Tools.py @@ -14,7 +14,7 @@ import json, logging, requests from requests.auth import HTTPBasicAuth -from typing import Optional, Set +from typing import Dict, Optional, Set from device.service.driver_api._Driver import RESOURCE_ENDPOINTS LOGGER = logging.getLogger(__name__) @@ -43,6 +43,14 @@ def is_exportable_endpoint(node, termination_point_id, links): return False return True +VLAN_CLASSIFICATION_TYPES = {'ietf-eth-tran-types:vlan-classification', 'vlan-classification'} +OUTER_TAG_C_TYPE = {'ietf-eth-tran-types:classify-c-vlan', 'classify-c-vlan'} +def get_vlan_outer_tag(endpoint : Dict) -> Optional[int]: + if endpoint.get('service-classification-type', '') not in VLAN_CLASSIFICATION_TYPES: return None + outer_tag = endpoint.get('outer-tag', {}) + if outer_tag.get('tag-type', '') not in OUTER_TAG_C_TYPE: return None + return outer_tag.get('vlan-value') + def config_getter( root_url : str, resource_key : str, auth : Optional[HTTPBasicAuth] = None, timeout : Optional[int] = None, node_ids : Set[str] = set() @@ -92,7 +100,35 @@ def config_getter( for service in service_instances: service_name = service['etht-svc-name'] resource_key = '/services/service[{:s}]'.format(service_name) - result.append((resource_key, service)) + resource_value = {'uuid': service.get('etht-svc-name', '')} + + for endpoint in service.get('etht-svc-end-points', []): + _vlan_id = get_vlan_outer_tag(endpoint) + if _vlan_id is not None: + vlan_id = resource_value.get('vlan_id') + if vlan_id is None: + resource_value['vlan_id'] = _vlan_id + elif vlan_id != _vlan_id: + raise Exception('Incompatible VLAN Ids: {:s}'.format(str(service))) + access_points = endpoint.get('etht-svc-access-points', []) + for access_point in access_points: + if access_point['access-point-id'] == '1': + resource_value['node_id_src'] = access_point['access-node-id'] + resource_value['tp_id_src'] = access_point['access-ltp-id'] + elif access_point['access-point-id'] == '2': + resource_value['node_id_dst'] = access_point['access-node-id'] + resource_value['tp_id_dst'] = access_point['access-ltp-id'] + + if len(node_ids) > 0: + node_id_src = resource_value.get('node_id_src') + if node_id_src is None: continue + if node_id_src not in node_ids: continue + + node_id_dst = resource_value.get('node_id_dst') + if node_id_dst is None: continue + if node_id_dst not in node_ids: continue + + result.append((resource_key, resource_value)) except requests.exceptions.Timeout: LOGGER.exception('Timeout connecting {:s}'.format(url)) except Exception as e: # pylint: disable=broad-except diff --git a/src/device/service/drivers/openconfig/OpenConfigDriver.py b/src/device/service/drivers/openconfig/OpenConfigDriver.py index ac03527529b603089c4f8233cb185f6427e0c360..569a0400ee709ebe3b0fa0694dd80801efc4f7fa 100644 --- a/src/device/service/drivers/openconfig/OpenConfigDriver.py +++ b/src/device/service/drivers/openconfig/OpenConfigDriver.py @@ -113,9 +113,11 @@ class NetconfSessionHandler: config, target=target, default_operation=default_operation, test_option=test_option, error_option=error_option, format=format) + @RETRY_DECORATOR def locked(self, target): return self.__manager.locked(target=target) + @RETRY_DECORATOR def commit(self, confirmed=False, timeout=None, persist=None, persist_id=None): return self.__manager.commit(confirmed=confirmed, timeout=timeout, persist=persist, persist_id=persist_id) diff --git a/src/device/service/drivers/openconfig/templates/EndPoints.py b/src/device/service/drivers/openconfig/templates/EndPoints.py index 02fda8f0e195c267fddb1109f184c8a06e4a6787..f16f0ffcd09a07f6c109328b1c5f0ee101af545a 100644 --- a/src/device/service/drivers/openconfig/templates/EndPoints.py +++ b/src/device/service/drivers/openconfig/templates/EndPoints.py @@ -55,5 +55,5 @@ def parse(xml_data : ET.Element) -> List[Tuple[str, Dict[str, Any]]]: add_value_from_collection(endpoint, 'sample_types', sample_types) if len(endpoint) == 0: continue - response.append(('/endpoint[{:s}]'.format(endpoint['uuid']), endpoint)) + response.append(('/endpoints/endpoint[{:s}]'.format(endpoint['uuid']), endpoint)) return response diff --git a/src/device/service/drivers/transport_api/Tools.py b/src/device/service/drivers/transport_api/Tools.py index 4943648dcab21159b213f9ee938987995be61b0e..bbd4247f0debdd17885c5aadccafc32607e4cbe5 100644 --- a/src/device/service/drivers/transport_api/Tools.py +++ b/src/device/service/drivers/transport_api/Tools.py @@ -15,7 +15,7 @@ import json, logging, operator, requests from requests.auth import HTTPBasicAuth from typing import Optional -from device.service.driver_api._Driver import RESOURCE_ENDPOINTS +from device.service.driver_api._Driver import RESOURCE_ENDPOINTS, RESOURCE_SERVICES LOGGER = logging.getLogger(__name__) @@ -52,27 +52,74 @@ def config_getter( result.append((resource_key, e)) return result - if resource_key != RESOURCE_ENDPOINTS: return result - - if 'tapi-common:context' in context: - context = context['tapi-common:context'] - elif 'context' in context: - context = context['context'] - - for sip in context['service-interface-point']: - layer_protocol_name = sip.get('layer-protocol-name', '?') - supportable_spectrum = sip.get('tapi-photonic-media:media-channel-service-interface-point-spec', {}) - supportable_spectrum = supportable_spectrum.get('mc-pool', {}) - supportable_spectrum = supportable_spectrum.get('supportable-spectrum', []) - supportable_spectrum = supportable_spectrum[0] if len(supportable_spectrum) == 1 else {} - grid_type = supportable_spectrum.get('frequency-constraint', {}).get('grid-type') - granularity = supportable_spectrum.get('frequency-constraint', {}).get('adjustment-granularity') - direction = sip.get('direction', '?') - endpoint_type = [layer_protocol_name, grid_type, granularity, direction] - str_endpoint_type = ':'.join(filter(lambda i: operator.is_not(i, None), endpoint_type)) - endpoint_url = '/endpoints/endpoint[{:s}]'.format(sip['uuid']) - endpoint_data = {'uuid': sip['uuid'], 'type': str_endpoint_type} - result.append((endpoint_url, endpoint_data)) + if resource_key == RESOURCE_ENDPOINTS: + if 'tapi-common:context' in context: + context = context['tapi-common:context'] + elif 'context' in context: + context = context['context'] + + for sip in context['service-interface-point']: + layer_protocol_name = sip.get('layer-protocol-name', '?') + supportable_spectrum = sip.get('tapi-photonic-media:media-channel-service-interface-point-spec', {}) + supportable_spectrum = supportable_spectrum.get('mc-pool', {}) + supportable_spectrum = supportable_spectrum.get('supportable-spectrum', []) + supportable_spectrum = supportable_spectrum[0] if len(supportable_spectrum) == 1 else {} + grid_type = supportable_spectrum.get('frequency-constraint', {}).get('grid-type') + granularity = supportable_spectrum.get('frequency-constraint', {}).get('adjustment-granularity') + direction = sip.get('direction', '?') + + endpoint_type = [layer_protocol_name, grid_type, granularity, direction] + str_endpoint_type = ':'.join(filter(lambda i: operator.is_not(i, None), endpoint_type)) + sip_uuid = sip['uuid'] + + sip_names = sip.get('name', []) + sip_name = next(iter([ + sip_name['value'] + for sip_name in sip_names + if sip_name['value-name'] == 'local-name' + ]), sip_uuid) + + endpoint_url = '/endpoints/endpoint[{:s}]'.format(sip_uuid) + endpoint_data = {'uuid': sip_uuid, 'name': sip_name, 'type': str_endpoint_type} + result.append((endpoint_url, endpoint_data)) + + elif resource_key == RESOURCE_SERVICES: + if 'tapi-common:context' in context: + context = context['tapi-common:context'] + elif 'context' in context: + context = context['context'] + + if 'tapi-connectivity:connectivity-context' in context: + context = context['tapi-connectivity:connectivity-context'] + elif 'connectivity-context' in context: + context = context['connectivity-context'] + + for conn_svc in context['connectivity-service']: + service_uuid = conn_svc['uuid'] + constraints = conn_svc.get('connectivity-constraint', {}) + total_req_cap = constraints.get('requested-capacity', {}).get('total-size', {}) + + service_url = '/services/service[{:s}]'.format(service_uuid) + service_data = { + 'uuid': service_uuid, + 'direction': constraints.get('connectivity-direction', 'UNIDIRECTIONAL'), + 'capacity_unit': total_req_cap.get('unit', ''), + 'capacity_value': total_req_cap.get('value', ''), + } + + for i,endpoint in enumerate(conn_svc.get('end-point', [])): + layer_protocol_name = endpoint.get('layer-protocol-name') + if layer_protocol_name is not None: + service_data['layer_protocol_name'] = layer_protocol_name + + layer_protocol_qualifier = endpoint.get('layer-protocol-qualifier') + if layer_protocol_qualifier is not None: + service_data['layer_protocol_qualifier'] = layer_protocol_qualifier + + sip = endpoint['service-interface-point']['service-interface-point-uuid'] + service_data['input_sip' if i == 0 else 'output_sip'] = sip + + result.append((service_url, service_data)) return result diff --git a/src/device/service/drivers/transport_api/TransportApiDriver.py b/src/device/service/drivers/transport_api/TransportApiDriver.py index 8b84274e075e10af04924cefa03768d1c340fb52..1991a34d0d797c48b6c2296435c0ebd0f3a8125a 100644 --- a/src/device/service/drivers/transport_api/TransportApiDriver.py +++ b/src/device/service/drivers/transport_api/TransportApiDriver.py @@ -85,9 +85,9 @@ class TransportApiDriver(_Driver): for resource in resources: LOGGER.info('resource = {:s}'.format(str(resource))) - input_sip = find_key(resource, 'input_sip') - output_sip = find_key(resource, 'output_sip') uuid = find_key(resource, 'uuid') + input_sip = find_key(resource, 'input_sip_uuid') + output_sip = find_key(resource, 'output_sip_uuid') capacity_value = find_key(resource, 'capacity_value') capacity_unit = find_key(resource, 'capacity_unit') layer_protocol_name = find_key(resource, 'layer_protocol_name') diff --git a/src/device/service/drivers/transport_api/__init__.py b/src/device/service/drivers/transport_api/__init__.py index 2d3f6df3276f063cd9b414f47bba41b656682049..d5073c330b89bed63f08b0da86c4a7649c87b3dd 100644 --- a/src/device/service/drivers/transport_api/__init__.py +++ b/src/device/service/drivers/transport_api/__init__.py @@ -12,16 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -from device.service.driver_api._Driver import RESOURCE_ENDPOINTS, RESOURCE_INTERFACES, RESOURCE_NETWORK_INSTANCES +from device.service.driver_api._Driver import RESOURCE_ENDPOINTS, RESOURCE_SERVICES ALL_RESOURCE_KEYS = [ RESOURCE_ENDPOINTS, - RESOURCE_INTERFACES, - RESOURCE_NETWORK_INSTANCES, + RESOURCE_SERVICES, ] - -RESOURCE_KEY_MAPPINGS = { - RESOURCE_ENDPOINTS : 'component', - RESOURCE_INTERFACES : 'interface', - RESOURCE_NETWORK_INSTANCES: 'network_instance', -} diff --git a/src/device/service/drivers/xr/XrDriver.py b/src/device/service/drivers/xr/XrDriver.py index 605f4ce8d0f9c875a4b1736ff0aaa02fcb468778..2b53de8e71f84aada460f444a3663238290f6ea5 100644 --- a/src/device/service/drivers/xr/XrDriver.py +++ b/src/device/service/drivers/xr/XrDriver.py @@ -16,10 +16,13 @@ import logging import threading import json -from typing import Any, Iterator, List, Optional, Tuple, Union +from typing import Any, Iterator, List, Optional, Set, Tuple, Union import urllib3 +from common.DeviceTypes import DeviceTypeEnum from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method +from common.proto.context_pb2 import DeviceDriverEnum, DeviceOperationalStatusEnum from common.type_checkers.Checkers import chk_type +from device.service.driver_api.ImportTopologyEnum import ImportTopologyEnum, get_import_topology from device.service.driver_api._Driver import _Driver from .cm.cm_connection import CmConnection, ConsistencyMode from .cm import tf @@ -46,6 +49,14 @@ class XrDriver(_Driver): username = settings.get("username", "xr-user-1") password = settings.get("password", "xr-user-1") + # Options are: + # disabled --> just import endpoints as usual + # devices --> imports sub-devices but not links connecting them. + # (a remotely-controlled transport domain might exist between them) + # topology --> imports sub-devices and links connecting them. + # (not supported by XR driver) + self.__import_topology = get_import_topology(settings, default=ImportTopologyEnum.DISABLED) + # Options are: # asynchronous --> operation considered complete when IPM responds with suitable status code, # including "accepted", that only means request is semantically good and queued. @@ -98,7 +109,56 @@ class XrDriver(_Driver): constellation = self.__cm_connection.get_constellation_by_hub_name(self.__hub_module_name) if constellation: self.__constellation = constellation - return [(f"/endpoints/endpoint[{ifname}]", {'uuid': ifname, 'type': 'optical', 'sample_types': {}}) for ifname in constellation.ifnames()] + if self.__import_topology == ImportTopologyEnum.DISABLED: + return [ + (f"/endpoints/endpoint[{ifname}]", {'uuid': ifname, 'type': 'optical', 'sample_types': {}}) + for ifname in constellation.ifnames() + ] + elif self.__import_topology == ImportTopologyEnum.DEVICES: + devices : Set[str] = set() + pluggables : Set[str] = set() + devices_and_endpoints = [] + for ifname in constellation.ifnames(): + device_name,pluggable_name = ifname.split('|') + + if device_name not in devices: + device_url = '/devices/device[{:s}]'.format(device_name) + device_data = { + 'uuid': device_name, 'name': device_name, + 'type': DeviceTypeEnum.EMULATED_PACKET_ROUTER.value, + 'status': DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED, + 'drivers': [DeviceDriverEnum.DEVICEDRIVER_UNDEFINED], + } + devices_and_endpoints.append((device_url, device_data)) + + for copper_if_index in range(4): + copper_ifname = '1/{:d}'.format(copper_if_index + 1) + endpoint_url = '/endpoints/endpoint[{:s}]'.format(copper_ifname) + endpoint_data = { + 'device_uuid': device_name, 'uuid': copper_ifname, 'name': copper_ifname, + 'type': 'copper/internal', 'sample_types': {} + } + devices_and_endpoints.append((endpoint_url, endpoint_data)) + + devices.add(device_name) + + if ifname not in pluggables: + endpoint_url = '/endpoints/endpoint[{:s}]'.format(ifname) + if 'hub' in ifname.lower(): + endpoint_type = 'optical/xr-hub' + elif 'leaf' in ifname.lower(): + endpoint_type = 'optical/xr-leaf' + else: + endpoint_type = 'optical/xr' + endpoint_data = { + 'device_uuid': device_name, 'uuid': pluggable_name, 'name': pluggable_name, + 'type': endpoint_type, 'sample_types': {} + } + devices_and_endpoints.append((endpoint_url, endpoint_data)) + + return devices_and_endpoints + else: + raise Exception('Unsupported import_topology mode: {:s}'.format(str(self.__import_topology))) else: return [] diff --git a/src/device/service/drivers/xr/cm-cli.py b/src/device/service/drivers/xr/cm-cli.py index 924ca0c966bbefd8b72c655ea788bdfd0ed08c5d..14c6d24b6da05a6f506152d0099d4739c2858e2c 100755 --- a/src/device/service/drivers/xr/cm-cli.py +++ b/src/device/service/drivers/xr/cm-cli.py @@ -160,8 +160,8 @@ if args.emulate_tf_set_config_service: hub_module_name, uuid, input_sip, output_sip, capacity_value = eargs[0:5] capacity_value = int(capacity_value) config = { - "input_sip": input_sip, - "output_sip": output_sip, + "input_sip_name": input_sip, + "output_sip_name": output_sip, "capacity_value": capacity_value, "capacity_unit": "gigabit" } diff --git a/src/device/service/drivers/xr/cm/tests/test_xr_service_set_config.py b/src/device/service/drivers/xr/cm/tests/test_xr_service_set_config.py index e9b16b62034bcd42061907d920b757b59766f562..05f55d0df471115ea2e5ace7c3014333aa94e0fe 100644 --- a/src/device/service/drivers/xr/cm/tests/test_xr_service_set_config.py +++ b/src/device/service/drivers/xr/cm/tests/test_xr_service_set_config.py @@ -44,8 +44,8 @@ def mock_cm(): uuid = "12345ABCDEFGHIJKLMN" config = { - "input_sip": "XR HUB 1|XR-T4;", - "output_sip": "XR LEAF 1|XR-T1", + "input_sip_name": "XR HUB 1|XR-T4;", + "output_sip_name": "XR LEAF 1|XR-T1", "capacity_value": 125, "capacity_unit": "gigabit" } diff --git a/src/device/service/drivers/xr/cm/tf.py b/src/device/service/drivers/xr/cm/tf.py index c44cb0c9f3ce0e755ce375908a520374e639e40f..ace3cd2888809e2b498a82b1ade9983419033c20 100644 --- a/src/device/service/drivers/xr/cm/tf.py +++ b/src/device/service/drivers/xr/cm/tf.py @@ -38,7 +38,7 @@ def _get_capacity(config) -> int: def set_config_for_service(cm_connection: CmConnection, constellation: Constellation, uuid: str, config: Dict[str, any]) -> Union[bool, Exception]: try: - service = TFService(uuid, config["input_sip"], config["output_sip"], _get_capacity(config)) + service = TFService(uuid, config["input_sip_name"], config["output_sip_name"], _get_capacity(config)) if constellation.is_vti_mode(): desired_tc = TransportCapacity(from_tf_service=service) active_tc = cm_connection.get_transport_capacity_by_name(service.name()) diff --git a/src/pathcomp/backend/pathComp_RESTapi.c b/src/pathcomp/backend/pathComp_RESTapi.c index 8ee7f6d82537b7eab297334a0a0dcfad13f8af44..82d4b38a840cf813dab850c4cb136ff05b503cbd 100644 --- a/src/pathcomp/backend/pathComp_RESTapi.c +++ b/src/pathcomp/backend/pathComp_RESTapi.c @@ -1211,7 +1211,9 @@ void parsing_json_obj_pathComp_request(cJSON * root, GIOChannel * source) // In the context information, if solely the list of links are passed for a single direction, // the reverse direction MUST be created sythetically - generate_reverse_linkList(); + + // LGR: deactivated; link duplication needs to be done smartly with TAPI. done manually in topology by now + //generate_reverse_linkList(); } return; } diff --git a/src/pathcomp/backend/pathComp_sp.c b/src/pathcomp/backend/pathComp_sp.c index 447b0d2a6d002d12808f80c855c74f8d0b489743..b143b04933f1ac9099af3edf3af087cc58e32c5b 100644 --- a/src/pathcomp/backend/pathComp_sp.c +++ b/src/pathcomp/backend/pathComp_sp.c @@ -296,8 +296,8 @@ void sp_execution_services(struct compRouteOutputList_t* oPathList) continue; } struct path_t* path = &(pathService->paths[pathService->numPaths - 1]); - allocate_graph_resources(path, service, g); - allocate_graph_reverse_resources(path, service, g); + //allocate_graph_resources(path, service, g); // LGR: crashes in some cases with assymetric topos + //allocate_graph_reverse_resources(path, service, g); // LGR: crashes in some cases with assymetric topos print_graph(g); } return; diff --git a/src/pathcomp/backend/pathComp_tools.h b/src/pathcomp/backend/pathComp_tools.h index b6bcea04c8aa01b6cf730460e0075327f872f344..b770788910a04f76a8625a7e2d74fca5f2f6ecad 100644 --- a/src/pathcomp/backend/pathComp_tools.h +++ b/src/pathcomp/backend/pathComp_tools.h @@ -117,7 +117,7 @@ struct map_nodes_t { }; #define MAX_NUM_VERTICES 20 // 100 # LGR: reduced from 100 to 20 to divide by 5 the memory used -#define MAX_NUM_EDGES 40 // 100 # LGR: reduced from 100 to 40 to divide by 2.5 the memory used +#define MAX_NUM_EDGES 5 // 100 # LGR: reduced from 100 to 5 to divide by 20 the memory used // Structures for the graph composition struct targetNodes_t { // remote / targeted node @@ -247,7 +247,7 @@ struct endPoint_t { // Structure for the device contents /////////////////////////////////////////////////////////////////// #define MAX_DEV_TYPE_SIZE 128 -#define MAX_DEV_ENDPOINT_LENGTH 40 // 10 # LGR: controllers might have large number of endpoints +#define MAX_DEV_ENDPOINT_LENGTH 50 // 10 # LGR: controllers might have large number of endpoints struct device_t { gchar deviceId[UUID_CHAR_LENGTH]; // device ID using UUID (128 bits) diff --git a/src/pathcomp/frontend/service/algorithms/_Algorithm.py b/src/pathcomp/frontend/service/algorithms/_Algorithm.py index b6316774921171eb8ed6cf3faafd4b607bdcb831..b486ec1b59457b1ac575fb6197c7713b10c306e3 100644 --- a/src/pathcomp/frontend/service/algorithms/_Algorithm.py +++ b/src/pathcomp/frontend/service/algorithms/_Algorithm.py @@ -15,17 +15,20 @@ import json, logging, requests from typing import Dict, List, Optional, Tuple, Union from common.proto.context_pb2 import ( - ConfigRule, Connection, Device, DeviceList, EndPointId, Link, LinkList, Service, ServiceStatusEnum, - ServiceTypeEnum) + Connection, Device, DeviceList, EndPointId, Link, LinkList, Service, ServiceStatusEnum, ServiceTypeEnum) from common.proto.pathcomp_pb2 import PathCompReply, PathCompRequest -from common.tools.object_factory.ConfigRule import json_config_rule_set from pathcomp.frontend.Config import BACKEND_URL -from pathcomp.frontend.service.algorithms.tools.ConstantsMappings import DEVICE_LAYER_TO_SERVICE_TYPE, DeviceLayerEnum from .tools.EroPathToHops import eropath_to_hops +from .tools.ComposeConfigRules import ( + compose_device_config_rules, compose_l2nm_config_rules, compose_l3nm_config_rules, compose_tapi_config_rules) from .tools.ComposeRequest import compose_device, compose_link, compose_service from .tools.ComputeSubServices import ( convert_explicit_path_hops_to_connections, convert_explicit_path_hops_to_plain_connection) +SRC_END = 'src' +DST_END = 'dst' +SENSE = [SRC_END, DST_END] + class _Algorithm: def __init__(self, algorithm_id : str, sync_paths : bool, class_name=__name__) -> None: # algorithm_id: algorithm to be executed @@ -45,7 +48,7 @@ class _Algorithm: self.endpoint_name_mapping : Dict[Tuple[str, str], str] = dict() self.link_list : List[Dict] = list() self.link_dict : Dict[str, Tuple[Dict, Link]] = dict() - self.endpoint_to_link_dict : Dict[Tuple[str, str], Tuple[Dict, Link]] = dict() + self.endpoint_to_link_dict : Dict[Tuple[str, str, str], Tuple[Dict, Link]] = dict() self.service_list : List[Dict] = list() self.service_dict : Dict[Tuple[str, str], Tuple[Dict, Service]] = dict() @@ -61,6 +64,7 @@ class _Algorithm: _device_uuid = grpc_device.device_id.device_uuid.uuid _device_name = grpc_device.name self.device_name_mapping[_device_name] = _device_uuid + self.device_name_mapping[_device_uuid] = _device_uuid device_endpoint_dict : Dict[str, Tuple[Dict, EndPointId]] = dict() for json_endpoint,grpc_endpoint in zip(json_device['device_endpoints'], grpc_device.device_endpoints): @@ -72,12 +76,16 @@ class _Algorithm: _endpoint_name = grpc_endpoint.name self.endpoint_name_mapping[(_device_uuid, _endpoint_name)] = _endpoint_uuid self.endpoint_name_mapping[(_device_name, _endpoint_name)] = _endpoint_uuid + self.endpoint_name_mapping[(_device_uuid, _endpoint_uuid)] = _endpoint_uuid + self.endpoint_name_mapping[(_device_name, _endpoint_uuid)] = _endpoint_uuid self.endpoint_dict[device_uuid] = device_endpoint_dict def add_links(self, grpc_links : Union[List[Link], LinkList]) -> None: if isinstance(grpc_links, LinkList): grpc_links = grpc_links.links for grpc_link in grpc_links: + if 'mgmt' in grpc_link.name.lower(): continue + json_link = compose_link(grpc_link) if len(json_link['link_endpoint_ids']) != 2: continue self.link_list.append(json_link) @@ -85,11 +93,11 @@ class _Algorithm: link_uuid = json_link['link_Id'] self.link_dict[link_uuid] = (json_link, grpc_link) - for link_endpoint_id in json_link['link_endpoint_ids']: + for i,link_endpoint_id in enumerate(json_link['link_endpoint_ids']): link_endpoint_id = link_endpoint_id['endpoint_id'] device_uuid = link_endpoint_id['device_id'] endpoint_uuid = link_endpoint_id['endpoint_uuid'] - endpoint_key = (device_uuid, endpoint_uuid) + endpoint_key = (device_uuid, endpoint_uuid, SENSE[i]) link_tuple = (json_link, grpc_link) self.endpoint_to_link_dict[endpoint_key] = link_tuple @@ -148,9 +156,8 @@ class _Algorithm: return connection def add_service_to_reply( - self, reply : PathCompReply, context_uuid : str, service_uuid : str, - device_layer : Optional[DeviceLayerEnum] = None, path_hops : List[Dict] = [], - config_rules : List = [] + self, reply : PathCompReply, context_uuid : str, service_uuid : str, service_type : ServiceTypeEnum, + path_hops : List[Dict] = [], config_rules : List = [] ) -> Service: # TODO: implement support for multi-point services # Control deactivated to enable disjoint paths with multiple redundant endpoints on each side @@ -159,44 +166,43 @@ class _Algorithm: service_key = (context_uuid, service_uuid) tuple_service = self.service_dict.get(service_key) - if tuple_service is not None: - service = reply.services.add() - service.CopyFrom(tuple_service[1]) + + service = reply.services.add() + service.service_id.context_id.context_uuid.uuid = context_uuid + service.service_id.service_uuid.uuid = service_uuid + service.service_type = service_type + + if service_type == ServiceTypeEnum.SERVICETYPE_L2NM: + compose_l2nm_config_rules(config_rules, service.service_config.config_rules) + elif service_type == ServiceTypeEnum.SERVICETYPE_L3NM: + compose_l3nm_config_rules(config_rules, service.service_config.config_rules) + elif service_type == ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE: + compose_tapi_config_rules(config_rules, service.service_config.config_rules) else: - service = reply.services.add() - service.service_id.context_id.context_uuid.uuid = context_uuid - service.service_id.service_uuid.uuid = service_uuid - - if device_layer is not None: - service_type = DEVICE_LAYER_TO_SERVICE_TYPE.get(device_layer.value) - if service_type is None: - MSG = 'Unable to map DeviceLayer({:s}) to ServiceType' - raise Exception(MSG.format(str(device_layer))) - service.service_type = service_type - - if service_type == ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE: - json_tapi_settings = { - 'capacity_value' : 50.0, - 'capacity_unit' : 'GHz', - 'layer_proto_name': 'PHOTONIC_MEDIA', - 'layer_proto_qual': 'tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_NMC', - 'direction' : 'UNIDIRECTIONAL', - } - config_rule = ConfigRule(**json_config_rule_set('/settings', json_tapi_settings)) - service.service_config.config_rules.append(config_rule) - else: - service.service_config.config_rules.extend(config_rules) + MSG = 'Unhandled generic Config Rules for service {:s} {:s}' + self.logger.warning(MSG.format(str(service_uuid), str(ServiceTypeEnum.Name(service_type)))) - service.service_status.service_status = ServiceStatusEnum.SERVICESTATUS_PLANNED + compose_device_config_rules( + config_rules, service.service_config.config_rules, path_hops, + self.device_name_mapping, self.endpoint_name_mapping) - if path_hops is not None and len(path_hops) > 0: - ingress_endpoint_id = service.service_endpoint_ids.add() - ingress_endpoint_id.device_id.device_uuid.uuid = path_hops[0]['device'] - ingress_endpoint_id.endpoint_uuid.uuid = path_hops[0]['ingress_ep'] + if path_hops is not None and len(path_hops) > 0: + ingress_endpoint_id = service.service_endpoint_ids.add() + ingress_endpoint_id.device_id.device_uuid.uuid = path_hops[0]['device'] + ingress_endpoint_id.endpoint_uuid.uuid = path_hops[0]['ingress_ep'] - egress_endpoint_id = service.service_endpoint_ids.add() - egress_endpoint_id.device_id.device_uuid.uuid = path_hops[-1]['device'] - egress_endpoint_id.endpoint_uuid.uuid = path_hops[-1]['egress_ep'] + egress_endpoint_id = service.service_endpoint_ids.add() + egress_endpoint_id.device_id.device_uuid.uuid = path_hops[-1]['device'] + egress_endpoint_id.endpoint_uuid.uuid = path_hops[-1]['egress_ep'] + + if tuple_service is None: + service.service_status.service_status = ServiceStatusEnum.SERVICESTATUS_PLANNED + else: + service.name = tuple_service[1].name + service.service_status.CopyFrom(tuple_service[1].service_status) + service.timestamp.CopyFrom(tuple_service[1].timestamp) + for constraint in tuple_service[1].service_constraints: + service.service_constraints.add().CopyFrom(constraint) return service @@ -206,41 +212,54 @@ class _Algorithm: grpc_services : Dict[Tuple[str, str], Service] = {} grpc_connections : Dict[str, Connection] = {} for response in response_list: - service_id = response['serviceId'] - context_uuid = service_id['contextId'] - service_uuid = service_id['service_uuid'] - service_key = (context_uuid, service_uuid) - upper_service = self.add_service_to_reply(reply, context_uuid, service_uuid) - grpc_services[service_key] = upper_service + orig_service_id = response['serviceId'] + context_uuid = orig_service_id['contextId'] + main_service_uuid = orig_service_id['service_uuid'] + orig_service_key = (context_uuid, main_service_uuid) + _,grpc_orig_service = self.service_dict[orig_service_key] + main_service_type = grpc_orig_service.service_type no_path_issue = response.get('noPath', {}).get('issue') if no_path_issue is not None: # no path found: leave connection with no endpoints # no_path_issue == 1 => no path due to a constraint + grpc_services[orig_service_key] = grpc_orig_service continue + orig_config_rules = grpc_orig_service.service_config.config_rules + for service_path_ero in response['path']: + self.logger.debug('service_path_ero["devices"] = {:s}'.format(str(service_path_ero['devices']))) + _endpoint_to_link_dict = {k:v[0] for k,v in self.endpoint_to_link_dict.items()} + self.logger.debug('self.endpoint_to_link_dict = {:s}'.format(str(_endpoint_to_link_dict))) path_hops = eropath_to_hops(service_path_ero['devices'], self.endpoint_to_link_dict) + self.logger.debug('path_hops = {:s}'.format(str(path_hops))) try: - connections = convert_explicit_path_hops_to_connections(path_hops, self.device_dict, service_uuid) + _device_dict = {k:v[0] for k,v in self.device_dict.items()} + self.logger.debug('self.device_dict = {:s}'.format(str(_device_dict))) + connections = convert_explicit_path_hops_to_connections( + path_hops, self.device_dict, main_service_uuid, main_service_type) + self.logger.debug('EXTRAPOLATED connections = {:s}'.format(str(connections))) except: # pylint: disable=bare-except - # if not able to extrapolate sub-services and sub-connections, - # assume single service and single connection - connections = convert_explicit_path_hops_to_plain_connection(path_hops, service_uuid) + MSG = ' '.join([ + 'Unable to Extrapolate sub-services and sub-connections.', + 'Assuming single-service and single-connection.', + ]) + self.logger.exception(MSG) + connections = convert_explicit_path_hops_to_plain_connection( + path_hops, main_service_uuid, main_service_type) + self.logger.debug('BASIC connections = {:s}'.format(str(connections))) for connection in connections: - connection_uuid,device_layer,path_hops,_ = connection + connection_uuid,service_type,path_hops,_ = connection service_key = (context_uuid, connection_uuid) - grpc_service = grpc_services.get(service_key) - if grpc_service is None: - config_rules = upper_service.service_config.config_rules - grpc_service = self.add_service_to_reply( - reply, context_uuid, connection_uuid, device_layer=device_layer, path_hops=path_hops, - config_rules=config_rules) - grpc_services[service_key] = grpc_service + grpc_service = self.add_service_to_reply( + reply, context_uuid, connection_uuid, service_type, path_hops=path_hops, + config_rules=orig_config_rules) + grpc_services[service_key] = grpc_service for connection in connections: - connection_uuid,device_layer,path_hops,dependencies = connection + connection_uuid,_,path_hops,dependencies = connection service_key = (context_uuid, connection_uuid) grpc_service = grpc_services.get(service_key) @@ -251,8 +270,8 @@ class _Algorithm: grpc_connection = self.add_connection_to_reply(reply, connection_uuid, grpc_service, path_hops) grpc_connections[connection_uuid] = grpc_connection - for service_uuid in dependencies: - sub_service_key = (context_uuid, service_uuid) + for sub_service_uuid in dependencies: + sub_service_key = (context_uuid, sub_service_uuid) grpc_sub_service = grpc_services.get(sub_service_key) if grpc_sub_service is None: raise Exception('Service({:s}) not found'.format(str(sub_service_key))) diff --git a/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py b/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py new file mode 100644 index 0000000000000000000000000000000000000000..91367e23f29a02aa3e9605fcd0d2864b9191d800 --- /dev/null +++ b/src/pathcomp/frontend/service/algorithms/tools/ComposeConfigRules.py @@ -0,0 +1,101 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import itertools, json, re +from typing import Dict, List, Optional, Tuple +from common.proto.context_pb2 import ConfigRule +from common.tools.object_factory.ConfigRule import json_config_rule_set + +SETTINGS_RULE_NAME = '/settings' + +DEV_EP_SETTINGS = re.compile(r'\/device\[([^\]]+)\]\/endpoint\[([^\]]+)\]\/settings') + +L2NM_SETTINGS_FIELD_DEFAULTS = { + 'encapsulation_type': 'dot1q', + 'vlan_id' : 100, + 'mtu' : 1450, +} + +L3NM_SETTINGS_FIELD_DEFAULTS = { + 'encapsulation_type': 'dot1q', + 'vlan_id' : 100, + 'mtu' : 1450, +} + +TAPI_SETTINGS_FIELD_DEFAULTS = { + 'capacity_value' : 50.0, + 'capacity_unit' : 'GHz', + 'layer_proto_name': 'PHOTONIC_MEDIA', + 'layer_proto_qual': 'tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_NMC', + 'direction' : 'UNIDIRECTIONAL', +} + +def find_custom_config_rule(config_rules : List, resource_name : str) -> Optional[Dict]: + resource_value : Optional[Dict] = None + for config_rule in config_rules: + if config_rule.WhichOneof('config_rule') != 'custom': continue + if config_rule.custom.resource_key != resource_name: continue + resource_value = json.loads(config_rule.custom.resource_value) + return resource_value + +def compose_config_rules( + main_service_config_rules : List, subservice_config_rules : List, field_defaults : Dict +) -> None: + settings = find_custom_config_rule(main_service_config_rules, SETTINGS_RULE_NAME) + if settings is None: return + + json_settings = {} + for field_name,default_value in field_defaults.items(): + json_settings[field_name] = settings.get(field_name, default_value) + + config_rule = ConfigRule(**json_config_rule_set('/settings', json_settings)) + subservice_config_rules.append(config_rule) + +def compose_l2nm_config_rules(main_service_config_rules : List, subservice_config_rules : List) -> None: + compose_config_rules(main_service_config_rules, subservice_config_rules, L2NM_SETTINGS_FIELD_DEFAULTS) + +def compose_l3nm_config_rules(main_service_config_rules : List, subservice_config_rules : List) -> None: + compose_config_rules(main_service_config_rules, subservice_config_rules, L3NM_SETTINGS_FIELD_DEFAULTS) + +def compose_tapi_config_rules(main_service_config_rules : List, subservice_config_rules : List) -> None: + compose_config_rules(main_service_config_rules, subservice_config_rules, TAPI_SETTINGS_FIELD_DEFAULTS) + +def compose_device_config_rules( + config_rules : List, subservice_config_rules : List, path_hops : List, + device_name_mapping : Dict[str, str], endpoint_name_mapping : Dict[Tuple[str, str], str] +) -> None: + + endpoints_traversed = set() + for path_hop in path_hops: + device_uuid_or_name = path_hop['device'] + endpoints_traversed.add((device_uuid_or_name, path_hop['ingress_ep'])) + endpoints_traversed.add((device_uuid_or_name, path_hop['egress_ep'])) + + for config_rule in config_rules: + if config_rule.WhichOneof('config_rule') != 'custom': continue + match = DEV_EP_SETTINGS.match(config_rule.custom.resource_key) + if match is None: continue + + device_uuid_or_name = match.group(1) + device_name_or_uuid = device_name_mapping[device_uuid_or_name] + device_keys = {device_uuid_or_name, device_name_or_uuid} + + endpoint_uuid_or_name = match.group(2) + endpoint_name_or_uuid_1 = endpoint_name_mapping[(device_uuid_or_name, endpoint_uuid_or_name)] + endpoint_name_or_uuid_2 = endpoint_name_mapping[(device_name_or_uuid, endpoint_uuid_or_name)] + endpoint_keys = {endpoint_uuid_or_name, endpoint_name_or_uuid_1, endpoint_name_or_uuid_2} + + device_endpoint_keys = set(itertools.product(device_keys, endpoint_keys)) + if len(device_endpoint_keys.intersection(endpoints_traversed)) == 0: continue + subservice_config_rules.append(config_rule) diff --git a/src/pathcomp/frontend/service/algorithms/tools/ComposeRequest.py b/src/pathcomp/frontend/service/algorithms/tools/ComposeRequest.py index ee85f0bb083500c655e78798bbcd2bd00e8a4501..e2c6dc13804703d89242b27156763ce887aa4884 100644 --- a/src/pathcomp/frontend/service/algorithms/tools/ComposeRequest.py +++ b/src/pathcomp/frontend/service/algorithms/tools/ComposeRequest.py @@ -118,11 +118,11 @@ def compose_link(grpc_link : Link) -> Dict: for link_endpoint_id in grpc_link.link_endpoint_ids ] - forwarding_direction = LinkForwardingDirection.BIDIRECTIONAL.value + forwarding_direction = LinkForwardingDirection.UNIDIRECTIONAL.value total_potential_capacity = compose_capacity(200, CapacityUnit.MBPS.value) available_capacity = compose_capacity(200, CapacityUnit.MBPS.value) cost_characteristics = compose_cost_characteristics('linkcost', '1', '0') - latency_characteristics = compose_latency_characteristics('2') + latency_characteristics = compose_latency_characteristics('1') return { 'link_Id': link_uuid, 'link_endpoint_ids': endpoint_ids, 'forwarding_direction': forwarding_direction, diff --git a/src/pathcomp/frontend/service/algorithms/tools/ComputeSubServices.py b/src/pathcomp/frontend/service/algorithms/tools/ComputeSubServices.py index b92a19b52c4887e01f7f1bc58de897c783683eeb..40cb0857617983df4cfd926baebcbff85e169894 100644 --- a/src/pathcomp/frontend/service/algorithms/tools/ComputeSubServices.py +++ b/src/pathcomp/frontend/service/algorithms/tools/ComputeSubServices.py @@ -30,55 +30,77 @@ # ] # # connections=[ -# (UUID('7548edf7-ee7c-4adf-ac0f-c7a0c0dfba8e'), , [ +# (UUID('7548edf7-ee7c-4adf-ac0f-c7a0c0dfba8e'), ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE, [ # {'device': 'TN-OLS', 'ingress_ep': '833760219d0f', 'egress_ep': 'cf176771a4b9'} # ], []), -# (UUID('c2e57966-5d82-4705-a5fe-44cf6487219e'), , [ +# (UUID('c2e57966-5d82-4705-a5fe-44cf6487219e'), ServiceTypeEnum.SERVICETYPE_L2NM, [ # {'device': 'CS1-GW1', 'ingress_ep': '10/1', 'egress_ep': '1/2'}, # {'device': 'TN-R2', 'ingress_ep': '1/2', 'egress_ep': '2/1'}, # {'device': 'TN-R3', 'ingress_ep': '2/1', 'egress_ep': '1/1'}, # {'device': 'CS2-GW1', 'ingress_ep': '1/1', 'egress_ep': '10/1'} # ], [UUID('7548edf7-ee7c-4adf-ac0f-c7a0c0dfba8e')]), -# (UUID('1e205c82-f6ea-4977-9e97-dc27ef1f4802'), , [ +# (UUID('1e205c82-f6ea-4977-9e97-dc27ef1f4802'), ServiceTypeEnum.SERVICETYPE_L2NM, [ # {'device': 'DC1-GW', 'ingress_ep': 'int', 'egress_ep': 'eth1'}, # {'device': 'DC2-GW', 'ingress_ep': 'eth1', 'egress_ep': 'int'} # ], [UUID('c2e57966-5d82-4705-a5fe-44cf6487219e')]) # ] -import queue, uuid -from typing import Dict, List, Tuple -from common.proto.context_pb2 import Device -from .ConstantsMappings import DEVICE_TYPE_TO_LAYER, DeviceLayerEnum +import logging, queue, uuid +from typing import Dict, List, Optional, Tuple +from common.DeviceTypes import DeviceTypeEnum +from common.proto.context_pb2 import Device, ServiceTypeEnum +from .ResourceGroups import IGNORED_DEVICE_TYPES, get_resource_classification +from .ServiceTypes import get_service_type + +LOGGER = logging.getLogger(__name__) def convert_explicit_path_hops_to_connections( - path_hops : List[Dict], device_dict : Dict[str, Tuple[Dict, Device]], main_connection_uuid : str -) -> List[Tuple[str, DeviceLayerEnum, List[str], List[str]]]: + path_hops : List[Dict], device_dict : Dict[str, Tuple[Dict, Device]], + main_service_uuid : str, main_service_type : ServiceTypeEnum +) -> List[Tuple[str, int, List[str], List[str]]]: + + LOGGER.debug('path_hops={:s}'.format(str(path_hops))) connection_stack = queue.LifoQueue() - connections : List[Tuple[str, DeviceLayerEnum, List[str], List[str]]] = list() - old_device_layer = None - last_device_uuid = None + connections : List[Tuple[str, int, List[str], List[str]]] = list() + prv_device_uuid = None + prv_res_class : Tuple[Optional[int], Optional[DeviceTypeEnum], Optional[str]] = None, None, None + for path_hop in path_hops: device_uuid = path_hop['device'] - if last_device_uuid == device_uuid: continue + if prv_device_uuid == device_uuid: continue device_tuple = device_dict.get(device_uuid) if device_tuple is None: raise Exception('Device({:s}) not found'.format(str(device_uuid))) - json_device,_ = device_tuple - device_type = json_device['device_type'] - device_layer = DEVICE_TYPE_TO_LAYER.get(device_type) - if device_layer is None: raise Exception('Undefined Layer for DeviceType({:s})'.format(str(device_type))) + _,grpc_device = device_tuple - if old_device_layer is None: + res_class = get_resource_classification(grpc_device, device_dict) + if res_class[1] in IGNORED_DEVICE_TYPES: continue + + if prv_res_class[0] is None: # path ingress - connection_stack.put((main_connection_uuid, device_layer, [path_hop], [])) - elif old_device_layer > device_layer: - # underlying connection begins + connection_stack.put((main_service_uuid, main_service_type, [path_hop], [])) + elif prv_res_class[0] > res_class[0]: + # create underlying connection connection_uuid = str(uuid.uuid4()) - connection_stack.put((connection_uuid, device_layer, [path_hop], [])) - elif old_device_layer == device_layer: - # same connection continues - connection_stack.queue[-1][2].append(path_hop) - elif old_device_layer < device_layer: + prv_service_type = connection_stack.queue[-1][1] + service_type = get_service_type(res_class[1], prv_service_type) + connection_stack.put((connection_uuid, service_type, [path_hop], [])) + elif prv_res_class[0] == res_class[0]: + # same resource group kind + if prv_res_class[1] == res_class[1] and prv_res_class[2] == res_class[2]: + # same device type and device controller: connection continues + connection_stack.queue[-1][2].append(path_hop) + else: + # different device type or device controller: chain connections + connection = connection_stack.get() + connections.append(connection) + connection_stack.queue[-1][3].append(connection[0]) + + connection_uuid = str(uuid.uuid4()) + prv_service_type = connection_stack.queue[-1][1] + service_type = get_service_type(res_class[1], prv_service_type) + connection_stack.put((connection_uuid, service_type, [path_hop], [])) + elif prv_res_class[0] < res_class[0]: # underlying connection ended connection = connection_stack.get() connections.append(connection) @@ -87,26 +109,27 @@ def convert_explicit_path_hops_to_connections( else: raise Exception('Uncontrolled condition') - old_device_layer = device_layer - last_device_uuid = device_uuid + prv_device_uuid = device_uuid + prv_res_class = res_class # path egress connections.append(connection_stack.get()) + LOGGER.debug('connections={:s}'.format(str(connections))) assert connection_stack.empty() return connections def convert_explicit_path_hops_to_plain_connection( - path_hops : List[Dict], main_connection_uuid : str -) -> List[Tuple[str, DeviceLayerEnum, List[str], List[str]]]: + path_hops : List[Dict], main_service_uuid : str, main_service_type : ServiceTypeEnum +) -> List[Tuple[str, int, List[str], List[str]]]: - connection : Tuple[str, DeviceLayerEnum, List[str], List[str]] = \ - (main_connection_uuid, DeviceLayerEnum.PACKET_DEVICE, [], []) + connection : Tuple[str, int, List[str], List[str]] = \ + (main_service_uuid, main_service_type, [], []) - last_device_uuid = None + prv_device_uuid = None for path_hop in path_hops: device_uuid = path_hop['device'] - if last_device_uuid == device_uuid: continue + if prv_device_uuid == device_uuid: continue connection[2].append(path_hop) - last_device_uuid = device_uuid + prv_device_uuid = device_uuid return [connection] diff --git a/src/pathcomp/frontend/service/algorithms/tools/ConstantsMappings.py b/src/pathcomp/frontend/service/algorithms/tools/ConstantsMappings.py index cd1956a873dd2170c7a75db0c677db34162449ee..bd06e6ba19b3da9e2d38d5b83e1d7d3a806ff14f 100644 --- a/src/pathcomp/frontend/service/algorithms/tools/ConstantsMappings.py +++ b/src/pathcomp/frontend/service/algorithms/tools/ConstantsMappings.py @@ -13,8 +13,6 @@ # limitations under the License. from enum import IntEnum -from common.DeviceTypes import DeviceTypeEnum -from common.proto.context_pb2 import ServiceTypeEnum class CapacityUnit(IntEnum): TB = 0 @@ -66,50 +64,3 @@ class LinkForwardingDirection(IntEnum): BIDIRECTIONAL = 0 UNIDIRECTIONAL = 1 UNKNOWN = 2 - -class DeviceLayerEnum(IntEnum): - APPLICATION_CONTROLLER = 41 # Layer 4 domain controller - APPLICATION_DEVICE = 40 # Layer 4 domain device - PACKET_CONTROLLER = 31 # Layer 3 domain controller - PACKET_DEVICE = 30 # Layer 3 domain device - MAC_LAYER_CONTROLLER = 21 # Layer 2 domain controller - MAC_LAYER_DEVICE = 20 # Layer 2 domain device - OPTICAL_CONTROLLER = 1 # Layer 0 domain controller - OPTICAL_DEVICE = 0 # Layer 0 domain device - -DEVICE_TYPE_TO_LAYER = { - DeviceTypeEnum.EMULATED_DATACENTER.value : DeviceLayerEnum.APPLICATION_DEVICE, - DeviceTypeEnum.DATACENTER.value : DeviceLayerEnum.APPLICATION_DEVICE, - DeviceTypeEnum.NETWORK.value : DeviceLayerEnum.APPLICATION_DEVICE, - - DeviceTypeEnum.EMULATED_PACKET_ROUTER.value : DeviceLayerEnum.PACKET_DEVICE, - DeviceTypeEnum.PACKET_ROUTER.value : DeviceLayerEnum.PACKET_DEVICE, - DeviceTypeEnum.EMULATED_PACKET_SWITCH.value : DeviceLayerEnum.MAC_LAYER_DEVICE, - DeviceTypeEnum.PACKET_SWITCH.value : DeviceLayerEnum.MAC_LAYER_DEVICE, - - DeviceTypeEnum.EMULATED_P4_SWITCH.value : DeviceLayerEnum.MAC_LAYER_DEVICE, - DeviceTypeEnum.P4_SWITCH.value : DeviceLayerEnum.MAC_LAYER_DEVICE, - - DeviceTypeEnum.EMULATED_MICROWAVE_RADIO_SYSTEM.value : DeviceLayerEnum.MAC_LAYER_CONTROLLER, - DeviceTypeEnum.MICROWAVE_RADIO_SYSTEM.value : DeviceLayerEnum.MAC_LAYER_CONTROLLER, - - DeviceTypeEnum.EMULATED_OPEN_LINE_SYSTEM.value : DeviceLayerEnum.OPTICAL_CONTROLLER, - DeviceTypeEnum.OPEN_LINE_SYSTEM.value : DeviceLayerEnum.OPTICAL_CONTROLLER, - DeviceTypeEnum.XR_CONSTELLATION.value : DeviceLayerEnum.OPTICAL_CONTROLLER, - - DeviceTypeEnum.EMULATED_OPTICAL_ROADM.value : DeviceLayerEnum.OPTICAL_DEVICE, - DeviceTypeEnum.OPTICAL_ROADM.value : DeviceLayerEnum.OPTICAL_DEVICE, - DeviceTypeEnum.EMULATED_OPTICAL_TRANSPONDER.value : DeviceLayerEnum.OPTICAL_DEVICE, - DeviceTypeEnum.OPTICAL_TRANSPONDER.value : DeviceLayerEnum.OPTICAL_DEVICE, -} - -DEVICE_LAYER_TO_SERVICE_TYPE = { - DeviceLayerEnum.APPLICATION_DEVICE.value : ServiceTypeEnum.SERVICETYPE_L3NM, - DeviceLayerEnum.PACKET_DEVICE.value : ServiceTypeEnum.SERVICETYPE_L3NM, - - DeviceLayerEnum.MAC_LAYER_CONTROLLER.value : ServiceTypeEnum.SERVICETYPE_L2NM, - DeviceLayerEnum.MAC_LAYER_DEVICE.value : ServiceTypeEnum.SERVICETYPE_L2NM, - - DeviceLayerEnum.OPTICAL_CONTROLLER.value : ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE, - DeviceLayerEnum.OPTICAL_DEVICE.value : ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE, -} diff --git a/src/pathcomp/frontend/service/algorithms/tools/EroPathToHops.py b/src/pathcomp/frontend/service/algorithms/tools/EroPathToHops.py index c8a902999ddfb5011fd7ec09fa99ff6fa697ea40..670757d76b7d21ecf28f6ead4e8bc4e21951d18e 100644 --- a/src/pathcomp/frontend/service/algorithms/tools/EroPathToHops.py +++ b/src/pathcomp/frontend/service/algorithms/tools/EroPathToHops.py @@ -43,13 +43,17 @@ # import logging -from typing import Dict, List +from typing import Dict, List, Tuple +from common.proto.context_pb2 import Link LOGGER = logging.getLogger(__name__) -def eropath_to_hops(ero_path : List[Dict], endpoint_to_link_dict : Dict) -> List[Dict]: +def eropath_to_hops( + ero_path : List[Dict], endpoint_to_link_dict : Dict[Tuple[str, str, str], Tuple[Dict, Link]] +) -> List[Dict]: try: path_hops = [] + num_ero_hops = len(ero_path) for endpoint in ero_path: device_uuid = endpoint['device_id'] endpoint_uuid = endpoint['endpoint_uuid'] @@ -59,23 +63,17 @@ def eropath_to_hops(ero_path : List[Dict], endpoint_to_link_dict : Dict) -> List continue last_hop = path_hops[-1] - if (last_hop['device'] == device_uuid): - if ('ingress_ep' not in last_hop) or ('egress_ep' in last_hop): continue - last_hop['egress_ep'] = endpoint_uuid - continue + if last_hop['device'] != device_uuid: raise Exception('Malformed path') + last_hop['egress_ep'] = endpoint_uuid + + if num_ero_hops - 1 == len(path_hops): break - endpoint_key = (last_hop['device'], last_hop['egress_ep']) - link_tuple = endpoint_to_link_dict.get(endpoint_key) - ingress = next(iter([ - ep_id for ep_id in link_tuple[0]['link_endpoint_ids'] - if (ep_id['endpoint_id']['device_id'] == device_uuid) and\ - (ep_id['endpoint_id']['endpoint_uuid'] != endpoint_uuid) - ]), None) - if ingress['endpoint_id']['device_id'] != device_uuid: raise Exception('Malformed path') + link_tuple = endpoint_to_link_dict[(device_uuid, endpoint_uuid, 'src')] + if link_tuple is None: raise Exception('Malformed path') + ingress = link_tuple[0]['link_endpoint_ids'][-1] path_hops.append({ 'device': ingress['endpoint_id']['device_id'], - 'ingress_ep': ingress['endpoint_id']['endpoint_uuid'], - 'egress_ep': endpoint_uuid, + 'ingress_ep': ingress['endpoint_id']['endpoint_uuid'] }) return path_hops except: diff --git a/src/pathcomp/frontend/service/algorithms/tools/ResourceGroups.py b/src/pathcomp/frontend/service/algorithms/tools/ResourceGroups.py new file mode 100644 index 0000000000000000000000000000000000000000..53c89cd124cb7d3431b37a50596b0b793cfa83eb --- /dev/null +++ b/src/pathcomp/frontend/service/algorithms/tools/ResourceGroups.py @@ -0,0 +1,94 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import json +from typing import Dict, Optional, Tuple +from common.DeviceTypes import DeviceTypeEnum +from common.proto.context_pb2 import Device +from common.tools.grpc.Tools import grpc_message_to_json_string + +DEVICE_TYPE_TO_DEEPNESS = { + DeviceTypeEnum.EMULATED_DATACENTER.value : 90, + DeviceTypeEnum.DATACENTER.value : 90, + DeviceTypeEnum.NETWORK.value : 90, + + DeviceTypeEnum.TERAFLOWSDN_CONTROLLER.value : 80, + DeviceTypeEnum.EMULATED_PACKET_ROUTER.value : 70, + DeviceTypeEnum.PACKET_ROUTER.value : 70, + + DeviceTypeEnum.EMULATED_PACKET_SWITCH.value : 60, + DeviceTypeEnum.PACKET_SWITCH.value : 60, + DeviceTypeEnum.EMULATED_P4_SWITCH.value : 60, + DeviceTypeEnum.P4_SWITCH.value : 60, + + DeviceTypeEnum.EMULATED_MICROWAVE_RADIO_SYSTEM.value : 40, + DeviceTypeEnum.MICROWAVE_RADIO_SYSTEM.value : 40, + + DeviceTypeEnum.EMULATED_XR_CONSTELLATION.value : 40, + DeviceTypeEnum.XR_CONSTELLATION.value : 40, + + DeviceTypeEnum.EMULATED_OPEN_LINE_SYSTEM.value : 30, + DeviceTypeEnum.OPEN_LINE_SYSTEM.value : 30, + + DeviceTypeEnum.EMULATED_PACKET_RADIO_ROUTER.value : 10, + DeviceTypeEnum.PACKET_RADIO_ROUTER.value : 10, + DeviceTypeEnum.EMULATED_OPTICAL_TRANSPONDER.value : 10, + DeviceTypeEnum.OPTICAL_TRANSPONDER.value : 10, + DeviceTypeEnum.EMULATED_OPTICAL_ROADM.value : 10, + DeviceTypeEnum.OPTICAL_ROADM.value : 10, + + DeviceTypeEnum.EMULATED_OPTICAL_SPLITTER.value : 0, +} + +IGNORED_DEVICE_TYPES = {DeviceTypeEnum.EMULATED_OPTICAL_SPLITTER} + +def get_device_controller_uuid( + device : Device +) -> Optional[str]: + for config_rule in device.device_config.config_rules: + if config_rule.WhichOneof('config_rule') != 'custom': continue + if config_rule.custom.resource_key != '_controller': continue + device_controller_id = json.loads(config_rule.custom.resource_value) + return device_controller_id['uuid'] + return None + +def _map_device_type(device : Device) -> DeviceTypeEnum: + device_type = DeviceTypeEnum._value2member_map_.get(device.device_type) # pylint: disable=no-member + if device_type is None: + MSG = 'Unsupported DeviceType({:s}) for Device({:s})' + raise Exception(MSG.format(str(device.device_type), grpc_message_to_json_string(device))) + return device_type + +def _map_resource_to_deepness(device_type : DeviceTypeEnum) -> int: + deepness = DEVICE_TYPE_TO_DEEPNESS.get(device_type.value) + if deepness is None: raise Exception('Unsupported DeviceType({:s})'.format(str(device_type.value))) + return deepness + +def get_device_type( + device : Device, device_dict : Dict[str, Tuple[Dict, Device]], device_controller_uuid : Optional[str] +) -> DeviceTypeEnum: + if device_controller_uuid is None: return _map_device_type(device) + device_controller_tuple = device_dict.get(device_controller_uuid) + if device_controller_tuple is None: raise Exception('Device({:s}) not found'.format(str(device_controller_uuid))) + _,device = device_controller_tuple + return _map_device_type(device) + +def get_resource_classification( + device : Device, device_dict : Dict[str, Tuple[Dict, Device]] +) -> Tuple[int, DeviceTypeEnum, Optional[str]]: + device_controller_uuid = get_device_controller_uuid(device) + device_type = get_device_type(device, device_dict, device_controller_uuid) + resource_deepness = _map_resource_to_deepness(device_type) + return resource_deepness, device_type, device_controller_uuid diff --git a/src/pathcomp/frontend/service/algorithms/tools/ServiceTypes.py b/src/pathcomp/frontend/service/algorithms/tools/ServiceTypes.py new file mode 100644 index 0000000000000000000000000000000000000000..463b8039b6c8c611b579bdb74933c06fb0f99507 --- /dev/null +++ b/src/pathcomp/frontend/service/algorithms/tools/ServiceTypes.py @@ -0,0 +1,53 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from common.DeviceTypes import DeviceTypeEnum +from common.proto.context_pb2 import ServiceTypeEnum + +PACKET_DEVICE_TYPES = { + DeviceTypeEnum.TERAFLOWSDN_CONTROLLER, + DeviceTypeEnum.PACKET_ROUTER, DeviceTypeEnum.EMULATED_PACKET_ROUTER, + DeviceTypeEnum.PACKET_SWITCH, DeviceTypeEnum.EMULATED_PACKET_SWITCH, +} + +L2_DEVICE_TYPES = { + DeviceTypeEnum.PACKET_SWITCH, DeviceTypeEnum.EMULATED_PACKET_SWITCH, + DeviceTypeEnum.MICROWAVE_RADIO_SYSTEM, DeviceTypeEnum.EMULATED_MICROWAVE_RADIO_SYSTEM, + DeviceTypeEnum.PACKET_RADIO_ROUTER, DeviceTypeEnum.EMULATED_PACKET_RADIO_ROUTER, + DeviceTypeEnum.P4_SWITCH, DeviceTypeEnum.EMULATED_P4_SWITCH, +} + +OPTICAL_DEVICE_TYPES = { + DeviceTypeEnum.OPEN_LINE_SYSTEM, DeviceTypeEnum.EMULATED_OPEN_LINE_SYSTEM, + DeviceTypeEnum.XR_CONSTELLATION, DeviceTypeEnum.EMULATED_XR_CONSTELLATION, + DeviceTypeEnum.OPTICAL_ROADM, DeviceTypeEnum.EMULATED_OPTICAL_ROADM, + DeviceTypeEnum.OPTICAL_TRANSPONDER, DeviceTypeEnum.EMULATED_OPTICAL_TRANSPONDER, +} + +SERVICE_TYPE_L2NM = {ServiceTypeEnum.SERVICETYPE_L2NM} +SERVICE_TYPE_L3NM = {ServiceTypeEnum.SERVICETYPE_L3NM} +SERVICE_TYPE_LXNM = {ServiceTypeEnum.SERVICETYPE_L3NM, ServiceTypeEnum.SERVICETYPE_L2NM} +SERVICE_TYPE_TAPI = {ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE} + +def get_service_type(device_type : DeviceTypeEnum, prv_service_type : ServiceTypeEnum) -> ServiceTypeEnum: + if device_type in PACKET_DEVICE_TYPES and prv_service_type in SERVICE_TYPE_LXNM: return prv_service_type + if device_type in L2_DEVICE_TYPES: return ServiceTypeEnum.SERVICETYPE_L2NM + if device_type in OPTICAL_DEVICE_TYPES: return ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE + + str_fields = ', '.join([ + 'device_type={:s}'.format(str(device_type)), + 'prv_service_type={:s}'.format(str(prv_service_type)), + ]) + raise Exception('Undefined Service Type for ({:s})'.format(str_fields)) diff --git a/src/pathcomp/frontend/tests/test_pathcomp/__init__.py b/src/pathcomp/frontend/tests/test_pathcomp/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1549d9811aa5d1c193a44ad45d0d7773236c0612 --- /dev/null +++ b/src/pathcomp/frontend/tests/test_pathcomp/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/src/pathcomp/frontend/tests/test_pathcomp/__main__.py b/src/pathcomp/frontend/tests/test_pathcomp/__main__.py new file mode 100644 index 0000000000000000000000000000000000000000..ba1cc4a2c1838248882b983c1ed25c27b395aa9a --- /dev/null +++ b/src/pathcomp/frontend/tests/test_pathcomp/__main__.py @@ -0,0 +1,33 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import logging, sys +from common.proto.context_pb2 import ServiceTypeEnum +from pathcomp.frontend.service.algorithms.tools.ComputeSubServices import convert_explicit_path_hops_to_connections +from .data import path_hops, device_dict + +logging.basicConfig(level=logging.DEBUG) +LOGGER = logging.getLogger(__name__) + +def main(): + service_uuid = 'dc-2-dc-svc' + service_type = ServiceTypeEnum.SERVICETYPE_L2NM + connections = convert_explicit_path_hops_to_connections(path_hops, device_dict, service_uuid, service_type) + str_connections = '\n'.join([' ' + str(connection) for connection in connections]) + LOGGER.debug('connections = [\n{:s}\n]'.format(str_connections)) + return 0 + +if __name__ == '__main__': + sys.exit(main()) diff --git a/src/pathcomp/frontend/tests/test_pathcomp/data.py b/src/pathcomp/frontend/tests/test_pathcomp/data.py new file mode 100644 index 0000000000000000000000000000000000000000..aeac5e38a222fb2dfc3f7ae98b2737b47f855ee4 --- /dev/null +++ b/src/pathcomp/frontend/tests/test_pathcomp/data.py @@ -0,0 +1,70 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import json +from typing import Dict, Tuple +from common.DeviceTypes import DeviceTypeEnum +from common.proto.context_pb2 import ConfigActionEnum, Device + +path_hops = [ + {'device': 'DC1', 'ingress_ep': 'int', 'egress_ep': 'eth1' }, + {'device': 'PE1', 'ingress_ep': '1/1', 'egress_ep': '1/2' }, + {'device': 'MW1-2', 'ingress_ep': '172.18.0.1:1', 'egress_ep': '172.18.0.2:1' }, + {'device': 'HUB1', 'ingress_ep': '1/1', 'egress_ep': 'XR-T1' }, + {'device': 'splitter', 'ingress_ep': 'common', 'egress_ep': 'leaf1' }, + {'device': 'OLS', 'ingress_ep': 'node_1_port_13-input', 'egress_ep': 'node_4_port_13-output'}, + {'device': 'LEAF2', 'ingress_ep': 'XR-T1', 'egress_ep': '1/1' }, + {'device': 'PE4', 'ingress_ep': '1/1', 'egress_ep': '1/2' }, + {'device': 'DC2', 'ingress_ep': 'eth2', 'egress_ep': 'int' } +] + +device_data = { + 'TFS' : {'controller_uuid': None, 'device_type': DeviceTypeEnum.TERAFLOWSDN_CONTROLLER }, + 'IPM' : {'controller_uuid': None, 'device_type': DeviceTypeEnum.XR_CONSTELLATION }, + 'OLS' : {'controller_uuid': None, 'device_type': DeviceTypeEnum.OPEN_LINE_SYSTEM }, + 'MW1-2' : {'controller_uuid': None, 'device_type': DeviceTypeEnum.MICROWAVE_RADIO_SYSTEM }, + 'MW3-4' : {'controller_uuid': None, 'device_type': DeviceTypeEnum.MICROWAVE_RADIO_SYSTEM }, + + 'DC1' : {'controller_uuid': None, 'device_type': DeviceTypeEnum.EMULATED_DATACENTER }, + 'DC2' : {'controller_uuid': None, 'device_type': DeviceTypeEnum.EMULATED_DATACENTER }, + + 'PE1' : {'controller_uuid': 'TFS', 'device_type': DeviceTypeEnum.PACKET_ROUTER }, + 'PE2' : {'controller_uuid': 'TFS', 'device_type': DeviceTypeEnum.PACKET_ROUTER }, + 'PE3' : {'controller_uuid': 'TFS', 'device_type': DeviceTypeEnum.PACKET_ROUTER }, + 'PE4' : {'controller_uuid': 'TFS', 'device_type': DeviceTypeEnum.PACKET_ROUTER }, + + 'HUB1' : {'controller_uuid': 'IPM', 'device_type': DeviceTypeEnum.PACKET_ROUTER }, + 'LEAF1' : {'controller_uuid': 'IPM', 'device_type': DeviceTypeEnum.PACKET_ROUTER }, + 'LEAF2' : {'controller_uuid': 'IPM', 'device_type': DeviceTypeEnum.PACKET_ROUTER }, + + 'splitter': {'controller_uuid': None, 'device_type': DeviceTypeEnum.EMULATED_OPTICAL_SPLITTER}, +} + +def process_device(device_uuid, json_device) -> Tuple[Dict, Device]: + grpc_device = Device() + grpc_device.device_id.device_uuid.uuid = device_uuid # pylint: disable=no-member + grpc_device.device_type = json_device['device_type'].value + controller_uuid = json_device.get('controller_uuid') + if controller_uuid is not None: + config_rule = grpc_device.device_config.config_rules.add() # pylint: disable=no-member + config_rule.action = ConfigActionEnum.CONFIGACTION_SET + config_rule.custom.resource_key = '_controller' + config_rule.custom.resource_value = json.dumps({'uuid': controller_uuid}) + return json_device, grpc_device + +device_dict = { + device_uuid:process_device(device_uuid, json_device) + for device_uuid,json_device in device_data.items() +} diff --git a/src/policy/src/main/java/eu/teraflow/policy/Serializer.java b/src/policy/src/main/java/eu/teraflow/policy/Serializer.java index 529ec633426e41f3218857642aa6751ac574ab23..967d1d6e604e312fe9d8314beea023f902af776b 100644 --- a/src/policy/src/main/java/eu/teraflow/policy/Serializer.java +++ b/src/policy/src/main/java/eu/teraflow/policy/Serializer.java @@ -2245,6 +2245,8 @@ public class Serializer { return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352; case XR: return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_XR; + case IETF_L2VPN: + return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN; case UNDEFINED: default: return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED; @@ -2266,6 +2268,8 @@ public class Serializer { return DeviceDriverEnum.ONF_TR_352; case DEVICEDRIVER_XR: return DeviceDriverEnum.XR; + case DEVICEDRIVER_IETF_L2VPN: + return DeviceDriverEnum.IETF_L2VPN; case DEVICEDRIVER_UNDEFINED: case UNRECOGNIZED: default: diff --git a/src/policy/src/test/java/eu/teraflow/policy/SerializerTest.java b/src/policy/src/test/java/eu/teraflow/policy/SerializerTest.java index d284840b8dedb0320d49a5d5c6a1943c10d2afed..64102646119585e1f837b12a9be022d95a29c54f 100644 --- a/src/policy/src/test/java/eu/teraflow/policy/SerializerTest.java +++ b/src/policy/src/test/java/eu/teraflow/policy/SerializerTest.java @@ -3600,6 +3600,8 @@ class SerializerTest { DeviceDriverEnum.ONF_TR_352, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352), Arguments.of(DeviceDriverEnum.XR, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_XR), + Arguments.of( + DeviceDriverEnum.IETF_L2VPN, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN), Arguments.of( DeviceDriverEnum.UNDEFINED, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED)); } diff --git a/src/policy/target/generated-sources/grpc/context/ContextOuterClass.java b/src/policy/target/generated-sources/grpc/context/ContextOuterClass.java index fbbba62a2baa1c2fe2b3c3fe090883d6542996e4..53252341b30dc093c79d5a54baf98b82e6a24b75 100644 --- a/src/policy/target/generated-sources/grpc/context/ContextOuterClass.java +++ b/src/policy/target/generated-sources/grpc/context/ContextOuterClass.java @@ -177,6 +177,10 @@ public final class ContextOuterClass { * 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/') + + LOGGER.info('Listening on {:s}...'.format(str(STR_ENDPOINT))) + app.run(debug=True, host=BIND_ADDRESS, port=BIND_PORT, ssl_context='adhoc') + + LOGGER.info('Bye') + return 0 + +if __name__ == '__main__': + sys.exit(main()) diff --git a/src/tests/tools/mock_ipm_sdn_ctrl/run.sh b/src/tests/tools/mock_ipm_sdn_ctrl/run.sh new file mode 100755 index 0000000000000000000000000000000000000000..2aa78712c58d8cc255b60202d1576de683798d2e --- /dev/null +++ b/src/tests/tools/mock_ipm_sdn_ctrl/run.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +python MockIPMSdnCtrl.py diff --git a/src/tests/tools/mock_sdn_ctrl/MockMWSdnCtrl.py b/src/tests/tools/mock_mw_sdn_ctrl/MockMWSdnCtrl.py similarity index 54% rename from src/tests/tools/mock_sdn_ctrl/MockMWSdnCtrl.py rename to src/tests/tools/mock_mw_sdn_ctrl/MockMWSdnCtrl.py index 63be214b671c2ee9b222d92e6f964a4203b8a587..c20dde1b9958fb92e4c6f026fbfe55f9ba348ba1 100644 --- a/src/tests/tools/mock_sdn_ctrl/MockMWSdnCtrl.py +++ b/src/tests/tools/mock_mw_sdn_ctrl/MockMWSdnCtrl.py @@ -25,8 +25,7 @@ # Ref: https://blog.miguelgrinberg.com/post/designing-a-restful-api-using-flask-restful import functools, logging, sys, time -from flask import Flask, abort, request -from flask.json import jsonify +from flask import Flask, abort, jsonify, make_response, request from flask_restful import Api, Resource BIND_ADDRESS = '0.0.0.0' @@ -36,31 +35,51 @@ STR_ENDPOINT = 'https://{:s}:{:s}{:s}'.format(str(BIND_ADDRESS), str(BIND_PORT), LOG_LEVEL = logging.DEBUG NETWORK_NODES = [ - {'node-id': '172.18.0.1', 'ietf-network-topology:termination-point': [ - {'tp-id': '172.18.0.1:1', 'ietf-te-topology:te': {'name': 'ethernet'}}, - {'tp-id': '172.18.0.1:2', 'ietf-te-topology:te': {'name': 'antena' }}, + {'node-id': '192.168.27.139', 'ietf-network-topology:termination-point': [ + {'tp-id': '1', 'ietf-te-topology:te': {'name': 'ethernet'}}, + {'tp-id': '2', 'ietf-te-topology:te': {'name': 'ethernet'}}, + {'tp-id': '3', 'ietf-te-topology:te': {'name': 'ethernet'}}, + {'tp-id': '4', 'ietf-te-topology:te': {'name': 'ethernet'}}, + {'tp-id': '5', 'ietf-te-topology:te': {'name': 'ethernet'}}, + {'tp-id': '6', 'ietf-te-topology:te': {'name': 'ethernet'}}, + {'tp-id': '10', 'ietf-te-topology:te': {'name': 'antena' }}, ]}, - {'node-id': '172.18.0.2', 'ietf-network-topology:termination-point': [ - {'tp-id': '172.18.0.2:1', 'ietf-te-topology:te': {'name': 'ethernet'}}, - {'tp-id': '172.18.0.2:2', 'ietf-te-topology:te': {'name': 'antena' }}, + {'node-id': '192.168.27.140', 'ietf-network-topology:termination-point': [ + {'tp-id': '1', 'ietf-te-topology:te': {'name': 'ethernet'}}, + {'tp-id': '2', 'ietf-te-topology:te': {'name': 'ethernet'}}, + {'tp-id': '3', 'ietf-te-topology:te': {'name': 'ethernet'}}, + {'tp-id': '4', 'ietf-te-topology:te': {'name': 'ethernet'}}, + {'tp-id': '5', 'ietf-te-topology:te': {'name': 'ethernet'}}, + {'tp-id': '6', 'ietf-te-topology:te': {'name': 'ethernet'}}, + {'tp-id': '10', 'ietf-te-topology:te': {'name': 'antena' }}, ]}, - {'node-id': '172.18.0.3', 'ietf-network-topology:termination-point': [ - {'tp-id': '172.18.0.3:1', 'ietf-te-topology:te': {'name': 'ethernet'}}, - {'tp-id': '172.18.0.3:2', 'ietf-te-topology:te': {'name': 'antena' }}, + {'node-id': '192.168.27.141', 'ietf-network-topology:termination-point': [ + {'tp-id': '1', 'ietf-te-topology:te': {'name': 'ethernet'}}, + {'tp-id': '2', 'ietf-te-topology:te': {'name': 'ethernet'}}, + {'tp-id': '3', 'ietf-te-topology:te': {'name': 'ethernet'}}, + {'tp-id': '4', 'ietf-te-topology:te': {'name': 'ethernet'}}, + {'tp-id': '5', 'ietf-te-topology:te': {'name': 'ethernet'}}, + {'tp-id': '6', 'ietf-te-topology:te': {'name': 'ethernet'}}, + {'tp-id': '10', 'ietf-te-topology:te': {'name': 'antena' }}, ]}, - {'node-id': '172.18.0.4', 'ietf-network-topology:termination-point': [ - {'tp-id': '172.18.0.4:1', 'ietf-te-topology:te': {'name': 'ethernet'}}, - {'tp-id': '172.18.0.4:2', 'ietf-te-topology:te': {'name': 'antena' }}, + {'node-id': '192.168.27.142', 'ietf-network-topology:termination-point': [ + {'tp-id': '1', 'ietf-te-topology:te': {'name': 'ethernet'}}, + {'tp-id': '2', 'ietf-te-topology:te': {'name': 'ethernet'}}, + {'tp-id': '3', 'ietf-te-topology:te': {'name': 'ethernet'}}, + {'tp-id': '4', 'ietf-te-topology:te': {'name': 'ethernet'}}, + {'tp-id': '5', 'ietf-te-topology:te': {'name': 'ethernet'}}, + {'tp-id': '6', 'ietf-te-topology:te': {'name': 'ethernet'}}, + {'tp-id': '10', 'ietf-te-topology:te': {'name': 'antena' }}, ]} ] NETWORK_LINKS = [ - { - 'source' : {'source-node': '172.18.0.1', 'source-tp': '172.18.0.1:2'}, - 'destination': {'dest-node' : '172.18.0.2', 'dest-tp' : '172.18.0.2:2'}, + { 'link-id' : '192.168.27.139:10--192.168.27.140:10', + 'source' : {'source-node': '192.168.27.139', 'source-tp': '10'}, + 'destination': {'dest-node' : '192.168.27.140', 'dest-tp' : '10'}, }, - { - 'source' : {'source-node': '172.18.0.3', 'source-tp': '172.18.0.3:2'}, - 'destination': {'dest-node' : '172.18.0.4', 'dest-tp' : '172.18.0.4:2'}, + { 'link-id' : '192.168.27.141:10--192.168.27.142:10', + 'source' : {'source-node': '192.168.27.141', 'source-tp': '10'}, + 'destination': {'dest-node' : '192.168.27.142', 'dest-tp' : '10'}, } ] NETWORK_SERVICES = {} @@ -77,21 +96,22 @@ def log_request(logger : logging.Logger, response): return response class Health(Resource): - def get(self): return jsonify({}) + def get(self): + return make_response(jsonify({}), 200) class Network(Resource): def get(self, network_uuid : str): if network_uuid != 'SIAE-ETH-TOPOLOGY': abort(400) network = {'node': NETWORK_NODES, 'ietf-network-topology:link': NETWORK_LINKS} - return jsonify({'ietf-network:network': network}) + return make_response(jsonify({'ietf-network:network': network}), 200) class Services(Resource): def get(self): services = [service for service in NETWORK_SERVICES.values()] - return jsonify({'ietf-eth-tran-service:etht-svc': {'etht-svc-instances': services}}) + return make_response(jsonify({'ietf-eth-tran-service:etht-svc': {'etht-svc-instances': services}}), 200) def post(self): - json_request = request.json + json_request = request.get_json() if not json_request: abort(400) if not isinstance(json_request, dict): abort(400) if 'etht-svc-instances' not in json_request: abort(400) @@ -101,12 +121,12 @@ class Services(Resource): svc_data = json_services[0] etht_svc_name = svc_data['etht-svc-name'] NETWORK_SERVICES[etht_svc_name] = svc_data - return jsonify({}), 201 + return make_response(jsonify({}), 201) class DelServices(Resource): def delete(self, service_uuid : str): NETWORK_SERVICES.pop(service_uuid, None) - return jsonify({}), 204 + return make_response(jsonify({}), 204) def main(): LOGGER.info('Starting...') diff --git a/src/tests/tools/mock_sdn_ctrl/README.md b/src/tests/tools/mock_mw_sdn_ctrl/README.md similarity index 94% rename from src/tests/tools/mock_sdn_ctrl/README.md rename to src/tests/tools/mock_mw_sdn_ctrl/README.md index d8a6fe6b279553e54f13792cbf12f15b2b380dc2..8568c89ed22e2010bc565e28dc42821181dd6e0a 100644 --- a/src/tests/tools/mock_sdn_ctrl/README.md +++ b/src/tests/tools/mock_mw_sdn_ctrl/README.md @@ -12,8 +12,8 @@ Follow the steps below to perform the test: ## 1. Deploy TeraFlowSDN controller and the scenario Deploy the test scenario "microwave_deploy.sh": ```bash -source src/tests/tools/microwave_deploy.sh -./deploy.sh +source src/tests/tools/mock_mw_sdn_ctrl/scenario/microwave_deploy.sh +./deploy/all.sh ``` ## 2. Install requirements and run the Mock MicroWave SDN controller @@ -27,7 +27,7 @@ pip install Flask==2.1.3 Flask-RESTful==0.3.9 Run the Mock MicroWave SDN Controller as follows: ```bash -python src/tests/tools/mock_sdn_ctrl/MockMWSdnCtrl.py +python src/tests/tools/mock_mw_sdn_ctrl/MockMWSdnCtrl.py ``` ## 3. Deploy the test descriptors diff --git a/src/tests/tools/mock_mw_sdn_ctrl/run.sh b/src/tests/tools/mock_mw_sdn_ctrl/run.sh new file mode 100755 index 0000000000000000000000000000000000000000..415fc1751f132478889fba2e0ec1f5da742e23f1 --- /dev/null +++ b/src/tests/tools/mock_mw_sdn_ctrl/run.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +python MockMWSdnCtrl.py diff --git a/src/tests/tools/mock_sdn_ctrl/microwave_deploy.sh b/src/tests/tools/mock_mw_sdn_ctrl/scenario/microwave_deploy.sh similarity index 100% rename from src/tests/tools/mock_sdn_ctrl/microwave_deploy.sh rename to src/tests/tools/mock_mw_sdn_ctrl/scenario/microwave_deploy.sh diff --git a/src/tests/tools/mock_sdn_ctrl/network_descriptors.json b/src/tests/tools/mock_mw_sdn_ctrl/scenario/network_descriptors.json similarity index 100% rename from src/tests/tools/mock_sdn_ctrl/network_descriptors.json rename to src/tests/tools/mock_mw_sdn_ctrl/scenario/network_descriptors.json diff --git a/src/tests/tools/mock_sdn_ctrl/service_descriptor.json b/src/tests/tools/mock_mw_sdn_ctrl/scenario/service_descriptor.json similarity index 100% rename from src/tests/tools/mock_sdn_ctrl/service_descriptor.json rename to src/tests/tools/mock_mw_sdn_ctrl/scenario/service_descriptor.json diff --git a/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/Dockerfile b/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..ad214b97c091bf82a8bfe5c0ce4183d0bae2766e --- /dev/null +++ b/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/Dockerfile @@ -0,0 +1,35 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM python:3.9-slim + +# Set Python to show logs as they occur +ENV PYTHONUNBUFFERED=0 + +# Get generic Python packages +RUN python3 -m pip install --upgrade pip +RUN python3 -m pip install --upgrade setuptools wheel +RUN python3 -m pip install --upgrade pip-tools + +# Create component sub-folder, and copy content +RUN mkdir -p /var/teraflow/mock_mw_sdn_ctrl +WORKDIR /var/teraflow/mock_mw_sdn_ctrl +COPY . . + +# Get specific Python packages +RUN pip-compile --quiet --output-file=requirements.txt requirements.in +RUN python3 -m pip install -r requirements.txt + +# Start the service +ENTRYPOINT ["python", "MockMWSdnCtrl.py"] diff --git a/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/build.sh b/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/build.sh new file mode 100755 index 0000000000000000000000000000000000000000..4df315cec178cef13eaa059a739bc22efc011d4d --- /dev/null +++ b/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/build.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +docker build -t mock-mw-sdn-ctrl:test -f Dockerfile . +docker tag mock-mw-sdn-ctrl:test localhost:32000/tfs/mock-mw-sdn-ctrl:test +docker push localhost:32000/tfs/mock-mw-sdn-ctrl:test diff --git a/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/deploy.sh b/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/deploy.sh new file mode 100755 index 0000000000000000000000000000000000000000..ded232e5c50f8cd5ed448ec0193f58c43626f4ad --- /dev/null +++ b/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/deploy.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +kubectl delete namespace mocks +kubectl --namespace mocks apply -f mock-mw-sdn-ctrl.yaml diff --git a/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/mock-mw-sdn-ctrl.yaml b/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/mock-mw-sdn-ctrl.yaml new file mode 100644 index 0000000000000000000000000000000000000000..05b89f949e940ae55ad592b9cc0e82a6eea2e343 --- /dev/null +++ b/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/mock-mw-sdn-ctrl.yaml @@ -0,0 +1,46 @@ +kind: Namespace +apiVersion: v1 +metadata: + name: mocks +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mock-mw-sdn-ctrl +spec: + selector: + matchLabels: + app: mock-mw-sdn-ctrl + replicas: 1 + template: + metadata: + labels: + app: mock-mw-sdn-ctrl + spec: + terminationGracePeriodSeconds: 5 + containers: + - name: server + image: localhost:32000/tfs/mock-mw-sdn-ctrl:test + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8443 + resources: + requests: + cpu: 250m + memory: 512Mi + limits: + cpu: 700m + memory: 1024Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: mock-mw-sdn-ctrl +spec: + type: ClusterIP + selector: + app: mock-mw-sdn-ctrl + ports: + - name: https + port: 8443 + targetPort: 8443 diff --git a/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/requirements.in b/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/requirements.in new file mode 100644 index 0000000000000000000000000000000000000000..f4bc191062c8385b2eeb003832b284313305c795 --- /dev/null +++ b/src/tests/tools/mock_mw_sdn_ctrl/ssl_not_working/requirements.in @@ -0,0 +1,21 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cryptography==39.0.1 +pyopenssl==23.0.0 +Flask==2.1.3 +Flask-HTTPAuth==4.5.0 +Flask-RESTful==0.3.9 +jsonschema==4.4.0 +requests==2.27.1 diff --git a/src/tests/tools/mock_mw_sdn_ctrl/test_mw.py b/src/tests/tools/mock_mw_sdn_ctrl/test_mw.py new file mode 100644 index 0000000000000000000000000000000000000000..0329d30ad234398200c0fe29aac46f72f5a2e924 --- /dev/null +++ b/src/tests/tools/mock_mw_sdn_ctrl/test_mw.py @@ -0,0 +1,84 @@ +import json, logging, requests +from requests.auth import HTTPBasicAuth +from typing import Optional + +LOGGER = logging.getLogger(__name__) + +HTTP_OK_CODES = { + 200, # OK + 201, # Created + 202, # Accepted + 204, # No Content +} + +def create_connectivity_service( + root_url, uuid, node_id_src, tp_id_src, node_id_dst, tp_id_dst, vlan_id, + auth : Optional[HTTPBasicAuth] = None, timeout : Optional[int] = None +): + + url = '{:s}/nmswebs/restconf/data/ietf-eth-tran-service:etht-svc'.format(root_url) + headers = {'content-type': 'application/json'} + data = { + 'etht-svc-instances': [ + { + 'etht-svc-name': uuid, + 'etht-svc-type': 'ietf-eth-tran-types:p2p-svc', + 'etht-svc-end-points': [ + { + 'etht-svc-access-points': [ + {'access-node-id': node_id_src, 'access-ltp-id': tp_id_src, 'access-point-id': '1'} + ], + 'outer-tag': {'vlan-value': vlan_id, 'tag-type': 'ietf-eth-tran-types:classify-c-vlan'}, + 'etht-svc-end-point-name': '{:s}:{:s}'.format(str(node_id_src), str(tp_id_src)), + 'service-classification-type': 'ietf-eth-tran-types:vlan-classification' + }, + { + 'etht-svc-access-points': [ + {'access-node-id': node_id_dst, 'access-ltp-id': tp_id_dst, 'access-point-id': '2'} + ], + 'outer-tag': {'vlan-value': vlan_id, 'tag-type': 'ietf-eth-tran-types:classify-c-vlan'}, + 'etht-svc-end-point-name': '{:s}:{:s}'.format(str(node_id_dst), str(tp_id_dst)), + 'service-classification-type': 'ietf-eth-tran-types:vlan-classification' + } + ] + } + ] + } + results = [] + try: + LOGGER.info('Connectivity service {:s}: {:s}'.format(str(uuid), str(data))) + response = requests.post( + url=url, data=json.dumps(data), timeout=timeout, headers=headers, verify=False, auth=auth) + LOGGER.info('Microwave Driver response: {:s}'.format(str(response))) + except Exception as e: # pylint: disable=broad-except + LOGGER.exception('Exception creating ConnectivityService(uuid={:s}, data={:s})'.format(str(uuid), str(data))) + results.append(e) + else: + if response.status_code not in HTTP_OK_CODES: + msg = 'Could not create ConnectivityService(uuid={:s}, data={:s}). status_code={:s} reply={:s}' + LOGGER.error(msg.format(str(uuid), str(data), str(response.status_code), str(response))) + results.append(response.status_code in HTTP_OK_CODES) + return results + +def delete_connectivity_service(root_url, uuid, auth : Optional[HTTPBasicAuth] = None, timeout : Optional[int] = None): + url = '{:s}/nmswebs/restconf/data/ietf-eth-tran-service:etht-svc/etht-svc-instances={:s}' + url = url.format(root_url, uuid) + results = [] + try: + response = requests.delete(url=url, timeout=timeout, verify=False, auth=auth) + except Exception as e: # pylint: disable=broad-except + LOGGER.exception('Exception deleting ConnectivityService(uuid={:s})'.format(str(uuid))) + results.append(e) + else: + if response.status_code not in HTTP_OK_CODES: + msg = 'Could not delete ConnectivityService(uuid={:s}). status_code={:s} reply={:s}' + LOGGER.error(msg.format(str(uuid), str(response.status_code), str(response))) + results.append(response.status_code in HTTP_OK_CODES) + return results + +if __name__ == '__main__': + ROOT_URL = 'https://127.0.0.1:8443' + SERVICE_UUID = 'my-service' + + create_connectivity_service(ROOT_URL, SERVICE_UUID, '172.18.0.1', '1', '172.18.0.2', '2', 300) + delete_connectivity_service(ROOT_URL, SERVICE_UUID) diff --git a/src/webui/service/device/forms.py b/src/webui/service/device/forms.py index c6bacac9bc1723a020f3057fad9c9e8306c9dbca..24bc92b3a5a4aec4321c07b17830f6111be7176d 100644 --- a/src/webui/service/device/forms.py +++ b/src/webui/service/device/forms.py @@ -29,6 +29,7 @@ class AddDeviceForm(FlaskForm): device_drivers_ietf_network_topology = BooleanField('IETF_NETWORK_TOPOLOGY') device_drivers_onf_tr_352 = BooleanField('ONF_TR_352') device_drivers_xr = BooleanField('XR') + device_drivers_ietf_l2vpn = BooleanField('IETF L2VPN') device_config_address = StringField('connect/address',default='127.0.0.1',validators=[DataRequired(), Length(min=5)]) device_config_port = StringField('connect/port',default='0',validators=[DataRequired(), Length(min=1)]) device_config_settings = TextAreaField('connect/settings',default='{}',validators=[DataRequired(), Length(min=2)]) diff --git a/src/webui/service/device/routes.py b/src/webui/service/device/routes.py index ebf77a35ffdf9c2546ddbdd1bac0c8c1f54a2b56..bc46847704b28fb6ef44de0aae030ccb67935928 100644 --- a/src/webui/service/device/routes.py +++ b/src/webui/service/device/routes.py @@ -120,6 +120,8 @@ def add(): device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352) if form.device_drivers_xr.data: device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_XR) + if form.device_drivers_ietf_l2vpn.data: + device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN) device_obj.device_drivers.extend(device_drivers) # pylint: disable=no-member try: diff --git a/src/webui/service/static/topology_icons/Acknowledgements.txt b/src/webui/service/static/topology_icons/Acknowledgements.txt index b285d225957b0a4e8c14ac4ae5e078597d2a1b27..de69c89cee25dc93856761ac2dd08d9988a35095 100644 --- a/src/webui/service/static/topology_icons/Acknowledgements.txt +++ b/src/webui/service/static/topology_icons/Acknowledgements.txt @@ -24,3 +24,10 @@ https://symbols.getvecta.com/stencil_241/213_programmable-sw.32d3794d56.png => e https://symbols.getvecta.com/stencil_240/275_wae.c06b769cd7.png => optical-transponder.png https://symbols.getvecta.com/stencil_241/289_wae.216d930c17.png => emu-optical-transponder.png + +https://symbols.getvecta.com/stencil_240/128_localdirector.c1e561769f.png => optical-splitter.png +https://symbols.getvecta.com/stencil_241/158_local-director.6b38eab9e4.png => emu-optical-splitter.png + + +https://symbols.getvecta.com/stencil_240/197_radio-tower.b6138c8c29.png => radio-router.png +https://symbols.getvecta.com/stencil_241/216_radio-tower.5159339bc0.png => emu-radio-router.png \ No newline at end of file diff --git a/src/webui/service/static/topology_icons/emu-optical-splitter.png b/src/webui/service/static/topology_icons/emu-optical-splitter.png new file mode 100644 index 0000000000000000000000000000000000000000..12b7727d68ef749b52fcdd592c0427f63b58dc75 Binary files /dev/null and b/src/webui/service/static/topology_icons/emu-optical-splitter.png differ diff --git a/src/webui/service/static/topology_icons/emu-packet-radio-router.png b/src/webui/service/static/topology_icons/emu-packet-radio-router.png new file mode 100644 index 0000000000000000000000000000000000000000..00257d0e2ee357dbdd392a408cfdbe07e006ff2a Binary files /dev/null and b/src/webui/service/static/topology_icons/emu-packet-radio-router.png differ diff --git a/src/webui/service/static/topology_icons/emu-xr-constellation.png b/src/webui/service/static/topology_icons/emu-xr-constellation.png new file mode 100644 index 0000000000000000000000000000000000000000..d3bea498a4cd6d8a455d997e4833079f3e6b714f Binary files /dev/null and b/src/webui/service/static/topology_icons/emu-xr-constellation.png differ diff --git a/src/webui/service/static/topology_icons/optical-splitter.png b/src/webui/service/static/topology_icons/optical-splitter.png new file mode 100644 index 0000000000000000000000000000000000000000..90a3d79b8ed4b8ae15f3d4a349cd08d741dcfdaf Binary files /dev/null and b/src/webui/service/static/topology_icons/optical-splitter.png differ diff --git a/src/webui/service/static/topology_icons/packet-radio-router.png b/src/webui/service/static/topology_icons/packet-radio-router.png new file mode 100644 index 0000000000000000000000000000000000000000..025172a587890341061b11ae57ce30184b8bc2f0 Binary files /dev/null and b/src/webui/service/static/topology_icons/packet-radio-router.png differ diff --git a/src/webui/service/static/topology_icons/teraflowsdn.png b/src/webui/service/static/topology_icons/teraflowsdn.png new file mode 100644 index 0000000000000000000000000000000000000000..ed2232e8223a39eb0d829e0e50975a697b0660fc Binary files /dev/null and b/src/webui/service/static/topology_icons/teraflowsdn.png differ diff --git a/src/webui/service/templates/js/topology.js b/src/webui/service/templates/js/topology.js index 50486d2a6826fedace55f7a62592fa083e7256a6..1b34f2b2c737c96ef18e925bd76ef28451f4120f 100644 --- a/src/webui/service/templates/js/topology.js +++ b/src/webui/service/templates/js/topology.js @@ -31,8 +31,8 @@ const margin = {top: 5, right: 5, bottom: 5, left: 5}; const icon_width = 40; const icon_height = 40; -width = 1000 - margin.left - margin.right; -height = 600 - margin.top - margin.bottom; +width = 1400 - margin.left - margin.right; +height = 800 - margin.top - margin.bottom; //function handleZoom(e) { // console.dir(e); @@ -70,11 +70,21 @@ var simulation = d3.forceSimulation(); // load the data d3.json("{{ url_for('main.topology') }}", function(data) { // set the data and properties of link lines and node circles - link = svg.append("g").attr("class", "links").style('stroke', '#aaa') + link = svg.append("g").attr("class", "links")//.style('stroke', '#aaa') .selectAll("line") .data(data.links) .enter() - .append("line"); + .append("line") + .attr("opacity", 1) + .attr("stroke", function(l) { + return l.name.toLowerCase().includes('mgmt') ? '#AAAAAA' : '#555555'; + }) + .attr("stroke-width", function(l) { + return l.name.toLowerCase().includes('mgmt') ? 1 : 2; + }) + .attr("stroke-dasharray", function(l) { + return l.name.toLowerCase().includes('mgmt') ? "5,5" : "0"; + }); node = svg.append("g").attr("class", "devices").attr('r', 20).style('fill', '#69b3a2') .selectAll("circle") .data(data.devices) @@ -93,9 +103,9 @@ d3.json("{{ url_for('main.topology') }}", function(data) { link.append("title").text(function(l) { return l.name; }); // link style - link - .attr("stroke-width", forceProperties.link.enabled ? 2 : 1) - .attr("opacity", forceProperties.link.enabled ? 1 : 0); + //link + // .attr("stroke-width", forceProperties.link.enabled ? 2 : 1) + // .attr("opacity", forceProperties.link.enabled ? 1 : 0); // set up the simulation and event to update locations after each tick simulation.nodes(data.devices); diff --git a/src/webui/service/templates/link/detail.html b/src/webui/service/templates/link/detail.html index acac4a55392c2bf7f6261707ae1627a486affd10..916abafde05b3ec990346ff7966f207b1dafc10a 100644 --- a/src/webui/service/templates/link/detail.html +++ b/src/webui/service/templates/link/detail.html @@ -37,6 +37,7 @@ Endpoint UUID + Name Device Endpoint Type @@ -44,6 +45,9 @@ {% for endpoint in link.link_endpoint_ids %} + + {{ endpoint.endpoint_uuid.uuid }} + {{ endpoints_data.get(endpoint.endpoint_uuid.uuid, (endpoint.endpoint_uuid.uuid, ''))[0] }} diff --git a/src/webui/service/templates/service/detail.html b/src/webui/service/templates/service/detail.html index bee2e93c53896a8eeac826703a60afe02a5aa825..414aa19d0165ed7138f277005d5573c9242daefb 100644 --- a/src/webui/service/templates/service/detail.html +++ b/src/webui/service/templates/service/detail.html @@ -55,6 +55,7 @@ Endpoint UUID + Name Device Endpoint Type @@ -62,6 +63,9 @@ {% for endpoint in service.service_endpoint_ids %} + + {{ endpoint.endpoint_uuid.uuid }} + {{ endpoints_data.get(endpoint.endpoint_uuid.uuid, (endpoint.endpoint_uuid.uuid, ''))[0] }} diff --git a/src/webui/service/templates/slice/detail.html b/src/webui/service/templates/slice/detail.html index 8f223e44deda37b177a360a51b1e366f680fac27..13b69defeb95f66aba47a4aa78f98631ca8cc367 100644 --- a/src/webui/service/templates/slice/detail.html +++ b/src/webui/service/templates/slice/detail.html @@ -55,6 +55,7 @@ Endpoint UUID + Name Device Endpoint Type @@ -62,6 +63,9 @@ {% for endpoint in slice.slice_endpoint_ids %} + + {{ endpoint.endpoint_uuid.uuid }} + {{ endpoints_data.get(endpoint.endpoint_uuid.uuid, (endpoint.endpoint_uuid.uuid, ''))[0] }}