Commit 1e3dfb1a authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

PathComp component - Frontend:

- Added logic to ignore mgmt-related links
- Improved composition of subservice config rules
- Cleaned up Constants
- Updated sub-service computation logic
parent 912d24e5
Loading
Loading
Loading
Loading
+72 −62
Original line number Diff line number Diff line
@@ -15,13 +15,12 @@
import json, logging, requests
from typing import Dict, List, Optional, Tuple, Union
from common.proto.context_pb2 import (
    ConfigRule, Connection, Device, DeviceList, EndPointId, Link, LinkList, Service, ServiceStatusEnum,
    ServiceTypeEnum)
    Connection, Device, DeviceList, EndPointId, Link, LinkList, Service, ServiceStatusEnum, ServiceTypeEnum)
from common.proto.pathcomp_pb2 import PathCompReply, PathCompRequest
from common.tools.object_factory.ConfigRule import json_config_rule_set
from pathcomp.frontend.Config import BACKEND_URL
from pathcomp.frontend.service.algorithms.tools.ConstantsMappings import DEVICE_LAYER_TO_SERVICE_TYPE, DeviceLayerEnum
from .tools.EroPathToHops import eropath_to_hops
from .tools.ComposeConfigRules import (
    compose_device_config_rules, compose_l2nm_config_rules, compose_l3nm_config_rules, compose_tapi_config_rules)
from .tools.ComposeRequest import compose_device, compose_link, compose_service
from .tools.ComputeSubServices import (
    convert_explicit_path_hops_to_connections, convert_explicit_path_hops_to_plain_connection)
@@ -78,6 +77,8 @@ class _Algorithm:
    def add_links(self, grpc_links : Union[List[Link], LinkList]) -> None:
        if isinstance(grpc_links, LinkList): grpc_links = grpc_links.links
        for grpc_link in grpc_links:
            if 'mgmt' in grpc_link.name.lower(): continue

            json_link = compose_link(grpc_link)
            if len(json_link['link_endpoint_ids']) != 2: continue
            self.link_list.append(json_link)
