Scheduled maintenance on Saturday, 27 September 2025, from 07:00 AM to 4:00 PM GMT (09:00 AM to 6:00 PM CEST) - some services may be unavailable -

Skip to content
Snippets Groups Projects
ComputeSubServices.py 9.53 KiB
Newer Older
  • Learn to ignore specific revisions
  • # 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, uuid
    
    from typing import Dict, List, Optional, Tuple
    from common.DeviceTypes import DeviceTypeEnum
    from common.proto.context_pb2 import Device, ServiceTypeEnum
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
    from .ResourceGroups import IGNORED_DEVICE_TYPES, REMOTEDOMAIN_DEVICE_TYPES, get_resource_classification
    
    from .ServiceTypes import get_service_type
    
    LOGGER = logging.getLogger(__name__)
    
    
    def convert_explicit_path_hops_to_connections(
    
        path_hops : List[Dict], device_dict : Dict[str, Tuple[Dict, Device]],
        main_service_uuid : str, main_service_type : ServiceTypeEnum
    ) -> List[Tuple[str, int, List[str], List[str]]]:
    
        LOGGER.debug('path_hops={:s}'.format(str(path_hops)))
    
    
        connection_stack = queue.LifoQueue()
    
        connections : List[Tuple[str, int, List[str], List[str]]] = list()
        prv_device_uuid = None
    
        prv_res_class : Tuple[Optional[int], Optional[DeviceTypeEnum], Optional[str]] = None, None, None
    
        for path_hop in path_hops:
    
            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
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
            if res_class[1] in REMOTEDOMAIN_DEVICE_TYPES:
                LOGGER.debug('  create and terminate underlying connection')
    
                # create underlying connection
    
                sub_service_uuid = str(uuid.uuid4())
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
                prv_service_type = connection_stack.queue[-1][1]
                service_type = get_service_type(res_class[1], prv_service_type)
    
                connection_stack.put((sub_service_uuid, service_type, [path_hop], []))
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
    
                # underlying connection ended
                connection = connection_stack.get()
                connections.append(connection)
                connection_stack.queue[-1][3].append(connection[0])
                #connection_stack.queue[-1][2].append(path_hop)
    
            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')
                sub_service_uuid = str(uuid.uuid4())
                prv_service_type = connection_stack.queue[-1][1]
                service_type = get_service_type(res_class[1], prv_service_type)
                connection_stack.put((sub_service_uuid, service_type, [path_hop], []))
            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][3].append(connection[0])
                connection_stack.queue[-1][2].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][2].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][3].append(connection[0])
    
                    sub_service_uuid = str(uuid.uuid4())
                    prv_service_type = connection_stack.queue[-1][1]
                    service_type = get_service_type(res_class[1], prv_service_type)
                    connection_stack.put((sub_service_uuid, service_type, [path_hop], []))
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
            elif prv_res_class[0] is None:
    
                # path ingress
    
                LOGGER.debug('  path ingress')
    
                connection_stack.put((main_service_uuid, main_service_type, [path_hop], []))
    
            elif prv_res_class[0] > res_class[0]:
                # create underlying connection
    
                LOGGER.debug('  create underlying connection')
    
                sub_service_uuid = str(uuid.uuid4())
    
                prv_service_type = connection_stack.queue[-1][1]
    
                service_type = get_service_type(res_class[1], prv_service_type)
    
                connection_stack.put((sub_service_uuid, service_type, [path_hop], []))
    
            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][2].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][3].append(connection[0])
    
    
                    sub_service_uuid = str(uuid.uuid4())
    
                    prv_service_type = connection_stack.queue[-1][1]
                    service_type = get_service_type(res_class[1], prv_service_type)
    
                    connection_stack.put((sub_service_uuid, service_type, [path_hop], []))
    
            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][3].append(connection[0])
                connection_stack.queue[-1][2].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()
        return connections
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
    
    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
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
        for path_hop in path_hops:
            device_uuid = path_hop['device']
    
            if prv_device_uuid == device_uuid: continue
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
            connection[2].append(path_hop)
    
            prv_device_uuid = device_uuid
    
    Lluis Gifre Renom's avatar
    Lluis Gifre Renom committed
    
        return [connection]