Commit ae072b22 authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

PathComp component - Frontend:

- Corrected endpoint-to-link datastructure
- Improved identification of config rules by device/endpoint uuid/name
- Assumed links are unidirectional
- Rewritten sub-serice computation logic to consider intermediate controllers
- Rewritten method eropath_to_hops based on new endpoint-to-link datastructure
- Minor bug resolutions
- Updated test pathcomp temporary code
parent 79f603af
Loading
Loading
Loading
Loading
+11 −5
Original line number Diff line number Diff line
@@ -25,6 +25,10 @@ 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)

SRC_END = 'src'
DST_END = 'dst'
SENSE = [SRC_END, DST_END]

class _Algorithm:
    def __init__(self, algorithm_id : str, sync_paths : bool, class_name=__name__) -> None:
        # algorithm_id: algorithm to be executed
@@ -44,7 +48,7 @@ class _Algorithm:
        self.endpoint_name_mapping : Dict[Tuple[str, str], str] = dict()
        self.link_list : List[Dict] = list()
        self.link_dict : Dict[str, Tuple[Dict, Link]] = dict()
        self.endpoint_to_link_dict : Dict[Tuple[str, str], Tuple[Dict, Link]] = dict()
        self.endpoint_to_link_dict : Dict[Tuple[str, str, str], Tuple[Dict, Link]] = dict()
        self.service_list : List[Dict] = list()
        self.service_dict : Dict[Tuple[str, str], Tuple[Dict, Service]] = dict()

@@ -86,11 +90,11 @@ class _Algorithm:
            link_uuid = json_link['link_Id']
            self.link_dict[link_uuid] = (json_link, grpc_link)

            for link_endpoint_id in json_link['link_endpoint_ids']:
            for i,link_endpoint_id in enumerate(json_link['link_endpoint_ids']):
                link_endpoint_id = link_endpoint_id['endpoint_id']
                device_uuid = link_endpoint_id['device_id']
                endpoint_uuid = link_endpoint_id['endpoint_uuid']
                endpoint_key = (device_uuid, endpoint_uuid)
                endpoint_key = (device_uuid, endpoint_uuid, SENSE[i])
                link_tuple = (json_link, grpc_link)
                self.endpoint_to_link_dict[endpoint_key] = link_tuple

@@ -175,7 +179,9 @@ class _Algorithm:
            MSG = 'Unhandled generic Config Rules for service {:s} {:s}'
            self.logger.warning(MSG.format(str(service_uuid), str(ServiceTypeEnum.Name(service_type))))

        compose_device_config_rules(config_rules, service.service_config.config_rules, path_hops)
        compose_device_config_rules(
            config_rules, service.service_config.config_rules, path_hops,
            self.device_name_mapping, self.endpoint_name_mapping)

        if path_hops is not None and len(path_hops) > 0:
            ingress_endpoint_id = service.service_endpoint_ids.add()
@@ -214,7 +220,7 @@ class _Algorithm:
            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
                grpc_services[orig_service_key] = grpc_orig_service
                continue

            orig_config_rules = grpc_orig_service.service_config.config_rules
+17 −5
Original line number Diff line number Diff line
@@ -12,8 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import json, re
from typing import Dict, List, Optional
import itertools, json, re
from typing import Dict, List, Optional, Tuple
from common.proto.context_pb2 import ConfigRule
from common.tools.object_factory.ConfigRule import json_config_rule_set