@@ -148,9 +149,8 @@ class _Algorithm:
        return connection

    def add_service_to_reply(
        self, reply : PathCompReply, context_uuid : str, service_uuid : str,
        device_layer : Optional[DeviceLayerEnum] = None, path_hops : List[Dict] = [],
        config_rules : List = []
        self, reply : PathCompReply, context_uuid : str, service_uuid : str, service_type : ServiceTypeEnum,
        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
@@ -159,35 +159,23 @@ class _Algorithm:

        service_key = (context_uuid, service_uuid)
        tuple_service = self.service_dict.get(service_key)
        if tuple_service is not None:
            service = reply.services.add()
            service.CopyFrom(tuple_service[1])
        else:

        service = reply.services.add()
        service.service_id.context_id.context_uuid.uuid = context_uuid
        service.service_id.service_uuid.uuid = service_uuid

            if device_layer is not None:
                service_type = DEVICE_LAYER_TO_SERVICE_TYPE.get(device_layer.value)
                if service_type is None:
                    MSG = 'Unable to map DeviceLayer({:s}) to ServiceType'
                    raise Exception(MSG.format(str(device_layer)))
        service.service_type = service_type

                if service_type == ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE:
                    json_tapi_settings = {
                        'capacity_value'  : 50.0,
                        'capacity_unit'   : 'GHz',
                        'layer_proto_name': 'PHOTONIC_MEDIA',
                        'layer_proto_qual': 'tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_NMC',
                        'direction'       : 'UNIDIRECTIONAL',
                    }
                    config_rule = ConfigRule(**json_config_rule_set('/settings', json_tapi_settings))
                    service.service_config.config_rules.append(config_rule)
        if service_type == ServiceTypeEnum.SERVICETYPE_L2NM:
            compose_l2nm_config_rules(config_rules, service.service_config.config_rules)
        elif service_type == ServiceTypeEnum.SERVICETYPE_L3NM:
            compose_l3nm_config_rules(config_rules, service.service_config.config_rules)
        elif service_type == ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE:
            compose_tapi_config_rules(config_rules, service.service_config.config_rules)
        else:
                    service.service_config.config_rules.extend(config_rules)
            MSG = 'Unhandled generic Config Rules for service {:s} {:s}'
            self.logger.warning(MSG.format(str(service_uuid), str(ServiceTypeEnum.Name(service_type))))

            service.service_status.service_status = ServiceStatusEnum.SERVICESTATUS_PLANNED
        compose_device_config_rules(config_rules, service.service_config.config_rules, path_hops)

        if path_hops is not None and len(path_hops) > 0:
            ingress_endpoint_id = service.service_endpoint_ids.add()
@@ -198,6 +186,15 @@ class _Algorithm:
            egress_endpoint_id.device_id.device_uuid.uuid = path_hops[-1]['device']
            egress_endpoint_id.endpoint_uuid.uuid = path_hops[-1]['egress_ep']

        if tuple_service is None:
            service.service_status.service_status = ServiceStatusEnum.SERVICESTATUS_PLANNED
        else:
            service.name = tuple_service[1].name
            service.service_status.CopyFrom(tuple_service[1].service_status)
            service.timestamp.CopyFrom(tuple_service[1].timestamp)
            for constraint in tuple_service[1].service_constraints:
                service.service_constraints.add().CopyFrom(constraint)

        return service

    def get_reply(self) -> PathCompReply:
@@ -206,41 +203,54 @@ class _Algorithm:
        grpc_services : Dict[Tuple[str, str], Service] = {}
        grpc_connections : Dict[str, Connection] = {}
        for response in response_list:
            service_id = response['serviceId']
            context_uuid = service_id['contextId']
            service_uuid = service_id['service_uuid']
            service_key = (context_uuid, service_uuid)
            upper_service = self.add_service_to_reply(reply, context_uuid, service_uuid)
            grpc_services[service_key] = upper_service
            orig_service_id = response['serviceId']
            context_uuid = orig_service_id['contextId']
            main_service_uuid = orig_service_id['service_uuid']
            orig_service_key = (context_uuid, main_service_uuid)
            _,grpc_orig_service = self.service_dict[orig_service_key]
            main_service_type = grpc_orig_service.service_type

            no_path_issue = response.get('noPath', {}).get('issue')
            if no_path_issue is not None:
                # no path found: leave connection with no endpoints
                # no_path_issue == 1 => no path due to a constraint
                grpc_services[service_key] = grpc_orig_service
                continue

            orig_config_rules = grpc_orig_service.service_config.config_rules

            for service_path_ero in response['path']:
                self.logger.debug('service_path_ero["devices"] = {:s}'.format(str(service_path_ero['devices'])))
                _endpoint_to_link_dict = {k:v[0] for k,v in self.endpoint_to_link_dict.items()}
                self.logger.debug('self.endpoint_to_link_dict = {:s}'.format(str(_endpoint_to_link_dict)))
                path_hops = eropath_to_hops(service_path_ero['devices'], self.endpoint_to_link_dict)
                self.logger.debug('path_hops = {:s}'.format(str(path_hops)))
                try:
                    connections = convert_explicit_path_hops_to_connections(path_hops, self.device_dict, service_uuid)
                    _device_dict = {k:v[0] for k,v in self.device_dict.items()}
                    self.logger.debug('self.device_dict = {:s}'.format(str(_device_dict)))
                    connections = convert_explicit_path_hops_to_connections(
                        path_hops, self.device_dict, main_service_uuid, main_service_type)
                    self.logger.debug('EXTRAPOLATED connections = {:s}'.format(str(connections)))
                except: # pylint: disable=bare-except
                    # if not able to extrapolate sub-services and sub-connections,
                    # assume single service and single connection
                    connections = convert_explicit_path_hops_to_plain_connection(path_hops, service_uuid)
                    MSG = ' '.join([
                        'Unable to Extrapolate sub-services and sub-connections.',
                        'Assuming single-service and single-connection.',
                    ])
                    self.logger.exception(MSG)
                    connections = convert_explicit_path_hops_to_plain_connection(
                        path_hops, main_service_uuid, main_service_type)
                    self.logger.debug('BASIC connections = {:s}'.format(str(connections)))

                for connection in connections:
                    connection_uuid,device_layer,path_hops,_ = connection
                    connection_uuid,service_type,path_hops,_ = connection
                    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,
                            config_rules=config_rules)
                        reply, context_uuid, connection_uuid, service_type, path_hops=path_hops,
                        config_rules=orig_config_rules)
                    grpc_services[service_key] = grpc_service

                for connection in connections:
                    connection_uuid,device_layer,path_hops,dependencies = connection
                    connection_uuid,_,path_hops,dependencies = connection

                    service_key = (context_uuid, connection_uuid)
                    grpc_service = grpc_services.get(service_key)
@@ -251,8 +261,8 @@ class _Algorithm:
                    grpc_connection = self.add_connection_to_reply(reply, connection_uuid, grpc_service, path_hops)
                    grpc_connections[connection_uuid] = grpc_connection

                    for service_uuid in dependencies:
                        sub_service_key = (context_uuid, service_uuid)
                    for sub_service_uuid in dependencies:
                        sub_service_key = (context_uuid, sub_service_uuid)
                        grpc_sub_service = grpc_services.get(sub_service_key)
                        if grpc_sub_service is None:
                            raise Exception('Service({:s}) not found'.format(str(sub_service_key)))
+89 −0
Original line number Diff line number Diff line
# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (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, re
from typing import Dict, List, Optional
from common.proto.context_pb2 import ConfigRule
from common.tools.object_factory.ConfigRule import json_config_rule_set

SETTINGS_RULE_NAME = '/settings'

