From 293db8511b97c30ec10aee8ff3548df7aa9e8c38 Mon Sep 17 00:00:00 2001 From: Roberto Rubino Date: Tue, 14 Jun 2022 14:58:19 +0000 Subject: [PATCH 01/11] new dev of microwave service handler --- .../service/service_handlers/__init__.py | 7 + .../microwave/MicrowaveServiceHandler.py | 175 ++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 src/service/service/service_handlers/microwave/MicrowaveServiceHandler.py diff --git a/src/service/service/service_handlers/__init__.py b/src/service/service/service_handlers/__init__.py index 6abe4048f..3cff3ac09 100644 --- a/src/service/service/service_handlers/__init__.py +++ b/src/service/service/service_handlers/__init__.py @@ -16,6 +16,7 @@ from ..service_handler_api.FilterFields import FilterFieldEnum, ORM_DeviceDriver from .l3nm_emulated.L3NMEmulatedServiceHandler import L3NMEmulatedServiceHandler from .l3nm_openconfig.L3NMOpenConfigServiceHandler import L3NMOpenConfigServiceHandler from .tapi_tapi.TapiServiceHandler import TapiServiceHandler +from .microwave.MicrowaveServiceHandler import MicrowaveServiceHandler SERVICE_HANDLERS = [ (L3NMEmulatedServiceHandler, [ @@ -36,4 +37,10 @@ SERVICE_HANDLERS = [ FilterFieldEnum.DEVICE_DRIVER : ORM_DeviceDriverEnum.TRANSPORT_API, } ]), + (MicrowaveServiceHandler, [ + { + FilterFieldEnum.SERVICE_TYPE : ORM_ServiceTypeEnum.L2NM, + FilterFieldEnum.DEVICE_DRIVER : ORM_DeviceDriverEnum.IETF_NETWORK_TOPOLOGY, + } + ]), ] diff --git a/src/service/service/service_handlers/microwave/MicrowaveServiceHandler.py b/src/service/service/service_handlers/microwave/MicrowaveServiceHandler.py new file mode 100644 index 000000000..6ac58e303 --- /dev/null +++ b/src/service/service/service_handlers/microwave/MicrowaveServiceHandler.py @@ -0,0 +1,175 @@ +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# 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 anytree, json, logging +from typing import Any, Dict, List, Optional, Tuple, Union +from common.orm.Database import Database +from common.orm.HighLevel import get_object +from common.orm.backend.Tools import key_to_str +from common.type_checkers.Checkers import chk_type +from context.client.ContextClient import ContextClient +from device.client.DeviceClient import DeviceClient +from device.proto.context_pb2 import Device +from service.service.database.ConfigModel import ORM_ConfigActionEnum, get_config_rules +from service.service.database.ContextModel import ContextModel +from service.service.database.DeviceModel import DeviceModel +from service.service.database.ServiceModel import ServiceModel +from service.service.service_handler_api._ServiceHandler import _ServiceHandler +from service.service.service_handler_api.AnyTreeTools import TreeNode, delete_subnode, get_subnode, set_subnode_value +from service.service.service_handlers.Tools import config_rule_set, config_rule_delete + +LOGGER = logging.getLogger(__name__) + +class MicrowaveServiceHandler(_ServiceHandler): + def __init__( # pylint: disable=super-init-not-called + self, db_service : ServiceModel, database : Database, context_client : ContextClient, + device_client : DeviceClient, **settings + ) -> None: + self.__db_service = db_service + self.__database = database + self.__context_client = context_client # pylint: disable=unused-private-member + self.__device_client = device_client + + self.__db_context : ContextModel = get_object(self.__database, ContextModel, self.__db_service.context_fk) + str_service_key = key_to_str([self.__db_context.context_uuid, self.__db_service.service_uuid]) + db_config = get_config_rules(self.__database, str_service_key, 'running') + self.__resolver = anytree.Resolver(pathattr='name') + self.__config = TreeNode('.') + for action, resource_key, resource_value in db_config: + if action == ORM_ConfigActionEnum.SET: + try: + resource_value = json.loads(resource_value) + except: # pylint: disable=bare-except + pass + set_subnode_value(self.__resolver, self.__config, resource_key, resource_value) + elif action == ORM_ConfigActionEnum.DELETE: + delete_subnode(self.__resolver, self.__config, resource_key) + + def SetEndpoint(self, endpoints : List[Tuple[str, str, Optional[str]]]) -> List[Union[bool, Exception]]: + chk_type('endpoints', endpoints, list) + if len(endpoints) != 2: return [] + + service_uuid = self.__db_service.service_uuid + service_settings : TreeNode = get_subnode(self.__resolver, self.__config, 'settings', None) + if service_settings is None: raise Exception('Unable to settings for Service({:s})'.format(str(service_uuid))) + + json_settings : Dict = service_settings.value + vlan_id = json_settings.get('vlan_id', 121) + # endpoints are retrieved in the following format --> '/endpoints/endpoint[172.26.60.243:9]' + try: + endpoint_src_split = endpoints[0][1].split(':') + endpoint_dst_split = endpoints[1][1].split(':') + if len(endpoint_src_split) != 2 and len(endpoint_dst_split) != 2: return [] + node_id_src = endpoint_src_split[0] + tp_id_src = endpoint_src_split[1] + node_id_dst = endpoint_dst_split[0] + tp_id_dst = endpoint_dst_split[1] + except ValueError: + return [] + + results = [] + try: + device_uuid = endpoints[0][0] + db_device : DeviceModel = get_object(self.__database, DeviceModel, device_uuid, raise_if_not_found=True) + json_device = db_device.dump(include_config_rules=False, include_drivers=True, include_endpoints=True) + json_device_config : Dict = json_device.setdefault('device_config', {}) + json_device_config_rules : List = json_device_config.setdefault('config_rules', []) + json_device_config_rules.extend([ + config_rule_set('/service[{:s}]'.format(service_uuid), { + 'uuid' : service_uuid, + 'node_id_src' : node_id_src, + 'tp_id_src' : tp_id_src, + 'node_id_dst' : node_id_dst, + 'tp_id_dst' : tp_id_dst, + 'vlan_id' : vlan_id, + }), + ]) + self.__device_client.ConfigureDevice(Device(**json_device)) + 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 + + def DeleteEndpoint(self, endpoints : List[Tuple[str, str, Optional[str]]]) -> List[Union[bool, Exception]]: + chk_type('endpoints', endpoints, list) + if len(endpoints) != 2: return [] + + service_uuid = self.__db_service.service_uuid + results = [] + try: + device_uuid = endpoints[0][0] + db_device : DeviceModel = get_object(self.__database, DeviceModel, device_uuid, raise_if_not_found=True) + json_device = db_device.dump(include_config_rules=False, include_drivers=True, include_endpoints=True) + json_device_config : Dict = json_device.setdefault('device_config', {}) + json_device_config_rules : List = json_device_config.setdefault('config_rules', []) + json_device_config_rules.extend([ + config_rule_delete('/service[{:s}]'.format(service_uuid), {'uuid': service_uuid}) + ]) + self.__device_client.ConfigureDevice(Device(**json_device)) + 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 + + 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))] + + 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))] + + 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_key, resource_value = resource + resource_value = json.loads(resource_value) + set_subnode_value(self.__resolver, self.__config, resource_key, 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 + + 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: + resource_key, _ = resource + delete_subnode(self.__resolver, self.__config, resource_key) + except Exception as e: # pylint: disable=broad-except + LOGGER.exception('Unable to DeleteConfig({:s})'.format(str(resource))) + results.append(e) + + return results -- GitLab From 4a3028d2a1aea96d74f1781af970b0742aa71448 Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Mon, 21 Nov 2022 17:07:56 +0100 Subject: [PATCH 02/11] Device Driver MicroWave: - minor improvement in error checking --- src/device/service/drivers/microwave/Tools.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/device/service/drivers/microwave/Tools.py b/src/device/service/drivers/microwave/Tools.py index 93498f72d..4f74def4d 100644 --- a/src/device/service/drivers/microwave/Tools.py +++ b/src/device/service/drivers/microwave/Tools.py @@ -17,6 +17,12 @@ from device.service.driver_api._Driver import RESOURCE_ENDPOINTS LOGGER = logging.getLogger(__name__) +HTTP_OK_CODES = { + 200, # OK + 201, # Created + 202, # Accepted + 204, # No Content +} def find_key(resource, key): return json.loads(resource[1])[key] @@ -128,10 +134,10 @@ def create_connectivity_service( LOGGER.exception('Exception creating ConnectivityService(uuid={:s}, data={:s})'.format(str(uuid), str(data))) results.append(e) else: - if response.status_code != 201: + 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 == 201) + results.append(response.status_code in HTTP_OK_CODES) return results def delete_connectivity_service(root_url, timeout, uuid): @@ -144,8 +150,8 @@ def delete_connectivity_service(root_url, timeout, uuid): LOGGER.exception('Exception deleting ConnectivityService(uuid={:s})'.format(str(uuid))) results.append(e) else: - if response.status_code != 201: + 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 == 202) + results.append(response.status_code in HTTP_OK_CODES) return results -- GitLab From b0be7852c310fcc78b906e12e25890baf9033740 Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Mon, 21 Nov 2022 17:09:20 +0100 Subject: [PATCH 03/11] Service - MicroWaveServiceHandler: - migrated to new ServiceHandler API - added missing __init__.py file --- .../service/service_handlers/__init__.py | 4 +- .../microwave/MicrowaveServiceHandler.py | 103 ++++++++---------- .../service_handlers/microwave/__init__.py | 14 +++ 3 files changed, 64 insertions(+), 57 deletions(-) create mode 100644 src/service/service/service_handlers/microwave/__init__.py diff --git a/src/service/service/service_handlers/__init__.py b/src/service/service/service_handlers/__init__.py index 6c3231b46..c6cb589d5 100644 --- a/src/service/service/service_handlers/__init__.py +++ b/src/service/service/service_handlers/__init__.py @@ -47,8 +47,8 @@ SERVICE_HANDLERS = [ ]), (MicrowaveServiceHandler, [ { - FilterFieldEnum.SERVICE_TYPE : ORM_ServiceTypeEnum.L2NM, - FilterFieldEnum.DEVICE_DRIVER : ORM_DeviceDriverEnum.IETF_NETWORK_TOPOLOGY, + FilterFieldEnum.SERVICE_TYPE : ServiceTypeEnum.SERVICETYPE_L2NM, + FilterFieldEnum.DEVICE_DRIVER : DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY, } ]), ] diff --git a/src/service/service/service_handlers/microwave/MicrowaveServiceHandler.py b/src/service/service/service_handlers/microwave/MicrowaveServiceHandler.py index 6ac58e303..1fe59db2b 100644 --- a/src/service/service/service_handlers/microwave/MicrowaveServiceHandler.py +++ b/src/service/service/service_handlers/microwave/MicrowaveServiceHandler.py @@ -14,57 +14,51 @@ import anytree, json, logging from typing import Any, Dict, List, Optional, Tuple, Union -from common.orm.Database import Database -from common.orm.HighLevel import get_object -from common.orm.backend.Tools import key_to_str +from common.proto.context_pb2 import ConfigActionEnum, 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 context.client.ContextClient import ContextClient -from device.client.DeviceClient import DeviceClient -from device.proto.context_pb2 import Device -from service.service.database.ConfigModel import ORM_ConfigActionEnum, get_config_rules -from service.service.database.ContextModel import ContextModel -from service.service.database.DeviceModel import DeviceModel -from service.service.database.ServiceModel import ServiceModel from service.service.service_handler_api._ServiceHandler import _ServiceHandler from service.service.service_handler_api.AnyTreeTools import TreeNode, delete_subnode, get_subnode, set_subnode_value -from service.service.service_handlers.Tools import config_rule_set, config_rule_delete +from service.service.task_scheduler.TaskExecutor import TaskExecutor LOGGER = logging.getLogger(__name__) class MicrowaveServiceHandler(_ServiceHandler): def __init__( # pylint: disable=super-init-not-called - self, db_service : ServiceModel, database : Database, context_client : ContextClient, - device_client : DeviceClient, **settings + self, service : Service, task_executor : TaskExecutor, **settings ) -> None: - self.__db_service = db_service - self.__database = database - self.__context_client = context_client # pylint: disable=unused-private-member - self.__device_client = device_client - - self.__db_context : ContextModel = get_object(self.__database, ContextModel, self.__db_service.context_fk) - str_service_key = key_to_str([self.__db_context.context_uuid, self.__db_service.service_uuid]) - db_config = get_config_rules(self.__database, str_service_key, 'running') + self.__service = service + self.__task_executor = task_executor # pylint: disable=unused-private-member self.__resolver = anytree.Resolver(pathattr='name') self.__config = TreeNode('.') - for action, resource_key, resource_value in db_config: - if action == ORM_ConfigActionEnum.SET: + for config_rule in service.service_config.config_rules: + action = config_rule.action + if config_rule.WhichOneof('config_rule') != 'custom': continue + resource_key = config_rule.custom.resource_key + resource_value = config_rule.custom.resource_value + if action == ConfigActionEnum.CONFIGACTION_SET: try: resource_value = json.loads(resource_value) except: # pylint: disable=bare-except pass set_subnode_value(self.__resolver, self.__config, resource_key, resource_value) - elif action == ORM_ConfigActionEnum.DELETE: + elif action == ConfigActionEnum.CONFIGACTION_DELETE: delete_subnode(self.__resolver, self.__config, resource_key) - def SetEndpoint(self, endpoints : List[Tuple[str, str, Optional[str]]]) -> List[Union[bool, Exception]]: + def SetEndpoint( + self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None + ) -> List[Union[bool, Exception]]: + LOGGER.info('[SetEndpoint] endpoints={:s}'.format(str(endpoints))) + LOGGER.info('[SetEndpoint] connection_uuid={:s}'.format(str(connection_uuid))) chk_type('endpoints', endpoints, list) if len(endpoints) != 2: return [] - service_uuid = self.__db_service.service_uuid - service_settings : TreeNode = get_subnode(self.__resolver, self.__config, 'settings', None) - if service_settings is None: raise Exception('Unable to settings for Service({:s})'.format(str(service_uuid))) + service_uuid = self.__service.service_id.service_uuid.uuid + settings : TreeNode = get_subnode(self.__resolver, self.__config, '/settings', None) + if settings is None: raise Exception('Unable to retrieve settings for Service({:s})'.format(str(service_uuid))) - json_settings : Dict = service_settings.value + json_settings : Dict = settings.value vlan_id = json_settings.get('vlan_id', 121) # endpoints are retrieved in the following format --> '/endpoints/endpoint[172.26.60.243:9]' try: @@ -81,44 +75,43 @@ class MicrowaveServiceHandler(_ServiceHandler): results = [] try: device_uuid = endpoints[0][0] - db_device : DeviceModel = get_object(self.__database, DeviceModel, device_uuid, raise_if_not_found=True) - json_device = db_device.dump(include_config_rules=False, include_drivers=True, include_endpoints=True) - json_device_config : Dict = json_device.setdefault('device_config', {}) - json_device_config_rules : List = json_device_config.setdefault('config_rules', []) - json_device_config_rules.extend([ - config_rule_set('/service[{:s}]'.format(service_uuid), { - 'uuid' : service_uuid, - 'node_id_src' : node_id_src, - 'tp_id_src' : tp_id_src, - 'node_id_dst' : node_id_dst, - 'tp_id_dst' : tp_id_dst, - 'vlan_id' : vlan_id, - }), - ]) - self.__device_client.ConfigureDevice(Device(**json_device)) + device = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid))) + json_config_rule = json_config_rule_set('/service[{:s}]'.format(service_uuid), { + 'uuid' : service_uuid, + 'node_id_src': node_id_src, + 'tp_id_src' : tp_id_src, + 'node_id_dst': node_id_dst, + 'tp_id_dst' : tp_id_dst, + 'vlan_id' : vlan_id, + }) + del device.device_config.config_rules[:] + device.device_config.config_rules.append(ConfigRule(**json_config_rule)) + self.__task_executor.configure_device(device) results.append(True) except Exception as e: # pylint: disable=broad-except - LOGGER.exception('Unable to SetEndpoint for Service({:s})'.format(str(service_uuid))) + LOGGER.exception('Unable to configure Service({:s})'.format(str(service_uuid))) results.append(e) return results - def DeleteEndpoint(self, endpoints : List[Tuple[str, str, Optional[str]]]) -> List[Union[bool, Exception]]: + def DeleteEndpoint( + self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None + ) -> List[Union[bool, Exception]]: + LOGGER.info('[DeleteEndpoint] endpoints={:s}'.format(str(endpoints))) + LOGGER.info('[DeleteEndpoint] connection_uuid={:s}'.format(str(connection_uuid))) + chk_type('endpoints', endpoints, list) if len(endpoints) != 2: return [] - service_uuid = self.__db_service.service_uuid + service_uuid = self.__service.service_id.service_uuid.uuid results = [] try: device_uuid = endpoints[0][0] - db_device : DeviceModel = get_object(self.__database, DeviceModel, device_uuid, raise_if_not_found=True) - json_device = db_device.dump(include_config_rules=False, include_drivers=True, include_endpoints=True) - json_device_config : Dict = json_device.setdefault('device_config', {}) - json_device_config_rules : List = json_device_config.setdefault('config_rules', []) - json_device_config_rules.extend([ - config_rule_delete('/service[{:s}]'.format(service_uuid), {'uuid': service_uuid}) - ]) - self.__device_client.ConfigureDevice(Device(**json_device)) + device = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid))) + json_config_rule = json_config_rule_delete('/service[{:s}]'.format(service_uuid), {'uuid': service_uuid}) + del device.device_config.config_rules[:] + device.device_config.config_rules.append(ConfigRule(**json_config_rule)) + self.__task_executor.configure_device(device) 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/microwave/__init__.py b/src/service/service/service_handlers/microwave/__init__.py new file mode 100644 index 000000000..70a332512 --- /dev/null +++ b/src/service/service/service_handlers/microwave/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# 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. + -- GitLab From ed544a4afc290bb38f94d93542e8eca9e13dd679 Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Mon, 21 Nov 2022 18:04:38 +0100 Subject: [PATCH 04/11] Common & PathComp: - corrected device type name typo - added missing devicetype-to-layer rules --- src/common/DeviceTypes.py | 4 +-- src/common/tools/object_factory/Device.py | 3 +- src/device/service/drivers/__init__.py | 8 ++--- .../algorithms/tools/ConstantsMappings.py | 36 ++++++++++--------- 4 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/common/DeviceTypes.py b/src/common/DeviceTypes.py index 08f18dd40..5bc16dd3e 100644 --- a/src/common/DeviceTypes.py +++ b/src/common/DeviceTypes.py @@ -18,7 +18,7 @@ class DeviceTypeEnum(Enum): # Emulated device types EMULATED_DATACENTER = 'emu-datacenter' - EMULATED_MICROVAWE_RADIO_SYSTEM = 'emu-microwave-radio-system' + EMULATED_MICROWAVE_RADIO_SYSTEM = 'emu-microwave-radio-system' EMULATED_OPEN_LINE_SYSTEM = 'emu-open-line-system' EMULATED_OPTICAL_ROADM = 'emu-optical-roadm' EMULATED_OPTICAL_TRANSPONDER = 'emu-optical-transponder' @@ -28,7 +28,7 @@ class DeviceTypeEnum(Enum): # Real device types DATACENTER = 'datacenter' - MICROVAWE_RADIO_SYSTEM = 'microwave-radio-system' + MICROWAVE_RADIO_SYSTEM = 'microwave-radio-system' OPEN_LINE_SYSTEM = 'open-line-system' OPTICAL_ROADM = 'optical-roadm' OPTICAL_TRANSPONDER = 'optical-transponder' diff --git a/src/common/tools/object_factory/Device.py b/src/common/tools/object_factory/Device.py index 4a590134d..406af80a8 100644 --- a/src/common/tools/object_factory/Device.py +++ b/src/common/tools/object_factory/Device.py @@ -33,8 +33,7 @@ DEVICE_PR_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG] DEVICE_TAPI_TYPE = DeviceTypeEnum.OPEN_LINE_SYSTEM.value DEVICE_TAPI_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_TRANSPORT_API] -# check which enum type and value assign to microwave device -DEVICE_MICROWAVE_TYPE = DeviceTypeEnum.MICROVAWE_RADIO_SYSTEM.value +DEVICE_MICROWAVE_TYPE = DeviceTypeEnum.MICROWAVE_RADIO_SYSTEM.value DEVICE_MICROWAVE_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY] DEVICE_P4_TYPE = DeviceTypeEnum.P4_SWITCH.value diff --git a/src/device/service/drivers/__init__.py b/src/device/service/drivers/__init__.py index 535b553a8..3a56420c9 100644 --- a/src/device/service/drivers/__init__.py +++ b/src/device/service/drivers/__init__.py @@ -29,7 +29,7 @@ DRIVERS.append( { FilterFieldEnum.DEVICE_TYPE: [ DeviceTypeEnum.EMULATED_DATACENTER, - DeviceTypeEnum.EMULATED_MICROVAWE_RADIO_SYSTEM, + DeviceTypeEnum.EMULATED_MICROWAVE_RADIO_SYSTEM, DeviceTypeEnum.EMULATED_OPEN_LINE_SYSTEM, DeviceTypeEnum.EMULATED_OPTICAL_ROADM, DeviceTypeEnum.EMULATED_OPTICAL_TRANSPONDER, @@ -38,7 +38,7 @@ DRIVERS.append( DeviceTypeEnum.EMULATED_PACKET_SWITCH, #DeviceTypeEnum.DATACENTER, - #DeviceTypeEnum.MICROVAWE_RADIO_SYSTEM, + #DeviceTypeEnum.MICROWAVE_RADIO_SYSTEM, #DeviceTypeEnum.OPEN_LINE_SYSTEM, #DeviceTypeEnum.OPTICAL_ROADM, #DeviceTypeEnum.OPTICAL_TRANSPONDER, @@ -54,7 +54,7 @@ DRIVERS.append( # # Emulated devices, all drivers => use Emulated # FilterFieldEnum.DEVICE_TYPE: [ # DeviceTypeEnum.EMULATED_DATACENTER, - # DeviceTypeEnum.EMULATED_MICROVAWE_RADIO_SYSTEM, + # DeviceTypeEnum.EMULATED_MICROWAVE_RADIO_SYSTEM, # DeviceTypeEnum.EMULATED_OPEN_LINE_SYSTEM, # DeviceTypeEnum.EMULATED_OPTICAL_ROADM, # DeviceTypeEnum.EMULATED_OPTICAL_TRANSPONDER, @@ -111,7 +111,7 @@ if LOAD_ALL_DEVICE_DRIVERS: DRIVERS.append( (IETFApiDriver, [ { - FilterFieldEnum.DEVICE_TYPE: DeviceTypeEnum.MICROVAWE_RADIO_SYSTEM, + FilterFieldEnum.DEVICE_TYPE: DeviceTypeEnum.MICROWAVE_RADIO_SYSTEM, FilterFieldEnum.DRIVER : ORM_DeviceDriverEnum.IETF_NETWORK_TOPOLOGY, } ])) diff --git a/src/pathcomp/frontend/service/algorithms/tools/ConstantsMappings.py b/src/pathcomp/frontend/service/algorithms/tools/ConstantsMappings.py index 8561ab110..332d38fd4 100644 --- a/src/pathcomp/frontend/service/algorithms/tools/ConstantsMappings.py +++ b/src/pathcomp/frontend/service/algorithms/tools/ConstantsMappings.py @@ -78,22 +78,26 @@ class DeviceLayerEnum(IntEnum): 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.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.P4_SWITCH.value : DeviceLayerEnum.MAC_LAYER_DEVICE, - - DeviceTypeEnum.MICROVAWE_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.OPTICAL_ROADM.value : DeviceLayerEnum.OPTICAL_DEVICE, - DeviceTypeEnum.OPTICAL_TRANSPONDER.value : DeviceLayerEnum.OPTICAL_DEVICE, + DeviceTypeEnum.EMULATED_DATACENTER.value : DeviceLayerEnum.APPLICATION_DEVICE, + DeviceTypeEnum.DATACENTER.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.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 = { -- GitLab From 9f4826d7d0dc230b07a19896954270cd84ca0a9e Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Tue, 22 Nov 2022 11:58:50 +0100 Subject: [PATCH 05/11] WebUI component: - added missing node icon for microwave radio systems (by now as a virtual controller) --- .../static/topology_icons/Acknowledgements.txt | 4 ++++ .../emu-microwave-radio-system.png | Bin 0 -> 12869 bytes .../topology_icons/microwave-radio-system.png | Bin 0 -> 13777 bytes 3 files changed, 4 insertions(+) create mode 100644 src/webui/service/static/topology_icons/emu-microwave-radio-system.png create mode 100644 src/webui/service/static/topology_icons/microwave-radio-system.png diff --git a/src/webui/service/static/topology_icons/Acknowledgements.txt b/src/webui/service/static/topology_icons/Acknowledgements.txt index ddf7a8d0d..df5d16dc7 100644 --- a/src/webui/service/static/topology_icons/Acknowledgements.txt +++ b/src/webui/service/static/topology_icons/Acknowledgements.txt @@ -11,6 +11,10 @@ https://symbols.getvecta.com/stencil_241/224_router.be30fb87e7.png => emu-packet https://symbols.getvecta.com/stencil_240/269_virtual-layer-switch.ed10fdede6.png => open-line-system.png https://symbols.getvecta.com/stencil_241/281_virtual-layer-switch.29420aff2f.png => emu-open-line-system.png +# Temporal icon; to be updated +https://symbols.getvecta.com/stencil_240/269_virtual-layer-switch.ed10fdede6.png => microwave-radio-system.png +https://symbols.getvecta.com/stencil_241/281_virtual-layer-switch.29420aff2f.png => emu-microwave-radio-system.png + https://symbols.getvecta.com/stencil_240/102_ibm-tower.2cc133f3d0.png => datacenter.png https://symbols.getvecta.com/stencil_241/133_ibm-tower.995c44696c.png => emu-datacenter.png diff --git a/src/webui/service/static/topology_icons/emu-microwave-radio-system.png b/src/webui/service/static/topology_icons/emu-microwave-radio-system.png new file mode 100644 index 0000000000000000000000000000000000000000..a5c30d679170c6e080dee3cc5239bf7ecaefe743 GIT binary patch literal 12869 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⁣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 NsVZqIR>@mN{6A11CyD?7 literal 0 HcmV?d00001 -- GitLab From 42c31e2e10ab0c5e9be857097ceade82d8e78606 Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Tue, 22 Nov 2022 11:59:21 +0100 Subject: [PATCH 06/11] PathComp Frontend component: - added device layer to service type mapping for MAC layer controllers --- .../service/algorithms/tools/ConstantsMappings.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pathcomp/frontend/service/algorithms/tools/ConstantsMappings.py b/src/pathcomp/frontend/service/algorithms/tools/ConstantsMappings.py index 332d38fd4..5e9b9a893 100644 --- a/src/pathcomp/frontend/service/algorithms/tools/ConstantsMappings.py +++ b/src/pathcomp/frontend/service/algorithms/tools/ConstantsMappings.py @@ -101,10 +101,12 @@ DEVICE_TYPE_TO_LAYER = { } DEVICE_LAYER_TO_SERVICE_TYPE = { - DeviceLayerEnum.APPLICATION_DEVICE.value: ServiceTypeEnum.SERVICETYPE_L3NM, + DeviceLayerEnum.APPLICATION_DEVICE.value : ServiceTypeEnum.SERVICETYPE_L3NM, + DeviceLayerEnum.PACKET_DEVICE.value : ServiceTypeEnum.SERVICETYPE_L3NM, - DeviceLayerEnum.PACKET_DEVICE.value : ServiceTypeEnum.SERVICETYPE_L3NM, - DeviceLayerEnum.MAC_LAYER_DEVICE.value : ServiceTypeEnum.SERVICETYPE_L2NM, + 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_CONTROLLER.value : ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE, + DeviceLayerEnum.OPTICAL_DEVICE.value : ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE, } -- GitLab From 8b72fc48fb706ed7f8cf3d67cf71844502aa3b71 Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Tue, 22 Nov 2022 15:29:25 +0100 Subject: [PATCH 07/11] Test tools: - Descriptors for a test topology - Descriptiors for a test microwave service - Implemented Mock MicroWave SDN Controller to test MicroWaveDeviceDriver --- .../tools/mock_sdn_ctrl/MockMWSdnCtrl.py | 130 ++++++++++++++++++ .../tools/mock_sdn_ctrl/microwave_deploy.sh | 22 +++ .../mock_sdn_ctrl/network_descriptors.json | 117 ++++++++++++++++ .../mock_sdn_ctrl/service_descriptor.json | 25 ++++ 4 files changed, 294 insertions(+) create mode 100644 src/tests/tools/mock_sdn_ctrl/MockMWSdnCtrl.py create mode 100644 src/tests/tools/mock_sdn_ctrl/microwave_deploy.sh create mode 100644 src/tests/tools/mock_sdn_ctrl/network_descriptors.json create mode 100644 src/tests/tools/mock_sdn_ctrl/service_descriptor.json diff --git a/src/tests/tools/mock_sdn_ctrl/MockMWSdnCtrl.py b/src/tests/tools/mock_sdn_ctrl/MockMWSdnCtrl.py new file mode 100644 index 000000000..61eec6fe6 --- /dev/null +++ b/src/tests/tools/mock_sdn_ctrl/MockMWSdnCtrl.py @@ -0,0 +1,130 @@ +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# 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 MicroWave SDN controller +# ----------------------------- +# REST server implementing minimal support for: +# - IETF YANG data model for Network Topology +# Ref: https://www.rfc-editor.org/rfc/rfc8345.html +# - IETF YANG data model for Transport Network Client Signals +# Ref: https://www.ietf.org/archive/id/draft-ietf-ccamp-client-signal-yang-07.html + + +# Ref: https://blog.miguelgrinberg.com/post/running-your-flask-application-over-https +# 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_restful import Api, Resource + +BIND_ADDRESS = '0.0.0.0' +BIND_PORT = 8443 +BASE_URL = '/nmswebs/restconf/data' +STR_ENDPOINT = 'https://{:s}:{:s}{:s}'.format(str(BIND_ADDRESS), str(BIND_PORT), str(BASE_URL)) +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': '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': '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': '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' }}, + ]} +] +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'}, + }, + { + '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'}, + } +] +NETWORK_SERVICES = {} + + +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 Health(Resource): + def get(self): return jsonify({}) + +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}) + +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}}) + + def post(self): + json_request = request.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) + json_services = json_request['etht-svc-instances'] + if not isinstance(json_services, list): abort(400) + if len(json_services) != 1: abort(400) + svc_data = json_services[0] + etht_svc_name = svc_data['etht-svc-name'] + NETWORK_SERVICES[etht_svc_name] = svc_data + return jsonify({}), 201 + +class DelServices(Resource): + def delete(self, service_uuid : str): + NETWORK_SERVICES.pop(service_uuid, None) + return jsonify({}), 204 + +def main(): + LOGGER.info('Starting...') + + app = Flask(__name__) + app.after_request(functools.partial(log_request, LOGGER)) + + api = Api(app, prefix=BASE_URL) + api.add_resource(Health, '/ietf-network:networks') + api.add_resource(Network, '/ietf-network:networks/network=') + api.add_resource(Services, '/ietf-eth-tran-service:etht-svc') + api.add_resource(DelServices, '/ietf-eth-tran-service:etht-svc/etht-svc-instances=') + + 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_sdn_ctrl/microwave_deploy.sh b/src/tests/tools/mock_sdn_ctrl/microwave_deploy.sh new file mode 100644 index 000000000..2da884899 --- /dev/null +++ b/src/tests/tools/mock_sdn_ctrl/microwave_deploy.sh @@ -0,0 +1,22 @@ +# Set the URL of your local Docker registry where the images will be uploaded to. +export TFS_REGISTRY_IMAGE="http://localhost:32000/tfs/" + +# Set the list of components, separated by spaces, you want to build images for, and deploy. +# Supported components are: +# context device automation policy service compute monitoring webui +# interdomain slice pathcomp dlt +# dbscanserving opticalattackmitigator opticalattackdetector +# l3_attackmitigator l3_centralizedattackdetector l3_distributedattackdetector +export TFS_COMPONENTS="context device pathcomp service slice 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 to. +export TFS_K8S_NAMESPACE="tfs" + +# Set additional manifest files to be applied after the deployment +export TFS_EXTRA_MANIFESTS="manifests/nginx_ingress_http.yaml" + +# Set the neew Grafana admin password +export TFS_GRAFANA_PASSWORD="admin123+" diff --git a/src/tests/tools/mock_sdn_ctrl/network_descriptors.json b/src/tests/tools/mock_sdn_ctrl/network_descriptors.json new file mode 100644 index 000000000..d42fe61dc --- /dev/null +++ b/src/tests/tools/mock_sdn_ctrl/network_descriptors.json @@ -0,0 +1,117 @@ +{ + "contexts": [ + { + "context_id": {"context_uuid": {"uuid": "admin"}}, + "topology_ids": [], + "service_ids": [] + } + ], + "topologies": [ + { + "topology_id": {"topology_uuid": {"uuid": "admin"}, "context_id": {"context_uuid": {"uuid": "admin"}}}, + "device_ids": [], + "link_ids": [] + } + ], + "devices": [ + { + "device_id": {"device_uuid": {"uuid": "R1"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_operational_status": 2, "device_endpoints": [], + "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": [ + {"type": "copper", "uuid": "MW", "sample_types": []}, + {"type": "copper", "uuid": "R2", "sample_types": []}, + {"type": "copper", "uuid": "EXT", "sample_types": []} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "R2"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_operational_status": 2, "device_endpoints": [], + "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": [ + {"type": "copper", "uuid": "MW", "sample_types": []}, + {"type": "copper", "uuid": "R1", "sample_types": []}, + {"type": "copper", "uuid": "EXT", "sample_types": []} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "R3"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_operational_status": 2, "device_endpoints": [], + "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": [ + {"type": "copper", "uuid": "MW", "sample_types": []}, + {"type": "copper", "uuid": "R4", "sample_types": []}, + {"type": "copper", "uuid": "EXT", "sample_types": []} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "R4"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_operational_status": 2, "device_endpoints": [], + "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": [ + {"type": "copper", "uuid": "MW", "sample_types": []}, + {"type": "copper", "uuid": "R3", "sample_types": []}, + {"type": "copper", "uuid": "EXT", "sample_types": []} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "MW"}}, "device_type": "microwave-radio-system", "device_drivers": [4], + "device_operational_status": 2, "device_endpoints": [], + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "192.168.1.56"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8443"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"timeout": 120}}} + ]} + } + ], + "links": [ + { + "link_id": {"link_uuid": {"uuid": "R1/R2==R2/R1"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "R2"}}, + {"device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "R1"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R3/R4==R4/R3"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "R4"}}, + {"device_id": {"device_uuid": {"uuid": "R4"}}, "endpoint_uuid": {"uuid": "R3"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R1/MW==MW/172.18.0.1:1"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "MW"}}, + {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "172.18.0.1:1"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R2/MW==MW/172.18.0.2:1"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "MW"}}, + {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "172.18.0.2:1"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R3/MW==MW/172.18.0.3:1"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "MW"}}, + {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "172.18.0.3:1"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R4/MW==MW/172.18.0.4:1"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R4"}}, "endpoint_uuid": {"uuid": "MW"}}, + {"device_id": {"device_uuid": {"uuid": "MW"}}, "endpoint_uuid": {"uuid": "172.18.0.4:1"}} + ] + } + ] +} \ No newline at end of file diff --git a/src/tests/tools/mock_sdn_ctrl/service_descriptor.json b/src/tests/tools/mock_sdn_ctrl/service_descriptor.json new file mode 100644 index 000000000..3e15bed5c --- /dev/null +++ b/src/tests/tools/mock_sdn_ctrl/service_descriptor.json @@ -0,0 +1,25 @@ +{ + "services": [ + { + "service_id": { + "context_id": {"context_uuid": {"uuid": "admin"}}, + "service_uuid": {"uuid": "siae-svc"} + }, + "service_type": 2, + "service_status": {"service_status": 1}, + "service_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "EXT"}}, + {"device_id": {"device_uuid": {"uuid": "R3"}}, "endpoint_uuid": {"uuid": "EXT"}} + ], + "service_constraints": [ + {"custom": {"constraint_type": "bandwidth[gbps]", "constraint_value": "10.0"}}, + {"custom": {"constraint_type": "latency[ms]", "constraint_value": "15.2"}} + ], + "service_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "/settings", "resource_value": { + "vlan_id": 121 + }}} + ]} + } + ] +} -- GitLab From 6df7c1c16da73673a9963fa645dcc5e82f19b768 Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Tue, 22 Nov 2022 15:29:51 +0100 Subject: [PATCH 08/11] Manifests: - Reduced log level of device, service and slice components to INFO --- manifests/deviceservice.yaml | 2 +- manifests/serviceservice.yaml | 2 +- manifests/sliceservice.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/manifests/deviceservice.yaml b/manifests/deviceservice.yaml index 46c7557d9..171394f7c 100644 --- a/manifests/deviceservice.yaml +++ b/manifests/deviceservice.yaml @@ -34,7 +34,7 @@ spec: - containerPort: 2020 env: - name: LOG_LEVEL - value: "DEBUG" + value: "INFO" readinessProbe: exec: command: ["/bin/grpc_health_probe", "-addr=:2020"] diff --git a/manifests/serviceservice.yaml b/manifests/serviceservice.yaml index efe43fe22..75832b94f 100644 --- a/manifests/serviceservice.yaml +++ b/manifests/serviceservice.yaml @@ -34,7 +34,7 @@ spec: - containerPort: 3030 env: - name: LOG_LEVEL - value: "DEBUG" + value: "INFO" readinessProbe: exec: command: ["/bin/grpc_health_probe", "-addr=:3030"] diff --git a/manifests/sliceservice.yaml b/manifests/sliceservice.yaml index eeed3776c..8c76618a9 100644 --- a/manifests/sliceservice.yaml +++ b/manifests/sliceservice.yaml @@ -34,7 +34,7 @@ spec: - containerPort: 4040 env: - name: LOG_LEVEL - value: "DEBUG" + value: "INFO" readinessProbe: exec: command: ["/bin/grpc_health_probe", "-addr=:4040"] -- GitLab From b481f48c62a7f3bbd1c1e91e11b4804c5a7af583 Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Tue, 22 Nov 2022 15:30:28 +0100 Subject: [PATCH 09/11] PathComputation component: - added logic to propagate config rules of services to subservices by default --- .../frontend/service/algorithms/_Algorithm.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/pathcomp/frontend/service/algorithms/_Algorithm.py b/src/pathcomp/frontend/service/algorithms/_Algorithm.py index b798813a8..43811c068 100644 --- a/src/pathcomp/frontend/service/algorithms/_Algorithm.py +++ b/src/pathcomp/frontend/service/algorithms/_Algorithm.py @@ -134,7 +134,8 @@ class _Algorithm: def add_service_to_reply( self, reply : PathCompReply, context_uuid : str, service_uuid : str, - device_layer : Optional[DeviceLayerEnum] = None, path_hops : List[Dict] = [] + device_layer : Optional[DeviceLayerEnum] = None, 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 @@ -168,6 +169,8 @@ class _Algorithm: } 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) service.service_status.service_status = ServiceStatusEnum.SERVICESTATUS_PLANNED @@ -192,7 +195,8 @@ class _Algorithm: context_uuid = service_id['contextId'] service_uuid = service_id['service_uuid'] service_key = (context_uuid, service_uuid) - grpc_services[service_key] = self.add_service_to_reply(reply, context_uuid, service_uuid) + upper_service = self.add_service_to_reply(reply, context_uuid, service_uuid) + grpc_services[service_key] = upper_service no_path_issue = response.get('noPath', {}).get('issue') if no_path_issue is not None: @@ -209,8 +213,10 @@ class _Algorithm: 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) + reply, context_uuid, connection_uuid, device_layer=device_layer, path_hops=path_hops, + config_rules=config_rules) grpc_services[service_key] = grpc_service for connection in connections: -- GitLab From 2ad66d5b59321ca1566df9732e32c2b741d4c387 Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Tue, 22 Nov 2022 15:31:26 +0100 Subject: [PATCH 10/11] Device MicroWave Device Driver: - aesthetic code formatting - improved error checking - factorized code --- .../microwave/MicrowaveServiceHandler.py | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/src/service/service/service_handlers/microwave/MicrowaveServiceHandler.py b/src/service/service/service_handlers/microwave/MicrowaveServiceHandler.py index 1fe59db2b..1ae08bbf6 100644 --- a/src/service/service/service_handlers/microwave/MicrowaveServiceHandler.py +++ b/src/service/service/service_handlers/microwave/MicrowaveServiceHandler.py @@ -24,6 +24,12 @@ from service.service.task_scheduler.TaskExecutor import TaskExecutor LOGGER = logging.getLogger(__name__) +def check_endpoint(endpoint : str, service_uuid : str) -> Tuple[str, str]: + endpoint_split = endpoint.split(':') + if len(endpoint_split) != 2: + raise Exception('Endpoint({:s}) is malformed for Service({:s})'.format(str(endpoint), str(service_uuid))) + return endpoint_split + class MicrowaveServiceHandler(_ServiceHandler): def __init__( # pylint: disable=super-init-not-called self, service : Service, task_executor : TaskExecutor, **settings @@ -51,29 +57,24 @@ class MicrowaveServiceHandler(_ServiceHandler): ) -> List[Union[bool, Exception]]: LOGGER.info('[SetEndpoint] endpoints={:s}'.format(str(endpoints))) LOGGER.info('[SetEndpoint] connection_uuid={:s}'.format(str(connection_uuid))) - chk_type('endpoints', endpoints, list) - if len(endpoints) != 2: return [] service_uuid = self.__service.service_id.service_uuid.uuid - settings : TreeNode = get_subnode(self.__resolver, self.__config, '/settings', None) - if settings is None: raise Exception('Unable to retrieve settings for Service({:s})'.format(str(service_uuid))) - - json_settings : Dict = settings.value - vlan_id = json_settings.get('vlan_id', 121) - # endpoints are retrieved in the following format --> '/endpoints/endpoint[172.26.60.243:9]' - try: - endpoint_src_split = endpoints[0][1].split(':') - endpoint_dst_split = endpoints[1][1].split(':') - if len(endpoint_src_split) != 2 and len(endpoint_dst_split) != 2: return [] - node_id_src = endpoint_src_split[0] - tp_id_src = endpoint_src_split[1] - node_id_dst = endpoint_dst_split[0] - tp_id_dst = endpoint_dst_split[1] - except ValueError: - return [] results = [] try: + chk_type('endpoints', endpoints, list) + if len(endpoints) != 2: raise Exception('len(endpoints) != 2') + + settings : TreeNode = get_subnode(self.__resolver, self.__config, '/settings', None) + if settings is None: + raise Exception('Unable to retrieve settings for Service({:s})'.format(str(service_uuid))) + + json_settings : Dict = settings.value + vlan_id = json_settings.get('vlan_id', 121) + # endpoints are retrieved in the following format --> '/endpoints/endpoint[172.26.60.243:9]' + node_id_src, tp_id_src = check_endpoint(endpoints[0][1], service_uuid) + node_id_dst, tp_id_dst = check_endpoint(endpoints[1][1], service_uuid) + device_uuid = endpoints[0][0] device = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid))) json_config_rule = json_config_rule_set('/service[{:s}]'.format(service_uuid), { @@ -89,7 +90,7 @@ class MicrowaveServiceHandler(_ServiceHandler): self.__task_executor.configure_device(device) results.append(True) except Exception as e: # pylint: disable=broad-except - LOGGER.exception('Unable to configure Service({:s})'.format(str(service_uuid))) + LOGGER.exception('Unable to SetEndpoint for Service({:s})'.format(str(service_uuid))) results.append(e) return results @@ -100,12 +101,13 @@ class MicrowaveServiceHandler(_ServiceHandler): LOGGER.info('[DeleteEndpoint] endpoints={:s}'.format(str(endpoints))) LOGGER.info('[DeleteEndpoint] connection_uuid={:s}'.format(str(connection_uuid))) - chk_type('endpoints', endpoints, list) - if len(endpoints) != 2: return [] - service_uuid = self.__service.service_id.service_uuid.uuid + results = [] try: + chk_type('endpoints', endpoints, list) + if len(endpoints) != 2: raise Exception('len(endpoints) != 2') + device_uuid = endpoints[0][0] device = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid))) json_config_rule = json_config_rule_delete('/service[{:s}]'.format(service_uuid), {'uuid': service_uuid}) -- GitLab From 9e3381e8d0416bd5dace217ee1a130d5c431063b Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Tue, 22 Nov 2022 16:07:49 +0100 Subject: [PATCH 11/11] Test tools: - Added readme file on how to use the Mock MicroWave SDN Controller and run the tests for the MicroWaveDeviceDriver and MicroWaveServiceHandler. - Updated the descriptors associated to this test. --- src/tests/tools/mock_sdn_ctrl/README.md | 53 +++++++++++++++++++ .../mock_sdn_ctrl/network_descriptors.json | 2 +- .../mock_sdn_ctrl/service_descriptor.json | 2 +- 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 src/tests/tools/mock_sdn_ctrl/README.md diff --git a/src/tests/tools/mock_sdn_ctrl/README.md b/src/tests/tools/mock_sdn_ctrl/README.md new file mode 100644 index 000000000..d8a6fe6b2 --- /dev/null +++ b/src/tests/tools/mock_sdn_ctrl/README.md @@ -0,0 +1,53 @@ +# Mock MicroWave SDN Controller + +This REST server implements very basic support for the following YANG data models: +- IETF YANG data model for Network Topology + - Ref: https://www.rfc-editor.org/rfc/rfc8345.html +- IETF YANG data model for Transport Network Client Signals + - Ref: https://www.ietf.org/archive/id/draft-ietf-ccamp-client-signal-yang-07.html + +The aim of this server is to enable testing the MicroWaveDeviceDriver and the MicroWaveServiceHandler. +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 +``` + +## 2. Install requirements and run the Mock MicroWave SDN controller +__NOTE__: if you run the Mock MicroWave SDN controller from the PyEnv used for developping on the TeraFlowSDN framework, +all the requirements are already in place. Install them only if you execute it in a separate/standalone environment. + +Install the required dependencies as follows: +```bash +pip install Flask==2.1.3 Flask-RESTful==0.3.9 +``` + +Run the Mock MicroWave SDN Controller as follows: +```bash +python src/tests/tools/mock_sdn_ctrl/MockMWSdnCtrl.py +``` + +## 3. Deploy the test descriptors +Edit the descriptors to meet your environment specifications. +Edit "network_descriptors.json" and change IP address and port of the MicroWave SDN controller of the "MW" device. +- Set value of config rule "_connect/address" to the address of the host where the Mock MicroWave SDN controller is + running (default="192.168.1.1"). +- Set value of config rule "_connect/port" to the port where your Mock MicroWave SDN controller is listening on + (default="8443"). + +Upload the "network_descriptors.json" through the TeraFlowSDN WebUI. +- If not already selected, select context "admin". +- Check that a network topology with 4 routers + 1 microwave radio system are loaded. They should form 2 rings. + +Upload the "service_descriptor.json" through the TeraFlowSDN WebUI. +- Check that 2 services have been created. +- The "mw-svc" should have a connection and be supported by a sub-service. +- The sub-service should also have a connection. +- The R1, R3, and MW devices should have configuration rules established. + +# 4. Delete the microwave service +Find the "mw-svc" on the WebUI, navigate to its details, and delete the service pressing the "Delete Service" button. +The service, sub-service, and device configuration rules should be removed. diff --git a/src/tests/tools/mock_sdn_ctrl/network_descriptors.json b/src/tests/tools/mock_sdn_ctrl/network_descriptors.json index d42fe61dc..25fa940a4 100644 --- a/src/tests/tools/mock_sdn_ctrl/network_descriptors.json +++ b/src/tests/tools/mock_sdn_ctrl/network_descriptors.json @@ -70,7 +70,7 @@ "device_id": {"device_uuid": {"uuid": "MW"}}, "device_type": "microwave-radio-system", "device_drivers": [4], "device_operational_status": 2, "device_endpoints": [], "device_config": {"config_rules": [ - {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "192.168.1.56"}}, + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "192.168.1.1"}}, {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "8443"}}, {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"timeout": 120}}} ]} diff --git a/src/tests/tools/mock_sdn_ctrl/service_descriptor.json b/src/tests/tools/mock_sdn_ctrl/service_descriptor.json index 3e15bed5c..a4109bc7b 100644 --- a/src/tests/tools/mock_sdn_ctrl/service_descriptor.json +++ b/src/tests/tools/mock_sdn_ctrl/service_descriptor.json @@ -3,7 +3,7 @@ { "service_id": { "context_id": {"context_uuid": {"uuid": "admin"}}, - "service_uuid": {"uuid": "siae-svc"} + "service_uuid": {"uuid": "mw-svc"} }, "service_type": 2, "service_status": {"service_status": 1}, -- GitLab