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

Interdomain Component:

- First functional version of TopologyAbstractor
- Updated default context/topology UUIDs
parent dad2ba06
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -32,7 +32,7 @@ DEFAULT_METRICS_PORT = 9192
# Default context and topology UUIDs
DEFAULT_CONTEXT_UUID      = 'admin'
DEFAULT_TOPOLOGY_UUID     = 'admin'     # contains the detailed local topology
DOMAINS_TOPOLOGY_UUID    = 'domains'    # contains the abstracted domains (abstracted local + abstracted remotes)
INTERDOMAIN_TOPOLOGY_UUID = 'inter'     # contains the abstract inter-domain topology

# Default service names
class ServiceNameEnum(Enum):
+103 −103
Original line number Diff line number Diff line
@@ -13,178 +13,178 @@
# limitations under the License.

import copy, logging
from typing import Dict, Optional, Tuple
from common.Constants import DEFAULT_TOPOLOGY_UUID, DOMAINS_TOPOLOGY_UUID
from typing import Dict, Optional
from common.Constants import DEFAULT_CONTEXT_UUID, INTERDOMAIN_TOPOLOGY_UUID
from common.DeviceTypes import DeviceTypeEnum
from common.proto.context_pb2 import (
    ContextId, Device, DeviceDriverEnum, DeviceId, DeviceOperationalStatusEnum, EndPoint)
