Newer
Older
# 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:
# Dict[device_name, Dict[endpoint_name, abstract EndPointId]]
self.__device_endpoint_to_abstract : Dict[str, Dict[str, EndPointId]] = dict()
'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()
},
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
# 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)
link_name = self.__link_name
link = Link(**json_link(link_name, name=link_name, endpoint_ids=[]))
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
self._add_endpoint(device_name, endpoint_name)