Skip to content
Snippets Groups Projects
AbstractLink.py 6.89 KiB
Newer Older
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
# 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 copy, logging
from typing import Dict, List, Optional, Tuple
Lluis Gifre Renom's avatar
Lluis Gifre Renom committed
from common.Constants import DEFAULT_CONTEXT_NAME, INTERDOMAIN_TOPOLOGY_NAME
from common.proto.context_pb2 import ContextId, EndPointId, Link
from common.tools.context_queries.Link import add_link_to_topology, get_link
from common.tools.grpc.Tools import grpc_message_to_json
from common.tools.object_factory.Context import json_context_id
from common.tools.object_factory.Link import json_link
from context.client.ContextClient import ContextClient
from .Tools import replace_link_uuids_by_names

# Remark on UUIDs:
# TopologyAbstractor, AbstractDevice and AbstractLink are used
# to compose network reporesentations to be forwarded to remote
# instances. Constraining it to use UUIDs is pointless given
# these UUIDs, to be unique, need to be bound to the local
# context/topology UUIDs, which might be different than that for
# the remote TeraFlowSDN instances. For this very reason, we use
# the device/link/endpoint/topology/context names as UUIDs, to
# prevent UUID-related issues.

LOGGER = logging.getLogger(__name__)

class AbstractLink:
    def __init__(self, link_name : str):
        self.__context_client = ContextClient()
        self.__link_name : str = link_name
        self.__link : Optional[Link] = None

        # Dict[device_name, Dict[endpoint_name, abstract EndPointId]]
        self.__device_endpoint_to_abstract : Dict[str, Dict[str, EndPointId]] = dict()
    def to_json(self) -> Dict:
        return {
            'link_name' : self.__link_name,
            'link' : grpc_message_to_json(self.__link),
            'device_endpoint_to_abstract' : {
                device_name : {
                    endpoint_name : grpc_message_to_json(endpoint_id)
                    for endpoint_name, endpoint_id in endpoint_ids.items()
                }
                for device_name, endpoint_ids in self.__device_endpoint_to_abstract.items()
            },
    @property
    def name(self) -> str: return self.__link_name

    @property
    def link(self) -> Optional[Link]: return self.__link

    @staticmethod
    def compose_name(
        device_name_a : str, endpoint_name_a : str, device_name_z : str, endpoint_name_z : str
    ) -> str:
        # sort endpoints lexicographically to prevent duplicities
        link_endpoint_names = sorted([
            (device_name_a, endpoint_name_a),
            (device_name_z, endpoint_name_z)
        link_name = '{:s}/{:s}=={:s}/{:s}'.format(
            link_endpoint_names[0][0], link_endpoint_names[0][1],
            link_endpoint_names[1][0], link_endpoint_names[1][1])
        return link_name

    def initialize(self) -> bool:
        if self.__link is not None: return False

        local_interdomain_link = get_link(self.__context_client, self.__link_name, rw_copy=False)
        create_abstract_link = local_interdomain_link is None
        if create_abstract_link:
            self._create_empty()
        else:
            self._load_existing(local_interdomain_link)
        # Add abstract link to INTERDOMAIN_TOPOLOGY_NAME topology
        admin_context_id = ContextId(**json_context_id(DEFAULT_CONTEXT_NAME))
        topology_name = INTERDOMAIN_TOPOLOGY_NAME
        add_link_to_topology(self.__context_client, admin_context_id, topology_name, self.__link_name)
        return create_abstract_link

    def _create_empty(self) -> None:
        link_name = self.__link_name
        link = Link(**json_link(link_name, name=link_name, endpoint_ids=[]))
        self.__context_client.SetLink(link)
        self.__link = link # Store copy with names as UUIDs
    def _load_existing(self, local_interdomain_link : Link) -> None:
        self.__device_endpoint_to_abstract = dict()

        self.__link = local_interdomain_link
        replace_link_uuids_by_names(self.__context_client, local_interdomain_link)

        # for each endpoint in abstract link, populate internal data structures and mappings
        for endpoint_id in self.__link.link_endpoint_ids:
            device_name = endpoint_id.device_id.device_uuid.uuid
            endpoint_name = endpoint_id.endpoint_uuid.uuid
            endpoints = self.__device_endpoint_to_abstract.setdefault(device_name, dict())
            endpoints.setdefault(endpoint_name, endpoint_id)
    def _add_endpoint(self, device_name : str, endpoint_name : str) -> None:
        endpoint_id = self.__link.link_endpoint_ids.add()
        endpoint_id.topology_id.topology_uuid.uuid = INTERDOMAIN_TOPOLOGY_NAME
        endpoint_id.topology_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME
        endpoint_id.device_id.device_uuid.uuid = device_name
        endpoint_id.endpoint_uuid.uuid = endpoint_name
        endpoints = self.__device_endpoint_to_abstract.setdefault(device_name, dict())
        endpoints.setdefault(endpoint_name, endpoint_id)
    def _remove_endpoint(self, device_name : str, endpoint_name : str) -> None:
        device_endpoint_to_abstract = self.__device_endpoint_to_abstract.get(device_name, {})
        endpoint_id = device_endpoint_to_abstract.pop(endpoint_name, None)
        if endpoint_id is not None: self.__link.link_endpoint_ids.remove(endpoint_id)

    def update_endpoints(self, link_endpoint_names : List[Tuple[str, str]] = []) -> bool:
        updated = False

        # for each endpoint in abstract link that is not in link; remove from abstract link
        device_endpoint_to_abstract = copy.deepcopy(self.__device_endpoint_to_abstract)
        for device_name, endpoints in device_endpoint_to_abstract.items():
            for endpoint_name in endpoints.keys():
                if (device_name, endpoint_name) in link_endpoint_names: continue
                # remove endpoint_id that is not in link
                self._remove_endpoint(device_name, endpoint_name)
                updated = True

        # for each endpoint in link that is not in abstract link; add to abstract link
        for device_name, endpoint_name in link_endpoint_names:
            # if already added; just check endpoint type is not modified
            endpoints = self.__device_endpoint_to_abstract.get(device_name, {})
            endpoint = endpoints.get(endpoint_name)
            if endpoint is not None: continue

            # otherwise, add it to the abstract device
            self._add_endpoint(device_name, endpoint_name)
            updated = True

        return updated