from common.tools.object_factory.Context import json_context_id
from common.tools.object_factory.Device import json_device, json_device_id
from context.client.ContextClient import ContextClient
from interdomain.service.topology_abstractor.Tools import (
    add_device_to_topology, create_missing_topologies, find_own_domain_uuid, get_devices_in_topology,
from .Tools import (
    add_device_to_topology, device_type_is_datacenter, device_type_is_network, endpoint_type_is_border,
    get_existing_device_uuids)

LOGGER = logging.getLogger(__name__)

class AbstractDevice:
    def __init__(self):
    def __init__(self, device_uuid : str, device_type : DeviceTypeEnum):
        self.__context_client = ContextClient()
        self.__device_uuid : str = device_uuid
        self.__device_type : DeviceTypeEnum = device_type
        self.__device : Optional[Device] = None
        self.__device_id : Optional[DeviceId] = None

        self.__own_context_id : Optional[ContextId] = None
        self.__own_domain_uuid : Optional[str] = None # uuid of own_context_id
        # Dict[device_uuid, Dict[endpoint_uuid, abstract EndPoint]]
        self.__device_endpoint_to_abstract : Dict[str, Dict[str, EndPoint]] = dict()

        self.__own_abstract_device : Optional[Device] = None
        self.__own_abstract_device_id : Optional[DeviceId] = None

        # Dict[device_uuid, Dict[endpoint_uuid, Tuple[interdomain_endpoint_uuid, abstract EndPoint]]]
        self.__device_endpoint_to_abstract : Dict[str, Dict[str, Tuple[str, EndPoint]]] = dict()

        # Dict[interdomain_endpoint_uuid, Tuple[device_uuid, endpoint_uuid]]
        self.__abstract_to_device_endpoint : Dict[str, Tuple[str, str]] = dict()
        # Dict[endpoint_uuid, device_uuid]
        self.__abstract_endpoint_to_device : Dict[str, str] = dict()

    @property
    def own_context_id(self): return self.__own_context_id
    def uuid(self) -> str: return self.__device_uuid

    @property
    def own_domain_uuid(self): return self.__own_domain_uuid
    def device_id(self) -> Optional[DeviceId]: return self.__device_id

    @property
    def own_abstract_device_uuid(self): return self.__own_domain_uuid
    def device(self) -> Optional[Device]: return self.__device

    @property
    def own_abstract_device_id(self): return self.__own_abstract_device_id
    def get_endpoint(self, device_uuid : str, endpoint_uuid : str) -> Optional[EndPoint]:
        return self.__device_endpoint_to_abstract.get(device_uuid, {}).get(endpoint_uuid)

    @property
    def own_abstract_device(self): return self.__own_abstract_device
    def initialize(self) -> bool:
        if self.__device is not None: return False

    def _load_existing_abstract_device(self) -> None:
        self.__device_endpoint_to_abstract = dict()
        self.__abstract_to_device_endpoint = dict()

        self.__own_abstract_device_id = DeviceId(**json_device_id(self.__own_domain_uuid))
        self.__own_abstract_device = self.__context_client.GetDevice(self.__own_abstract_device_id)
        existing_device_uuids = get_existing_device_uuids(self.__context_client)
        create_abstract_device = self.__device_uuid not in existing_device_uuids

        # for each endpoint in own_abstract_device, populate internal data structures and mappings
        for interdomain_endpoint in self.__own_abstract_device.device_endpoints:
            interdomain_endpoint_uuid : str = interdomain_endpoint.endpoint_id.endpoint_uuid.uuid
            endpoint_uuid,device_uuid = interdomain_endpoint_uuid.split('@', maxsplit=1)
        if create_abstract_device:
            self._create_empty()
        else:
            self._load_existing()

        is_datacenter = device_type_is_datacenter(self.__device_type)
        is_network = device_type_is_network(self.__device_type)
        if is_datacenter or is_network:
            # Add abstract device to topologies [INTERDOMAIN_TOPOLOGY_UUID]
            context_id = ContextId(**json_context_id(DEFAULT_CONTEXT_UUID))
            topology_uuids = [INTERDOMAIN_TOPOLOGY_UUID]
            for topology_uuid in topology_uuids:
                add_device_to_topology(self.__context_client, context_id, topology_uuid, self.__device_uuid)

        # seems not needed; to be removed in future releases
        #if is_datacenter and create_abstract_device:
        #    dc_device = self.__context_client.GetDevice(DeviceId(**json_device_id(self.__device_uuid)))
        #    if device_type_is_datacenter(dc_device.device_type):
        #        self.update_endpoints(dc_device)
        #elif is_network:
        #    devices_in_admin_topology = get_devices_in_topology(
        #        self.__context_client, context_id, DEFAULT_TOPOLOGY_UUID)
        #    for device in devices_in_admin_topology:
        #        if device_type_is_datacenter(device.device_type): continue
        #        self.update_endpoints(device)

            interdomain_endpoint_tuple = (interdomain_endpoint_uuid, interdomain_endpoint)
            self.__device_endpoint_to_abstract\
                .setdefault(device_uuid, {}).setdefault(endpoint_uuid, interdomain_endpoint_tuple)
            self.__abstract_to_device_endpoint\
                .setdefault(interdomain_endpoint_uuid, (device_uuid, endpoint_uuid))
        return True

    def _create_empty_abstract_device(self) -> None:
        own_abstract_device_uuid = self.__own_domain_uuid
    def _create_empty(self) -> None:
        device_uuid = self.__device_uuid

        own_abstract_device = Device(**json_device(
            own_abstract_device_uuid, DeviceTypeEnum.NETWORK.value,
            DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED,
        device = Device(**json_device(
            device_uuid, self.__device_type.value, DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED,
            endpoints=[], config_rules=[], drivers=[DeviceDriverEnum.DEVICEDRIVER_UNDEFINED]
        ))
        self.__context_client.SetDevice(own_abstract_device)
        self.__own_abstract_device = own_abstract_device
        self.__own_abstract_device_id = self.__own_abstract_device.device_id
        self.__context_client.SetDevice(device)
        self.__device = device
        self.__device_id = self.__device.device_id

        # Add own abstract device to topologies ["domains"]
        topology_uuids = [DOMAINS_TOPOLOGY_UUID]
        for topology_uuid in topology_uuids:
            add_device_to_topology(
                self.__context_client, self.__own_context_id, topology_uuid, own_abstract_device_uuid)
    def _load_existing(self) -> None:
        self.__device_endpoint_to_abstract = dict()
        self.__abstract_endpoint_to_device = dict()

    def _discover_or_create_abstract_device(self) -> bool:
        # already discovered
        if self.__own_abstract_device is not None: return False
        self.__device_id = DeviceId(**json_device_id(self.__device_uuid))
        self.__device = self.__context_client.GetDevice(self.__device_id)
        self.__device_type = self.__device.device_type
        device_uuid = self.__device_id.device_uuid.uuid

        # discover from existing devices; should have name of the own domain context
        existing_device_uuids = get_existing_device_uuids(self.__context_client)
        create_abstract_device = self.__own_domain_uuid not in existing_device_uuids
        if create_abstract_device:
            self._create_empty_abstract_device()
        else:
            self._load_existing_abstract_device()
        return create_abstract_device
        device_type = self.__device_type
        is_datacenter = device_type_is_datacenter(device_type)
        is_network = device_type_is_network(device_type)
        if not is_datacenter and not is_network:
            LOGGER.warning('Unsupported InterDomain Device Type: {:s}'.format(str(device_type)))
            return

        # for each endpoint in abstract device, populate internal data structures and mappings
        for interdomain_endpoint in self.__device.device_endpoints:
            endpoint_uuid : str = interdomain_endpoint.endpoint_id.endpoint_uuid.uuid

            if is_network:
                endpoint_uuid,device_uuid = endpoint_uuid.split('@', maxsplit=1)

            self.__device_endpoint_to_abstract\
                .setdefault(device_uuid, {}).setdefault(endpoint_uuid, interdomain_endpoint)
            self.__abstract_endpoint_to_device\
                .setdefault(endpoint_uuid, device_uuid)

    def _update_endpoint_type(self, device_uuid : str, endpoint_uuid : str, endpoint_type : str) -> bool:
        device_endpoint_to_abstract = self.__device_endpoint_to_abstract.get(device_uuid, {})
        interdomain_endpoint_tuple = device_endpoint_to_abstract.get(endpoint_uuid)
        _, interdomain_endpoint = interdomain_endpoint_tuple
        interdomain_endpoint = device_endpoint_to_abstract.get(endpoint_uuid)
        interdomain_endpoint_type = interdomain_endpoint.endpoint_type
        if endpoint_type == interdomain_endpoint_type: return False
        interdomain_endpoint.endpoint_type = endpoint_type
        return True

    def _add_interdomain_endpoint(
        self, device_uuid : str, endpoint_uuid : str, endpoint_type : str, interdomain_endpoint_uuid : str
    ) -> EndPoint:
        interdomain_endpoint = self.__own_abstract_device.device_endpoints.add()
        interdomain_endpoint.endpoint_id.device_id.CopyFrom(self.__own_abstract_device_id)
        interdomain_endpoint.endpoint_id.endpoint_uuid.uuid = interdomain_endpoint_uuid
    def _add_endpoint(self, device_uuid : str, endpoint_uuid : str, endpoint_type : str) -> EndPoint:
        interdomain_endpoint = self.__device.device_endpoints.add()
        interdomain_endpoint.endpoint_id.device_id.CopyFrom(self.__device_id)
        interdomain_endpoint.endpoint_id.endpoint_uuid.uuid = endpoint_uuid
        interdomain_endpoint.endpoint_type = endpoint_type

        interdomain_endpoint_tuple = (interdomain_endpoint_uuid, interdomain_endpoint)
        self.__device_endpoint_to_abstract\
            .setdefault(device_uuid, {}).setdefault(endpoint_uuid, interdomain_endpoint_tuple)
        self.__abstract_to_device_endpoint\
            .setdefault(interdomain_endpoint_uuid, (device_uuid, endpoint_uuid))
            .setdefault(device_uuid, {}).setdefault(endpoint_uuid, interdomain_endpoint)
        self.__abstract_endpoint_to_device\
            .setdefault(endpoint_uuid, device_uuid)

        return interdomain_endpoint

    def _remove_interdomain_endpoint(
        self, device_uuid : str, endpoint_uuid : str, interdomain_endpoint_tuple : Tuple[str, EndPoint]
    def _remove_endpoint(
        self, device_uuid : str, endpoint_uuid : str, interdomain_endpoint : EndPoint
    ) -> None:
        interdomain_endpoint_uuid, interdomain_endpoint = interdomain_endpoint_tuple
        self.__abstract_to_device_endpoint.pop(interdomain_endpoint_uuid, None)
        self.__abstract_endpoint_to_device.pop(endpoint_uuid, None)
        device_endpoint_to_abstract = self.__device_endpoint_to_abstract.get(device_uuid, {})
        device_endpoint_to_abstract.pop(endpoint_uuid, None)
        self.__own_abstract_device.device_endpoints.remove(interdomain_endpoint)
        self.__device.device_endpoints.remove(interdomain_endpoint)

    def update_endpoints(self, device : Device) -> bool:
        if device_type_is_datacenter(self.__device.device_type): return False

    def update_abstract_device_endpoints(self, device : Device) -> bool:
        device_uuid = device.device_id.device_uuid.uuid
        LOGGER
        device_border_endpoint_uuids = {
            endpoint.endpoint_id.endpoint_uuid.uuid : endpoint.endpoint_type
            for endpoint in device.device_endpoints
            if str(endpoint.endpoint_type).endswith('/border')
            if endpoint_type_is_border(endpoint.endpoint_type)
        }

        updated = False

        # for each border endpoint in own_abstract_device that is not in device; remove from own_abstract_device
        # for each border endpoint in abstract device that is not in device; remove from abstract device
        device_endpoint_to_abstract = self.__device_endpoint_to_abstract.get(device_uuid, {})
        _device_endpoint_to_abstract = copy.deepcopy(device_endpoint_to_abstract)
        for endpoint_uuid, interdomain_endpoint_tuple in _device_endpoint_to_abstract.items():
        for endpoint_uuid, interdomain_endpoint in _device_endpoint_to_abstract.items():
            if endpoint_uuid in device_border_endpoint_uuids: continue
            # remove interdomain endpoint that is not in device
            self._remove_interdomain_endpoint(device_uuid, endpoint_uuid, interdomain_endpoint_tuple)
            self._remove_endpoint(device_uuid, endpoint_uuid, interdomain_endpoint)
            updated = True

        # for each border endpoint in device that is not in own_abstract_device; add to own_abstract_device
        # for each border endpoint in device that is not in abstract device; add to abstract device
        for endpoint_uuid,endpoint_type in device_border_endpoint_uuids.items():
            # compose interdomain endpoint uuid
            interdomain_endpoint_uuid = '{:s}@{:s}'.format(endpoint_uuid, device_uuid)

            # if already added; just check endpoint type is not modified
            if interdomain_endpoint_uuid in self.__abstract_to_device_endpoint:
            if endpoint_uuid in self.__abstract_endpoint_to_device:
                updated = updated or self._update_endpoint_type(device_uuid, endpoint_uuid, endpoint_type)
                continue

            # otherwise, add it to the abstract device
            self._add_interdomain_endpoint(device_uuid, endpoint_uuid, endpoint_type, interdomain_endpoint_uuid)
            self._add_endpoint(device_uuid, endpoint_uuid, endpoint_type)
            updated = True

        return updated

    def initialize(self) -> Optional[bool]:
        if self.__own_abstract_device is not None: return False

        # Discover or Create device representing abstract local domain
        self._discover_or_create_abstract_device()

        devices_in_admin_topology = get_devices_in_topology(
            self.__context_client, self.__own_context_id, DEFAULT_TOPOLOGY_UUID)
        for device in devices_in_admin_topology:
            self.update_abstract_device_endpoints(device)

        return True
+126 −0
Original line number Diff line number Diff line
# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
#
# 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_UUID, INTERDOMAIN_TOPOLOGY_UUID
from common.proto.context_pb2 import ContextId, EndPointId, Link, LinkId
from common.tools.object_factory.Context import json_context_id
from common.tools.object_factory.Link import json_link, json_link_id
from context.client.ContextClient import ContextClient
from .Tools import add_link_to_topology, get_existing_link_uuids

LOGGER = logging.getLogger(__name__)

class AbstractLink:
    def __init__(self, link_uuid : str):
        self.__context_client = ContextClient()
        self.__link_uuid : str = link_uuid
        self.__link : Optional[Link] = None
        self.__link_id : Optional[LinkId] = None

        # Dict[(device_uuid, endpoint_uuid), abstract EndPointId]
        self.__device_endpoint_to_abstract : Dict[Tuple[str, str], EndPointId] = dict()

    @property
    def uuid(self) -> str: return self.__link_uuid

    @property
    def link_id(self) -> Optional[LinkId]: return self.__link_id

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

    @staticmethod
    def compose_uuid(
        device_uuid_a : str, endpoint_uuid_a : str, device_uuid_z : str, endpoint_uuid_z : str
    ) -> str:
        # sort endpoints lexicographically to prevent duplicities
        link_endpoint_uuids = sorted([
            (device_uuid_a, endpoint_uuid_a),
            (device_uuid_z, endpoint_uuid_z)
        ])
        link_uuid = '{:s}/{:s}=={:s}/{:s}'.format(
            link_endpoint_uuids[0][0], link_endpoint_uuids[0][1],
            link_endpoint_uuids[1][0], link_endpoint_uuids[1][1])
        return link_uuid

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

        existing_link_uuids = get_existing_link_uuids(self.__context_client)

        create = self.__link_uuid not in existing_link_uuids
        if create:
            self._create_empty()
        else:
            self._load_existing()

        # Add abstract link to topologies [INTERDOMAIN_TOPOLOGY_UUID]
        context_id = ContextId(**json_context_id(DEFAULT_CONTEXT_UUID))
        topology_uuids = [INTERDOMAIN_TOPOLOGY_UUID]
        for topology_uuid in topology_uuids:
            add_link_to_topology(self.__context_client, context_id, topology_uuid, self.__link_uuid)

        return create

    def _create_empty(self) -> None:
        link = Link(**json_link(self.__link_uuid, endpoint_ids=[]))
        self.__context_client.SetLink(link)
        self.__link = link
        self.__link_id = self.__link.link_id
    
    def _load_existing(self) -> None:
        self.__link_id = LinkId(**json_link_id(self.__link_uuid))
        self.__link = self.__context_client.GetLink(self.__link_id)

        self.__device_endpoint_to_abstract = dict()

        # for each endpoint in abstract link, populate internal data structures and mappings
        for endpoint_id in self.__link.link_endpoint_ids:
            device_uuid : str = endpoint_id.device_id.device_uuid.uuid
            endpoint_uuid : str = endpoint_id.endpoint_uuid.uuid
            self.__device_endpoint_to_abstract.setdefault((device_uuid, endpoint_uuid), endpoint_id)

    def _add_endpoint(self, device_uuid : str, endpoint_uuid : str) -> None:
        endpoint_id = self.__link.link_endpoint_ids.add()
        endpoint_id.device_id.device_uuid.uuid = device_uuid
        endpoint_id.endpoint_uuid.uuid = endpoint_uuid
        self.__device_endpoint_to_abstract.setdefault((device_uuid, endpoint_uuid), endpoint_id)

    def _remove_endpoint(self, device_uuid : str, endpoint_uuid : str) -> None:
        device_endpoint_to_abstract = self.__device_endpoint_to_abstract.get(device_uuid, {})
        endpoint_id = device_endpoint_to_abstract.pop(endpoint_uuid, None)
        if endpoint_id is not None: self.__link.link_endpoint_ids.remove(endpoint_id)

    def update_endpoints(self, link_endpoint_uuids : 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_uuid, endpoint_uuid in device_endpoint_to_abstract.keys():
            if (device_uuid, endpoint_uuid) in link_endpoint_uuids: continue
            # remove endpoint_id that is not in link
            self._remove_endpoint(device_uuid, endpoint_uuid)
            updated = True

        # for each endpoint in link that is not in abstract link; add to abstract link
        for device_uuid, endpoint_uuid in link_endpoint_uuids:
            # if already added; just check endpoint type is not modified
            if (device_uuid, endpoint_uuid) in self.__device_endpoint_to_abstract: continue
            # otherwise, add it to the abstract device
            self._add_endpoint(device_uuid, endpoint_uuid)
            updated = True

        return updated
+91 −0

File added.

Preview size limit exceeded, changes collapsed.

+0 −56
Original line number Diff line number Diff line
# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
#
# 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 Optional, Set
from common.Constants import DEFAULT_CONTEXT_UUID
from common.proto.context_pb2 import Empty
from context.client.ContextClient import ContextClient

LOGGER = logging.getLogger(__name__)

class OwnDomainFinder:
    def __new__(cls):
        if not hasattr(cls, 'instance'):
            cls.instance = super(OwnDomainFinder, cls).__new__(cls)
        return cls.instance

    def __init__(self) -> None:
        self.__context_client = ContextClient()
        self.__own_domain_uuid : Optional[str] = None
        self.__existing_context_uuids : Optional[Set[str]] = None

    def __update(self) -> None:
        existing_context_ids = self.__context_client.ListContextIds(Empty())
        existing_context_uuids = {context_id.context_uuid.uuid for context_id in existing_context_ids.context_ids}

        # Detect local context name (will be used as abstracted device name); exclude DEFAULT_CONTEXT_UUID
        existing_non_admin_context_uuids = copy.deepcopy(existing_context_uuids)
        existing_non_admin_context_uuids.discard(DEFAULT_CONTEXT_UUID)
        if len(existing_non_admin_context_uuids) != 1:
            MSG = 'Unable to identify own domain name. Existing Contexts({:s})'
            raise Exception(MSG.format(str(existing_context_uuids)))
        
        self.__own_domain_uuid = existing_non_admin_context_uuids.pop()
        self.__existing_context_uuids = existing_context_uuids

    @property
    def own_domain_uuid(self) -> Optional[str]:
        if self.__own_domain_uuid is None: self.__update()
        return self.__own_domain_uuid

    @property
    def existing_context_uuids(self) -> Optional[Set[str]]:
        if self.__existing_context_uuids is None: self.__update()
        return self.__existing_context_uuids
Loading