DEV_EP_SETTINGS = re.compile(r'\/device\[([^\]]+)\]\/endpoint\[([^\]]+)\]\/settings')

L2NM_SETTINGS_FIELD_DEFAULTS = {
    'encapsulation_type': 'dot1q',
    'vlan_id'           : 100,
    'mtu'               : 1450,
}

L3NM_SETTINGS_FIELD_DEFAULTS = {
    'encapsulation_type': 'dot1q',
    'vlan_id'           : 100,
    'mtu'               : 1450,
}

TAPI_SETTINGS_FIELD_DEFAULTS = {
    'capacity_value'  : 50.0,
    'capacity_unit'   : 'GHz',
    'layer_proto_name': 'PHOTONIC_MEDIA',
    'layer_proto_qual': 'tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_NMC',
    'direction'       : 'UNIDIRECTIONAL',
}

def find_custom_config_rule(config_rules : List, resource_name : str) -> Optional[Dict]:
    resource_value : Optional[Dict] = None
    for config_rule in config_rules:
        if config_rule.WhichOneof('config_rule') != 'custom': continue
        if config_rule.custom.resource_key != resource_name: continue
        resource_value = json.loads(config_rule.custom.resource_value)
    return resource_value

def compose_config_rules(
    main_service_config_rules : List, subservice_config_rules : List, field_defaults : Dict
) -> None:
    settings = find_custom_config_rule(main_service_config_rules, SETTINGS_RULE_NAME)
    if settings is None: return

    json_settings = {}
    for field_name,default_value in field_defaults.items():
        json_settings[field_name] = settings.get(field_name, default_value)

    config_rule = ConfigRule(**json_config_rule_set('/settings', json_settings))
    subservice_config_rules.append(config_rule)

def compose_l2nm_config_rules(main_service_config_rules : List, subservice_config_rules : List) -> None:
    compose_config_rules(main_service_config_rules, subservice_config_rules, L2NM_SETTINGS_FIELD_DEFAULTS)

def compose_l3nm_config_rules(main_service_config_rules : List, subservice_config_rules : List) -> None:
    compose_config_rules(main_service_config_rules, subservice_config_rules, L3NM_SETTINGS_FIELD_DEFAULTS)

def compose_tapi_config_rules(main_service_config_rules : List, subservice_config_rules : List) -> None:
    compose_config_rules(main_service_config_rules, subservice_config_rules, TAPI_SETTINGS_FIELD_DEFAULTS)

def compose_device_config_rules(config_rules : List, subservice_config_rules : List, path_hops : List) -> None:
    endpoints_traversed = set()
    for path_hop in path_hops:
        device_uuid_or_name = path_hop['device']
        endpoints_traversed.add((device_uuid_or_name, path_hop['ingress_ep']))
        endpoints_traversed.add((device_uuid_or_name, path_hop['egress_ep']))

    for config_rule in config_rules:
        if config_rule.WhichOneof('config_rule') != 'custom': continue
        match = DEV_EP_SETTINGS.match(config_rule.custom.resource_key)
        if match is None: continue
        device_uuid_or_name = match.group(1)
        endpoint_uuid_or_name = match.group(2)
        dev_ep_kep = (device_uuid_or_name, endpoint_uuid_or_name)
        if dev_ep_kep not in endpoints_traversed: continue
        subservice_config_rules.append(config_rule)
+158 −37

File changed.

Preview size limit exceeded, changes collapsed.

+0 −49
Original line number Diff line number Diff line
@@ -13,8 +13,6 @@
# limitations under the License.

from enum import IntEnum
from common.DeviceTypes import DeviceTypeEnum
from common.proto.context_pb2 import ServiceTypeEnum

class CapacityUnit(IntEnum):
    TB   = 0
@@ -66,50 +64,3 @@ class LinkForwardingDirection(IntEnum):
    BIDIRECTIONAL  = 0
    UNIDIRECTIONAL = 1
    UNKNOWN        = 2

class DeviceLayerEnum(IntEnum):
    APPLICATION_CONTROLLER = 41     # Layer 4 domain controller
    APPLICATION_DEVICE     = 40     # Layer 4 domain device
    PACKET_CONTROLLER      = 31     # Layer 3 domain controller
    PACKET_DEVICE          = 30     # Layer 3 domain device
    MAC_LAYER_CONTROLLER   = 21     # Layer 2 domain controller
    MAC_LAYER_DEVICE       = 20     # Layer 2 domain device
    OPTICAL_CONTROLLER     =  1     # Layer 0 domain controller
    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.NETWORK.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.XR_CONSTELLATION.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 = {
    DeviceLayerEnum.APPLICATION_DEVICE.value   : ServiceTypeEnum.SERVICETYPE_L3NM,
    DeviceLayerEnum.PACKET_DEVICE.value        : ServiceTypeEnum.SERVICETYPE_L3NM,

    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_DEVICE.value       : ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE,
}