Skip to content
Snippets Groups Projects
Commit 9b17d6a6 authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

Merge branch 'feat/132-opt-qkd-network-control-and-management-integration-service' into 'develop'

Resolve "(OPT) QKD Network Control and Management Integration (Service)"

See merge request !263
parents d0382c83 a1f718e7
No related branches found
No related tags found
2 merge requests!294Release TeraFlowSDN 4.0,!263Resolve "(OPT) QKD Network Control and Management Integration (Service)"
......@@ -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)
......@@ -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
......
......@@ -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],
}
])
]
# 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.
# 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()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment