Commit 022cd29d authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

PathComp component - Front-end:

- Added method generate_neighbor_endpoint_config_rules to compose config rules for neighbor endpoints
parent 538582a7
Loading
Loading
Loading
Loading
+20 −3
Original line number Diff line number Diff line
@@ -15,12 +15,16 @@
import json, logging, requests, uuid
from typing import Dict, List, Optional, Tuple, Union
from common.proto.context_pb2 import (
    Connection, Device, DeviceList, EndPointId, Link, LinkList, Service, ServiceStatusEnum, ServiceTypeEnum)
    ConfigRule, Connection, Device, DeviceList, EndPointId, Link, LinkList, Service, ServiceStatusEnum, ServiceTypeEnum
)
from common.proto.pathcomp_pb2 import PathCompReply, PathCompRequest
from common.tools.grpc.Tools import grpc_message_list_to_json
from pathcomp.frontend.Config import BACKEND_URL
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)
    compose_device_config_rules, compose_l2nm_config_rules, compose_l3nm_config_rules, compose_tapi_config_rules,
    generate_neighbor_endpoint_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)
@@ -227,12 +231,25 @@ class _Algorithm:
                continue

            orig_config_rules = grpc_orig_service.service_config.config_rules
            json_orig_config_rules = grpc_message_list_to_json(orig_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)

                json_generated_config_rules = generate_neighbor_endpoint_config_rules(
                    json_orig_config_rules, path_hops, self.device_name_mapping, self.endpoint_name_mapping
                )
                json_extended_config_rules = list()
                json_extended_config_rules.extend(json_orig_config_rules)
                json_extended_config_rules.extend(json_generated_config_rules)
                extended_config_rules = [
                    ConfigRule(**json_extended_config_rule)
                    for json_extended_config_rule in json_extended_config_rules
                ]

                self.logger.debug('path_hops = {:s}'.format(str(path_hops)))
                try:
                    _device_dict = {k:v[0] for k,v in self.device_dict.items()}
@@ -256,7 +273,7 @@ class _Algorithm:
                    if service_key in grpc_services: continue
                    grpc_service = self.add_service_to_reply(
                        reply, context_uuid, service_uuid, service_type, path_hops=path_hops,
                        config_rules=orig_config_rules)
                        config_rules=extended_config_rules)
                    grpc_services[service_key] = grpc_service

                for connection in connections:
+149 −2
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 itertools, json, logging, re
from typing import Dict, List, Optional, Tuple
import copy, itertools, json, logging, re
from typing import Dict, Iterable, List, Optional, Set, Tuple
from common.proto.context_pb2 import ConfigRule
from common.tools.grpc.Tools import grpc_message_to_json_string
from common.tools.object_factory.ConfigRule import json_config_rule_set
@@ -23,10 +23,15 @@ LOGGER = logging.getLogger(__name__)
SETTINGS_RULE_NAME = '/settings'
STATIC_ROUTING_RULE_NAME = '/static_routing'

RE_UUID = re.compile(r'([0-9a-fA-F]{8})\-([0-9a-fA-F]{4})\-([0-9a-fA-F]{4})\-([0-9a-fA-F]{4})\-([0-9a-fA-F]{12})')

RE_DEVICE_SETTINGS        = re.compile(r'\/device\[([^\]]+)\]\/settings')
RE_ENDPOINT_SETTINGS      = re.compile(r'\/device\[([^\]]+)\]\/endpoint\[([^\]]+)\]\/settings')
RE_ENDPOINT_VLAN_SETTINGS = re.compile(r'\/device\[([^\]]+)\]\/endpoint\[([^\]]+)\]\/vlan\[([^\]]+)\]\/settings')

TMPL_ENDPOINT_SETTINGS      = '/device[{:s}]/endpoint[{:s}]/settings'
TMPL_ENDPOINT_VLAN_SETTINGS = '/device[{:s}]/endpoint[{:s}]/vlan[{:s}]/settings'

L2NM_SETTINGS_FIELD_DEFAULTS = {
    #'encapsulation_type': 'dot1q',
    #'vlan_id'           : 100,
@@ -183,4 +188,146 @@ def compose_device_config_rules(
        else:
            continue

    for config_rule in subservice_config_rules:
        LOGGER.debug('[compose_device_config_rules] result config_rule: {:s}'.format(
            grpc_message_to_json_string(config_rule)))

    LOGGER.debug('[compose_device_config_rules] end')

def pairwise(iterable : Iterable) -> Tuple[Iterable, Iterable]:
    # TODO: To be replaced by itertools.pairwise() when we move to Python 3.10
    # Python 3.10 introduced method itertools.pairwise()
    # Standalone method extracted from:
    # - https://docs.python.org/3/library/itertools.html#itertools.pairwise
    a, b = itertools.tee(iterable, 2)
    next(b, None)
    return zip(a, b)

def compute_device_keys(
    device_uuid_or_name : str, device_name_mapping : Dict[str, str]
) -> Set[str]:
    LOGGER.debug('[compute_device_keys] begin')
    LOGGER.debug('[compute_device_keys] device_uuid_or_name={:s}'.format(str(device_uuid_or_name)))
    #LOGGER.debug('[compute_device_keys] device_name_mapping={:s}'.format(str(device_name_mapping)))

    device_keys = {device_uuid_or_name}
    for k,v in device_name_mapping.items():
        if device_uuid_or_name not in {k, v}: continue
        device_keys.add(k)
        device_keys.add(v)

    LOGGER.debug('[compute_device_keys] device_keys={:s}'.format(str(device_keys)))
    LOGGER.debug('[compute_device_keys] end')
    return device_keys

def compute_endpoint_keys(
    device_keys : Set[str], endpoint_uuid_or_name : str, endpoint_name_mapping : Dict[str, str]
) -> Set[str]:
    LOGGER.debug('[compute_endpoint_keys] begin')
    LOGGER.debug('[compute_endpoint_keys] device_keys={:s}'.format(str(device_keys)))
    LOGGER.debug('[compute_endpoint_keys] endpoint_uuid_or_name={:s}'.format(str(endpoint_uuid_or_name)))
    #LOGGER.debug('[compute_device_endpoint_keys] endpoint_name_mapping={:s}'.format(str(endpoint_name_mapping)))

    endpoint_keys = {endpoint_uuid_or_name}
    for k,v in endpoint_name_mapping.items():
        if (k[0] in device_keys or v in device_keys) and (endpoint_uuid_or_name in {k[1], v}):
            endpoint_keys.add(k[1])
            endpoint_keys.add(v)

    LOGGER.debug('[compute_endpoint_keys] endpoint_keys={:s}'.format(str(endpoint_keys)))
    LOGGER.debug('[compute_endpoint_keys] end')
    return endpoint_keys

def compute_device_endpoint_keys(
    device_uuid_or_name : str, endpoint_uuid_or_name : str,
    device_name_mapping : Dict[str, str], endpoint_name_mapping : Dict[Tuple[str, str], str]
) -> Set[Tuple[str, str]]:
    LOGGER.debug('[compute_device_endpoint_keys] begin')
    LOGGER.debug('[compute_device_endpoint_keys] device_uuid_or_name={:s}'.format(str(device_uuid_or_name)))
    LOGGER.debug('[compute_device_endpoint_keys] endpoint_uuid_or_name={:s}'.format(str(endpoint_uuid_or_name)))
    #LOGGER.debug('[compute_device_endpoint_keys] device_name_mapping={:s}'.format(str(device_name_mapping)))
    #LOGGER.debug('[compute_device_endpoint_keys] endpoint_name_mapping={:s}'.format(str(endpoint_name_mapping)))

    device_keys = compute_device_keys(device_uuid_or_name, device_name_mapping)
    endpoint_keys = compute_endpoint_keys(device_keys, endpoint_uuid_or_name, endpoint_name_mapping)
    device_endpoint_keys = set(itertools.product(device_keys, endpoint_keys))

    LOGGER.debug('[compute_device_endpoint_keys] device_endpoint_keys={:s}'.format(str(device_endpoint_keys)))
    LOGGER.debug('[compute_device_endpoint_keys] end')
    return device_endpoint_keys

def generate_neighbor_endpoint_config_rules(
    config_rules : List[Dict], path_hops : List[Dict],
    device_name_mapping : Dict[str, str], endpoint_name_mapping : Dict[Tuple[str, str], str]
) -> List[Dict]:
    LOGGER.debug('[generate_neighbor_endpoint_config_rules] begin')
    LOGGER.debug('[generate_neighbor_endpoint_config_rules] config_rules={:s}'.format(str(config_rules)))
    LOGGER.debug('[generate_neighbor_endpoint_config_rules] path_hops={:s}'.format(str(path_hops)))
    LOGGER.debug('[generate_neighbor_endpoint_config_rules] device_name_mapping={:s}'.format(str(device_name_mapping)))
    LOGGER.debug('[generate_neighbor_endpoint_config_rules] endpoint_name_mapping={:s}'.format(str(endpoint_name_mapping)))

    generated_config_rules = list()
    for link_endpoint_a, link_endpoint_b in pairwise(path_hops):
        LOGGER.debug('[generate_neighbor_endpoint_config_rules] loop begin')
        LOGGER.debug('[generate_neighbor_endpoint_config_rules] link_endpoint_a={:s}'.format(str(link_endpoint_a)))
        LOGGER.debug('[generate_neighbor_endpoint_config_rules] link_endpoint_b={:s}'.format(str(link_endpoint_b)))

        device_endpoint_keys_a = compute_device_endpoint_keys(
            link_endpoint_a['device'], link_endpoint_a['egress_ep'],
            device_name_mapping, endpoint_name_mapping
        )

        device_endpoint_keys_b = compute_device_endpoint_keys(
            link_endpoint_b['device'], link_endpoint_b['ingress_ep'],
            device_name_mapping, endpoint_name_mapping
        )

        for config_rule in config_rules:
            # Only applicable, by now, to Custom Config Rules for endpoint settings
            if 'custom' not in config_rule: continue
            match = RE_ENDPOINT_SETTINGS.match(config_rule['custom']['resource_key'])
            if match is None:
                match = RE_ENDPOINT_VLAN_SETTINGS.match(config_rule['custom']['resource_key'])
            if match is None: continue

            resource_key_values = match.groups()
            if resource_key_values[0:2] in device_endpoint_keys_a:
                resource_key_values = list(resource_key_values)
                resource_key_values[0] = link_endpoint_b['device']
                resource_key_values[1] = link_endpoint_b['ingress_ep']
            elif resource_key_values[0:2] in device_endpoint_keys_b:
                resource_key_values = list(resource_key_values)
                resource_key_values[0] = link_endpoint_a['device']
                resource_key_values[1] = link_endpoint_a['egress_ep']
            else:
                continue

            device_keys = compute_device_keys(resource_key_values[0], device_name_mapping)
            device_names = {device_key for device_key in device_keys if RE_UUID.match(device_key) is None}
            if len(device_names) != 1:
                MSG = 'Unable to identify name for Device({:s}): device_keys({:s})'
                raise Exception(MSG.format(str(resource_key_values[0]), str(device_keys)))
            resource_key_values[0] = device_names.pop()

            endpoint_keys = compute_endpoint_keys(device_keys, resource_key_values[1], endpoint_name_mapping)
            endpoint_names = {endpoint_key for endpoint_key in endpoint_keys if RE_UUID.match(endpoint_key) is None}
            if len(endpoint_names) != 1:
                MSG = 'Unable to identify name for Endpoint({:s}): endpoint_keys({:s})'
                raise Exception(MSG.format(str(resource_key_values[1]), str(endpoint_keys)))
            resource_key_values[1] = endpoint_names.pop()

            resource_value : Dict = json.loads(config_rule['custom']['resource_value'])
            if 'neighbor_address' not in resource_value: continue
            resource_value['ip_address'] = resource_value.pop('neighbor_address')

            # remove neighbor_address also from original rule as it is already consumed

            resource_key_template = TMPL_ENDPOINT_VLAN_SETTINGS if len(match.groups()) == 3 else TMPL_ENDPOINT_SETTINGS
            generated_config_rule = copy.deepcopy(config_rule)
            generated_config_rule['custom']['resource_key'] = resource_key_template.format(*resource_key_values)
            generated_config_rule['custom']['resource_value'] = json.dumps(resource_value)
            generated_config_rules.append(generated_config_rule)

    LOGGER.debug('[generate_neighbor_endpoint_config_rules] generated_config_rules={:s}'.format(str(generated_config_rules)))
    LOGGER.debug('[generate_neighbor_endpoint_config_rules] end')
    return generated_config_rules