diff --git a/src/context/service/database/models/enums/ServiceType.py b/src/context/service/database/models/enums/ServiceType.py index 62d5380b56803b3cc21dd1456292ec9df470cb15..cd6819999a585b541ef716fa481910ae3b53ecbf 100644 --- a/src/context/service/database/models/enums/ServiceType.py +++ b/src/context/service/database/models/enums/ServiceType.py @@ -29,6 +29,7 @@ class ORM_ServiceTypeEnum(enum.Enum): TE = ServiceTypeEnum.SERVICETYPE_TE E2E = ServiceTypeEnum.SERVICETYPE_E2E OPTICAL_CONNECTIVITY = ServiceTypeEnum.SERVICETYPE_OPTICAL_CONNECTIVITY + QKD = ServiceTypeEnum.SERVICETYPE_QKD grpc_to_enum__service_type = functools.partial( grpc_to_enum, ServiceTypeEnum, ORM_ServiceTypeEnum) diff --git a/src/service/service/service_handler_api/FilterFields.py b/src/service/service/service_handler_api/FilterFields.py index ca70fa9386e356e7e49397365701013e1d3a1697..494e59e3c6698c6ae052e1f4b35b7a56e8dc3fba 100644 --- a/src/service/service/service_handler_api/FilterFields.py +++ b/src/service/service/service_handler_api/FilterFields.py @@ -26,7 +26,8 @@ SERVICE_TYPE_VALUES = { ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE, ServiceTypeEnum.SERVICETYPE_TE, ServiceTypeEnum.SERVICETYPE_E2E, - ServiceTypeEnum.SERVICETYPE_OPTICAL_CONNECTIVITY + ServiceTypeEnum.SERVICETYPE_OPTICAL_CONNECTIVITY, + ServiceTypeEnum.SERVICETYPE_QKD, } DEVICE_DRIVER_VALUES = { @@ -41,7 +42,8 @@ DEVICE_DRIVER_VALUES = { DeviceDriverEnum.DEVICEDRIVER_GNMI_OPENCONFIG, DeviceDriverEnum.DEVICEDRIVER_OPTICAL_TFS, DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN, - DeviceDriverEnum.DEVICEDRIVER_OC + DeviceDriverEnum.DEVICEDRIVER_OC, + DeviceDriverEnum.DEVICEDRIVER_QKD, } # 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_handlers/__init__.py b/src/service/service/service_handlers/__init__.py index 8b5e2b2834f37dc3d616907e46cf1fa5b2f1274f..3beb7c9ee95c79d7219550a62166c648e061a01d 100644 --- a/src/service/service/service_handlers/__init__.py +++ b/src/service/service/service_handlers/__init__.py @@ -27,6 +27,7 @@ from .tapi_tapi.TapiServiceHandler import TapiServiceHandler from .tapi_xr.TapiXrServiceHandler import TapiXrServiceHandler from .e2e_orch.E2EOrchestratorServiceHandler import E2EOrchestratorServiceHandler from .oc.OCServiceHandler import OCServiceHandler +from .qkd.qkd_service_handler import QKDServiceHandler SERVICE_HANDLERS = [ (L2NMEmulatedServiceHandler, [ @@ -106,5 +107,11 @@ SERVICE_HANDLERS = [ FilterFieldEnum.SERVICE_TYPE : ServiceTypeEnum.SERVICETYPE_OPTICAL_CONNECTIVITY, FilterFieldEnum.DEVICE_DRIVER : DeviceDriverEnum.DEVICEDRIVER_OC, } + ]), + (QKDServiceHandler, [ + { + FilterFieldEnum.SERVICE_TYPE : ServiceTypeEnum.SERVICETYPE_QKD, + FilterFieldEnum.DEVICE_DRIVER : [DeviceDriverEnum.DEVICEDRIVER_QKD], + } ]) ] diff --git a/src/service/service/service_handlers/qkd/__init__.py b/src/service/service/service_handlers/qkd/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..3ee6f7071f145e06c3aeaefc09a43ccd88e619e3 --- /dev/null +++ b/src/service/service/service_handlers/qkd/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (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/qkd/qkd_service_handler.py b/src/service/service/service_handlers/qkd/qkd_service_handler.py new file mode 100644 index 0000000000000000000000000000000000000000..76c67867ee2f4bae60b8dd6e187f221f2efc1eb0 --- /dev/null +++ b/src/service/service/service_handlers/qkd/qkd_service_handler.py @@ -0,0 +1,394 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (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, uuid +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.proto.app_pb2 import App, QKDAppStatusEnum, QKDAppTypesEnum +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__) + +def get_endpoint_name_by_uuid(device, uuid): + for device_endpoint in device.device_endpoints: + if device_endpoint.endpoint_id.endpoint_uuid.uuid == uuid: + return device_endpoint.name + return None + +class QKDServiceHandler(_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) + + + # Optare: This function is where the service is created + # Optare: It already receives the path provided by pathcomp in endpoints variable + # Optare: It then checks for each respective QKD Node and requests SBI to inform of the new connection + # Optare: It also requests app module for a creation of internal service if the service is virtual + 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 or len(endpoints) % 2: return [] + + LOGGER.info('Endpoints: ' + str(endpoints)) + + + service_uuid = self.__service.service_id.service_uuid.uuid + settings = self.__settings_handler.get('/settings') + + context_uuid = self.__service.service_id.context_id.context_uuid.uuid + + results = [] + try: + + if len(endpoints) > 4: + is_virtual = True + else: + is_virtual = False + + devices = [] + qkdn_ids = [] + interfaces = [] + links = [] + + # Optare: First a big iteration through all devices is done in order to obtain all information needed for the whole operation + # Optare: This is a way to minimize time of operation. Otherwise it would require many O(N) operations. This way we can reduce it to one + + # Populate devices and QKDN ids + for idx, endpoint in enumerate(endpoints[::2]): + device_uuid, endpoint_left_uuid = get_device_endpoint_uuids(endpoint) + _, endpoint_right_uuid = get_device_endpoint_uuids(endpoints[2 * idx + 1]) + + device = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid))) + devices.append(device) + interfaces.append([0,0]) + links.append([]) + + + + + + endpoint_left = get_endpoint_name_by_uuid(device, endpoint_left_uuid) if idx > 0 else None + endpoint_right = get_endpoint_name_by_uuid(device, endpoint_right_uuid) if 2 * idx + 2 < len(endpoints) else None + + + for config_rule in device.device_config.config_rules: + resource_key = config_rule.custom.resource_key + + if resource_key == '__node__': + value = json.loads(config_rule.custom.resource_value) + qkdn_ids.append(value['qkdn_id']) + + elif resource_key.startswith('/interface'): + value = json.loads(config_rule.custom.resource_value) + try: + endpoint_str = value['qkdi_att_point']['uuid'] + LOGGER.info("A: " + str(endpoint_str) + "....." + str(endpoint_left) + "....." + str(endpoint_right)) + + if endpoint_str == endpoint_left: + interfaces[idx][0] = value['qkdi_id'] + elif endpoint_str == endpoint_right: + interfaces[idx][1] = value['qkdi_id'] + except KeyError: + pass + + elif resource_key.startswith('/link'): + value = json.loads(config_rule.custom.resource_value) + links[idx].append(( value['uuid'], + (value['src_qkdn_id'], value['src_interface_id']), + (value['dst_qkdn_id'], value['dst_interface_id']) + )) + + + LOGGER.info("IFs: " + str(interfaces)) + LOGGER.info("Links: " + str(links)) + LOGGER.info("context_: " + context_uuid) + + + # Optare: From here now is where the work is really done. It iterates over every device in use for the service (ordered) + + src_device_uuid, src_endpoint_uuid = get_device_endpoint_uuids(endpoints[0]) + src_device = devices[0] + src_endpoint = get_endpoint_matching(src_device, src_endpoint_uuid) + + dst_device_uuid, dst_endpoint_uuid = get_device_endpoint_uuids(endpoints[-1]) + dst_device = devices[-1] + dst_endpoint = get_endpoint_matching(dst_device, dst_endpoint_uuid) + + src_qkdn_id = qkdn_ids[0] + dst_qkdn_id = qkdn_ids[-1] + + src_interface_id = interfaces[0][1] + dst_interface_id = interfaces[-1][0] + + service_qkdl_id_src_dst = str(uuid.uuid4()) + service_qkdl_id_dst_src = str(uuid.uuid4()) + + + for idx, device in enumerate(devices): + + # Even though we always create them together. There is a chance the admin deletes one of the rules manually + phys_qkdl_id_right = None if idx == (len(devices) - 1) else '' # None == impossible + phys_qkdl_id_left = None if idx == 0 else '' # None == impossible + + for link_uuid, link_src, link_dst in links[idx]: + qkdn_link_src, qkdn_interface_src = link_src + qkdn_link_dst, qkdn_interface_dst = link_dst + + if phys_qkdl_id_right == '' and \ + qkdn_link_src == qkdn_ids[idx] and qkdn_interface_src == interfaces[idx][1] and \ + qkdn_link_dst[idx+1] and qkdn_interface_dst == interfaces[idx+1][0]: + phys_qkdl_id_right = link_uuid + + + if phys_qkdl_id_left == '' and \ + qkdn_link_src == qkdn_ids[idx] and qkdn_interface_src == interfaces[idx][0] and \ + qkdn_link_dst[idx-1] and qkdn_interface_dst == interfaces[idx-1][1]: + phys_qkdl_id_left = link_uuid + + + # Optare: Before adding information to config_rules you have to delete the list first otherwise old content will be called again + del device.device_config.config_rules[:] + + + if phys_qkdl_id_right: + if not is_virtual: + service_qkdl_id_src_dst = phys_qkdl_id_right + + elif phys_qkdl_id_right == '': + qkdl_id_src_dst = str(uuid.uuid4()) if is_virtual else service_qkdl_id_src_dst + + json_config_rule = json_config_rule_set('/link/link[{:s}]'.format(qkdl_id_src_dst), { + 'uuid' : qkdl_id_src_dst, + 'type' : 'DIRECT', + 'src_qkdn_id' : qkdn_ids[idx], + 'src_interface_id' : interfaces[idx][1], + 'dst_qkdn_id' : qkdn_ids[idx+1], + 'dst_interface_id' : interfaces[idx+1][0], + }) + + device.device_config.config_rules.append(ConfigRule(**json_config_rule)) + + + if phys_qkdl_id_left: + if not is_virtual: + service_qkdl_id_dst_src = phys_qkdl_id_left + + elif phys_qkdl_id_left == '': + qkdl_id_dst_src = str(uuid.uuid4()) if is_virtual else service_qkdl_id_dst_src + + json_config_rule = json_config_rule_set('/link/link[{:s}]'.format(qkdl_id_dst_src), { + 'uuid' : qkdl_id_dst_src, + 'type' : 'DIRECT', + 'src_qkdn_id' : qkdn_ids[idx], + 'src_interface_id' : interfaces[idx][0], + 'dst_qkdn_id' : qkdn_ids[idx-1], + 'dst_interface_id' : interfaces[idx-1][1], + }) + + device.device_config.config_rules.append(ConfigRule(**json_config_rule)) + + + + if is_virtual: + if idx < len(qkdn_ids) - 1: + json_config_rule = json_config_rule_set('/link/link[{:s}]'.format(service_qkdl_id_src_dst), { + 'uuid' : service_qkdl_id_src_dst, + 'type' : 'VIRTUAL', + 'src_qkdn_id' : src_qkdn_id, + 'src_interface_id' : src_interface_id, + 'dst_qkdn_id' : dst_qkdn_id, + 'dst_interface_id' : dst_interface_id, + 'virt_prev_hop' : qkdn_ids[idx-1] if idx > 0 else None, + 'virt_next_hops' : qkdn_ids[idx+1:], + 'virt_bandwidth' : 0, + }) + + device.device_config.config_rules.append(ConfigRule(**json_config_rule)) + + if idx > 0: + json_config_rule = json_config_rule_set('/link/link[{:s}]'.format(service_qkdl_id_dst_src), { + 'uuid' : service_qkdl_id_dst_src, + 'type' : 'VIRTUAL', + 'src_qkdn_id' : dst_qkdn_id, + 'src_interface_id' : dst_interface_id, + 'dst_qkdn_id' : src_qkdn_id, + 'dst_interface_id' : src_interface_id, + 'virt_prev_hop' : qkdn_ids[idx+1] if idx < len(qkdn_ids) - 1 else None, + 'virt_next_hops' : qkdn_ids[idx-1::-1], + 'virt_bandwidth' : 0, + }) + + device.device_config.config_rules.append(ConfigRule(**json_config_rule)) + + + + json_config_rule = json_config_rule_set('/services/service[{:s}]'.format(service_uuid), { + 'uuid' : service_uuid, + 'qkdl_id_src_dst' : service_qkdl_id_src_dst, + 'qkdl_id_dst_src' : service_qkdl_id_dst_src, + }) + + device.device_config.config_rules.append(ConfigRule(**json_config_rule)) + self.__task_executor.configure_device(device) + + + if is_virtual: + + # Register App + internal_app_src_dst = { + 'app_id': {'context_id': {'context_uuid': {'uuid': context_uuid}}, 'app_uuid': {'uuid': str(uuid.uuid4())}}, + 'app_status': QKDAppStatusEnum.QKDAPPSTATUS_ON, + 'app_type': QKDAppTypesEnum.QKDAPPTYPES_INTERNAL, + 'server_app_id': '', + 'client_app_id': [], + 'backing_qkdl_id': [{'qkdl_uuid': {'uuid': service_qkdl_id_src_dst}}], + 'local_device_id': src_device.device_id, + 'remote_device_id': dst_device.device_id, + } + + self.__task_executor.register_app(App(**internal_app_src_dst)) + + + # Register App + internal_app_dst_src = { + 'app_id': {'context_id': {'context_uuid': {'uuid': context_uuid}}, 'app_uuid': {'uuid': str(uuid.uuid4())}}, + 'app_status': QKDAppStatusEnum.QKDAPPSTATUS_ON, + 'app_type': QKDAppTypesEnum.QKDAPPTYPES_INTERNAL, + 'server_app_id': '', + 'client_app_id': [], + 'backing_qkdl_id': [{'qkdl_uuid': {'uuid': service_qkdl_id_dst_src}}], + 'local_device_id': dst_device.device_id, + 'remote_device_id': src_device.device_id, + } + + self.__task_executor.register_app(App(**internal_app_dst_src)) + + 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 + + # Optare: This will be to delete a service + def DeleteEndpoint( + self, endpoints : List[Tuple[str, str, Optional[str]]], + connection_uuid : Optional[str] = None + ) -> List[Union[bool, Exception]]: + """ Delete service endpoints form a list. + Parameters: + endpoints: List[Tuple[str, str, Optional[str]]] + List of tuples, each containing a device_uuid, + endpoint_uuid, and the topology_uuid of the endpoint + to be removed. + connection_uuid : Optional[str] + If specified, is the UUID of the connection this endpoint is associated to. + Returns: + results: List[Union[bool, Exception]] + List of results for endpoint deletions requested. + Return values must be in the same order as the requested + endpoints. If an endpoint is properly deleted, True must be + returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + raise NotImplementedError() + + # Optare: Can be ingored. It's in case if a service is later updated. Not required to proper functioning + + def SetConstraint(self, constraints: List[Tuple[str, Any]]) \ + -> List[Union[bool, Exception]]: + """ Create/Update service constraints. + Parameters: + constraints: List[Tuple[str, Any]] + List of tuples, each containing a constraint_type and the + new constraint_value to be set. + Returns: + results: List[Union[bool, Exception]] + List of results for constraint changes requested. + Return values must be in the same order as the requested + constraints. If a constraint is properly set, True must be + returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + raise NotImplementedError() + + def DeleteConstraint(self, constraints: List[Tuple[str, Any]]) \ + -> List[Union[bool, Exception]]: + """ Delete service constraints. + Parameters: + constraints: List[Tuple[str, Any]] + List of tuples, each containing a constraint_type pointing + to the constraint to be deleted, and a constraint_value + containing possible additionally required values to locate + the constraint to be removed. + Returns: + results: List[Union[bool, Exception]] + List of results for constraint deletions requested. + Return values must be in the same order as the requested + constraints. If a constraint is properly deleted, True must + be returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + raise NotImplementedError() + + def SetConfig(self, resources: List[Tuple[str, Any]]) \ + -> List[Union[bool, Exception]]: + """ Create/Update configuration for a list of service resources. + Parameters: + resources: List[Tuple[str, Any]] + List of tuples, each containing a resource_key pointing to + the resource to be modified, and a resource_value + containing the new value to be set. + Returns: + results: List[Union[bool, Exception]] + List of results for resource key changes requested. + Return values must be in the same order as the requested + resource keys. If a resource is properly set, True must be + returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + raise NotImplementedError() + + def DeleteConfig(self, resources: List[Tuple[str, Any]]) \ + -> List[Union[bool, Exception]]: + """ Delete configuration for a list of service resources. + Parameters: + resources: List[Tuple[str, Any]] + List of tuples, each containing a resource_key pointing to + the resource to be modified, and a resource_value containing + possible additionally required values to locate the value + to be removed. + Returns: + results: List[Union[bool, Exception]] + List of results for resource key deletions requested. + Return values must be in the same order as the requested + resource keys. If a resource is properly deleted, True must + be returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + raise NotImplementedError()