# Copyright 2022-2024 ETSI 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. # Convert the path defined as explicit hops with ingress and egress endpoints per device into a set of connections and # compute the dependencies among them. # # Example: # o-- int DC1 eth1 -- 10/1 CS1 1/2 -- 1/2 R2 2/1 -- a7.. OLS 60.. -- 2/1 R3 1/1 -- 1/1 CS2 10/1 -- eth1 DC2 int --o # APP PKT PKT CTRL PKT PKT APP # # path_hops = [ # {'device': 'DC1-GW', 'ingress_ep': 'int', 'egress_ep': 'eth1'}, # {'device': 'CS1-GW1', 'ingress_ep': '10/1', 'egress_ep': '1/2'}, # {'device': 'TN-R2', 'ingress_ep': '1/2', 'egress_ep': '2/1'}, # {'device': 'TN-OLS', 'ingress_ep': 'a7a80b23a703', 'egress_ep': '60519106029e'}, # {'device': 'TN-R3', 'ingress_ep': '2/1', 'egress_ep': '1/1'}, # {'device': 'CS2-GW1', 'ingress_ep': '1/1', 'egress_ep': '10/1'}, # {'device': 'DC2-GW', 'ingress_ep': 'eth1', 'egress_ep': 'int'} # ] # # connections=[ # (UUID('7548edf7-ee7c-4adf-ac0f-c7a0c0dfba8e'), ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE, [ # {'device': 'TN-OLS', 'ingress_ep': '833760219d0f', 'egress_ep': 'cf176771a4b9'} # ], []), # (UUID('c2e57966-5d82-4705-a5fe-44cf6487219e'), ServiceTypeEnum.SERVICETYPE_L2NM, [ # {'device': 'CS1-GW1', 'ingress_ep': '10/1', 'egress_ep': '1/2'}, # {'device': 'TN-R2', 'ingress_ep': '1/2', 'egress_ep': '2/1'}, # {'device': 'TN-R3', 'ingress_ep': '2/1', 'egress_ep': '1/1'}, # {'device': 'CS2-GW1', 'ingress_ep': '1/1', 'egress_ep': '10/1'} # ], [UUID('7548edf7-ee7c-4adf-ac0f-c7a0c0dfba8e')]), # (UUID('1e205c82-f6ea-4977-9e97-dc27ef1f4802'), ServiceTypeEnum.SERVICETYPE_L2NM, [ # {'device': 'DC1-GW', 'ingress_ep': 'int', 'egress_ep': 'eth1'}, # {'device': 'DC2-GW', 'ingress_ep': 'eth1', 'egress_ep': 'int'} # ], [UUID('c2e57966-5d82-4705-a5fe-44cf6487219e')]) # ] import logging, queue from dataclasses import dataclass, field from typing import Dict, List, Optional, Tuple from common.DeviceTypes import DeviceTypeEnum from common.proto.context_pb2 import Device, ServiceTypeEnum from common.tools.context_queries.Slice import get_uuid_from_string from .ResourceGroups import IGNORED_DEVICE_TYPES, REMOTEDOMAIN_DEVICE_TYPES, get_resource_classification from .ServiceTypes import get_service_type LOGGER = logging.getLogger(__name__) @dataclass class ConnectionEntry: uuid: str = '' service_type : ServiceTypeEnum = ServiceTypeEnum.SERVICETYPE_UNKNOWN path_hops : List[Dict] = field(default_factory=list) dependencies : List['ConnectionEntry'] = field(default_factory=list) def calculate_subservice_uuid(self, main_service_uuid: str) -> None: if self.uuid: return composed_string = main_service_uuid + '-'.join( f'{path_hop["device"]}/{path_hop["ingress_ep"]}/{path_hop["egress_ep"]}' for path_hop in self.path_hops ) self.uuid = get_uuid_from_string(composed_string) def convert_explicit_path_hops_to_connections( path_hops : List[Dict], device_dict : Dict[str, Tuple[Dict, Device]], main_service_uuid : str, main_service_type : ServiceTypeEnum ) -> List[Tuple[str, int, List[Dict], List[str]]]: LOGGER.debug('path_hops={:s}'.format(str(path_hops))) connection_stack = queue.LifoQueue() connections : List[ConnectionEntry] = list() prv_device_uuid = None prv_res_class : Tuple[Optional[int], Optional[DeviceTypeEnum], Optional[str]] = None, None, None for path_hop in path_hops: LOGGER.debug('path_hop={:s}'.format(str(path_hop))) device_uuid = path_hop['device'] if prv_device_uuid == device_uuid: continue device_tuple = device_dict.get(device_uuid) if device_tuple is None: raise Exception('Device({:s}) not found'.format(str(device_uuid))) _,grpc_device = device_tuple res_class = get_resource_classification(grpc_device, device_dict) LOGGER.debug(' prv_res_class={:s}'.format(str(prv_res_class))) LOGGER.debug(' res_class={:s}'.format(str(res_class))) if res_class[1] in IGNORED_DEVICE_TYPES: LOGGER.debug(' ignored') continue if res_class[1] in REMOTEDOMAIN_DEVICE_TYPES: LOGGER.debug(' create and terminate underlying connection') # create underlying connection prv_service_type = connection_stack.queue[-1].service_type service_type = get_service_type(res_class[1], prv_service_type) connection_entry = ConnectionEntry(service_type=service_type, path_hops=[path_hop]) connection_stack.put(connection_entry) # underlying connection ended connection: ConnectionEntry = connection_stack.get() connections.append(connection) connection_stack.queue[-1].dependencies.append(connection) elif prv_res_class[2] is None and res_class[2] is not None: # entering domain of a device controller, create underlying connection LOGGER.debug(' entering domain of a device controller, create underlying connection') prv_service_type = connection_stack.queue[-1].service_type service_type = get_service_type(res_class[1], prv_service_type) connection_entry = ConnectionEntry(service_type=service_type, path_hops=[path_hop]) connection_stack.put(connection_entry) elif prv_res_class[2] is not None and res_class[2] is None: # leaving domain of a device controller, terminate underlying connection LOGGER.debug(' leaving domain of a device controller, terminate underlying connection') connection = connection_stack.get() connections.append(connection) connection_stack.queue[-1].dependencies.append(connection) connection_stack.queue[-1].path_hops.append(path_hop) elif prv_res_class[2] is not None and res_class[2] is not None: if prv_res_class[2] == res_class[2]: # stay in domain of a device controller, connection continues LOGGER.debug(' stay in domain of a device controller, connection continues') connection_stack.queue[-1].path_hops.append(path_hop) else: # switching to different device controller, chain connections LOGGER.debug(' switching to different device controller, chain connections') connection = connection_stack.get() connections.append(connection) connection_stack.queue[-1].dependencies.append(connection) prv_service_type = connection_stack.queue[-1].service_type service_type = get_service_type(res_class[1], prv_service_type) connection_entry = ConnectionEntry(service_type=service_type, path_hops=[path_hop]) connection_stack.put(connection_entry) elif prv_res_class[0] is None: # path ingress LOGGER.debug(' path ingress') connection_entry = ConnectionEntry(uuid=main_service_uuid, service_type=main_service_type, path_hops=[path_hop]) connection_stack.put(connection_entry) elif prv_res_class[0] > res_class[0]: # create underlying connection LOGGER.debug(' create underlying connection') prv_service_type = connection_stack.queue[-1].service_type service_type = get_service_type(res_class[1], prv_service_type) connection_entry = ConnectionEntry(service_type=service_type, path_hops=[path_hop]) connection_stack.put(connection_entry) elif prv_res_class[0] == res_class[0]: # same resource group kind LOGGER.debug(' same resource group kind') if prv_res_class[1] == res_class[1] and prv_res_class[2] == res_class[2]: # same device type and device controller: connection continues LOGGER.debug(' connection continues') connection_stack.queue[-1].path_hops.append(path_hop) else: # different device type or device controller: chain connections LOGGER.debug(' chain connections') connection = connection_stack.get() connections.append(connection) connection_stack.queue[-1].dependencies.append(connection) prv_service_type = connection_stack.queue[-1].service_type service_type = get_service_type(res_class[1], prv_service_type) connection_entry = ConnectionEntry(service_type=service_type, path_hops=[path_hop]) connection_stack.put(connection_entry) elif prv_res_class[0] < res_class[0]: # underlying connection ended LOGGER.debug(' underlying connection ended') connection = connection_stack.get() connections.append(connection) connection_stack.queue[-1].dependencies.append(connection) connection_stack.queue[-1].path_hops.append(path_hop) else: raise Exception('Uncontrolled condition') prv_device_uuid = device_uuid prv_res_class = res_class # path egress LOGGER.debug(' path egress') connections.append(connection_stack.get()) LOGGER.debug('connections={:s}'.format(str(connections))) assert connection_stack.empty() for c in connections: c.calculate_subservice_uuid(main_service_uuid) return [(c.uuid, c.service_type, c.path_hops, [cd.uuid for cd in c.dependencies]) for c in connections] def convert_explicit_path_hops_to_plain_connection( path_hops : List[Dict], main_service_uuid : str, main_service_type : ServiceTypeEnum ) -> List[Tuple[str, int, List[str], List[str]]]: connection : Tuple[str, int, List[str], List[str]] = \ (main_service_uuid, main_service_type, [], []) prv_device_uuid = None for path_hop in path_hops: device_uuid = path_hop['device'] if prv_device_uuid == device_uuid: continue connection[2].append(path_hop) prv_device_uuid = device_uuid return [connection]