@@ -71,7 +71,11 @@ def compose_l3nm_config_rules(main_service_config_rules : List, subservice_confi
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:
def compose_device_config_rules(
    config_rules : List, subservice_config_rules : List, path_hops : List,
    device_name_mapping : Dict[str, str], endpoint_name_mapping : Dict[Tuple[str, str], str]
) -> None:

    endpoints_traversed = set()
    for path_hop in path_hops:
        device_uuid_or_name = path_hop['device']
@@ -82,8 +86,16 @@ def compose_device_config_rules(config_rules : List, subservice_config_rules : L
        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)
        device_name_or_uuid = device_name_mapping[device_uuid_or_name]
        device_keys = {device_uuid_or_name, device_name_or_uuid}

        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
        endpoint_name_or_uuid_1 = endpoint_name_mapping[(device_uuid_or_name, endpoint_uuid_or_name)]
        endpoint_name_or_uuid_2 = endpoint_name_mapping[(device_name_or_uuid, endpoint_uuid_or_name)]
        endpoint_keys = {endpoint_uuid_or_name, endpoint_name_or_uuid_1, endpoint_name_or_uuid_2}

        device_endpoint_keys = set(itertools.product(device_keys, endpoint_keys))
        if len(device_endpoint_keys.intersection(endpoints_traversed)) == 0: continue
        subservice_config_rules.append(config_rule)
+2 −2
Original line number Diff line number Diff line
@@ -118,11 +118,11 @@ def compose_link(grpc_link : Link) -> Dict:
        for link_endpoint_id in grpc_link.link_endpoint_ids
    ]

    forwarding_direction = LinkForwardingDirection.BIDIRECTIONAL.value
    forwarding_direction = LinkForwardingDirection.UNIDIRECTIONAL.value
    total_potential_capacity = compose_capacity(200, CapacityUnit.MBPS.value)
    available_capacity = compose_capacity(200, CapacityUnit.MBPS.value)
    cost_characteristics = compose_cost_characteristics('linkcost', '1', '0')
    latency_characteristics = compose_latency_characteristics('2')
    latency_characteristics = compose_latency_characteristics('1')

    return {
        'link_Id': link_uuid, 'link_endpoint_ids': endpoint_ids, 'forwarding_direction': forwarding_direction,
+36 −134
Original line number Diff line number Diff line
@@ -30,145 +30,41 @@
# ]
#
# connections=[
#     (UUID('7548edf7-ee7c-4adf-ac0f-c7a0c0dfba8e'), ServiceTypeEnum.TAPI, [
#     (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.L2NM, [
#     (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.L2NM, [
#     (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 enum, json, queue, uuid
import logging, queue, uuid
from typing import Dict, List, Optional, Tuple
from common.DeviceTypes import DeviceTypeEnum
from common.proto.context_pb2 import Device, ServiceTypeEnum
from .ResourceGroups import IGNORED_DEVICE_TYPES, get_resource_classification
from .ServiceTypes import get_service_type

class StackActionEnum(enum.Enum):
    PATH_INGRESS         = 'ingress'
    CREATE_CONNECTION    = 'create'
    APPEND_PATH_HOP      = 'append'
    CHAIN_CONNECTION     = 'chain'
    TERMINATE_CONNECTION = 'terminate'

def is_datacenter(dev_type : Optional[DeviceTypeEnum]) -> bool:
    return dev_type in {DeviceTypeEnum.DATACENTER, DeviceTypeEnum.EMULATED_DATACENTER}

def is_packet_router(dev_type : Optional[DeviceTypeEnum]) -> bool:
    return dev_type in {DeviceTypeEnum.PACKET_ROUTER, DeviceTypeEnum.EMULATED_PACKET_ROUTER}

def is_packet_switch(dev_type : Optional[DeviceTypeEnum]) -> bool:
    return dev_type in {DeviceTypeEnum.PACKET_SWITCH, DeviceTypeEnum.EMULATED_PACKET_SWITCH}

def is_packet_device(dev_type : Optional[DeviceTypeEnum]) -> bool:
    return is_packet_router(dev_type) or is_packet_switch(dev_type)

def is_tfs_controller(dev_type : Optional[DeviceTypeEnum]) -> bool:
    return dev_type in {DeviceTypeEnum.TERAFLOWSDN_CONTROLLER}

def is_mw_controller(dev_type : Optional[DeviceTypeEnum]) -> bool:
    return dev_type in {DeviceTypeEnum.MICROWAVE_RADIO_SYSTEM, DeviceTypeEnum.EMULATED_MICROWAVE_RADIO_SYSTEM}

def is_ipm_controller(dev_type : Optional[DeviceTypeEnum]) -> bool:
    return dev_type in {DeviceTypeEnum.XR_CONSTELLATION, DeviceTypeEnum.EMULATED_XR_CONSTELLATION}

def is_ols_controller(dev_type : Optional[DeviceTypeEnum]) -> bool:
    return dev_type in {DeviceTypeEnum.OPEN_LINE_SYSTEM, DeviceTypeEnum.EMULATED_OPEN_LINE_SYSTEM}

def is_subdevice(dev_manager : Optional[str]) -> bool:
    return dev_manager is not None

def is_subdevice_equal(dev_manager_a : Optional[str], dev_manager_b : Optional[str]) -> bool:
    if dev_manager_a is None and dev_manager_b is None: return True
    if dev_manager_a is not None and dev_manager_b is not None: return dev_manager_a == dev_manager_b
    return False

def get_action(
    prv_type : Optional[DeviceTypeEnum], prv_manager : Optional[str],
    cur_type : DeviceTypeEnum, cur_manager : Optional[str]
) -> StackActionEnum:
    if prv_type is None:
        return StackActionEnum.PATH_INGRESS

    if is_datacenter(prv_type):
        if is_packet_device(cur_type): return StackActionEnum.CREATE_CONNECTION
        if is_tfs_controller(cur_type): return StackActionEnum.CREATE_CONNECTION

    if is_packet_device(prv_type):
        if is_datacenter(cur_type): return StackActionEnum.TERMINATE_CONNECTION
        if is_packet_device(cur_type):
            if is_subdevice_equal(cur_manager, prv_manager): return StackActionEnum.APPEND_PATH_HOP
            if is_subdevice(prv_manager) and not is_subdevice(cur_manager): return StackActionEnum.TERMINATE_CONNECTION
            if not is_subdevice(prv_manager) and is_subdevice(cur_manager): return StackActionEnum.CREATE_CONNECTION

        if is_mw_controller(cur_type) and not is_subdevice(cur_manager): return StackActionEnum.CREATE_CONNECTION
        if is_ols_controller(cur_type) and not is_subdevice(cur_manager): return StackActionEnum.CREATE_CONNECTION
        if is_tfs_controller(cur_type) and is_subdevice(cur_manager): return StackActionEnum.CREATE_CONNECTION

    if is_mw_controller(prv_type) or is_ols_controller(prv_type):
        if is_packet_device(cur_type): return StackActionEnum.TERMINATE_CONNECTION

    if is_tfs_controller(prv_type):
        if is_tfs_controller(cur_type) and is_subdevice_equal(prv_manager, cur_manager): return StackActionEnum.APPEND_PATH_HOP
        if is_datacenter(cur_type): return StackActionEnum.TERMINATE_CONNECTION
        if is_packet_device(cur_type): return StackActionEnum.TERMINATE_CONNECTION
        if is_mw_controller(cur_type) or is_ols_controller(cur_type): return StackActionEnum.CHAIN_CONNECTION

    str_fields = ', '.join([
        'prv_type={:s}'.format(str(prv_type)), 'prv_manager={:s}'.format(str(prv_manager)),
        'cur_type={:s}'.format(str(cur_type)), 'cur_manager={:s}'.format(str(cur_manager)),
    ])
    raise Exception('Undefined Action for ({:s})'.format(str_fields))

def get_device_manager_uuid(device : Device) -> Optional[str]:
    for config_rule in device.device_config.config_rules:
        if config_rule.WhichOneof('config_rule') != 'custom': continue
        if config_rule.custom.resource_key != '_manager': continue
        device_manager_id = json.loads(config_rule.custom.resource_value)
        return device_manager_id['uuid']
    return None

def get_device_type(
    grpc_device : Device, device_dict : Dict[str, Tuple[Dict, Device]], device_manager_uuid : Optional[str]
) -> DeviceTypeEnum:
    if device_manager_uuid is None:
        return DeviceTypeEnum._value2member_map_[grpc_device.device_type] # pylint: disable=no-member
    device_manager_tuple = device_dict.get(device_manager_uuid)
    if device_manager_tuple is None: raise Exception('Device({:s}) not found'.format(str(device_manager_uuid)))
    _,grpc_device = device_manager_tuple
    return DeviceTypeEnum._value2member_map_[grpc_device.device_type] # pylint: disable=no-member

SERVICE_TYPE_LXNM = {ServiceTypeEnum.SERVICETYPE_L3NM, ServiceTypeEnum.SERVICETYPE_L2NM}

def get_service_type(device_type : DeviceTypeEnum, prv_service_type : ServiceTypeEnum) -> ServiceTypeEnum:
    if is_tfs_controller(device_type) or is_packet_router(device_type):
        if prv_service_type in SERVICE_TYPE_LXNM: return prv_service_type
    if is_packet_switch(device_type) or is_mw_controller(device_type):
        if prv_service_type == ServiceTypeEnum.SERVICETYPE_L2NM: return prv_service_type
    if is_ols_controller(device_type) or is_ipm_controller(device_type):
        return ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE

    str_fields = ', '.join([
        'device_type={:s}'.format(str(device_type)),
    ])
    raise Exception('Undefined Service Type for ({:s})'.format(str_fields))
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_device_type = None
    prv_manager_uuid = None
    prv_res_class : Tuple[Optional[int], Optional[DeviceTypeEnum], Optional[str]] = None, None, None

    for path_hop in path_hops:
        device_uuid = path_hop['device']
@@ -177,29 +73,35 @@ def convert_explicit_path_hops_to_connections(
        if device_tuple is None: raise Exception('Device({:s}) not found'.format(str(device_uuid)))
        _,grpc_device = device_tuple

        manager_uuid = get_device_manager_uuid(grpc_device)
        device_type = get_device_type(grpc_device, device_dict, manager_uuid)
        action = get_action(prv_device_type, prv_manager_uuid, device_type, manager_uuid)
        res_class = get_resource_classification(grpc_device, device_dict)
        if res_class[1] in IGNORED_DEVICE_TYPES: continue

        if action == StackActionEnum.PATH_INGRESS:
        if prv_res_class[0] is None:
            # path ingress
            connection_stack.put((main_service_uuid, main_service_type, [path_hop], []))
        elif action == StackActionEnum.CREATE_CONNECTION:
        elif prv_res_class[0] > res_class[0]:
            # create underlying connection
            connection_uuid = str(uuid.uuid4())
            prv_service_type = connection_stack.queue[-1][1]
            service_type = get_service_type(device_type, prv_service_type)
            service_type = get_service_type(res_class[1], prv_service_type)
            connection_stack.put((connection_uuid, service_type, [path_hop], []))
        elif action == StackActionEnum.APPEND_PATH_HOP:
        elif prv_res_class[0] == res_class[0]:
            # 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 manager: connection continues
                connection_stack.queue[-1][2].append(path_hop)
        elif action == StackActionEnum.CHAIN_CONNECTION:
            else:
                # different device type or device manager: chain connections
                connection = connection_stack.get()
                connections.append(connection)
                connection_stack.queue[-1][3].append(connection[0])

                connection_uuid = str(uuid.uuid4())
                prv_service_type = connection_stack.queue[-1][1]
            service_type = get_service_type(device_type, prv_service_type)
                service_type = get_service_type(res_class[1], prv_service_type)
                connection_stack.put((connection_uuid, service_type, [path_hop], []))
        elif action == StackActionEnum.TERMINATE_CONNECTION:
        elif prv_res_class[0] < res_class[0]:
            # underlying connection ended
            connection = connection_stack.get()
            connections.append(connection)
            connection_stack.queue[-1][3].append(connection[0])
@@ -208,11 +110,11 @@ def convert_explicit_path_hops_to_connections(
            raise Exception('Uncontrolled condition')

        prv_device_uuid = device_uuid
        prv_device_type = device_type
        prv_manager_uuid = manager_uuid
        prv_res_class = res_class

    # path egress
    connections.append(connection_stack.get())
    LOGGER.debug('connections={:s}'.format(str(connections)))
    assert connection_stack.empty()
    return connections

+14 −16
Original line number Diff line number Diff line
@@ -43,13 +43,17 @@
#

import logging
from typing import Dict, List
from typing import Dict, List, Tuple
from common.proto.context_pb2 import Link

LOGGER = logging.getLogger(__name__)

def eropath_to_hops(ero_path : List[Dict], endpoint_to_link_dict : Dict) -> List[Dict]:
def eropath_to_hops(
    ero_path : List[Dict], endpoint_to_link_dict : Dict[Tuple[str, str, str], Tuple[Dict, Link]]
) -> List[Dict]:
    try:
        path_hops = []
        num_ero_hops = len(ero_path)
        for endpoint in ero_path:
            device_uuid = endpoint['device_id']
            endpoint_uuid = endpoint['endpoint_uuid']
@@ -59,23 +63,17 @@ def eropath_to_hops(ero_path : List[Dict], endpoint_to_link_dict : Dict) -> List
                continue

            last_hop = path_hops[-1]
            if (last_hop['device'] == device_uuid):
                if ('ingress_ep' not in last_hop) or ('egress_ep' in last_hop): continue
            if last_hop['device'] != device_uuid: raise Exception('Malformed path')
            last_hop['egress_ep'] = endpoint_uuid
                continue

            endpoint_key = (last_hop['device'], last_hop['egress_ep'])
            link_tuple = endpoint_to_link_dict.get(endpoint_key)
            ingress = next(iter([
                ep_id for ep_id in link_tuple[0]['link_endpoint_ids']
                if (ep_id['endpoint_id']['device_id'] == device_uuid) and\
                    (ep_id['endpoint_id']['endpoint_uuid'] != endpoint_uuid)
            ]), None)
            if ingress['endpoint_id']['device_id'] != device_uuid: raise Exception('Malformed path')
            if num_ero_hops - 1 == len(path_hops): break

            link_tuple = endpoint_to_link_dict[(device_uuid, endpoint_uuid, 'src')]
            if link_tuple is None: raise Exception('Malformed path')
            ingress = link_tuple[0]['link_endpoint_ids'][-1]
            path_hops.append({
                'device': ingress['endpoint_id']['device_id'],
                'ingress_ep': ingress['endpoint_id']['endpoint_uuid'],
                'egress_ep': endpoint_uuid,
                'ingress_ep': ingress['endpoint_id']['endpoint_uuid']
            })
        return path_hops
    except:
Loading