# 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 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