From 56ab40bb6fed308208f3cf826ca89585e31cc8e8 Mon Sep 17 00:00:00 2001 From: Ville Petteri Hallivuori Date: Fri, 5 Aug 2022 04:30:41 -0700 Subject: [PATCH 01/10] Pull request #1: vhallivu/XR_OPTICS_DRIVER_LOGIN_TO_CM Merge in XRCA/teraflow from vhallivu/XR_OPTICS_DRIVER_LOGIN_TO_CM to xr_development Squashed commit of the following: commit ba53d51d94c52491c4f70587187bf6746a9613da Author: Ville Hallivuori Date: Fri Aug 5 13:26:40 2022 +0300 Initial code for XR Device Driver, can connect to CM and acquire access token. No other functionality --- proto/context.proto | 1 + .../eu/teraflow/automation/Serializer.java | 4 + .../context/model/DeviceDriverEnum.java | 3 +- .../teraflow/automation/SerializerTest.java | 3 + src/common/DeviceTypes.py | 1 + src/common/type_checkers/Assertions.py | 1 + src/context/service/database/DeviceModel.py | 1 + src/device/service/database/DeviceModel.py | 1 + src/device/service/drivers/__init__.py | 11 +- src/device/service/drivers/xr/XrDriver.py | 116 +++++++++++++++++ src/device/service/drivers/xr/__init__.py | 27 ++++ .../java/eu/teraflow/policy/Serializer.java | 2 + .../context/model/DeviceDriverEnum.java | 3 +- .../eu/teraflow/policy/SerializerTest.java | 3 + src/service/service/database/DeviceModel.py | 1 + .../PathComputationElement.py | 1 + src/tests/ofc22/descriptors_emulated_xr.json | 119 ++++++++++++++++++ 17 files changed, 295 insertions(+), 3 deletions(-) create mode 100644 src/device/service/drivers/xr/XrDriver.py create mode 100644 src/device/service/drivers/xr/__init__.py create mode 100644 src/tests/ofc22/descriptors_emulated_xr.json diff --git a/proto/context.proto b/proto/context.proto index 866876175..e17b2acfa 100644 --- a/proto/context.proto +++ b/proto/context.proto @@ -169,6 +169,7 @@ enum DeviceDriverEnum { DEVICEDRIVER_P4 = 3; DEVICEDRIVER_IETF_NETWORK_TOPOLOGY = 4; DEVICEDRIVER_ONF_TR_352 = 5; + DEVICEDRIVER_XR = 6; } enum DeviceOperationalStatusEnum { diff --git a/src/automation/src/main/java/eu/teraflow/automation/Serializer.java b/src/automation/src/main/java/eu/teraflow/automation/Serializer.java index 2b163fdff..9d9c3430f 100644 --- a/src/automation/src/main/java/eu/teraflow/automation/Serializer.java +++ b/src/automation/src/main/java/eu/teraflow/automation/Serializer.java @@ -797,6 +797,8 @@ public class Serializer { return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY; case ONF_TR_352: return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352; + case XR: + return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_XR; case UNDEFINED: default: return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED; @@ -816,6 +818,8 @@ public class Serializer { return DeviceDriverEnum.IETF_NETWORK_TOPOLOGY; case DEVICEDRIVER_ONF_TR_352: return DeviceDriverEnum.ONF_TR_352; + case DEVICEDRIVER_XR: + return DeviceDriverEnum.XR; case DEVICEDRIVER_UNDEFINED: case UNRECOGNIZED: default: diff --git a/src/automation/src/main/java/eu/teraflow/automation/context/model/DeviceDriverEnum.java b/src/automation/src/main/java/eu/teraflow/automation/context/model/DeviceDriverEnum.java index 8fc767ac2..fc0521927 100644 --- a/src/automation/src/main/java/eu/teraflow/automation/context/model/DeviceDriverEnum.java +++ b/src/automation/src/main/java/eu/teraflow/automation/context/model/DeviceDriverEnum.java @@ -22,5 +22,6 @@ public enum DeviceDriverEnum { TRANSPORT_API, P4, IETF_NETWORK_TOPOLOGY, - ONF_TR_352 + ONF_TR_352, + XR } diff --git a/src/automation/src/test/java/eu/teraflow/automation/SerializerTest.java b/src/automation/src/test/java/eu/teraflow/automation/SerializerTest.java index 74f8f301c..35ba1422b 100644 --- a/src/automation/src/test/java/eu/teraflow/automation/SerializerTest.java +++ b/src/automation/src/test/java/eu/teraflow/automation/SerializerTest.java @@ -1048,6 +1048,9 @@ class SerializerTest { Arguments.of( DeviceDriverEnum.ONF_TR_352, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352), + Arguments.of( + DeviceDriverEnum.XR, + ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_XR), Arguments.of( DeviceDriverEnum.UNDEFINED, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED)); } diff --git a/src/common/DeviceTypes.py b/src/common/DeviceTypes.py index bf871a2d5..3cd53ea09 100644 --- a/src/common/DeviceTypes.py +++ b/src/common/DeviceTypes.py @@ -24,3 +24,4 @@ class DeviceTypeEnum(Enum): PACKET_ROUTER = 'packet-router' PACKET_SWITCH = 'packet-switch' P4_SWITCH = 'p4-switch' + XR_CONSTELLATION = 'xr-constellation' diff --git a/src/common/type_checkers/Assertions.py b/src/common/type_checkers/Assertions.py index 20ffa9ad6..aa9ede333 100644 --- a/src/common/type_checkers/Assertions.py +++ b/src/common/type_checkers/Assertions.py @@ -32,6 +32,7 @@ def validate_device_driver_enum(message): 'DEVICEDRIVER_P4', 'DEVICEDRIVER_IETF_NETWORK_TOPOLOGY', 'DEVICEDRIVER_ONF_TR_352', + 'DEVICEDRIVER_XR', ] def validate_device_operational_status_enum(message): diff --git a/src/context/service/database/DeviceModel.py b/src/context/service/database/DeviceModel.py index 0d4232679..0ffb97fee 100644 --- a/src/context/service/database/DeviceModel.py +++ b/src/context/service/database/DeviceModel.py @@ -35,6 +35,7 @@ class ORM_DeviceDriverEnum(Enum): P4 = DeviceDriverEnum.DEVICEDRIVER_P4 IETF_NETWORK_TOPOLOGY = DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY ONF_TR_352 = DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352 + XR = DeviceDriverEnum.DEVICEDRIVER_XR grpc_to_enum__device_driver = functools.partial( grpc_to_enum, DeviceDriverEnum, ORM_DeviceDriverEnum) diff --git a/src/device/service/database/DeviceModel.py b/src/device/service/database/DeviceModel.py index 7a0a23259..9dd63d36e 100644 --- a/src/device/service/database/DeviceModel.py +++ b/src/device/service/database/DeviceModel.py @@ -35,6 +35,7 @@ class ORM_DeviceDriverEnum(Enum): P4 = DeviceDriverEnum.DEVICEDRIVER_P4 IETF_NETWORK_TOPOLOGY = DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY ONF_TR_352 = DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352 + XR = DeviceDriverEnum.DEVICEDRIVER_XR grpc_to_enum__device_driver = functools.partial( grpc_to_enum, DeviceDriverEnum, ORM_DeviceDriverEnum) diff --git a/src/device/service/drivers/__init__.py b/src/device/service/drivers/__init__.py index 40912f50b..4b67a77ea 100644 --- a/src/device/service/drivers/__init__.py +++ b/src/device/service/drivers/__init__.py @@ -17,6 +17,7 @@ from ..driver_api.FilterFields import FilterFieldEnum, ORM_DeviceDriverEnum from .emulated.EmulatedDriver import EmulatedDriver from .openconfig.OpenConfigDriver import OpenConfigDriver from .transport_api.TransportApiDriver import TransportApiDriver +from .xr.XrDriver import XrDriver from .p4.p4_driver import P4Driver from .microwave.IETFApiDriver import IETFApiDriver @@ -35,7 +36,8 @@ DRIVERS = [ FilterFieldEnum.DRIVER : [ ORM_DeviceDriverEnum.UNDEFINED, ORM_DeviceDriverEnum.OPENCONFIG, - ORM_DeviceDriverEnum.TRANSPORT_API + ORM_DeviceDriverEnum.TRANSPORT_API, + ORM_DeviceDriverEnum.XR ], } ]), @@ -66,4 +68,11 @@ DRIVERS = [ FilterFieldEnum.DRIVER : ORM_DeviceDriverEnum.IETF_NETWORK_TOPOLOGY, } ]), + (XrDriver, [ + { + # Close enough, it does optical switching + FilterFieldEnum.DEVICE_TYPE: DeviceTypeEnum.XR_CONSTELLATION, + FilterFieldEnum.DRIVER : ORM_DeviceDriverEnum.XR, + } + ]), ] diff --git a/src/device/service/drivers/xr/XrDriver.py b/src/device/service/drivers/xr/XrDriver.py new file mode 100644 index 000000000..8f40012a2 --- /dev/null +++ b/src/device/service/drivers/xr/XrDriver.py @@ -0,0 +1,116 @@ +# 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 logging, requests, threading +from typing import Any, Iterator, List, Optional, Tuple, Union +from common.type_checkers.Checkers import chk_string, chk_type +from device.service.driver_api._Driver import _Driver +from . import ALL_RESOURCE_KEYS +#from .Tools import create_connectivity_service, find_key, config_getter, delete_connectivity_service +import json + +LOGGER = logging.getLogger(__name__) + +class XrDriver(_Driver): + def __init__(self, address: str, port: int, **settings) -> None: # pylint: disable=super-init-not-called + self.__lock = threading.Lock() + self.__started = threading.Event() + self.__terminate = threading.Event() + self.__cm_root = 'https://' + address + ':' + str(port) + self.__timeout = int(settings.get('timeout', 120)) + self.__verify = False; # Currently using self signed certificates + self.__audience = settings["audience"] if "audience" in settings else "test" + self.__client_id = settings["client_id"] if "client_id" in settings else "test" + + # FIXME: remove + LOGGER.info(f"FIXME!!! XrDriver, cm {address}:{port}, {settings=}"); + + def Connect(self) -> bool: + url = self.__cm_root + '/oauth/token' + with self.__lock: + if self.__started.is_set(): return True + try: + # TODO: could also do get: https://${HOSTNAME}:443/oauth/token?client_id=test&audience=test" + req = {"grant_type":"client_credentials","client_id": self.__client_id, "audience": self.__audience} + response = requests.post(url,data=req,timeout=self.__timeout,verify=self.__verify) + resp = json.loads(response.text) + if 'access_token' in resp: + self.__access_token=resp['access_token'] + LOGGER.info(f"FIXME!!! CM connected, {self.__access_token=}") ## TODO: remove + + # Use in subsequend requests as named argument headers=self.__cm_http_headers + self.__cm_http_headers = {'Authorization': 'Bearer '+ self.__access_token} + else: + LOGGER.exception('No access token provided by {:s}'.format(str(self.__cm_root))) + return False + except requests.exceptions.Timeout: + LOGGER.exception('Timeout connecting {:s}'.format(str(self.__cm_root))) + return False + except json.JSONDecodeError as json_err: + LOGGER.exception(f"Exception parsing JSON access token from {str(self.__cm_root)}, {str(json_err)}") + return False + except Exception: # pylint: disable=broad-except + LOGGER.exception('Exception connecting {:s}'.format(str(self.__cm_root))) + return False + else: + self.__started.set() + return True + + def Disconnect(self) -> bool: + with self.__lock: + self.__terminate.set() + return True + + def GetInitialConfig(self) -> List[Tuple[str, Any]]: + with self.__lock: + return [] + + def GetConfig(self, resource_keys : List[str] = []) -> List[Tuple[str, Union[Any, None, Exception]]]: + chk_type('resources', resource_keys, list) + results = [] + + # TODO + + return results + + def SetConfig(self, resources: List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: + results = [] + if len(resources) == 0: + return results + + # TODO + + return results + + def DeleteConfig(self, resources: List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: + results = [] + if len(resources) == 0: return results + + #TODO + + return results + + def SubscribeState(self, subscriptions : List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]: + # Not supported + return [False for _ in subscriptions] + + def UnsubscribeState(self, subscriptions : List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]: + # Not supported + return [False for _ in subscriptions] + + def GetState( + self, blocking=False, terminate : Optional[threading.Event] = None + ) -> Iterator[Tuple[float, str, Any]]: + # Not supported + return [] diff --git a/src/device/service/drivers/xr/__init__.py b/src/device/service/drivers/xr/__init__.py new file mode 100644 index 000000000..329bbc306 --- /dev/null +++ b/src/device/service/drivers/xr/__init__.py @@ -0,0 +1,27 @@ +# 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. + +from device.service.driver_api._Driver import RESOURCE_ENDPOINTS, RESOURCE_INTERFACES, RESOURCE_NETWORK_INSTANCES + +ALL_RESOURCE_KEYS = [ +# RESOURCE_ENDPOINTS, +# RESOURCE_INTERFACES, +# RESOURCE_NETWORK_INSTANCES, +] + +RESOURCE_KEY_MAPPINGS = { +# RESOURCE_ENDPOINTS : 'component', +# RESOURCE_INTERFACES : 'interface', +# RESOURCE_NETWORK_INSTANCES: 'network_instance', +} diff --git a/src/policy/src/main/java/eu/teraflow/policy/Serializer.java b/src/policy/src/main/java/eu/teraflow/policy/Serializer.java index 35b6e2fd8..9121f1ba9 100644 --- a/src/policy/src/main/java/eu/teraflow/policy/Serializer.java +++ b/src/policy/src/main/java/eu/teraflow/policy/Serializer.java @@ -2099,6 +2099,8 @@ public class Serializer { return DeviceDriverEnum.IETF_NETWORK_TOPOLOGY; case DEVICEDRIVER_ONF_TR_352: return DeviceDriverEnum.ONF_TR_352; + case DEVICEDRIVER_XR: + return DeviceDriverEnum.XR; case DEVICEDRIVER_UNDEFINED: case UNRECOGNIZED: default: diff --git a/src/policy/src/main/java/eu/teraflow/policy/context/model/DeviceDriverEnum.java b/src/policy/src/main/java/eu/teraflow/policy/context/model/DeviceDriverEnum.java index c98fc1fce..ee1ebcbcf 100644 --- a/src/policy/src/main/java/eu/teraflow/policy/context/model/DeviceDriverEnum.java +++ b/src/policy/src/main/java/eu/teraflow/policy/context/model/DeviceDriverEnum.java @@ -22,5 +22,6 @@ public enum DeviceDriverEnum { TRANSPORT_API, P4, IETF_NETWORK_TOPOLOGY, - ONF_TR_352 + ONF_TR_352, + XR } diff --git a/src/policy/src/test/java/eu/teraflow/policy/SerializerTest.java b/src/policy/src/test/java/eu/teraflow/policy/SerializerTest.java index 05f835d58..050af7b0b 100644 --- a/src/policy/src/test/java/eu/teraflow/policy/SerializerTest.java +++ b/src/policy/src/test/java/eu/teraflow/policy/SerializerTest.java @@ -3489,6 +3489,9 @@ class SerializerTest { Arguments.of( DeviceDriverEnum.ONF_TR_352, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352), + Arguments.of( + DeviceDriverEnum.XR, + ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_XR), Arguments.of( DeviceDriverEnum.UNDEFINED, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED)); } diff --git a/src/service/service/database/DeviceModel.py b/src/service/service/database/DeviceModel.py index 0f0201190..1d1d6b80a 100644 --- a/src/service/service/database/DeviceModel.py +++ b/src/service/service/database/DeviceModel.py @@ -35,6 +35,7 @@ class ORM_DeviceDriverEnum(Enum): P4 = DeviceDriverEnum.DEVICEDRIVER_P4 IETF_NETWORK_TOPOLOGY = DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY ONF_TR_352 = DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352 + XR = DeviceDriverEnum.DEVICEDRIVER_XR grpc_to_enum__device_driver = functools.partial( grpc_to_enum, DeviceDriverEnum, ORM_DeviceDriverEnum) diff --git a/src/service/service/path_computation_element/PathComputationElement.py b/src/service/service/path_computation_element/PathComputationElement.py index 41a4c5d57..08331377d 100644 --- a/src/service/service/path_computation_element/PathComputationElement.py +++ b/src/service/service/path_computation_element/PathComputationElement.py @@ -35,6 +35,7 @@ SUB_SERVICE_TYPES = { DeviceTypeEnum.EMULATED_OPTICAL_LINE_SYSTEM.value: ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE, DeviceTypeEnum.PACKET_ROUTER.value : ServiceTypeEnum.SERVICETYPE_L3NM, DeviceTypeEnum.OPTICAL_LINE_SYSTEM.value : ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE, + DeviceTypeEnum.XR_CONSTELLATION.value : ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE, } DEFAULT_SUB_SERVICE_TYPE = ServiceTypeEnum.SERVICETYPE_UNKNOWN diff --git a/src/tests/ofc22/descriptors_emulated_xr.json b/src/tests/ofc22/descriptors_emulated_xr.json new file mode 100644 index 000000000..1f0474e86 --- /dev/null +++ b/src/tests/ofc22/descriptors_emulated_xr.json @@ -0,0 +1,119 @@ +{ + "contexts": [ + { + "context_id": {"context_uuid": {"uuid": "admin"}}, + "topology_ids": [], + "service_ids": [] + } + ], + "topologies": [ + { + "topology_id": {"topology_uuid": {"uuid": "admin"}, "context_id": {"context_uuid": {"uuid": "admin"}}}, + "device_ids": [], + "link_ids": [] + } + ], + "devices": [ + { + "device_id": {"device_uuid": {"uuid": "R1-EMU"}}, + "device_type": "emu-packet-router", + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": "{\"endpoints\": [{\"sample_types\": [], \"type\": \"optical\", \"uuid\": \"13/0/0\"}, {\"sample_types\": [101, 102, 201, 202], \"type\": \"copper\", \"uuid\": \"13/1/2\"}]}"}} + ]}, + "device_operational_status": 1, + "device_drivers": [0], + "device_endpoints": [] + }, + { + "device_id": {"device_uuid": {"uuid": "R2-EMU"}}, + "device_type": "emu-packet-router", + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": "{\"endpoints\": [{\"sample_types\": [], \"type\": \"optical\", \"uuid\": \"13/0/0\"}, {\"sample_types\": [101, 102, 201, 202], \"type\": \"copper\", \"uuid\": \"13/1/2\"}]}"}} + ]}, + "device_operational_status": 1, + "device_drivers": [0], + "device_endpoints": [] + }, + { + "device_id": {"device_uuid": {"uuid": "R3-EMU"}}, + "device_type": "emu-packet-router", + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": "{\"endpoints\": [{\"sample_types\": [], \"type\": \"optical\", \"uuid\": \"13/0/0\"}, {\"sample_types\": [101, 102, 201, 202], \"type\": \"copper\", \"uuid\": \"13/1/2\"}]}"}} + ]}, + "device_operational_status": 1, + "device_drivers": [0], + "device_endpoints": [] + }, + { + "device_id": {"device_uuid": {"uuid": "R4-EMU"}}, + "device_type": "emu-packet-router", + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": "{\"endpoints\": [{\"sample_types\": [], \"type\": \"optical\", \"uuid\": \"13/0/0\"}, {\"sample_types\": [101, 102, 201, 202], \"type\": \"copper\", \"uuid\": \"13/1/2\"}]}"}} + ]}, + "device_operational_status": 1, + "device_drivers": [0], + "device_endpoints": [] + }, + { + "device_id": {"device_uuid": {"uuid": "O1-OLS"}}, + "device_type": "emu-optical-line-system", + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": "{\"endpoints\": [{\"sample_types\": [], \"type\": \"optical\", \"uuid\": \"aade6001-f00b-5e2f-a357-6a0a9d3de870\"}, {\"sample_types\": [], \"type\": \"optical\", \"uuid\": \"eb287d83-f05e-53ec-ab5a-adf6bd2b5418\"}, {\"sample_types\": [], \"type\": \"optical\", \"uuid\": \"0ef74f99-1acc-57bd-ab9d-4b958b06c513\"}, {\"sample_types\": [], \"type\": \"optical\", \"uuid\": \"50296d99-58cc-5ce7-82f5-fc8ee4eec2ec\"}]}"}} + ]}, + "device_operational_status": 1, + "device_drivers": [0], + "device_endpoints": [] + }, + { + "device_id": {"device_uuid": {"uuid": "X1-XR-CONSTELLATION"}}, + "device_type": "xr-constellation", + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "172.19.219.44"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "443"}} + ]}, + "device_operational_status": 1, + "device_drivers": [0], + "device_endpoints": [] + } + ], + "links": [ + { + "link_id": {"link_uuid": {"uuid": "R1-EMU/13/0/0==O1-OLS/aade6001-f00b-5e2f-a357-6a0a9d3de870"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R1-EMU"}}, "endpoint_uuid": {"uuid": "13/0/0"}}, + {"device_id": {"device_uuid": {"uuid": "O1-OLS"}}, "endpoint_uuid": {"uuid": "aade6001-f00b-5e2f-a357-6a0a9d3de870"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R2-EMU/13/0/0==O1-OLS/eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R2-EMU"}}, "endpoint_uuid": {"uuid": "13/0/0"}}, + {"device_id": {"device_uuid": {"uuid": "O1-OLS"}}, "endpoint_uuid": {"uuid": "eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R3-EMU/13/0/0==O1-OLS/0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R3-EMU"}}, "endpoint_uuid": {"uuid": "13/0/0"}}, + {"device_id": {"device_uuid": {"uuid": "O1-OLS"}}, "endpoint_uuid": {"uuid": "0ef74f99-1acc-57bd-ab9d-4b958b06c513"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R4-EMU/13/0/0==O1-OLS/50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R4-EMU"}}, "endpoint_uuid": {"uuid": "13/0/0"}}, + {"device_id": {"device_uuid": {"uuid": "O1-OLS"}}, "endpoint_uuid": {"uuid": "50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}} + ] + } + ] +} -- GitLab From ac066a01a6ce73d9eaba4c0ed61083d865747897 Mon Sep 17 00:00:00 2001 From: Ville Petteri Hallivuori Date: Mon, 8 Aug 2022 05:42:10 -0700 Subject: [PATCH 02/10] Pull request #2: vhallivu/XR_DRIVER_EMULATE_ENDPOINTS Merge in XRCA/teraflow from vhallivu/XR_DRIVER_EMULATE_ENDPOINTS to xr_development Squashed commit of the following: commit da5b46937d8f0dd87f535e3d736dc1d9e2043e94 Author: Ville Hallivuori Date: Mon Aug 8 15:21:16 2022 +0300 Updates to emulation commit b9d9e566f4622f85767c85b72494fbb33896de60 Author: Ville Hallivuori Date: Mon Aug 8 13:49:55 2022 +0300 Add XR constellation icon for topology commit 6d24a643f11ecb684043332500c2ee8b2768e900 Author: Ville Hallivuori Date: Mon Aug 8 12:56:16 2022 +0300 Improved XR service creation stubs commit 7d31fd2ecc67848368e2d0eddb616faa6115128d Author: Ville Hallivuori Date: Mon Aug 8 08:07:10 2022 +0300 Eulation for XrDriver endpoints --- README_INFINERA.md | 82 +++++++ src/common/tools/object_factory/Device.py | 13 +- src/device/service/drivers/__init__.py | 3 +- src/device/service/drivers/xr/XrDriver.py | 41 +++- src/device/service/drivers/xr/__init__.py | 8 +- .../java/eu/teraflow/policy/Serializer.java | 2 + .../service/service_handlers/__init__.py | 4 +- src/tests/ofc22/descriptors_emulated_xr.json | 33 +-- src/tests/ofc22/setup_test_env.sh | 9 + src/tests/ofc22/tests/ObjectsXr.py | 232 ++++++++++++++++++ .../test_functional_create_service_xr.py | 130 ++++++++++ .../topology_icons/xr-constellation.png | Bin 0 -> 37336 bytes 12 files changed, 523 insertions(+), 34 deletions(-) create mode 100644 README_INFINERA.md create mode 100755 src/tests/ofc22/setup_test_env.sh create mode 100644 src/tests/ofc22/tests/ObjectsXr.py create mode 100644 src/tests/ofc22/tests/test_functional_create_service_xr.py create mode 100644 src/webui/service/static/topology_icons/xr-constellation.png diff --git a/README_INFINERA.md b/README_INFINERA.md new file mode 100644 index 000000000..4d3c1a3f0 --- /dev/null +++ b/README_INFINERA.md @@ -0,0 +1,82 @@ +# Infinera Readme + +This file will be removed before uploading to origin. + +There are some instructions at https://gitlab.com/teraflow-h2020/controller/-/tree/develop/tutorial . They are not completely up to date and don't 100% work. + +Note that many of the scripts expect this and that K8s namespace being used, they are not consistent, so use manual kubectl commands where necessary. + +Infinera repo (cloned from upstream) is https://bitbucket.infinera.com/projects/XRCA/repos/teraflow/browse . The main development branch for us is xr-development (branched of origin/develop). + +## Preliminaries + +Kubernetes must be installed and configured. + +Note that if runninc MicroK8s (I would highly recommend it), then install also regular kubectl so that scripts work. That is, download the kubectl, and also export credidentials to standard location. + +```bash +# As a root +su - +cd /usr/local/bin +curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" +chmod 755 kubectl +exit + +# As your local user +cd ~/.kube +microk8s config > config +``` + +Local Docker registry is needed for build results. Use the following command to start local registry (docker will pull necessary images from Internet) + +```bash +docker run -d -p 32000:5000 --restart=always --name registry registry:2 +``` + +Setup mydeploy script outside the git repo. E.g. following will do. SOURCE IT ON ALL SHELLS. + +```bash +export TFS_REGISTRY_IMAGE="http://localhost:32000/tfs/" + +export TFS_COMPONENTS="context device automation service compute monitoring webui" +export TFS_IMAGE_TAG="dev" +export TFS_K8S_NAMESPACE="tfs" +export TFS_EXTRA_MANIFESTS="manifests/nginx_ingress_http.yaml" +export TFS_GRAFANA_PASSWORD="admin123+" +``` + +Build is containerized, pytest used for setup is not. Teraflow has some third party venv suggestion in docs. However standard venv works. Create: + +```bash +python -m venv .venv +source .venv/bin/activate +./install_requirements.sh +``` + +SOURCE VENV ACTIVATE ON ANY SHELL USED FOR PYTHON RELATED WORK (e.g. pytest). + +Use apt-get to install any missing tools (e.g. jq is required). + +## Building + +Run deploy script to build in docker containers and then instantiate to configured K8s cluster. Deploy script must be sources for this to work! + +```bash +./deploy.sh +``` + +## Testing + +Upload descriptors_emulatex_xr.json via WEB UI to setup fake topology. + +Setup service by following commands in src directory. Kubernetes endpoins change on every build, so setup script is mandatory. + +```bash + source tests/ofc22/setup_test_env.sh + python -m pytest --verbose tests/ofc22/tests/test_functional_create_service_xr.py +``` + +Good logs to check are: + +* kubectl logs service/deviceservice --namespace tfs +* kubectl logs service/webuiservice --namespace tfs diff --git a/src/common/tools/object_factory/Device.py b/src/common/tools/object_factory/Device.py index 32baff9ae..0b597a77a 100644 --- a/src/common/tools/object_factory/Device.py +++ b/src/common/tools/object_factory/Device.py @@ -15,7 +15,7 @@ import copy from typing import Dict, List, Tuple from common.DeviceTypes import DeviceTypeEnum -from common.proto.context_pb2 import DeviceDriverEnum, DeviceOperationalStatusEnum +from common.proto.context_pb2 import DEVICEDRIVER_XR, DeviceDriverEnum, DeviceOperationalStatusEnum from common.tools.object_factory.ConfigRule import json_config_rule_set DEVICE_DISABLED = DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_DISABLED @@ -32,6 +32,9 @@ DEVICE_PR_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG] DEVICE_TAPI_TYPE = DeviceTypeEnum.OPTICAL_LINE_SYSTEM.value DEVICE_TAPI_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_TRANSPORT_API] +DEVICE_XR_CONSTELLATION_TYPE = DeviceTypeEnum.XR_CONSTELLATION.value +DEVICE_XR_CONSTELLATION_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_XR] + # check which enum type and value assign to microwave device DEVICE_MICROWAVE_TYPE = DeviceTypeEnum.MICROVAWE_RADIO_SYSTEM.value DEVICE_MICROWAVE_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY] @@ -85,6 +88,14 @@ def json_device_tapi_disabled( return json_device( device_uuid, DEVICE_TAPI_TYPE, DEVICE_DISABLED, endpoints=endpoints, config_rules=config_rules, drivers=drivers) +def json_device_xr_constellation_disabled( + device_uuid : str, endpoints : List[Dict] = [], config_rules : List[Dict] = [], + drivers : List[Dict] = DEVICE_XR_CONSTELLATION_DRIVERS + ): + return json_device( + device_uuid, DEVICE_XR_CONSTELLATION_TYPE, DEVICE_DISABLED, endpoints=endpoints, config_rules=config_rules, drivers=drivers) + + def json_device_microwave_disabled( device_uuid : str, endpoints : List[Dict] = [], config_rules : List[Dict] = [], drivers : List[Dict] = DEVICE_MICROWAVE_DRIVERS diff --git a/src/device/service/drivers/__init__.py b/src/device/service/drivers/__init__.py index 4b67a77ea..e4f9c8d54 100644 --- a/src/device/service/drivers/__init__.py +++ b/src/device/service/drivers/__init__.py @@ -36,8 +36,7 @@ DRIVERS = [ FilterFieldEnum.DRIVER : [ ORM_DeviceDriverEnum.UNDEFINED, ORM_DeviceDriverEnum.OPENCONFIG, - ORM_DeviceDriverEnum.TRANSPORT_API, - ORM_DeviceDriverEnum.XR + ORM_DeviceDriverEnum.TRANSPORT_API ], } ]), diff --git a/src/device/service/drivers/xr/XrDriver.py b/src/device/service/drivers/xr/XrDriver.py index 8f40012a2..81898d7c7 100644 --- a/src/device/service/drivers/xr/XrDriver.py +++ b/src/device/service/drivers/xr/XrDriver.py @@ -33,6 +33,8 @@ class XrDriver(_Driver): self.__audience = settings["audience"] if "audience" in settings else "test" self.__client_id = settings["client_id"] if "client_id" in settings else "test" + self.__services = {} + # FIXME: remove LOGGER.info(f"FIXME!!! XrDriver, cm {address}:{port}, {settings=}"); @@ -76,28 +78,61 @@ class XrDriver(_Driver): with self.__lock: return [] + def fake_interface_names(self) -> List[str]: + interfaces = [] + # Using 4 as max leaf and lane to keep prints small during development + for lane in range(0,4): + interfaces.append(f"HUB-LANE-{lane:02}") + for leaf in range(1,5): + for lane in range(0,4): + interfaces.append(f"LEAF-{leaf:02}-LANE-{lane:02}") + return interfaces + def GetConfig(self, resource_keys : List[str] = []) -> List[Tuple[str, Union[Any, None, Exception]]]: chk_type('resources', resource_keys, list) results = [] - # TODO + # TODO: Completely fake interface information until we get same info from CM + for ifname in self.fake_interface_names(): + results.append((f"/endpoints/endpoint[{ifname}]", {'uuid': ifname, 'type': 'optical', 'sample_types': {}})) return results + def SetConfig(self, resources: List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: + LOGGER.info(f"FIXME!!! XrDriver, SetConfig {resources=}"); + + # Logged config seems like: + #[('/service[44ca3570-4e1a-49b5-8aab-06c92f239fab:optical]', '{"capacity_unit": "GHz", "capacity_value": 1, "direction": "UNIDIRECTIONAL", "input_sip": "HUB-LANE-01", "layer_protocol_name": "PHOTONIC_MEDIA", "layer_protocol_qualifier": "tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_NMC", "output_sip": "LEAF-02-LANE-01", "uuid": "44ca3570-4e1a-49b5-8aab-06c92f239fab:optical"}')] results = [] if len(resources) == 0: return results - # TODO + # Temporary dummy version + for key, config in resources: + self.__services[key] = config + + # TODO: config to CM + # Ignore "direction=UNIDIRECITONAL", it seems that controller only creates one direction... + results.append(True) return results def DeleteConfig(self, resources: List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: + LOGGER.info(f"FIXME!!! XrDriver, DeleteConfig {resources=}"); + results = [] if len(resources) == 0: return results - #TODO + # Temporary dummy version + for key, config in resources: + if key in self.__services[key]: + del self.__services[key] + # TODO: Delete config from CM + results.append(True) + else: + results.append(False) + return results diff --git a/src/device/service/drivers/xr/__init__.py b/src/device/service/drivers/xr/__init__.py index 329bbc306..6c466ff98 100644 --- a/src/device/service/drivers/xr/__init__.py +++ b/src/device/service/drivers/xr/__init__.py @@ -15,13 +15,13 @@ from device.service.driver_api._Driver import RESOURCE_ENDPOINTS, RESOURCE_INTERFACES, RESOURCE_NETWORK_INSTANCES ALL_RESOURCE_KEYS = [ -# RESOURCE_ENDPOINTS, -# RESOURCE_INTERFACES, + RESOURCE_ENDPOINTS, + RESOURCE_INTERFACES, # RESOURCE_NETWORK_INSTANCES, ] RESOURCE_KEY_MAPPINGS = { -# RESOURCE_ENDPOINTS : 'component', -# RESOURCE_INTERFACES : 'interface', + RESOURCE_ENDPOINTS : 'component', + RESOURCE_INTERFACES : 'interface', # RESOURCE_NETWORK_INSTANCES: 'network_instance', } diff --git a/src/policy/src/main/java/eu/teraflow/policy/Serializer.java b/src/policy/src/main/java/eu/teraflow/policy/Serializer.java index 9121f1ba9..ede45d0c2 100644 --- a/src/policy/src/main/java/eu/teraflow/policy/Serializer.java +++ b/src/policy/src/main/java/eu/teraflow/policy/Serializer.java @@ -2080,6 +2080,8 @@ public class Serializer { return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY; case ONF_TR_352: return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352; + case XR: + return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_XR; case UNDEFINED: default: return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED; diff --git a/src/service/service/service_handlers/__init__.py b/src/service/service/service_handlers/__init__.py index 6abe4048f..07a653a4f 100644 --- a/src/service/service/service_handlers/__init__.py +++ b/src/service/service/service_handlers/__init__.py @@ -33,7 +33,7 @@ SERVICE_HANDLERS = [ (TapiServiceHandler, [ { FilterFieldEnum.SERVICE_TYPE : ORM_ServiceTypeEnum.TAPI_CONNECTIVITY_SERVICE, - FilterFieldEnum.DEVICE_DRIVER : ORM_DeviceDriverEnum.TRANSPORT_API, + FilterFieldEnum.DEVICE_DRIVER : [ORM_DeviceDriverEnum.TRANSPORT_API, ORM_DeviceDriverEnum.XR], } ]), -] +] \ No newline at end of file diff --git a/src/tests/ofc22/descriptors_emulated_xr.json b/src/tests/ofc22/descriptors_emulated_xr.json index 1f0474e86..a3fc07bcf 100644 --- a/src/tests/ofc22/descriptors_emulated_xr.json +++ b/src/tests/ofc22/descriptors_emulated_xr.json @@ -62,57 +62,46 @@ "device_drivers": [0], "device_endpoints": [] }, - { - "device_id": {"device_uuid": {"uuid": "O1-OLS"}}, - "device_type": "emu-optical-line-system", - "device_config": {"config_rules": [ - {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, - {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, - {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": "{\"endpoints\": [{\"sample_types\": [], \"type\": \"optical\", \"uuid\": \"aade6001-f00b-5e2f-a357-6a0a9d3de870\"}, {\"sample_types\": [], \"type\": \"optical\", \"uuid\": \"eb287d83-f05e-53ec-ab5a-adf6bd2b5418\"}, {\"sample_types\": [], \"type\": \"optical\", \"uuid\": \"0ef74f99-1acc-57bd-ab9d-4b958b06c513\"}, {\"sample_types\": [], \"type\": \"optical\", \"uuid\": \"50296d99-58cc-5ce7-82f5-fc8ee4eec2ec\"}]}"}} - ]}, - "device_operational_status": 1, - "device_drivers": [0], - "device_endpoints": [] - }, { "device_id": {"device_uuid": {"uuid": "X1-XR-CONSTELLATION"}}, "device_type": "xr-constellation", "device_config": {"config_rules": [ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "172.19.219.44"}}, - {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "443"}} + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "443"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": "{\"endpoints\": [{\"sample_types\": [], \"type\": \"optical\", \"uuid\": \"HUB-LANE-01\"}, {\"sample_types\": [], \"type\": \"optical\", \"uuid\": \"LEAF-01-LANE-01\"}]}"}} ]}, "device_operational_status": 1, - "device_drivers": [0], + "device_drivers": [6], "device_endpoints": [] } ], "links": [ { - "link_id": {"link_uuid": {"uuid": "R1-EMU/13/0/0==O1-OLS/aade6001-f00b-5e2f-a357-6a0a9d3de870"}}, + "link_id": {"link_uuid": {"uuid": "R1-EMU/13/0/0==XR1-HUB-LANE-01"}}, "link_endpoint_ids": [ {"device_id": {"device_uuid": {"uuid": "R1-EMU"}}, "endpoint_uuid": {"uuid": "13/0/0"}}, - {"device_id": {"device_uuid": {"uuid": "O1-OLS"}}, "endpoint_uuid": {"uuid": "aade6001-f00b-5e2f-a357-6a0a9d3de870"}} + {"device_id": {"device_uuid": {"uuid": "X1-XR-CONSTELLATION"}}, "endpoint_uuid": {"uuid": "HUB-LANE-01"}} ] }, { - "link_id": {"link_uuid": {"uuid": "R2-EMU/13/0/0==O1-OLS/eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}}, + "link_id": {"link_uuid": {"uuid": "R2-EMU/13/0/0==XR1-LEAF-01-LANE-01"}}, "link_endpoint_ids": [ {"device_id": {"device_uuid": {"uuid": "R2-EMU"}}, "endpoint_uuid": {"uuid": "13/0/0"}}, - {"device_id": {"device_uuid": {"uuid": "O1-OLS"}}, "endpoint_uuid": {"uuid": "eb287d83-f05e-53ec-ab5a-adf6bd2b5418"}} + {"device_id": {"device_uuid": {"uuid": "X1-XR-CONSTELLATION"}}, "endpoint_uuid": {"uuid": "LEAF-01-LANE-01"}} ] }, { - "link_id": {"link_uuid": {"uuid": "R3-EMU/13/0/0==O1-OLS/0ef74f99-1acc-57bd-ab9d-4b958b06c513"}}, + "link_id": {"link_uuid": {"uuid": "R3-EMU/13/0/0==XR1-LEAF-02-LANE-01"}}, "link_endpoint_ids": [ {"device_id": {"device_uuid": {"uuid": "R3-EMU"}}, "endpoint_uuid": {"uuid": "13/0/0"}}, - {"device_id": {"device_uuid": {"uuid": "O1-OLS"}}, "endpoint_uuid": {"uuid": "0ef74f99-1acc-57bd-ab9d-4b958b06c513"}} + {"device_id": {"device_uuid": {"uuid": "X1-XR-CONSTELLATION"}}, "endpoint_uuid": {"uuid": "LEAF-02-LANE-01"}} ] }, { - "link_id": {"link_uuid": {"uuid": "R4-EMU/13/0/0==O1-OLS/50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}}, + "link_id": {"link_uuid": {"uuid": "R4-EMU/13/0/0==XR1-LEAF-03-LANE-01"}}, "link_endpoint_ids": [ {"device_id": {"device_uuid": {"uuid": "R4-EMU"}}, "endpoint_uuid": {"uuid": "13/0/0"}}, - {"device_id": {"device_uuid": {"uuid": "O1-OLS"}}, "endpoint_uuid": {"uuid": "50296d99-58cc-5ce7-82f5-fc8ee4eec2ec"}} + {"device_id": {"device_uuid": {"uuid": "X1-XR-CONSTELLATION"}}, "endpoint_uuid": {"uuid": "LEAF-03-LANE-01"}} ] } ] diff --git a/src/tests/ofc22/setup_test_env.sh b/src/tests/ofc22/setup_test_env.sh new file mode 100755 index 000000000..1f8b0a5a7 --- /dev/null +++ b/src/tests/ofc22/setup_test_env.sh @@ -0,0 +1,9 @@ +#!/bin/sh +export CONTEXTSERVICE_SERVICE_HOST=$(kubectl get service/contextservice --namespace tfs --template '{{.spec.clusterIP}}') +export CONTEXTSERVICE_SERVICE_PORT_GRPC=$(kubectl get service/contextservice --namespace tfs -o jsonpath='{.spec.ports[?(@.name=="grpc")].port}') +export COMPUTESERVICE_SERVICE_HOST=$(kubectl get service/computeservice --namespace tfs --template '{{.spec.clusterIP}}') +export COMPUTESERVICE_SERVICE_PORT_HTTP=$(kubectl get service/computeservice --namespace tfs -o jsonpath='{.spec.ports[?(@.name=="http")].port}') +echo "CONTEXTSERVICE_SERVICE_HOST=$CONTEXTSERVICE_SERVICE_HOST" +echo "CONTEXTSERVICE_SERVICE_PORT_GRPC=$CONTEXTSERVICE_SERVICE_PORT_GRPC" +echo "COMPUTESERVICE_SERVICE_HOST=$COMPUTESERVICE_SERVICE_HOST" +echo "COMPUTESERVICE_SERVICE_PORT_HTTP=$COMPUTESERVICE_SERVICE_PORT_HTTP" diff --git a/src/tests/ofc22/tests/ObjectsXr.py b/src/tests/ofc22/tests/ObjectsXr.py new file mode 100644 index 000000000..12d2b48ee --- /dev/null +++ b/src/tests/ofc22/tests/ObjectsXr.py @@ -0,0 +1,232 @@ +# 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. + +from typing import Dict, List, Tuple +from common.Constants import DEFAULT_CONTEXT_UUID, DEFAULT_TOPOLOGY_UUID +from common.tools.object_factory.Context import json_context, json_context_id +from common.tools.object_factory.Device import ( + json_device_connect_rules, json_device_emulated_connect_rules, json_device_emulated_packet_router_disabled, + json_device_emulated_tapi_disabled, json_device_id, json_device_packetrouter_disabled, json_device_tapi_disabled) +from common.tools.object_factory.EndPoint import json_endpoint, json_endpoint_id +from common.tools.object_factory.Link import json_link, json_link_id +from common.tools.object_factory.Topology import json_topology, json_topology_id +from context.proto.kpi_sample_types_pb2 import KpiSampleType + +# ----- Context -------------------------------------------------------------------------------------------------------- +CONTEXT_ID = json_context_id(DEFAULT_CONTEXT_UUID) +CONTEXT = json_context(DEFAULT_CONTEXT_UUID) + +# ----- Topology ------------------------------------------------------------------------------------------------------- +TOPOLOGY_ID = json_topology_id(DEFAULT_TOPOLOGY_UUID, context_id=CONTEXT_ID) +TOPOLOGY = json_topology(DEFAULT_TOPOLOGY_UUID, context_id=CONTEXT_ID) + +# ----- Monitoring Samples --------------------------------------------------------------------------------------------- +PACKET_PORT_SAMPLE_TYPES = [ + KpiSampleType.KPISAMPLETYPE_PACKETS_TRANSMITTED, + KpiSampleType.KPISAMPLETYPE_PACKETS_RECEIVED, + KpiSampleType.KPISAMPLETYPE_BYTES_TRANSMITTED, + KpiSampleType.KPISAMPLETYPE_BYTES_RECEIVED, +] + +# ----- Device Credentials and Settings -------------------------------------------------------------------------------- +try: + from .Credentials import DEVICE_R1_ADDRESS, DEVICE_R1_PORT, DEVICE_R1_USERNAME, DEVICE_R1_PASSWORD + from .Credentials import DEVICE_R3_ADDRESS, DEVICE_R3_PORT, DEVICE_R3_USERNAME, DEVICE_R3_PASSWORD + #from .Credentials import DEVICE_O1_ADDRESS, DEVICE_O1_PORT + USE_REAL_DEVICES = True # Use real devices +except ImportError: + USE_REAL_DEVICES = False # Use emulated devices + + DEVICE_R1_ADDRESS = '0.0.0.0' + DEVICE_R1_PORT = 830 + DEVICE_R1_USERNAME = 'admin' + DEVICE_R1_PASSWORD = 'admin' + + DEVICE_R3_ADDRESS = '0.0.0.0' + DEVICE_R3_PORT = 830 + DEVICE_R3_USERNAME = 'admin' + DEVICE_R3_PASSWORD = 'admin' + +DEVICE_X1_ADDRESS = '172.19.219.44' +DEVICE_X1_PORT = 443 + +#USE_REAL_DEVICES = False # Uncomment to force to use emulated devices + +def json_endpoint_ids(device_id : Dict, endpoint_descriptors : List[Tuple[str, str, List[int]]]): + return [ + json_endpoint_id(device_id, ep_uuid, topology_id=None) + for ep_uuid, _, _ in endpoint_descriptors + ] + +def json_endpoints(device_id : Dict, endpoint_descriptors : List[Tuple[str, str, List[int]]]): + return [ + json_endpoint(device_id, ep_uuid, ep_type, topology_id=None, kpi_sample_types=ep_sample_types) + for ep_uuid, ep_type, ep_sample_types in endpoint_descriptors + ] + +def get_link_uuid(a_device_id : Dict, a_endpoint_id : Dict, z_device_id : Dict, z_endpoint_id : Dict) -> str: + return '{:s}/{:s}=={:s}/{:s}'.format( + a_device_id['device_uuid']['uuid'], a_endpoint_id['endpoint_uuid']['uuid'], + z_device_id['device_uuid']['uuid'], z_endpoint_id['endpoint_uuid']['uuid']) + + +# ----- Devices -------------------------------------------------------------------------------------------------------- +if not USE_REAL_DEVICES: + json_device_packetrouter_disabled = json_device_emulated_packet_router_disabled + json_device_tapi_disabled = json_device_emulated_tapi_disabled + +DEVICE_R1_UUID = 'R1-EMU' +DEVICE_R1_TIMEOUT = 120 +DEVICE_R1_ENDPOINT_DEFS = [('13/0/0', 'optical', []), ('13/1/2', 'copper', PACKET_PORT_SAMPLE_TYPES)] +DEVICE_R1_ID = json_device_id(DEVICE_R1_UUID) +#DEVICE_R1_ENDPOINTS = json_endpoints(DEVICE_R1_ID, DEVICE_R1_ENDPOINT_DEFS) +DEVICE_R1_ENDPOINT_IDS = json_endpoint_ids(DEVICE_R1_ID, DEVICE_R1_ENDPOINT_DEFS) +DEVICE_R1 = json_device_packetrouter_disabled(DEVICE_R1_UUID) +ENDPOINT_ID_R1_13_0_0 = DEVICE_R1_ENDPOINT_IDS[0] +ENDPOINT_ID_R1_13_1_2 = DEVICE_R1_ENDPOINT_IDS[1] +DEVICE_R1_CONNECT_RULES = json_device_connect_rules(DEVICE_R1_ADDRESS, DEVICE_R1_PORT, { + 'username': DEVICE_R1_USERNAME, + 'password': DEVICE_R1_PASSWORD, + 'timeout' : DEVICE_R1_TIMEOUT, +}) if USE_REAL_DEVICES else json_device_emulated_connect_rules(DEVICE_R1_ENDPOINT_DEFS) + + +DEVICE_R2_UUID = 'R2-EMU' +DEVICE_R2_ENDPOINT_DEFS = [('13/0/0', 'optical', []), ('13/1/2', 'copper', PACKET_PORT_SAMPLE_TYPES)] +DEVICE_R2_ID = json_device_id(DEVICE_R2_UUID) +#DEVICE_R2_ENDPOINTS = json_endpoints(DEVICE_R2_ID, DEVICE_R2_ENDPOINT_DEFS) +DEVICE_R2_ENDPOINT_IDS = json_endpoint_ids(DEVICE_R2_ID, DEVICE_R2_ENDPOINT_DEFS) +DEVICE_R2 = json_device_emulated_packet_router_disabled(DEVICE_R2_UUID) +ENDPOINT_ID_R2_13_0_0 = DEVICE_R2_ENDPOINT_IDS[0] +ENDPOINT_ID_R2_13_1_2 = DEVICE_R2_ENDPOINT_IDS[1] +DEVICE_R2_CONNECT_RULES = json_device_emulated_connect_rules(DEVICE_R2_ENDPOINT_DEFS) + + +DEVICE_R3_UUID = 'R3-EMU' +DEVICE_R3_TIMEOUT = 120 +DEVICE_R3_ENDPOINT_DEFS = [('13/0/0', 'optical', []), ('13/1/2', 'copper', PACKET_PORT_SAMPLE_TYPES)] +DEVICE_R3_ID = json_device_id(DEVICE_R3_UUID) +#DEVICE_R3_ENDPOINTS = json_endpoints(DEVICE_R3_ID, DEVICE_R3_ENDPOINT_DEFS) +DEVICE_R3_ENDPOINT_IDS = json_endpoint_ids(DEVICE_R3_ID, DEVICE_R3_ENDPOINT_DEFS) +DEVICE_R3 = json_device_packetrouter_disabled(DEVICE_R3_UUID) +ENDPOINT_ID_R3_13_0_0 = DEVICE_R3_ENDPOINT_IDS[0] +ENDPOINT_ID_R3_13_1_2 = DEVICE_R3_ENDPOINT_IDS[1] +DEVICE_R3_CONNECT_RULES = json_device_connect_rules(DEVICE_R3_ADDRESS, DEVICE_R3_PORT, { + 'username': DEVICE_R3_USERNAME, + 'password': DEVICE_R3_PASSWORD, + 'timeout' : DEVICE_R3_TIMEOUT, +}) if USE_REAL_DEVICES else json_device_emulated_connect_rules(DEVICE_R3_ENDPOINT_DEFS) + + +DEVICE_R4_UUID = 'R4-EMU' +DEVICE_R4_ENDPOINT_DEFS = [('13/0/0', 'optical', []), ('13/1/2', 'copper', PACKET_PORT_SAMPLE_TYPES)] +DEVICE_R4_ID = json_device_id(DEVICE_R4_UUID) +#DEVICE_R4_ENDPOINTS = json_endpoints(DEVICE_R4_ID, DEVICE_R4_ENDPOINT_DEFS) +DEVICE_R4_ENDPOINT_IDS = json_endpoint_ids(DEVICE_R4_ID, DEVICE_R4_ENDPOINT_DEFS) +DEVICE_R4 = json_device_emulated_packet_router_disabled(DEVICE_R4_UUID) +ENDPOINT_ID_R4_13_0_0 = DEVICE_R4_ENDPOINT_IDS[0] +ENDPOINT_ID_R4_13_1_2 = DEVICE_R4_ENDPOINT_IDS[1] +DEVICE_R4_CONNECT_RULES = json_device_emulated_connect_rules(DEVICE_R4_ENDPOINT_DEFS) + + +DEVICE_X1_UUID = 'X1-XR-CONSTELLATION' +DEVICE_X1_TIMEOUT = 120 +DEVICE_X1_ENDPOINT_DEFS = [ + ('HUB-LANE-01', 'optical', []), + ('LEAF-01-LANE-01', 'optical', []), + ('LEAF-02-LANE-01', 'optical', []), + ('LEAF-03-LANE-01', 'optical', []), +] +DEVICE_X1_ID = json_device_id(DEVICE_X1_UUID) +DEVICE_X1 = json_device_tapi_disabled(DEVICE_X1_UUID) +DEVICE_X1_ENDPOINT_IDS = json_endpoint_ids(DEVICE_X1_ID, DEVICE_X1_ENDPOINT_DEFS) +ENDPOINT_ID_X1_EP1 = DEVICE_X1_ENDPOINT_IDS[0] +ENDPOINT_ID_X1_EP2 = DEVICE_X1_ENDPOINT_IDS[1] +ENDPOINT_ID_X1_EP3 = DEVICE_X1_ENDPOINT_IDS[2] +ENDPOINT_ID_X1_EP4 = DEVICE_X1_ENDPOINT_IDS[3] +DEVICE_X1_CONNECT_RULES = json_device_connect_rules(DEVICE_X1_ADDRESS, DEVICE_X1_PORT, { + 'timeout' : DEVICE_X1_TIMEOUT, +}) +# Always using real device (CM, whether CM has emulated backend is another story) +#if USE_REAL_DEVICES else json_device_emulated_connect_rules(DEVICE_X1_ENDPOINT_DEFS) + + +# ----- Links ---------------------------------------------------------------------------------------------------------- +LINK_R1_X1_UUID = get_link_uuid(DEVICE_R1_ID, ENDPOINT_ID_R1_13_0_0, DEVICE_X1_ID, ENDPOINT_ID_X1_EP1) +LINK_R1_X1_ID = json_link_id(LINK_R1_X1_UUID) +LINK_R1_X1 = json_link(LINK_R1_X1_UUID, [ENDPOINT_ID_R1_13_0_0, ENDPOINT_ID_X1_EP1]) + +LINK_R2_X1_UUID = get_link_uuid(DEVICE_R2_ID, ENDPOINT_ID_R2_13_0_0, DEVICE_X1_ID, ENDPOINT_ID_X1_EP2) +LINK_R2_X1_ID = json_link_id(LINK_R2_X1_UUID) +LINK_R2_X1 = json_link(LINK_R2_X1_UUID, [ENDPOINT_ID_R2_13_0_0, ENDPOINT_ID_X1_EP2]) + +LINK_R3_X1_UUID = get_link_uuid(DEVICE_R3_ID, ENDPOINT_ID_R3_13_0_0, DEVICE_X1_ID, ENDPOINT_ID_X1_EP3) +LINK_R3_X1_ID = json_link_id(LINK_R3_X1_UUID) +LINK_R3_X1 = json_link(LINK_R3_X1_UUID, [ENDPOINT_ID_R3_13_0_0, ENDPOINT_ID_X1_EP3]) + +LINK_R4_X1_UUID = get_link_uuid(DEVICE_R4_ID, ENDPOINT_ID_R4_13_0_0, DEVICE_X1_ID, ENDPOINT_ID_X1_EP4) +LINK_R4_X1_ID = json_link_id(LINK_R4_X1_UUID) +LINK_R4_X1 = json_link(LINK_R4_X1_UUID, [ENDPOINT_ID_R4_13_0_0, ENDPOINT_ID_X1_EP4]) + + +# ----- WIM Service Settings ------------------------------------------------------------------------------------------- + +def compose_service_endpoint_id(endpoint_id): + device_uuid = endpoint_id['device_id']['device_uuid']['uuid'] + endpoint_uuid = endpoint_id['endpoint_uuid']['uuid'] + return ':'.join([device_uuid, endpoint_uuid]) + +WIM_SEP_R1_ID = compose_service_endpoint_id(ENDPOINT_ID_R1_13_1_2) +WIM_SEP_R1_SITE_ID = '1' +WIM_SEP_R1_BEARER = WIM_SEP_R1_ID +WIM_SRV_R1_VLAN_ID = 400 + +WIM_SEP_R3_ID = compose_service_endpoint_id(ENDPOINT_ID_R3_13_1_2) +WIM_SEP_R3_SITE_ID = '2' +WIM_SEP_R3_BEARER = WIM_SEP_R3_ID +WIM_SRV_R3_VLAN_ID = 500 + +WIM_USERNAME = 'admin' +WIM_PASSWORD = 'admin' + +WIM_MAPPING = [ + {'device-id': DEVICE_R1_UUID, 'service_endpoint_id': WIM_SEP_R1_ID, + 'service_mapping_info': {'bearer': {'bearer-reference': WIM_SEP_R1_BEARER}, 'site-id': WIM_SEP_R1_SITE_ID}}, + {'device-id': DEVICE_R3_UUID, 'service_endpoint_id': WIM_SEP_R3_ID, + 'service_mapping_info': {'bearer': {'bearer-reference': WIM_SEP_R3_BEARER}, 'site-id': WIM_SEP_R3_SITE_ID}}, +] +WIM_SERVICE_TYPE = 'ELINE' +WIM_SERVICE_CONNECTION_POINTS = [ + {'service_endpoint_id': WIM_SEP_R1_ID, + 'service_endpoint_encapsulation_type': 'dot1q', + 'service_endpoint_encapsulation_info': {'vlan': WIM_SRV_R1_VLAN_ID}}, + {'service_endpoint_id': WIM_SEP_R3_ID, + 'service_endpoint_encapsulation_type': 'dot1q', + 'service_endpoint_encapsulation_info': {'vlan': WIM_SRV_R3_VLAN_ID}}, +] + +# ----- Object Collections --------------------------------------------------------------------------------------------- + +CONTEXTS = [CONTEXT] +TOPOLOGIES = [TOPOLOGY] + +DEVICES = [ + (DEVICE_R1, DEVICE_R1_CONNECT_RULES), + (DEVICE_R2, DEVICE_R2_CONNECT_RULES), + (DEVICE_R3, DEVICE_R3_CONNECT_RULES), + (DEVICE_R4, DEVICE_R4_CONNECT_RULES), + (DEVICE_X1, DEVICE_X1_CONNECT_RULES), +] + +LINKS = [LINK_R1_X1, LINK_R2_X1, LINK_R3_X1, LINK_R4_X1] diff --git a/src/tests/ofc22/tests/test_functional_create_service_xr.py b/src/tests/ofc22/tests/test_functional_create_service_xr.py new file mode 100644 index 000000000..882950cd5 --- /dev/null +++ b/src/tests/ofc22/tests/test_functional_create_service_xr.py @@ -0,0 +1,130 @@ +# 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 logging, pytest +from common.DeviceTypes import DeviceTypeEnum +from common.Settings import get_setting +from common.tests.EventTools import EVENT_CREATE, EVENT_UPDATE, check_events +from common.tools.object_factory.Connection import json_connection_id +from common.tools.object_factory.Device import json_device_id +from common.tools.object_factory.Service import json_service_id +from common.tools.grpc.Tools import grpc_message_to_json_string +from compute.tests.mock_osm.MockOSM import MockOSM +from context.client.ContextClient import ContextClient +from context.client.EventsCollector import EventsCollector +from context.proto.context_pb2 import ContextId, Empty +from .ObjectsXr import ( + CONTEXT_ID, CONTEXTS, DEVICE_X1_UUID, DEVICE_R1_UUID, DEVICE_R3_UUID, DEVICES, LINKS, TOPOLOGIES, + WIM_MAPPING, WIM_PASSWORD, WIM_SERVICE_CONNECTION_POINTS, WIM_SERVICE_TYPE, WIM_USERNAME) + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +DEVTYPE_EMU_PR = DeviceTypeEnum.EMULATED_PACKET_ROUTER.value +#DEVTYPE_EMU_OLS = DeviceTypeEnum.EMULATED_OPTICAL_LINE_SYSTEM.value +DEVTYPE_EMU_OLS = DeviceTypeEnum.XR_CONSTELLATION.value + + +@pytest.fixture(scope='session') +def context_client(): + _client = ContextClient(get_setting('CONTEXTSERVICE_SERVICE_HOST'), get_setting('CONTEXTSERVICE_SERVICE_PORT_GRPC')) + yield _client + _client.close() + + +@pytest.fixture(scope='session') +def osm_wim(): + wim_url = 'http://{:s}:{:s}'.format( + get_setting('COMPUTESERVICE_SERVICE_HOST'), str(get_setting('COMPUTESERVICE_SERVICE_PORT_HTTP'))) + return MockOSM(wim_url, WIM_MAPPING, WIM_USERNAME, WIM_PASSWORD) + + +def test_scenario_is_correct(context_client : ContextClient): # pylint: disable=redefined-outer-name + # ----- List entities - Ensure links are created ------------------------------------------------------------------- + response = context_client.ListContexts(Empty()) + assert len(response.contexts) == len(CONTEXTS) + + response = context_client.ListTopologies(ContextId(**CONTEXT_ID)) + assert len(response.topologies) == len(TOPOLOGIES) + + response = context_client.ListDevices(Empty()) + assert len(response.devices) == len(DEVICES) + + response = context_client.ListLinks(Empty()) + assert len(response.links) == len(LINKS) + + response = context_client.ListServices(ContextId(**CONTEXT_ID)) + assert len(response.services) == 0 + + +def test_service_creation(context_client : ContextClient, osm_wim : MockOSM): # pylint: disable=redefined-outer-name + # ----- Start the EventsCollector ---------------------------------------------------------------------------------- + events_collector = EventsCollector(context_client, log_events_received=True) + events_collector.start() + + # ----- Create Service --------------------------------------------------------------------------------------------- + service_uuid = osm_wim.create_connectivity_service(WIM_SERVICE_TYPE, WIM_SERVICE_CONNECTION_POINTS) + osm_wim.get_connectivity_service_status(service_uuid) + + # ----- Validate collected events ---------------------------------------------------------------------------------- + + packet_connection_uuid = '{:s}:{:s}'.format(service_uuid, DEVTYPE_EMU_PR) + optical_connection_uuid = '{:s}:optical:{:s}'.format(service_uuid, DEVTYPE_EMU_OLS) + optical_service_uuid = '{:s}:optical'.format(service_uuid) + + expected_events = [ + # Create packet service and add first endpoint + ('ServiceEvent', EVENT_CREATE, json_service_id(service_uuid, context_id=CONTEXT_ID)), + ('ServiceEvent', EVENT_UPDATE, json_service_id(service_uuid, context_id=CONTEXT_ID)), + + # Configure OLS controller, create optical service, create optical connection + ('DeviceEvent', EVENT_UPDATE, json_device_id(DEVICE_X1_UUID)), + ('ServiceEvent', EVENT_CREATE, json_service_id(optical_service_uuid, context_id=CONTEXT_ID)), + ('ConnectionEvent', EVENT_CREATE, json_connection_id(optical_connection_uuid)), + + # Configure endpoint packet devices, add second endpoint to service, create connection + ('DeviceEvent', EVENT_UPDATE, json_device_id(DEVICE_R1_UUID)), + ('DeviceEvent', EVENT_UPDATE, json_device_id(DEVICE_R3_UUID)), + ('ServiceEvent', EVENT_UPDATE, json_service_id(service_uuid, context_id=CONTEXT_ID)), + ('ConnectionEvent', EVENT_CREATE, json_connection_id(packet_connection_uuid)), + ] + check_events(events_collector, expected_events) + + # ----- Stop the EventsCollector ----------------------------------------------------------------------------------- + events_collector.stop() + + +def test_scenario_service_created(context_client : ContextClient): # pylint: disable=redefined-outer-name + # ----- List entities - Ensure service is created ------------------------------------------------------------------ + response = context_client.ListContexts(Empty()) + assert len(response.contexts) == len(CONTEXTS) + + response = context_client.ListTopologies(ContextId(**CONTEXT_ID)) + assert len(response.topologies) == len(TOPOLOGIES) + + response = context_client.ListDevices(Empty()) + assert len(response.devices) == len(DEVICES) + + response = context_client.ListLinks(Empty()) + assert len(response.links) == len(LINKS) + + response = context_client.ListServices(ContextId(**CONTEXT_ID)) + LOGGER.info('Services[{:d}] = {:s}'.format(len(response.services), grpc_message_to_json_string(response))) + assert len(response.services) == 2 # L3NM + TAPI + for service in response.services: + service_id = service.service_id + response = context_client.ListConnections(service_id) + LOGGER.info(' ServiceId[{:s}] => Connections[{:d}] = {:s}'.format( + grpc_message_to_json_string(service_id), len(response.connections), grpc_message_to_json_string(response))) + assert len(response.connections) == 1 # one connection per service diff --git a/src/webui/service/static/topology_icons/xr-constellation.png b/src/webui/service/static/topology_icons/xr-constellation.png new file mode 100644 index 0000000000000000000000000000000000000000..518ca5a60b1d6b9c674783873189566430adccf9 GIT binary patch literal 37336 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°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 Z|39ORx&-~ZicJ6j002ovPDHLkV1nd;djS9d literal 0 HcmV?d00001 -- GitLab From 6c9acafc46a2fcfcbe3a4c0527cd846845c545c6 Mon Sep 17 00:00:00 2001 From: Ville Petteri Hallivuori Date: Thu, 11 Aug 2022 21:52:38 -0700 Subject: [PATCH 03/10] Pull request #3: vhallivu/teraflow cm support Merge in XRCA/teraflow from vhallivu/update_cm_authentication to xr_development Squashed commit of the following: commit 6a7069ded9b899afe4c9a218f1062da5c69782e4 Author: Ville Hallivuori Date: Fri Aug 12 07:51:32 2022 +0300 review + lint fixes commit f02d6f4ce42f909067a2570daff136e1ed0c7bbd Author: Ville Hallivuori Date: Thu Aug 11 14:39:12 2022 +0300 XR connection delete support commit 81c3aae5ba105f6650f5392f438200c4b505eea1 Author: Ville Hallivuori Date: Thu Aug 11 12:39:29 2022 +0300 Create services to CM commit 56b3fb3acfc3281b3b3136269163697ca679913c Author: Ville Hallivuori Date: Thu Aug 11 11:17:28 2022 +0300 Improved connection management code, code refactoring commit 46ef072679ffd37c22c4bf959bb8ba009cf4a082 Author: Ville Hallivuori Date: Wed Aug 10 17:30:27 2022 +0300 More support for connection management commit 49a2762abd7c1c8adafd666624b31976281d3860 Author: Ville Hallivuori Date: Wed Aug 10 15:52:17 2022 +0300 Refactored CM connection, now using CM if database commit 1b404f2c10befeecb48a7a2278f5e9ec60c25d9a Author: Ville Hallivuori Date: Wed Aug 10 14:14:03 2022 +0300 Start of refactoring of CM connectivity commit 8ec8251bafbba559a4881e50be0aa068340fe536 Author: Ville Hallivuori Date: Tue Aug 9 16:37:49 2022 +0300 Improved CM authentication --- README_INFINERA.md | 25 ++ src/device/service/drivers/xr/CmConnection.py | 379 ++++++++++++++++++ src/device/service/drivers/xr/XrDriver.py | 116 +++--- src/device/service/drivers/xr/cm-cli.py | 79 ++++ src/tests/ofc22/descriptors_emulated_xr.json | 18 +- src/tests/ofc22/tests/ObjectsXr.py | 22 +- .../test_functional_create_service_xr.py | 5 +- .../test_functional_delete_service_xr.py | 133 ++++++ 8 files changed, 696 insertions(+), 81 deletions(-) create mode 100644 src/device/service/drivers/xr/CmConnection.py create mode 100755 src/device/service/drivers/xr/cm-cli.py create mode 100644 src/tests/ofc22/tests/test_functional_delete_service_xr.py diff --git a/README_INFINERA.md b/README_INFINERA.md index 4d3c1a3f0..339c516ae 100644 --- a/README_INFINERA.md +++ b/README_INFINERA.md @@ -57,6 +57,15 @@ SOURCE VENV ACTIVATE ON ANY SHELL USED FOR PYTHON RELATED WORK (e.g. pytest). Use apt-get to install any missing tools (e.g. jq is required). +For host based Python development (e.g. VS Code) and test script execution, generate protobuf stubs: + +```bash +cd proto +./generate_code_python.sh +cd ../src/context +ln -s ../../proto/src/python proto +``` + ## Building Run deploy script to build in docker containers and then instantiate to configured K8s cluster. Deploy script must be sources for this to work! @@ -80,3 +89,19 @@ Good logs to check are: * kubectl logs service/deviceservice --namespace tfs * kubectl logs service/webuiservice --namespace tfs + +## cm-cli + +The tool cm-cli in the xr driver directory can be use to connect to CM and test the connectivity. For example: + +```bash +./cm-cli.py 172.19.219.44 443 xr-user-1 xr-user-1 --show-constellation-by-hub-name="XR HUB 1" +./cm-cli.py 172.19.219.44 443 xr-user-1 xr-user-1 --list-constellations +./cm-cli.py 172.19.219.44 443 xr-user-1 xr-user-1 --create-connection="FOO;XR HUB 1|XR-T4;XR LEAF 1|XR-T1" +./cm-cli.py 172.19.219.44 443 xr-user-1 xr-user-1 --show-connection-by-name="FooBar123" +./cm-cli.py 172.19.219.44 443 xr-user-1 xr-user-1 --list-connections +# Modify argumens: href;uuid;ifname;ifname or href;uuid +# uuid translates to name TF:uuid +./cm-cli.py 172.19.219.44 443 xr-user-1 xr-user-1 --modify-connection="/network-connections/138f0cc0-3dc6-4195-97c0-2cbed5fd59ba;FooBarAaa" +./cm-cli.py 172.19.219.44 443 xr-user-1 xr-user-1 --delete-connection=/network-connections/138f0cc0-3dc6-4195-97c0-2cbed5fd59ba +``` diff --git a/src/device/service/drivers/xr/CmConnection.py b/src/device/service/drivers/xr/CmConnection.py new file mode 100644 index 000000000..490597860 --- /dev/null +++ b/src/device/service/drivers/xr/CmConnection.py @@ -0,0 +1,379 @@ +#pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +import logging +import json +import time +from typing import Tuple, Optional #Any, Iterator, List, , Union +import requests +import urllib3 +import re + +LOGGER = logging.getLogger(__name__) + +class InvalidIfnameError(Exception): + def __init__(self, ifname): + # Call the base class constructor with the parameters it needs + super().__init__(f"Invalid interface name {ifname}, expecting format \"MODULENAME|PORTNAME\"") + +class ConnectionDeserializationError(Exception): + def __init__(self, msg): + # Call the base class constructor with the parameters it needs + super().__init__(msg) + +def ifname_to_module_and_aid(ifname: str) -> Tuple[str, str]: + a = ifname.split("|") + if len(a) != 2: + raise InvalidIfnameError(ifname) + return (a[0], a[1]) + +class Connection: + def __init__(self, from_json=None): + def get_endpoint_ifname(endpoint): + try: + return endpoint["state"]["moduleIf"]["moduleName"] + "|" + endpoint["state"]["moduleIf"]["clientIfAid"] + except KeyError: + return None + + if from_json: + try: + state = from_json["state"] + self.name = state["name"] if "name" in state else None #Name is optional + self.serviceMode = state["serviceMode"] + self.href = from_json["href"] + + self.endpoints = [] + for ep in from_json["endpoints"]: + ifname = get_endpoint_ifname(ep) + if ifname: + self.endpoints.append(ifname) + except KeyError as e: + raise ConnectionDeserializationError(f"Missing mandatory key, f{str(e)}") + else: + # May support other initializations in future + raise ConnectionDeserializationError("JSON dict missing") + + def __str__(self): + name = self.name if self.name else "" + endpoints = ", ".join(self.endpoints) + return f"name: {name}, id: {self.href}, service-mode: {self.serviceMode}, end-points: [{endpoints}]" + +class ExpiringValue: + def __init__(self, value, expiry): + self.__value = value + self.__expiry = expiry + self.__created = time.monotonic() + + def get_value(self): + return self.__value + + def is_valid_for(self, duration): + if self.__created + self.__expiry >= time.monotonic()+duration: + return True + else: + return False + +class CmConnection: + def __init__(self, address: str, port: int, username: str, password: str, timeout=30, tls_verify=True) -> None: + self.__tls_verify = tls_verify + if not tls_verify: + urllib3.disable_warnings() + + self.__timeout = timeout + self.__username = username + self.__password = password + self.__cm_root = 'https://' + address + ':' + str(port) + self.__access_token = None + + def __post_w_headers(self, path, data, headers, data_as_json=True): + url = self.__cm_root + path + try: + if data_as_json: + response = requests.post(url, headers=headers, json=data, timeout=self.__timeout, verify=self.__tls_verify) + else: + response = requests.post(url, headers=headers, data=data, timeout=self.__timeout, verify=self.__tls_verify) + + LOGGER.info(f"POST: {url} ==> {response.status_code}") + resp = json.loads(response.text) + return (response.status_code, resp) + except requests.exceptions.Timeout: + LOGGER.info(f"POST: {url} ==> timeout") + return None + except json.JSONDecodeError as json_err: + LOGGER.info(f"POST: {url} ==> response json decode error: {str(json_err)}") + return None + except Exception as e: # pylint: disable=broad-except + es=str(e) + LOGGER.info(f"POST: {url} ==> unexpected exception: {es}") + return None + + def __post(self, path, data, data_as_json=True): + return self.__post_w_headers(path, data, self.__http_headers(), data_as_json=data_as_json) + + def __put(self, path, data, data_as_json=True): + url = self.__cm_root + path + headers = self.__http_headers() + try: + if data_as_json: + response = requests.put(url, headers=headers, json=data, timeout=self.__timeout, verify=self.__tls_verify) + else: + response = requests.put(url, headers=headers, data=data, timeout=self.__timeout, verify=self.__tls_verify) + + LOGGER.info(f"PUT: {url} ==> {response.status_code}") + + if response.content == b'null': + return (response.status_code, None) + resp = json.loads(response.text) + return (response.status_code, resp) + except requests.exceptions.Timeout: + LOGGER.info(f"PUT: {url} ==> timeout") + return None + except json.JSONDecodeError as json_err: + LOGGER.info(f"PUT: {url} ==> response json decode error: {str(json_err)}") + return None + except Exception as e: # pylint: disable=broad-except + es=str(e) + LOGGER.info(f"PUT: {url} ==> unexpected exception: {es}") + return None + + def __delete(self, path, data=None): + url = self.__cm_root + path + headers = self.__http_headers() + try: + response = requests.delete(url, headers=headers, data=data, timeout=self.__timeout, verify=self.__tls_verify) + LOGGER.info(f"DELETE: {url} ==> {response.status_code}") + + if response.content == b'null': + return (response.status_code, None) + resp = json.loads(response.text) + return (response.status_code, resp) + except requests.exceptions.Timeout: + LOGGER.info(f"DELETE: {url} ==> timeout") + return None + except json.JSONDecodeError as json_err: + LOGGER.info(f"DELETE: {url} ==> response json decode error: {str(json_err)}") + return None + except Exception as e: # pylint: disable=broad-except + es=str(e) + LOGGER.info(f"DELETE: {url} ==> unexpected exception: {es}") + return None + + def __http_headers(self): + self.__ensure_valid_access_token() + if self.__access_token: + return {'Authorization': 'Bearer '+ self.__access_token.get_value()} + else: + return {} + + def __get_json(self, path, params=None): + url = self.__cm_root + path + try: + response = requests.get(url,headers=self.__http_headers(), timeout=self.__timeout,verify=self.__tls_verify, params=params) + LOGGER.info(f"GET: {url} {params=} ==> {response.status_code}") + resp = json.loads(response.text) + return (response.status_code, resp) + except requests.exceptions.Timeout: + LOGGER.info(f"GET: {url} {params=} ==> timeout") + return None + except json.JSONDecodeError as json_err: + LOGGER.info(f"GET: {url} {params=} ==> response json decode error: {str(json_err)}") + return None + except Exception as e: # pylint: disable=broad-except + es=str(e) + LOGGER.info(f"GET: {url} {params=} ==> unexpected exception: {es}") + return None + + def __acquire_access_token(self): + path = '/realms/xr-cm/protocol/openid-connect/token' + req = { + "username": self.__username, + "password": self.__password, + "grant_type": "password", + "client_secret": "xr-web-client", + "client_id": "xr-web-client" + } + (status_code, response) = self.__post_w_headers(path, req, None, data_as_json=False) + if 200 != status_code or 'access_token' not in response: + LOGGER.error(f"Authentication failure, status code {status_code}, data {response}") + return False + access_token = response['access_token'] + expires = int(response["expires_in"]) if "expires_in" in response else 0 + LOGGER.info(f"Obtained access token {access_token}, expires in {expires}") + self.__access_token = ExpiringValue(access_token, expires) + return True + + def __ensure_valid_access_token(self): + if not self.__access_token or not self.__access_token.is_valid_for(60): + self.__acquire_access_token() + + def Connect(self) -> bool: + return self.__acquire_access_token() + + @staticmethod + def get_constellation_module_ifnames(module): + ifnames = [] + try: + module_state = module["state"] + module_name = module_state["module"]["moduleName"] + if "endpoints" in module_state: + for endpoint in module_state["endpoints"]: + try: + ifname = endpoint["moduleIf"]["clientIfAid"] + ifnames.append(f"{module_name}|{ifname}") + except KeyError: + pass + except KeyError: + pass + return ifnames + + @staticmethod + def get_constellation_ifnames(constellation): + ifnames = [] + if "hubModule" in constellation: + hub = constellation["hubModule"] + ifnames.extend(CmConnection.get_constellation_module_ifnames(hub)) + + if "leafModules" in constellation: + for leaf in constellation["leafModules"]: + ifnames.extend(CmConnection.get_constellation_module_ifnames(leaf)) + return ifnames + + @staticmethod + def get_ifnames_per_constellation(constellation): + ifnames = [] + try: + ports = CmConnection.get_constellation_ifnames(constellation) + constellation_id = constellation["id"] + for port in ports: + ifnames.append(port) + except KeyError: + return None + + return (constellation_id, ifnames) + + def list_constellations(self): + status_code, constellations = self.__get_json("/api/v1/ns/xr-networks?content=expanded") + if not constellations or status_code != 200: + return [] + return [CmConnection.get_ifnames_per_constellation(c) for c in constellations] + + def get_constellation_by_hub_name(self, hub_module_name: str): + qparams = [ + ('content', 'expanded'), + ('q', '{"hubModule.state.module.moduleName": "' + hub_module_name + '"}') + ] + status_code, constellations = self.__get_json("/api/v1/ns/xr-networks?content=expanded", params=qparams) + if not constellations or status_code != 200 or len(constellations) != 1: + return None + return CmConnection.get_ifnames_per_constellation(constellations[0]) + + @staticmethod + def create_connection_config(uid: str, serviceMode: Optional[str], mod1: Optional[str], aid1: Optional[str], mod2: Optional[str], aid2: Optional[str]) -> Connection: + name = f"TF:{uid}" + def create_endpoint(mod, aid): + ep = { + "selector": { + "ifSelectorByModuleName": { + "moduleName": mod, + "moduleClientIfAid": aid, + } + } + } + return ep + + connection = { "name" : name} + if serviceMode: + connection["serviceMode"] = serviceMode + endpoints = [] + if mod1: + endpoints.append(create_endpoint(mod1, aid1)) + if mod2: + endpoints.append(create_endpoint(mod2, aid2)) + if len(endpoints) > 0: + connection["endpoints"] = endpoints + return connection + + # All arguments are mandatory + def create_connection(self, uid, mod1, aid1, mod2, aid2) -> Optional[str]: + # Create wants a list, so wrap connection to list + connection = [CmConnection.create_connection_config(uid, "portMode", mod1, aid1, mod2, aid2)] + resp = self.__post("/api/v1/ncs/network-connections", connection) + if resp and resp[0] == 202 and len(resp[1]) == 1 and "href" in resp[1][0]: + created_resource = resp[1][0]["href"] + LOGGER.info(f"Created connection {created_resource} {uid=}, {mod1=}, {aid1=}, {mod2=}, {aid2=}") + # FIXME: remove + LOGGER.info(self.__get_json(f"/api/v1/ncs{created_resource}?content=expanded")) + return created_resource + else: + return None + + # Modules and aids are optional. Uid is Teraflow UID, and is stored in mae field + def modify_connection(self, href: str, uid: str, service_mode: Optional[str], mod1: Optional[str]=None, aid1: Optional[str]=None, mod2: Optional[str]=None, aid2: Optional[str]=None) -> Optional[str]: + connection = CmConnection.create_connection_config(uid, service_mode, mod1, aid1, mod2, aid2) + resp = self.__put(f"/api/v1/ncs{href}", connection) + # Returns empty body + if resp and resp[0] == 202: + LOGGER.info(f"Updated connection {href=}, {uid=}, {service_mode=}, {mod1=}, {aid1=}, {mod2=}, {aid2=}") + # Return href used for update to be consisten with create + return href + else: + return None + + def delete_connection(self, href: str) -> bool: + resp = self.__delete(f"/api/v1/ncs{href}") + print(resp) + # Returns empty body + if resp and resp[0] == 202: + LOGGER.info(f"Deleted connection {href=}") + return True + else: + return False + + def create_connection_ifnames(self, uid: str, ifname1: str, ifname2: str): + module1, aid1 = ifname_to_module_and_aid(ifname1) + module2, aid2 = ifname_to_module_and_aid(ifname2) + return self.create_connection(uid, module1, aid1, module2, aid2) + + def modify_connection_ifnames(self, href: str, uid: str, ifname1: Optional[str], ifname2: Optional[str], service_mode: Optional[str] =None): + # Only uid and href are mandatory + module1, aid1 = ifname_to_module_and_aid(ifname1) if ifname1 else (None, None) + module2, aid2 = ifname_to_module_and_aid(ifname2) if ifname2 else (None, None) + return self.modify_connection(href, uid, service_mode, module1, aid1, module2, aid2) + + # Always does the correct thing, that is update if present, otherwise create + def create_or_update_connection_ifnames(self, uid: str, ifname1: str, ifname2: str) -> Optional[str]: + module1, aid1 = ifname_to_module_and_aid(ifname1) + module2, aid2 = ifname_to_module_and_aid(ifname2) + + name = f"TF:{uid}" + existing_connection = self.get_connection_by_name(name) + if existing_connection: + return self.modify_connection(existing_connection.href, uid, module1, aid1, module2, aid2) + else: + return self.create_connection(uid, module1, aid1, module2, aid2) + + def get_connection_by_name(self, connection_name: str) -> Optional[Connection]: + qparams = [ + ('content', 'expanded'), + ('q', '{"state.name": "' + connection_name + '"}') + ] + r = self.__get_json("/api/v1/ncs/network-connections", params=qparams) + if r and r[0] == 200 and len(r[1]) == 1: + return Connection(from_json=r[1][0]) + else: + return None + + def get_connection_by_teraflow_uuid(self, uuid: str) -> Optional[Connection]: + return self.get_connection_by_name(f"TF:{uuid}") + + def get_connections(self): + r = self.__get_json("/api/v1/ncs/network-connections?content=expanded") + if r and r[0] == 200: + return [Connection(from_json=c) for c in r[1]] + else: + return [] + + def service_uuid(self, key: str) -> Optional[str]: + service = re.match(r"^/service\[(.+)\]$", key) + if service: + return service.group(1) + else: + return None diff --git a/src/device/service/drivers/xr/XrDriver.py b/src/device/service/drivers/xr/XrDriver.py index 81898d7c7..c40224cb4 100644 --- a/src/device/service/drivers/xr/XrDriver.py +++ b/src/device/service/drivers/xr/XrDriver.py @@ -11,6 +11,7 @@ # 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. +#pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring import logging, requests, threading from typing import Any, Iterator, List, Optional, Tuple, Union @@ -19,6 +20,12 @@ from device.service.driver_api._Driver import _Driver from . import ALL_RESOURCE_KEYS #from .Tools import create_connectivity_service, find_key, config_getter, delete_connectivity_service import json +from .CmConnection import CmConnection + +# Don't complain about non-verified SSL certificate. This driver is demo only +# and CM is not provisioned in demos with a proper certificate. +import urllib3 +urllib3.disable_warnings() LOGGER = logging.getLogger(__name__) @@ -27,43 +34,22 @@ class XrDriver(_Driver): self.__lock = threading.Lock() self.__started = threading.Event() self.__terminate = threading.Event() - self.__cm_root = 'https://' + address + ':' + str(port) self.__timeout = int(settings.get('timeout', 120)) - self.__verify = False; # Currently using self signed certificates - self.__audience = settings["audience"] if "audience" in settings else "test" - self.__client_id = settings["client_id"] if "client_id" in settings else "test" + # Mandatory key, an exception will get thrown if missing + self.__hub_module_name = settings["hub_module_name"] + + tls_verify = False # Currently using self signed certificates + username = settings["username"] if "username" in settings else "xr-user-1" + password = settings["password"] if "password" in settings else "xr-user-1" - self.__services = {} + self.__cm_connection = CmConnection(address, int(port), username, password, self.__timeout, tls_verify = tls_verify) - # FIXME: remove - LOGGER.info(f"FIXME!!! XrDriver, cm {address}:{port}, {settings=}"); + LOGGER.info(f"XrDriver instantiated, cm {address}:{port}, {settings=}") def Connect(self) -> bool: - url = self.__cm_root + '/oauth/token' with self.__lock: if self.__started.is_set(): return True - try: - # TODO: could also do get: https://${HOSTNAME}:443/oauth/token?client_id=test&audience=test" - req = {"grant_type":"client_credentials","client_id": self.__client_id, "audience": self.__audience} - response = requests.post(url,data=req,timeout=self.__timeout,verify=self.__verify) - resp = json.loads(response.text) - if 'access_token' in resp: - self.__access_token=resp['access_token'] - LOGGER.info(f"FIXME!!! CM connected, {self.__access_token=}") ## TODO: remove - - # Use in subsequend requests as named argument headers=self.__cm_http_headers - self.__cm_http_headers = {'Authorization': 'Bearer '+ self.__access_token} - else: - LOGGER.exception('No access token provided by {:s}'.format(str(self.__cm_root))) - return False - except requests.exceptions.Timeout: - LOGGER.exception('Timeout connecting {:s}'.format(str(self.__cm_root))) - return False - except json.JSONDecodeError as json_err: - LOGGER.exception(f"Exception parsing JSON access token from {str(self.__cm_root)}, {str(json_err)}") - return False - except Exception: # pylint: disable=broad-except - LOGGER.exception('Exception connecting {:s}'.format(str(self.__cm_root))) + if not self.__cm_connection.Connect(): return False else: self.__started.set() @@ -78,62 +64,70 @@ class XrDriver(_Driver): with self.__lock: return [] - def fake_interface_names(self) -> List[str]: - interfaces = [] - # Using 4 as max leaf and lane to keep prints small during development - for lane in range(0,4): - interfaces.append(f"HUB-LANE-{lane:02}") - for leaf in range(1,5): - for lane in range(0,4): - interfaces.append(f"LEAF-{leaf:02}-LANE-{lane:02}") - return interfaces - def GetConfig(self, resource_keys : List[str] = []) -> List[Tuple[str, Union[Any, None, Exception]]]: chk_type('resources', resource_keys, list) - results = [] - - # TODO: Completely fake interface information until we get same info from CM - for ifname in self.fake_interface_names(): - results.append((f"/endpoints/endpoint[{ifname}]", {'uuid': ifname, 'type': 'optical', 'sample_types': {}})) - return results + constellation = self.__cm_connection.get_constellation_by_hub_name(self.__hub_module_name) + if constellation: + _cid, if_list = constellation + return [(f"/endpoints/endpoint[{ifname}]", {'uuid': ifname, 'type': 'optical', 'sample_types': {}}) for ifname in if_list] + else: + return [] def SetConfig(self, resources: List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: - LOGGER.info(f"FIXME!!! XrDriver, SetConfig {resources=}"); + LOGGER.info(f"SetConfig {resources=}"); # Logged config seems like: - #[('/service[44ca3570-4e1a-49b5-8aab-06c92f239fab:optical]', '{"capacity_unit": "GHz", "capacity_value": 1, "direction": "UNIDIRECTIONAL", "input_sip": "HUB-LANE-01", "layer_protocol_name": "PHOTONIC_MEDIA", "layer_protocol_qualifier": "tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_NMC", "output_sip": "LEAF-02-LANE-01", "uuid": "44ca3570-4e1a-49b5-8aab-06c92f239fab:optical"}')] + #[('/service[52ff5f0f-fda4-40bd-a0b1-066f4ff04079:optical]', '{"capacity_unit": "GHz", "capacity_value": 1, "direction": "UNIDIRECTIONAL", "input_sip": "XR HUB 1|XR-T4", "layer_protocol_name": "PHOTONIC_MEDIA", "layer_protocol_qualifier": "tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_NMC", "output_sip": "XR LEAF 1|XR-T1", "uuid": "52ff5f0f-fda4-40bd-a0b1-066f4ff04079:optical"}')] + results = [] if len(resources) == 0: return results - # Temporary dummy version for key, config in resources: - self.__services[key] = config - - # TODO: config to CM - # Ignore "direction=UNIDIRECITONAL", it seems that controller only creates one direction... - results.append(True) + service_uuid = self.__cm_connection.service_uuid(key) + if service_uuid: + config = json.loads(config) + href = self.__cm_connection.create_or_update_connection_ifnames(service_uuid, config["input_sip"], config["output_sip"]) + if href: + LOGGER.info(f"SetConfig: Created service {service_uuid} as {href}") + results.append(True) + else: + LOGGER.error(f"SetConfig: Service creation failure for {service_uuid}") + results.append(False) + else: + results.append(False) return results def DeleteConfig(self, resources: List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: - LOGGER.info(f"FIXME!!! XrDriver, DeleteConfig {resources=}"); + LOGGER.info(f"DeleteConfig {resources=}"); + + # Input looks like: + # resources=[('/service[c8a35e81-88d8-4468-9afc-a8abd92a64d0:optical]', '{"uuid": "c8a35e81-88d8-4468-9afc-a8abd92a64d0:optical"}')] results = [] if len(resources) == 0: return results # Temporary dummy version - for key, config in resources: - if key in self.__services[key]: - del self.__services[key] - # TODO: Delete config from CM - results.append(True) + for key, _config in resources: + service_uuid = self.__cm_connection.service_uuid(key) + if service_uuid: + connection = self.__cm_connection.get_connection_by_teraflow_uuid(service_uuid) + if connection is None: + LOGGER.info(f"DeleteConfig: Connection {service_uuid} does not exist, delete is no-op") + results.append(True) + else: + was_deleted = self.__cm_connection.delete_connection(connection.href) + if was_deleted: + LOGGER.info(f"DeleteConfig: Connection {service_uuid} deleted (was {str(connection)})") + else: + LOGGER.info(f"DeleteConfig: Connection {service_uuid} delete failure (was {str(connection)})") + results.append(was_deleted) else: results.append(False) - return results def SubscribeState(self, subscriptions : List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]: diff --git a/src/device/service/drivers/xr/cm-cli.py b/src/device/service/drivers/xr/cm-cli.py new file mode 100755 index 000000000..2bd23fdb8 --- /dev/null +++ b/src/device/service/drivers/xr/cm-cli.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 + +# Test program for CmConnection + +import CmConnection +import argparse +import logging + +logging.basicConfig(level=logging.INFO) + +parser = argparse.ArgumentParser(description='CM Connectin Test Utility') +parser.add_argument('ip', help='CM IP address or domain name') +parser.add_argument('port', help='CM port', type=int) +parser.add_argument('username', help='Username') +parser.add_argument('password', help='Password') + +parser.add_argument('--list-constellations', action='store_true') +parser.add_argument('--show-constellation-by-hub-name', nargs='?', type=str) +parser.add_argument('--create-connection', nargs='?', type=str, help="uuid;ifname;ifname") +parser.add_argument('--modify-connection', nargs='?', type=str, help="href;uuid;ifname;ifname") +parser.add_argument('--show-connection-by-name', nargs='?', type=str) +parser.add_argument('--list-connections', action='store_true') +parser.add_argument('--delete-connection', nargs='?', type=str, help="connection id, e.g. \"/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03\"") + +args = parser.parse_args() + +cm = CmConnection.CmConnection(args.ip, args.port, args.username, args.password, tls_verify=False) +if not cm.Connect(): + exit(-1) + +if args.list_constellations: + constellations = cm.list_constellations() + for cid, if_list in constellations: + print("Constellation:", cid) + for if_name in if_list: + print(f" {if_name}") + +if args.show_constellation_by_hub_name: + constellation = cm.get_constellation_by_hub_name(args.show_constellation_by_hub_name) + if constellation: + (cid, if_list) = constellation + print("Constellation:", cid) + for if_name in if_list: + print(f" {if_name}") + +if args.create_connection: + cc_args = args.create_connection.split(";") + if len(cc_args) != 3: + print("Invalid create connection arguments. Expecting \"oid;ifname1;ifname2\", where ifname is form \"MODULE|PORT\"") + exit(-1) + cm.create_connection_ifnames(*cc_args) + +if args.modify_connection: + mc_args = args.modify_connection.split(";") + if len(mc_args) == 2: + cm.modify_connection_ifnames(mc_args[0], mc_args[1], None, None) + elif len(mc_args) == 4: + cm.modify_connection_ifnames(*mc_args) + else: + print("Invalid modify connection arguments. Expecting \"href;oid\" or \"href;oid;ifname1;ifname2\", where ifname is form \"MODULE|PORT\"") + exit(-1) + +if args.show_connection_by_name: + connection = cm.get_connection_by_name(args.show_connection_by_name) + if connection: + print(str(connection)) + +if args.list_connections: + connections = cm.get_connections() + for c in connections: + print(str(c)) + +if args.delete_connection: + was_deleted = cm.delete_connection(args.delete_connection) + if was_deleted: + print(f"Successfully deleted {args.delete_connection}") + else: + print(f"Failed to delete {args.delete_connection}") + diff --git a/src/tests/ofc22/descriptors_emulated_xr.json b/src/tests/ofc22/descriptors_emulated_xr.json index a3fc07bcf..30bd97ddd 100644 --- a/src/tests/ofc22/descriptors_emulated_xr.json +++ b/src/tests/ofc22/descriptors_emulated_xr.json @@ -68,7 +68,7 @@ "device_config": {"config_rules": [ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "172.19.219.44"}}, {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "443"}}, - {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": "{\"endpoints\": [{\"sample_types\": [], \"type\": \"optical\", \"uuid\": \"HUB-LANE-01\"}, {\"sample_types\": [], \"type\": \"optical\", \"uuid\": \"LEAF-01-LANE-01\"}]}"}} + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": "{\"username\": \"xr-user-1\", \"password\": \"xr-user-1\", \"hub_module_name\": \"XR HUB 1\"}"}} ]}, "device_operational_status": 1, "device_drivers": [6], @@ -77,31 +77,31 @@ ], "links": [ { - "link_id": {"link_uuid": {"uuid": "R1-EMU/13/0/0==XR1-HUB-LANE-01"}}, + "link_id": {"link_uuid": {"uuid": "R1-EMU/13/0/0==XR HUB 1|XR-T4"}}, "link_endpoint_ids": [ {"device_id": {"device_uuid": {"uuid": "R1-EMU"}}, "endpoint_uuid": {"uuid": "13/0/0"}}, - {"device_id": {"device_uuid": {"uuid": "X1-XR-CONSTELLATION"}}, "endpoint_uuid": {"uuid": "HUB-LANE-01"}} + {"device_id": {"device_uuid": {"uuid": "X1-XR-CONSTELLATION"}}, "endpoint_uuid": {"uuid": "XR HUB 1|XR-T4"}} ] }, { - "link_id": {"link_uuid": {"uuid": "R2-EMU/13/0/0==XR1-LEAF-01-LANE-01"}}, + "link_id": {"link_uuid": {"uuid": "R2-EMU/13/0/0==XR HUB 1|XR-T3"}}, "link_endpoint_ids": [ {"device_id": {"device_uuid": {"uuid": "R2-EMU"}}, "endpoint_uuid": {"uuid": "13/0/0"}}, - {"device_id": {"device_uuid": {"uuid": "X1-XR-CONSTELLATION"}}, "endpoint_uuid": {"uuid": "LEAF-01-LANE-01"}} + {"device_id": {"device_uuid": {"uuid": "X1-XR-CONSTELLATION"}}, "endpoint_uuid": {"uuid": "XR HUB 1|XR-T3"}} ] }, { - "link_id": {"link_uuid": {"uuid": "R3-EMU/13/0/0==XR1-LEAF-02-LANE-01"}}, + "link_id": {"link_uuid": {"uuid": "R3-EMU/13/0/0==XR1-XR LEAF 1|XR-T1"}}, "link_endpoint_ids": [ {"device_id": {"device_uuid": {"uuid": "R3-EMU"}}, "endpoint_uuid": {"uuid": "13/0/0"}}, - {"device_id": {"device_uuid": {"uuid": "X1-XR-CONSTELLATION"}}, "endpoint_uuid": {"uuid": "LEAF-02-LANE-01"}} + {"device_id": {"device_uuid": {"uuid": "X1-XR-CONSTELLATION"}}, "endpoint_uuid": {"uuid": "XR LEAF 1|XR-T1"}} ] }, { - "link_id": {"link_uuid": {"uuid": "R4-EMU/13/0/0==XR1-LEAF-03-LANE-01"}}, + "link_id": {"link_uuid": {"uuid": "R4-EMU/13/0/0==XR LEAF 2|XR-T1"}}, "link_endpoint_ids": [ {"device_id": {"device_uuid": {"uuid": "R4-EMU"}}, "endpoint_uuid": {"uuid": "13/0/0"}}, - {"device_id": {"device_uuid": {"uuid": "X1-XR-CONSTELLATION"}}, "endpoint_uuid": {"uuid": "LEAF-03-LANE-01"}} + {"device_id": {"device_uuid": {"uuid": "X1-XR-CONSTELLATION"}}, "endpoint_uuid": {"uuid": "XR LEAF 2|XR-T1"}} ] } ] diff --git a/src/tests/ofc22/tests/ObjectsXr.py b/src/tests/ofc22/tests/ObjectsXr.py index 12d2b48ee..e1ca0450b 100644 --- a/src/tests/ofc22/tests/ObjectsXr.py +++ b/src/tests/ofc22/tests/ObjectsXr.py @@ -143,20 +143,26 @@ DEVICE_R4_CONNECT_RULES = json_device_emulated_connect_rules(DEVICE_R4_ENDPOINT_ DEVICE_X1_UUID = 'X1-XR-CONSTELLATION' DEVICE_X1_TIMEOUT = 120 DEVICE_X1_ENDPOINT_DEFS = [ - ('HUB-LANE-01', 'optical', []), - ('LEAF-01-LANE-01', 'optical', []), - ('LEAF-02-LANE-01', 'optical', []), - ('LEAF-03-LANE-01', 'optical', []), + ('XR HUB 1|XR-T1', 'optical', []), + ('XR HUB 1|XR-T2', 'optical', []), + ('XR HUB 1|XR-T3', 'optical', []), + ('XR HUB 1|XR-T4', 'optical', []), + ('XR LEAF 1|XR-T1', 'optical', []), + ('XR LEAF 2|XR-T1', 'optical', []), ] DEVICE_X1_ID = json_device_id(DEVICE_X1_UUID) DEVICE_X1 = json_device_tapi_disabled(DEVICE_X1_UUID) DEVICE_X1_ENDPOINT_IDS = json_endpoint_ids(DEVICE_X1_ID, DEVICE_X1_ENDPOINT_DEFS) -ENDPOINT_ID_X1_EP1 = DEVICE_X1_ENDPOINT_IDS[0] -ENDPOINT_ID_X1_EP2 = DEVICE_X1_ENDPOINT_IDS[1] -ENDPOINT_ID_X1_EP3 = DEVICE_X1_ENDPOINT_IDS[2] -ENDPOINT_ID_X1_EP4 = DEVICE_X1_ENDPOINT_IDS[3] +# These match JSON, hence indexes are what theyt are +ENDPOINT_ID_X1_EP1 = DEVICE_X1_ENDPOINT_IDS[3] +ENDPOINT_ID_X1_EP2 = DEVICE_X1_ENDPOINT_IDS[2] +ENDPOINT_ID_X1_EP3 = DEVICE_X1_ENDPOINT_IDS[4] +ENDPOINT_ID_X1_EP4 = DEVICE_X1_ENDPOINT_IDS[5] DEVICE_X1_CONNECT_RULES = json_device_connect_rules(DEVICE_X1_ADDRESS, DEVICE_X1_PORT, { 'timeout' : DEVICE_X1_TIMEOUT, + "username": "xr-user-1", + "password": "xr-user-1", + "hub_module_name": "XR HUB 1" }) # Always using real device (CM, whether CM has emulated backend is another story) #if USE_REAL_DEVICES else json_device_emulated_connect_rules(DEVICE_X1_ENDPOINT_DEFS) diff --git a/src/tests/ofc22/tests/test_functional_create_service_xr.py b/src/tests/ofc22/tests/test_functional_create_service_xr.py index 882950cd5..7913aa9d7 100644 --- a/src/tests/ofc22/tests/test_functional_create_service_xr.py +++ b/src/tests/ofc22/tests/test_functional_create_service_xr.py @@ -32,8 +32,7 @@ LOGGER = logging.getLogger(__name__) LOGGER.setLevel(logging.DEBUG) DEVTYPE_EMU_PR = DeviceTypeEnum.EMULATED_PACKET_ROUTER.value -#DEVTYPE_EMU_OLS = DeviceTypeEnum.EMULATED_OPTICAL_LINE_SYSTEM.value -DEVTYPE_EMU_OLS = DeviceTypeEnum.XR_CONSTELLATION.value +DEVTYPE_XR_CONSTELLATION = DeviceTypeEnum.XR_CONSTELLATION.value @pytest.fixture(scope='session') @@ -80,7 +79,7 @@ def test_service_creation(context_client : ContextClient, osm_wim : MockOSM): # # ----- Validate collected events ---------------------------------------------------------------------------------- packet_connection_uuid = '{:s}:{:s}'.format(service_uuid, DEVTYPE_EMU_PR) - optical_connection_uuid = '{:s}:optical:{:s}'.format(service_uuid, DEVTYPE_EMU_OLS) + optical_connection_uuid = '{:s}:optical:{:s}'.format(service_uuid, DEVTYPE_XR_CONSTELLATION) optical_service_uuid = '{:s}:optical'.format(service_uuid) expected_events = [ diff --git a/src/tests/ofc22/tests/test_functional_delete_service_xr.py b/src/tests/ofc22/tests/test_functional_delete_service_xr.py new file mode 100644 index 000000000..efef7484a --- /dev/null +++ b/src/tests/ofc22/tests/test_functional_delete_service_xr.py @@ -0,0 +1,133 @@ +# 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 logging, pytest +from common.DeviceTypes import DeviceTypeEnum +from common.Settings import get_setting +from common.tests.EventTools import EVENT_REMOVE, EVENT_UPDATE, check_events +from common.tools.object_factory.Connection import json_connection_id +from common.tools.object_factory.Device import json_device_id +from common.tools.object_factory.Service import json_service_id +from common.tools.grpc.Tools import grpc_message_to_json_string +from compute.tests.mock_osm.MockOSM import MockOSM +from context.client.ContextClient import ContextClient +from context.client.EventsCollector import EventsCollector +from context.proto.context_pb2 import ContextId, Empty +from .ObjectsXr import ( + CONTEXT_ID, CONTEXTS, DEVICE_X1_UUID, DEVICE_R1_UUID, DEVICE_R3_UUID, DEVICES, LINKS, TOPOLOGIES, WIM_MAPPING, + WIM_PASSWORD, WIM_USERNAME) + + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +DEVTYPE_EMU_PR = DeviceTypeEnum.EMULATED_PACKET_ROUTER.value +DEVTYPE_XR_CONSTELLATION = DeviceTypeEnum.XR_CONSTELLATION.value + +@pytest.fixture(scope='session') +def context_client(): + _client = ContextClient(get_setting('CONTEXTSERVICE_SERVICE_HOST'), get_setting('CONTEXTSERVICE_SERVICE_PORT_GRPC')) + yield _client + _client.close() + + +@pytest.fixture(scope='session') +def osm_wim(): + wim_url = 'http://{:s}:{:s}'.format( + get_setting('COMPUTESERVICE_SERVICE_HOST'), str(get_setting('COMPUTESERVICE_SERVICE_PORT_HTTP'))) + return MockOSM(wim_url, WIM_MAPPING, WIM_USERNAME, WIM_PASSWORD) + + +def test_scenario_is_correct(context_client : ContextClient): # pylint: disable=redefined-outer-name + # ----- List entities - Ensure service is created ------------------------------------------------------------------ + response = context_client.ListContexts(Empty()) + assert len(response.contexts) == len(CONTEXTS) + + response = context_client.ListTopologies(ContextId(**CONTEXT_ID)) + assert len(response.topologies) == len(TOPOLOGIES) + + response = context_client.ListDevices(Empty()) + assert len(response.devices) == len(DEVICES) + + response = context_client.ListLinks(Empty()) + assert len(response.links) == len(LINKS) + + response = context_client.ListServices(ContextId(**CONTEXT_ID)) + LOGGER.info('Services[{:d}] = {:s}'.format(len(response.services), grpc_message_to_json_string(response))) + assert len(response.services) == 2 # L3NM + TAPI + for service in response.services: + service_id = service.service_id + response = context_client.ListConnections(service_id) + LOGGER.info(' ServiceId[{:s}] => Connections[{:d}] = {:s}'.format( + grpc_message_to_json_string(service_id), len(response.connections), grpc_message_to_json_string(response))) + assert len(response.connections) == 1 # one connection per service + + +def test_service_removal(context_client : ContextClient, osm_wim : MockOSM): # pylint: disable=redefined-outer-name + # ----- Start the EventsCollector ---------------------------------------------------------------------------------- + events_collector = EventsCollector(context_client, log_events_received=True) + events_collector.start() + + # ----- Delete Service --------------------------------------------------------------------------------------------- + response = context_client.ListServiceIds(ContextId(**CONTEXT_ID)) + LOGGER.info('Services[{:d}] = {:s}'.format(len(response.service_ids), grpc_message_to_json_string(response))) + assert len(response.service_ids) == 2 # L3NM + TAPI + service_uuids = set() + for service_id in response.service_ids: + service_uuid = service_id.service_uuid.uuid + if service_uuid.endswith(':optical'): continue + service_uuids.add(service_uuid) + osm_wim.conn_info[service_uuid] = {} + + assert len(service_uuids) == 1 # assume a single service has been created + service_uuid = set(service_uuids).pop() + + osm_wim.delete_connectivity_service(service_uuid) + + # ----- Validate collected events ---------------------------------------------------------------------------------- + packet_connection_uuid = '{:s}:{:s}'.format(service_uuid, DEVTYPE_EMU_PR) + optical_connection_uuid = '{:s}:optical:{:s}'.format(service_uuid, DEVTYPE_XR_CONSTELLATION) + optical_service_uuid = '{:s}:optical'.format(service_uuid) + + expected_events = [ + ('ConnectionEvent', EVENT_REMOVE, json_connection_id(packet_connection_uuid)), + ('DeviceEvent', EVENT_UPDATE, json_device_id(DEVICE_R1_UUID)), + ('DeviceEvent', EVENT_UPDATE, json_device_id(DEVICE_R3_UUID)), + ('ServiceEvent', EVENT_REMOVE, json_service_id(service_uuid, context_id=CONTEXT_ID)), + ('ConnectionEvent', EVENT_REMOVE, json_connection_id(optical_connection_uuid)), + ('DeviceEvent', EVENT_UPDATE, json_device_id(DEVICE_X1_UUID)), + ('ServiceEvent', EVENT_REMOVE, json_service_id(optical_service_uuid, context_id=CONTEXT_ID)), + ] + check_events(events_collector, expected_events) + + # ----- Stop the EventsCollector ----------------------------------------------------------------------------------- + events_collector.stop() + + +def test_services_removed(context_client : ContextClient): # pylint: disable=redefined-outer-name + # ----- List entities - Ensure service is removed ------------------------------------------------------------------ + response = context_client.ListContexts(Empty()) + assert len(response.contexts) == len(CONTEXTS) + + response = context_client.ListTopologies(ContextId(**CONTEXT_ID)) + assert len(response.topologies) == len(TOPOLOGIES) + + response = context_client.ListDevices(Empty()) + assert len(response.devices) == len(DEVICES) + + response = context_client.ListLinks(Empty()) + assert len(response.links) == len(LINKS) + + response = context_client.ListServices(ContextId(**CONTEXT_ID)) + assert len(response.services) == 0 -- GitLab From c2aeea9901644d4ef8481f4cde854e42c1af0f12 Mon Sep 17 00:00:00 2001 From: Ville Petteri Hallivuori Date: Thu, 18 Aug 2022 00:38:14 -0700 Subject: [PATCH 04/10] Pull request #4: XR VTI mode support Merge in XRCA/teraflow from vhallivu/vti_mode_prototyping to xr_development Squashed commit of the following: commit 6127856df3f3e4c9131f40972d799a6de32a38ef Author: Ville Hallivuori Date: Thu Aug 18 09:15:59 2022 +0300 Fix lint errors commit dbea279626606f62553b1d6e1a80ae12748cdfd5 Author: Ville Hallivuori Date: Thu Aug 18 08:48:57 2022 +0300 pytests improvements commit e7218f6369df648c73782d7b41b062bc50670490 Author: Ville Hallivuori Date: Wed Aug 17 17:54:04 2022 +0300 Some tests for TC commit 8327392bfa22d06d334349fcf872579414141277 Author: Ville Hallivuori Date: Wed Aug 17 15:59:01 2022 +0300 Unit test for connection commit 5148a84534b332ee82458b080fbc89d19bec3c63 Author: Ville Hallivuori Date: Wed Aug 17 13:26:23 2022 +0300 Incremental connection endpoint modifications support commit 10f3e1e43de483c4dc84a6dcd7b0f8bba37d97a2 Author: Ville Hallivuori Date: Wed Aug 17 11:32:05 2022 +0300 Refactored TF to use the new backend for create commit 1e678b0222b9ef7dbdaf09b83696f561c6889c08 Author: Ville Hallivuori Date: Wed Aug 17 10:33:34 2022 +0300 More code refactoring commit 991d951485fb7fa02692c08da7fe6261cc7f4ce7 Author: Ville Hallivuori Date: Tue Aug 16 18:25:34 2022 +0300 More refactoring commit 62f0f407ca78cadd5c6c581d98e47c8688909318 Author: Ville Hallivuori Date: Tue Aug 16 17:33:03 2022 +0300 Transport capacity testing and cm-cli support commit a8d0bd8797d6754e7adbfacf292831eaf69c83f6 Author: Ville Hallivuori Date: Tue Aug 16 16:35:14 2022 +0300 TC delete, more refactoring commit ec70cefccdf4dd94c70a9bc31d4f9c841177db9c Author: Ville Hallivuori Date: Tue Aug 16 14:48:23 2022 +0300 Support for creating transport capacities commit 3148f2c472452adcef78f1a80540091b6fcf505d Author: Ville Hallivuori Date: Tue Aug 16 11:19:53 2022 +0300 Code refactoring commit 6e980925e3605ade0e5b6e6d0447c29fc3badce5 Author: Ville Hallivuori Date: Mon Aug 15 08:42:43 2022 +0300 Refactoring + tuning gitignore + updating version controlled generated files --- .gitignore | 8 + .../grpc/context/ContextOuterClass.java | 184 +- .../device/service/drivers/xr/README_XR.md | 26 +- src/device/service/drivers/xr/XrDriver.py | 54 +- src/device/service/drivers/xr/__init__.py | 27 - src/device/service/drivers/xr/cm-cli.py | 117 +- src/device/service/drivers/xr/cm/__init__.py | 0 .../{CmConnection.py => cm/cm_connection.py} | 284 ++- .../service/drivers/xr/cm/connection.py | 165 ++ .../service/drivers/xr/cm/constellation.py | 55 + .../service/drivers/xr/cm/tests/__init__.py | 0 .../tests/resources/connections-expanded.json | 290 +++ .../resources/constellations-expanded.json | 1768 +++++++++++++++++ .../transport-capacities-swagger-example.json | 165 ++ .../drivers/xr/cm/tests/test_connection.py | 92 + .../drivers/xr/cm/tests/test_constellation.py | 31 + .../xr/cm/tests/test_transport_capacitity.py | 45 + src/device/service/drivers/xr/cm/tf.py | 57 + .../service/drivers/xr/cm/tf_service.py | 53 + .../drivers/xr/cm/transport_capacity.py | 96 + src/device/service/drivers/xr/cm/utils.py | 57 + .../grpc/context/ContextOuterClass.java | 184 +- 22 files changed, 3353 insertions(+), 405 deletions(-) rename README_INFINERA.md => src/device/service/drivers/xr/README_XR.md (79%) create mode 100644 src/device/service/drivers/xr/cm/__init__.py rename src/device/service/drivers/xr/{CmConnection.py => cm/cm_connection.py} (56%) create mode 100644 src/device/service/drivers/xr/cm/connection.py create mode 100644 src/device/service/drivers/xr/cm/constellation.py create mode 100644 src/device/service/drivers/xr/cm/tests/__init__.py create mode 100644 src/device/service/drivers/xr/cm/tests/resources/connections-expanded.json create mode 100644 src/device/service/drivers/xr/cm/tests/resources/constellations-expanded.json create mode 100644 src/device/service/drivers/xr/cm/tests/resources/transport-capacities-swagger-example.json create mode 100644 src/device/service/drivers/xr/cm/tests/test_connection.py create mode 100644 src/device/service/drivers/xr/cm/tests/test_constellation.py create mode 100644 src/device/service/drivers/xr/cm/tests/test_transport_capacitity.py create mode 100644 src/device/service/drivers/xr/cm/tf.py create mode 100644 src/device/service/drivers/xr/cm/tf_service.py create mode 100644 src/device/service/drivers/xr/cm/transport_capacity.py create mode 100644 src/device/service/drivers/xr/cm/utils.py diff --git a/.gitignore b/.gitignore index 71b77da25..00cedaff9 100644 --- a/.gitignore +++ b/.gitignore @@ -158,3 +158,11 @@ cython_debug/ # TeraFlowSDN-generated files tfs_runtime_env_vars.sh + +# Symlink for generated proto-files in host +src/context/proto + +# Generated files in dlt +src/dlt/.gradle +src/dlt/bin +src/dlt/gateway/bin diff --git a/src/automation/target/generated-sources/grpc/context/ContextOuterClass.java b/src/automation/target/generated-sources/grpc/context/ContextOuterClass.java index 45a64fabb..1e9537414 100644 --- a/src/automation/target/generated-sources/grpc/context/ContextOuterClass.java +++ b/src/automation/target/generated-sources/grpc/context/ContextOuterClass.java @@ -173,6 +173,10 @@ public final class ContextOuterClass { * DEVICEDRIVER_ONF_TR_352 = 5; */ DEVICEDRIVER_ONF_TR_352(5), + /** + * DEVICEDRIVER_XR = 6; + */ + DEVICEDRIVER_XR(6), UNRECOGNIZED(-1), ; @@ -204,6 +208,10 @@ public final class ContextOuterClass { * DEVICEDRIVER_ONF_TR_352 = 5; */ public static final int DEVICEDRIVER_ONF_TR_352_VALUE = 5; + /** + * DEVICEDRIVER_XR = 6; + */ + public static final int DEVICEDRIVER_XR_VALUE = 6; public final int getNumber() { @@ -236,6 +244,7 @@ public final class ContextOuterClass { case 3: return DEVICEDRIVER_P4; case 4: return DEVICEDRIVER_IETF_NETWORK_TOPOLOGY; case 5: return DEVICEDRIVER_ONF_TR_352; + case 6: return DEVICEDRIVER_XR; default: return null; } } @@ -62114,97 +62123,98 @@ public final class ContextOuterClass { "ontextId\022\025\n\rauthenticated\030\002 \001(\010*j\n\rEvent" + "TypeEnum\022\027\n\023EVENTTYPE_UNDEFINED\020\000\022\024\n\020EVE" + "NTTYPE_CREATE\020\001\022\024\n\020EVENTTYPE_UPDATE\020\002\022\024\n" + - "\020EVENTTYPE_REMOVE\020\003*\305\001\n\020DeviceDriverEnum" + + "\020EVENTTYPE_REMOVE\020\003*\332\001\n\020DeviceDriverEnum" + "\022\032\n\026DEVICEDRIVER_UNDEFINED\020\000\022\033\n\027DEVICEDR" + "IVER_OPENCONFIG\020\001\022\036\n\032DEVICEDRIVER_TRANSP" + "ORT_API\020\002\022\023\n\017DEVICEDRIVER_P4\020\003\022&\n\"DEVICE" + "DRIVER_IETF_NETWORK_TOPOLOGY\020\004\022\033\n\027DEVICE" + - "DRIVER_ONF_TR_352\020\005*\217\001\n\033DeviceOperationa" + - "lStatusEnum\022%\n!DEVICEOPERATIONALSTATUS_U" + - "NDEFINED\020\000\022$\n DEVICEOPERATIONALSTATUS_DI" + - "SABLED\020\001\022#\n\037DEVICEOPERATIONALSTATUS_ENAB" + - "LED\020\002*\201\001\n\017ServiceTypeEnum\022\027\n\023SERVICETYPE" + - "_UNKNOWN\020\000\022\024\n\020SERVICETYPE_L3NM\020\001\022\024\n\020SERV" + - "ICETYPE_L2NM\020\002\022)\n%SERVICETYPE_TAPI_CONNE" + - "CTIVITY_SERVICE\020\003*\250\001\n\021ServiceStatusEnum\022" + - "\033\n\027SERVICESTATUS_UNDEFINED\020\000\022\031\n\025SERVICES" + - "TATUS_PLANNED\020\001\022\030\n\024SERVICESTATUS_ACTIVE\020" + - "\002\022!\n\035SERVICESTATUS_PENDING_REMOVAL\020\003\022\036\n\032" + - "SERVICESTATUS_SLA_VIOLATED\020\004*\251\001\n\017SliceSt" + - "atusEnum\022\031\n\025SLICESTATUS_UNDEFINED\020\000\022\027\n\023S" + - "LICESTATUS_PLANNED\020\001\022\024\n\020SLICESTATUS_INIT" + - "\020\002\022\026\n\022SLICESTATUS_ACTIVE\020\003\022\026\n\022SLICESTATU" + - "S_DEINIT\020\004\022\034\n\030SLICESTATUS_SLA_VIOLATED\020\005" + - "*]\n\020ConfigActionEnum\022\032\n\026CONFIGACTION_UND" + - "EFINED\020\000\022\024\n\020CONFIGACTION_SET\020\001\022\027\n\023CONFIG" + - "ACTION_DELETE\020\002*\203\002\n\022IsolationLevelEnum\022\020" + - "\n\014NO_ISOLATION\020\000\022\026\n\022PHYSICAL_ISOLATION\020\001" + - "\022\025\n\021LOGICAL_ISOLATION\020\002\022\025\n\021PROCESS_ISOLA" + - "TION\020\003\022\035\n\031PHYSICAL_MEMORY_ISOLATION\020\004\022\036\n" + - "\032PHYSICAL_NETWORK_ISOLATION\020\005\022\036\n\032VIRTUAL" + - "_RESOURCE_ISOLATION\020\006\022\037\n\033NETWORK_FUNCTIO" + - "NS_ISOLATION\020\007\022\025\n\021SERVICE_ISOLATION\020\0102\357\022" + - "\n\016ContextService\022:\n\016ListContextIds\022\016.con" + - "text.Empty\032\026.context.ContextIdList\"\000\0226\n\014" + - "ListContexts\022\016.context.Empty\032\024.context.C" + - "ontextList\"\000\0224\n\nGetContext\022\022.context.Con" + - "textId\032\020.context.Context\"\000\0224\n\nSetContext" + - "\022\020.context.Context\032\022.context.ContextId\"\000" + - "\0225\n\rRemoveContext\022\022.context.ContextId\032\016." + - "context.Empty\"\000\022=\n\020GetContextEvents\022\016.co" + - "ntext.Empty\032\025.context.ContextEvent\"\0000\001\022@" + - "\n\017ListTopologyIds\022\022.context.ContextId\032\027." + - "context.TopologyIdList\"\000\022=\n\016ListTopologi" + - "es\022\022.context.ContextId\032\025.context.Topolog" + - "yList\"\000\0227\n\013GetTopology\022\023.context.Topolog" + - "yId\032\021.context.Topology\"\000\0227\n\013SetTopology\022" + - "\021.context.Topology\032\023.context.TopologyId\"" + - "\000\0227\n\016RemoveTopology\022\023.context.TopologyId" + - "\032\016.context.Empty\"\000\022?\n\021GetTopologyEvents\022" + - "\016.context.Empty\032\026.context.TopologyEvent\"" + - "\0000\001\0228\n\rListDeviceIds\022\016.context.Empty\032\025.c" + - "ontext.DeviceIdList\"\000\0224\n\013ListDevices\022\016.c" + - "ontext.Empty\032\023.context.DeviceList\"\000\0221\n\tG" + - "etDevice\022\021.context.DeviceId\032\017.context.De" + - "vice\"\000\0221\n\tSetDevice\022\017.context.Device\032\021.c" + - "ontext.DeviceId\"\000\0223\n\014RemoveDevice\022\021.cont" + - "ext.DeviceId\032\016.context.Empty\"\000\022;\n\017GetDev" + - "iceEvents\022\016.context.Empty\032\024.context.Devi" + - "ceEvent\"\0000\001\0224\n\013ListLinkIds\022\016.context.Emp" + - "ty\032\023.context.LinkIdList\"\000\0220\n\tListLinks\022\016" + - ".context.Empty\032\021.context.LinkList\"\000\022+\n\007G" + - "etLink\022\017.context.LinkId\032\r.context.Link\"\000" + - "\022+\n\007SetLink\022\r.context.Link\032\017.context.Lin" + - "kId\"\000\022/\n\nRemoveLink\022\017.context.LinkId\032\016.c" + - "ontext.Empty\"\000\0227\n\rGetLinkEvents\022\016.contex" + - "t.Empty\032\022.context.LinkEvent\"\0000\001\022>\n\016ListS" + - "erviceIds\022\022.context.ContextId\032\026.context." + - "ServiceIdList\"\000\022:\n\014ListServices\022\022.contex" + - "t.ContextId\032\024.context.ServiceList\"\000\0224\n\nG" + - "etService\022\022.context.ServiceId\032\020.context." + - "Service\"\000\0224\n\nSetService\022\020.context.Servic" + - "e\032\022.context.ServiceId\"\000\0225\n\rRemoveService" + - "\022\022.context.ServiceId\032\016.context.Empty\"\000\022=" + - "\n\020GetServiceEvents\022\016.context.Empty\032\025.con" + - "text.ServiceEvent\"\0000\001\022:\n\014ListSliceIds\022\022." + - "context.ContextId\032\024.context.SliceIdList\"" + - "\000\0226\n\nListSlices\022\022.context.ContextId\032\022.co" + - "ntext.SliceList\"\000\022.\n\010GetSlice\022\020.context." + - "SliceId\032\016.context.Slice\"\000\022.\n\010SetSlice\022\016." + - "context.Slice\032\020.context.SliceId\"\000\0221\n\013Rem" + - "oveSlice\022\020.context.SliceId\032\016.context.Emp" + - "ty\"\000\0229\n\016GetSliceEvents\022\016.context.Empty\032\023" + - ".context.SliceEvent\"\0000\001\022D\n\021ListConnectio" + - "nIds\022\022.context.ServiceId\032\031.context.Conne" + - "ctionIdList\"\000\022@\n\017ListConnections\022\022.conte" + - "xt.ServiceId\032\027.context.ConnectionList\"\000\022" + - "=\n\rGetConnection\022\025.context.ConnectionId\032" + - "\023.context.Connection\"\000\022=\n\rSetConnection\022" + - "\023.context.Connection\032\025.context.Connectio" + - "nId\"\000\022;\n\020RemoveConnection\022\025.context.Conn" + - "ectionId\032\016.context.Empty\"\000\022C\n\023GetConnect" + - "ionEvents\022\016.context.Empty\032\030.context.Conn" + - "ectionEvent\"\0000\001b\006proto3" + "DRIVER_ONF_TR_352\020\005\022\023\n\017DEVICEDRIVER_XR\020\006" + + "*\217\001\n\033DeviceOperationalStatusEnum\022%\n!DEVI" + + "CEOPERATIONALSTATUS_UNDEFINED\020\000\022$\n DEVIC" + + "EOPERATIONALSTATUS_DISABLED\020\001\022#\n\037DEVICEO" + + "PERATIONALSTATUS_ENABLED\020\002*\201\001\n\017ServiceTy" + + "peEnum\022\027\n\023SERVICETYPE_UNKNOWN\020\000\022\024\n\020SERVI" + + "CETYPE_L3NM\020\001\022\024\n\020SERVICETYPE_L2NM\020\002\022)\n%S" + + "ERVICETYPE_TAPI_CONNECTIVITY_SERVICE\020\003*\250" + + "\001\n\021ServiceStatusEnum\022\033\n\027SERVICESTATUS_UN" + + "DEFINED\020\000\022\031\n\025SERVICESTATUS_PLANNED\020\001\022\030\n\024" + + "SERVICESTATUS_ACTIVE\020\002\022!\n\035SERVICESTATUS_" + + "PENDING_REMOVAL\020\003\022\036\n\032SERVICESTATUS_SLA_V" + + "IOLATED\020\004*\251\001\n\017SliceStatusEnum\022\031\n\025SLICEST" + + "ATUS_UNDEFINED\020\000\022\027\n\023SLICESTATUS_PLANNED\020" + + "\001\022\024\n\020SLICESTATUS_INIT\020\002\022\026\n\022SLICESTATUS_A" + + "CTIVE\020\003\022\026\n\022SLICESTATUS_DEINIT\020\004\022\034\n\030SLICE" + + "STATUS_SLA_VIOLATED\020\005*]\n\020ConfigActionEnu" + + "m\022\032\n\026CONFIGACTION_UNDEFINED\020\000\022\024\n\020CONFIGA" + + "CTION_SET\020\001\022\027\n\023CONFIGACTION_DELETE\020\002*\203\002\n" + + "\022IsolationLevelEnum\022\020\n\014NO_ISOLATION\020\000\022\026\n" + + "\022PHYSICAL_ISOLATION\020\001\022\025\n\021LOGICAL_ISOLATI" + + "ON\020\002\022\025\n\021PROCESS_ISOLATION\020\003\022\035\n\031PHYSICAL_" + + "MEMORY_ISOLATION\020\004\022\036\n\032PHYSICAL_NETWORK_I" + + "SOLATION\020\005\022\036\n\032VIRTUAL_RESOURCE_ISOLATION" + + "\020\006\022\037\n\033NETWORK_FUNCTIONS_ISOLATION\020\007\022\025\n\021S" + + "ERVICE_ISOLATION\020\0102\357\022\n\016ContextService\022:\n" + + "\016ListContextIds\022\016.context.Empty\032\026.contex" + + "t.ContextIdList\"\000\0226\n\014ListContexts\022\016.cont" + + "ext.Empty\032\024.context.ContextList\"\000\0224\n\nGet" + + "Context\022\022.context.ContextId\032\020.context.Co" + + "ntext\"\000\0224\n\nSetContext\022\020.context.Context\032" + + "\022.context.ContextId\"\000\0225\n\rRemoveContext\022\022" + + ".context.ContextId\032\016.context.Empty\"\000\022=\n\020" + + "GetContextEvents\022\016.context.Empty\032\025.conte" + + "xt.ContextEvent\"\0000\001\022@\n\017ListTopologyIds\022\022" + + ".context.ContextId\032\027.context.TopologyIdL" + + "ist\"\000\022=\n\016ListTopologies\022\022.context.Contex" + + "tId\032\025.context.TopologyList\"\000\0227\n\013GetTopol" + + "ogy\022\023.context.TopologyId\032\021.context.Topol" + + "ogy\"\000\0227\n\013SetTopology\022\021.context.Topology\032" + + "\023.context.TopologyId\"\000\0227\n\016RemoveTopology" + + "\022\023.context.TopologyId\032\016.context.Empty\"\000\022" + + "?\n\021GetTopologyEvents\022\016.context.Empty\032\026.c" + + "ontext.TopologyEvent\"\0000\001\0228\n\rListDeviceId" + + "s\022\016.context.Empty\032\025.context.DeviceIdList" + + "\"\000\0224\n\013ListDevices\022\016.context.Empty\032\023.cont" + + "ext.DeviceList\"\000\0221\n\tGetDevice\022\021.context." + + "DeviceId\032\017.context.Device\"\000\0221\n\tSetDevice" + + "\022\017.context.Device\032\021.context.DeviceId\"\000\0223" + + "\n\014RemoveDevice\022\021.context.DeviceId\032\016.cont" + + "ext.Empty\"\000\022;\n\017GetDeviceEvents\022\016.context" + + ".Empty\032\024.context.DeviceEvent\"\0000\001\0224\n\013List" + + "LinkIds\022\016.context.Empty\032\023.context.LinkId" + + "List\"\000\0220\n\tListLinks\022\016.context.Empty\032\021.co" + + "ntext.LinkList\"\000\022+\n\007GetLink\022\017.context.Li" + + "nkId\032\r.context.Link\"\000\022+\n\007SetLink\022\r.conte" + + "xt.Link\032\017.context.LinkId\"\000\022/\n\nRemoveLink" + + "\022\017.context.LinkId\032\016.context.Empty\"\000\0227\n\rG" + + "etLinkEvents\022\016.context.Empty\032\022.context.L" + + "inkEvent\"\0000\001\022>\n\016ListServiceIds\022\022.context" + + ".ContextId\032\026.context.ServiceIdList\"\000\022:\n\014" + + "ListServices\022\022.context.ContextId\032\024.conte" + + "xt.ServiceList\"\000\0224\n\nGetService\022\022.context" + + ".ServiceId\032\020.context.Service\"\000\0224\n\nSetSer" + + "vice\022\020.context.Service\032\022.context.Service" + + "Id\"\000\0225\n\rRemoveService\022\022.context.ServiceI" + + "d\032\016.context.Empty\"\000\022=\n\020GetServiceEvents\022" + + "\016.context.Empty\032\025.context.ServiceEvent\"\000" + + "0\001\022:\n\014ListSliceIds\022\022.context.ContextId\032\024" + + ".context.SliceIdList\"\000\0226\n\nListSlices\022\022.c" + + "ontext.ContextId\032\022.context.SliceList\"\000\022." + + "\n\010GetSlice\022\020.context.SliceId\032\016.context.S" + + "lice\"\000\022.\n\010SetSlice\022\016.context.Slice\032\020.con" + + "text.SliceId\"\000\0221\n\013RemoveSlice\022\020.context." + + "SliceId\032\016.context.Empty\"\000\0229\n\016GetSliceEve" + + "nts\022\016.context.Empty\032\023.context.SliceEvent" + + "\"\0000\001\022D\n\021ListConnectionIds\022\022.context.Serv" + + "iceId\032\031.context.ConnectionIdList\"\000\022@\n\017Li" + + "stConnections\022\022.context.ServiceId\032\027.cont" + + "ext.ConnectionList\"\000\022=\n\rGetConnection\022\025." + + "context.ConnectionId\032\023.context.Connectio" + + "n\"\000\022=\n\rSetConnection\022\023.context.Connectio" + + "n\032\025.context.ConnectionId\"\000\022;\n\020RemoveConn" + + "ection\022\025.context.ConnectionId\032\016.context." + + "Empty\"\000\022C\n\023GetConnectionEvents\022\016.context" + + ".Empty\032\030.context.ConnectionEvent\"\0000\001b\006pr" + + "oto3" }; descriptor = com.google.protobuf.Descriptors.FileDescriptor .internalBuildGeneratedFileFrom(descriptorData, diff --git a/README_INFINERA.md b/src/device/service/drivers/xr/README_XR.md similarity index 79% rename from README_INFINERA.md rename to src/device/service/drivers/xr/README_XR.md index 339c516ae..234261124 100644 --- a/README_INFINERA.md +++ b/src/device/service/drivers/xr/README_XR.md @@ -1,7 +1,5 @@ # Infinera Readme -This file will be removed before uploading to origin. - There are some instructions at https://gitlab.com/teraflow-h2020/controller/-/tree/develop/tutorial . They are not completely up to date and don't 100% work. Note that many of the scripts expect this and that K8s namespace being used, they are not consistent, so use manual kubectl commands where necessary. @@ -66,6 +64,13 @@ cd ../src/context ln -s ../../proto/src/python proto ``` +For VS Code python extension imports it is convenient to set file .env to top level with content: + +``` +PYTHONPATH=src +``` +This will make imports to work properly in all cases. + ## Building Run deploy script to build in docker containers and then instantiate to configured K8s cluster. Deploy script must be sources for this to work! @@ -90,6 +95,15 @@ Good logs to check are: * kubectl logs service/deviceservice --namespace tfs * kubectl logs service/webuiservice --namespace tfs +## Unit Tests +Run in src directory (src under repo top level) with command: + +```bash +PYTHONPATH=. pytest device/service/drivers/xr/cm +``` + +The PYTHONPATH is vital for imports to work properly. + ## cm-cli The tool cm-cli in the xr driver directory can be use to connect to CM and test the connectivity. For example: @@ -100,8 +114,12 @@ The tool cm-cli in the xr driver directory can be use to connect to CM and test ./cm-cli.py 172.19.219.44 443 xr-user-1 xr-user-1 --create-connection="FOO;XR HUB 1|XR-T4;XR LEAF 1|XR-T1" ./cm-cli.py 172.19.219.44 443 xr-user-1 xr-user-1 --show-connection-by-name="FooBar123" ./cm-cli.py 172.19.219.44 443 xr-user-1 xr-user-1 --list-connections -# Modify argumens: href;uuid;ifname;ifname or href;uuid +# Modify argumens: href;uuid;ifname;ifname # uuid translates to name TF:uuid -./cm-cli.py 172.19.219.44 443 xr-user-1 xr-user-1 --modify-connection="/network-connections/138f0cc0-3dc6-4195-97c0-2cbed5fd59ba;FooBarAaa" +./cm-cli.py 172.19.219.44 443 xr-user-1 xr-user-1 --modify-connection="/network-connections/0637da3b-3b20-4b44-a513-035e6ef897a3;MyCon1;XR HUB 1|XR-T1;XR LEAF 1|XR-T2;25" ./cm-cli.py 172.19.219.44 443 xr-user-1 xr-user-1 --delete-connection=/network-connections/138f0cc0-3dc6-4195-97c0-2cbed5fd59ba + ./cm-cli.py 172.19.219.44 443 xr-user-1 xr-user-1 --create-transport-capacity="FOO;XR HUB 1|XR-T4;XR LEAF 1|XR-T1;12" + ./cm-cli.py 172.19.219.44 443 xr-user-1 xr-user-1 --list-transport-capacities +# Exercise almost full path of SetConfig. Can also be used for changing bandwidth (e.g. in demos) of an service +./cm-cli.py 172.19.219.44 443 xr-user-1 xr-user-1 --emulate-tf-set-config-service="XR HUB 1;teraflow_service_uuid;XR HUB 1|XR-T4;XR LEAF 1|XR-T1;125" ``` diff --git a/src/device/service/drivers/xr/XrDriver.py b/src/device/service/drivers/xr/XrDriver.py index c40224cb4..c3325b14f 100644 --- a/src/device/service/drivers/xr/XrDriver.py +++ b/src/device/service/drivers/xr/XrDriver.py @@ -13,18 +13,18 @@ # limitations under the License. #pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring -import logging, requests, threading +import logging +import threading +import json from typing import Any, Iterator, List, Optional, Tuple, Union -from common.type_checkers.Checkers import chk_string, chk_type +import urllib3 +from common.type_checkers.Checkers import chk_type from device.service.driver_api._Driver import _Driver -from . import ALL_RESOURCE_KEYS -#from .Tools import create_connectivity_service, find_key, config_getter, delete_connectivity_service -import json -from .CmConnection import CmConnection +from .cm.cm_connection import CmConnection +from .cm import tf # Don't complain about non-verified SSL certificate. This driver is demo only # and CM is not provisioned in demos with a proper certificate. -import urllib3 urllib3.disable_warnings() LOGGER = logging.getLogger(__name__) @@ -43,12 +43,14 @@ class XrDriver(_Driver): password = settings["password"] if "password" in settings else "xr-user-1" self.__cm_connection = CmConnection(address, int(port), username, password, self.__timeout, tls_verify = tls_verify) + self.__constellation = None LOGGER.info(f"XrDriver instantiated, cm {address}:{port}, {settings=}") def Connect(self) -> bool: with self.__lock: - if self.__started.is_set(): return True + if self.__started.is_set(): + return True if not self.__cm_connection.Connect(): return False else: @@ -64,23 +66,31 @@ class XrDriver(_Driver): with self.__lock: return [] + #pylint: disable=dangerous-default-value def GetConfig(self, resource_keys : List[str] = []) -> List[Tuple[str, Union[Any, None, Exception]]]: chk_type('resources', resource_keys, list) constellation = self.__cm_connection.get_constellation_by_hub_name(self.__hub_module_name) if constellation: - _cid, if_list = constellation - return [(f"/endpoints/endpoint[{ifname}]", {'uuid': ifname, 'type': 'optical', 'sample_types': {}}) for ifname in if_list] + self.__constellation = constellation + return [(f"/endpoints/endpoint[{ifname}]", {'uuid': ifname, 'type': 'optical', 'sample_types': {}}) for ifname in constellation.ifnames()] else: return [] def SetConfig(self, resources: List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: - LOGGER.info(f"SetConfig {resources=}"); + LOGGER.info(f"SetConfig {resources=}") # Logged config seems like: #[('/service[52ff5f0f-fda4-40bd-a0b1-066f4ff04079:optical]', '{"capacity_unit": "GHz", "capacity_value": 1, "direction": "UNIDIRECTIONAL", "input_sip": "XR HUB 1|XR-T4", "layer_protocol_name": "PHOTONIC_MEDIA", "layer_protocol_qualifier": "tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_NMC", "output_sip": "XR LEAF 1|XR-T1", "uuid": "52ff5f0f-fda4-40bd-a0b1-066f4ff04079:optical"}')] + if self.__constellation is None: + self.__constellation = self.__cm_connection.get_constellation_by_hub_name(self.__hub_module_name) + + if self.__constellation is None: + LOGGER.error("SetConfig: no valid constellation") + return [False] * len(resources) + results = [] if len(resources) == 0: return results @@ -89,26 +99,21 @@ class XrDriver(_Driver): service_uuid = self.__cm_connection.service_uuid(key) if service_uuid: config = json.loads(config) - href = self.__cm_connection.create_or_update_connection_ifnames(service_uuid, config["input_sip"], config["output_sip"]) - if href: - LOGGER.info(f"SetConfig: Created service {service_uuid} as {href}") - results.append(True) - else: - LOGGER.error(f"SetConfig: Service creation failure for {service_uuid}") - results.append(False) + results.append(tf.set_config_for_service(self.__cm_connection, self.__constellation, service_uuid, config)) else: results.append(False) return results def DeleteConfig(self, resources: List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: - LOGGER.info(f"DeleteConfig {resources=}"); + LOGGER.info(f"DeleteConfig {resources=}") # Input looks like: # resources=[('/service[c8a35e81-88d8-4468-9afc-a8abd92a64d0:optical]', '{"uuid": "c8a35e81-88d8-4468-9afc-a8abd92a64d0:optical"}')] results = [] - if len(resources) == 0: return results + if len(resources) == 0: + return results # Temporary dummy version for key, _config in resources: @@ -124,6 +129,15 @@ class XrDriver(_Driver): LOGGER.info(f"DeleteConfig: Connection {service_uuid} deleted (was {str(connection)})") else: LOGGER.info(f"DeleteConfig: Connection {service_uuid} delete failure (was {str(connection)})") + + if self.__constellation.is_vti_mode(): + active_tc = self.__cm_connection.get_transport_capacity_by_teraflow_uuid(service_uuid) + if active_tc is not None: + if self.__cm_connection.delete_transport_capacity(active_tc.href): + LOGGER.info(f"DeleteConfig: Transport Capacity {active_tc} deleted") + else: + LOGGER.error(f"DeleteConfig: Transport Capacity {active_tc} delete failure") + results.append(was_deleted) else: results.append(False) diff --git a/src/device/service/drivers/xr/__init__.py b/src/device/service/drivers/xr/__init__.py index 6c466ff98..e69de29bb 100644 --- a/src/device/service/drivers/xr/__init__.py +++ b/src/device/service/drivers/xr/__init__.py @@ -1,27 +0,0 @@ -# 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. - -from device.service.driver_api._Driver import RESOURCE_ENDPOINTS, RESOURCE_INTERFACES, RESOURCE_NETWORK_INSTANCES - -ALL_RESOURCE_KEYS = [ - RESOURCE_ENDPOINTS, - RESOURCE_INTERFACES, -# RESOURCE_NETWORK_INSTANCES, -] - -RESOURCE_KEY_MAPPINGS = { - RESOURCE_ENDPOINTS : 'component', - RESOURCE_INTERFACES : 'interface', -# RESOURCE_NETWORK_INSTANCES: 'network_instance', -} diff --git a/src/device/service/drivers/xr/cm-cli.py b/src/device/service/drivers/xr/cm-cli.py index 2bd23fdb8..7cfc5b9ed 100755 --- a/src/device/service/drivers/xr/cm-cli.py +++ b/src/device/service/drivers/xr/cm-cli.py @@ -1,10 +1,15 @@ #!/usr/bin/env python3 - +#pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring # Test program for CmConnection - -import CmConnection import argparse import logging +import traceback +from typing import Tuple +from cm.cm_connection import CmConnection +from cm.tf_service import TFService +from cm.transport_capacity import TransportCapacity +from cm.connection import Connection +import cm.tf as tf logging.basicConfig(level=logging.INFO) @@ -16,49 +21,73 @@ parser.add_argument('password', help='Password') parser.add_argument('--list-constellations', action='store_true') parser.add_argument('--show-constellation-by-hub-name', nargs='?', type=str) -parser.add_argument('--create-connection', nargs='?', type=str, help="uuid;ifname;ifname") -parser.add_argument('--modify-connection', nargs='?', type=str, help="href;uuid;ifname;ifname") +parser.add_argument('--create-connection', nargs='?', type=str, help="uuid;ifname;ifname;capacity") +parser.add_argument('--modify-connection', nargs='?', type=str, help="href;uuid;ifname;ifname;capacity") parser.add_argument('--show-connection-by-name', nargs='?', type=str) parser.add_argument('--list-connections', action='store_true') parser.add_argument('--delete-connection', nargs='?', type=str, help="connection id, e.g. \"/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03\"") +parser.add_argument('--list-transport-capacities', action='store_true') +parser.add_argument('--create-transport-capacity', nargs='?', type=str, help="uuid;ifname;ifname;capacity") +parser.add_argument('--emulate-tf-set-config-service', nargs='?', type=str, help="hubmodule;uuid;ifname;ifname;capacity or hubmodule;uuid;ifname;ifname;capacity;FORCE-VTI-ON") args = parser.parse_args() -cm = CmConnection.CmConnection(args.ip, args.port, args.username, args.password, tls_verify=False) +def cli_create_string_to_tf_service(cli_create_str: str) -> TFService: + sargs = cli_create_str.split(";") + if len(sargs) == 3: + return TFService(*sargs, 0) + if len(sargs) == 4: + sargs[-1] = int(sargs[-1]) + return TFService(*sargs) + print("Invalid object create arguments. Expecting \"oid;ifname1;ifname2;bandwidthgbits\" or \"oid;ifname1;ifname2\", where ifname is form \"MODULE|PORT\"") + exit(-1) + +def cli_modify_string_to_tf_service(cli_create_str: str) -> Tuple[str, TFService]: + sargs = cli_create_str.split(";") + if len(sargs) == 4: + return (sargs[0], TFService(*sargs[1:], 0)) + if len(sargs) == 5: + sargs[-1] = int(sargs[-1]) + return (sargs[0], TFService(*sargs[1:])) + print("Invalid object create arguments. Expecting \"href;oid;ifname1;ifname2;bandwidthgbits\" or \"href;oid;ifname1;ifname2\", where ifname is form \"MODULE|PORT\"") + exit(-1) + +cm = CmConnection(args.ip, args.port, args.username, args.password, tls_verify=False) if not cm.Connect(): exit(-1) if args.list_constellations: constellations = cm.list_constellations() - for cid, if_list in constellations: - print("Constellation:", cid) - for if_name in if_list: + for constellation in constellations: + print("Constellation:", constellation.constellation_id) + for if_name in constellation.ifnames(): print(f" {if_name}") if args.show_constellation_by_hub_name: constellation = cm.get_constellation_by_hub_name(args.show_constellation_by_hub_name) if constellation: - (cid, if_list) = constellation - print("Constellation:", cid) - for if_name in if_list: + print(f"Constellation: {constellation.constellation_id}, traffic-mode: {constellation.traffic_mode}") + for if_name in constellation.ifnames(): print(f" {if_name}") if args.create_connection: - cc_args = args.create_connection.split(";") - if len(cc_args) != 3: - print("Invalid create connection arguments. Expecting \"oid;ifname1;ifname2\", where ifname is form \"MODULE|PORT\"") - exit(-1) - cm.create_connection_ifnames(*cc_args) + tf_service = cli_create_string_to_tf_service(args.create_connection) + connection = Connection(from_tf_service=tf_service) + created_service = cm.create_connection(connection) + if created_service: + print(f"Created {created_service} for {connection}") + else: + print(f"Failed to create {connection}") if args.modify_connection: + href, tf_service = cli_modify_string_to_tf_service(args.modify_connection) mc_args = args.modify_connection.split(";") - if len(mc_args) == 2: - cm.modify_connection_ifnames(mc_args[0], mc_args[1], None, None) - elif len(mc_args) == 4: - cm.modify_connection_ifnames(*mc_args) + connection = Connection(from_tf_service=tf_service) + result = cm.update_connection(href, connection) + if result: + print(f"Updated {href} for {connection}") else: - print("Invalid modify connection arguments. Expecting \"href;oid\" or \"href;oid;ifname1;ifname2\", where ifname is form \"MODULE|PORT\"") - exit(-1) + print(f"Failed to update {href} for {connection}") if args.show_connection_by_name: connection = cm.get_connection_by_name(args.show_connection_by_name) @@ -77,3 +106,45 @@ if args.delete_connection: else: print(f"Failed to delete {args.delete_connection}") +if args.list_transport_capacities: + tcs = cm.get_transport_capacities() + for tc in tcs: + print(str(tc)) + +if args.create_transport_capacity: + tf_service = cli_create_string_to_tf_service(args.create_transport_capacity) + tc = TransportCapacity(from_tf_service=tf_service) + created_service = cm.create_transport_capacity(tc) + if created_service: + print(f"Created {created_service} for {tc}") + else: + print(f"Failed to create {tc}") + +if args.emulate_tf_set_config_service: + eargs = args.emulate_tf_set_config_service.split(";") + if len(eargs) < 5: + print("Mandatory tokens missing for --emulate-tf-set-config-service") + exit(-1) + + hub_module_name, uuid, input_sip, output_sip, capacity_value = eargs[0:5] + capacity_value = int(capacity_value) + config = { + "input_sip": input_sip, + "output_sip": output_sip, + "capacity_value": capacity_value, + "capacity_unit": "gigabit" + } + + constellation = cm.get_constellation_by_hub_name(hub_module_name) + + # Allow testing some of the VTI code before we have CM that has VTI + if len(eargs) > 5 and eargs[5] == "FORCE-VTI-ON": + constellation.traffic_mode = "VTIMode" + + if constellation is None: + print(f"Unable to find constellation for hub-module {hub_module_name}") + exit(-1) + result = tf.set_config_for_service(cm, constellation, uuid, config) + print(f"Emulated SetConfig() for service result: {result}") + if isinstance(result, Exception): + traceback.print_exception(result) diff --git a/src/device/service/drivers/xr/cm/__init__.py b/src/device/service/drivers/xr/cm/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/device/service/drivers/xr/CmConnection.py b/src/device/service/drivers/xr/cm/cm_connection.py similarity index 56% rename from src/device/service/drivers/xr/CmConnection.py rename to src/device/service/drivers/xr/cm/cm_connection.py index 490597860..f86bb3179 100644 --- a/src/device/service/drivers/xr/CmConnection.py +++ b/src/device/service/drivers/xr/cm/cm_connection.py @@ -2,59 +2,19 @@ import logging import json import time -from typing import Tuple, Optional #Any, Iterator, List, , Union +from typing import Optional, List #Any, Iterator, , Union +import re import requests import urllib3 -import re +from .connection import Connection +from .transport_capacity import TransportCapacity +from .constellation import Constellation -LOGGER = logging.getLogger(__name__) +# https://confluence.infinera.com/display/CR/XR+Network+Service +# https://confluence.infinera.com/pages/viewpage.action?spaceKey=CR&title=XR+Network+Connection+Service#XRNetworkConnectionService-North-boundInterface +# https://bitbucket.infinera.com/projects/XRCM/repos/cm-api/browse/yaml/ncs/v1/ncs.yaml -class InvalidIfnameError(Exception): - def __init__(self, ifname): - # Call the base class constructor with the parameters it needs - super().__init__(f"Invalid interface name {ifname}, expecting format \"MODULENAME|PORTNAME\"") - -class ConnectionDeserializationError(Exception): - def __init__(self, msg): - # Call the base class constructor with the parameters it needs - super().__init__(msg) - -def ifname_to_module_and_aid(ifname: str) -> Tuple[str, str]: - a = ifname.split("|") - if len(a) != 2: - raise InvalidIfnameError(ifname) - return (a[0], a[1]) - -class Connection: - def __init__(self, from_json=None): - def get_endpoint_ifname(endpoint): - try: - return endpoint["state"]["moduleIf"]["moduleName"] + "|" + endpoint["state"]["moduleIf"]["clientIfAid"] - except KeyError: - return None - - if from_json: - try: - state = from_json["state"] - self.name = state["name"] if "name" in state else None #Name is optional - self.serviceMode = state["serviceMode"] - self.href = from_json["href"] - - self.endpoints = [] - for ep in from_json["endpoints"]: - ifname = get_endpoint_ifname(ep) - if ifname: - self.endpoints.append(ifname) - except KeyError as e: - raise ConnectionDeserializationError(f"Missing mandatory key, f{str(e)}") - else: - # May support other initializations in future - raise ConnectionDeserializationError("JSON dict missing") - - def __str__(self): - name = self.name if self.name else "" - endpoints = ", ".join(self.endpoints) - return f"name: {name}, id: {self.href}, service-mode: {self.serviceMode}, end-points: [{endpoints}]" +LOGGER = logging.getLogger(__name__) class ExpiringValue: def __init__(self, value, expiry): @@ -91,7 +51,7 @@ class CmConnection: else: response = requests.post(url, headers=headers, data=data, timeout=self.__timeout, verify=self.__tls_verify) - LOGGER.info(f"POST: {url} ==> {response.status_code}") + LOGGER.info(f"POST: {url} ==> {response.status_code=}, {response.text=}") resp = json.loads(response.text) return (response.status_code, resp) except requests.exceptions.Timeout: @@ -207,55 +167,14 @@ class CmConnection: def Connect(self) -> bool: return self.__acquire_access_token() - @staticmethod - def get_constellation_module_ifnames(module): - ifnames = [] - try: - module_state = module["state"] - module_name = module_state["module"]["moduleName"] - if "endpoints" in module_state: - for endpoint in module_state["endpoints"]: - try: - ifname = endpoint["moduleIf"]["clientIfAid"] - ifnames.append(f"{module_name}|{ifname}") - except KeyError: - pass - except KeyError: - pass - return ifnames - - @staticmethod - def get_constellation_ifnames(constellation): - ifnames = [] - if "hubModule" in constellation: - hub = constellation["hubModule"] - ifnames.extend(CmConnection.get_constellation_module_ifnames(hub)) - - if "leafModules" in constellation: - for leaf in constellation["leafModules"]: - ifnames.extend(CmConnection.get_constellation_module_ifnames(leaf)) - return ifnames - - @staticmethod - def get_ifnames_per_constellation(constellation): - ifnames = [] - try: - ports = CmConnection.get_constellation_ifnames(constellation) - constellation_id = constellation["id"] - for port in ports: - ifnames.append(port) - except KeyError: - return None - - return (constellation_id, ifnames) - - def list_constellations(self): + def list_constellations(self) -> List[Constellation]: status_code, constellations = self.__get_json("/api/v1/ns/xr-networks?content=expanded") if not constellations or status_code != 200: return [] - return [CmConnection.get_ifnames_per_constellation(c) for c in constellations] + return [Constellation(c) for c in constellations] + - def get_constellation_by_hub_name(self, hub_module_name: str): + def get_constellation_by_hub_name(self, hub_module_name: str) -> Optional[Constellation]: qparams = [ ('content', 'expanded'), ('q', '{"hubModule.state.module.moduleName": "' + hub_module_name + '"}') @@ -263,63 +182,119 @@ class CmConnection: status_code, constellations = self.__get_json("/api/v1/ns/xr-networks?content=expanded", params=qparams) if not constellations or status_code != 200 or len(constellations) != 1: return None - return CmConnection.get_ifnames_per_constellation(constellations[0]) - - @staticmethod - def create_connection_config(uid: str, serviceMode: Optional[str], mod1: Optional[str], aid1: Optional[str], mod2: Optional[str], aid2: Optional[str]) -> Connection: - name = f"TF:{uid}" - def create_endpoint(mod, aid): - ep = { - "selector": { - "ifSelectorByModuleName": { - "moduleName": mod, - "moduleClientIfAid": aid, - } - } - } - return ep - - connection = { "name" : name} - if serviceMode: - connection["serviceMode"] = serviceMode - endpoints = [] - if mod1: - endpoints.append(create_endpoint(mod1, aid1)) - if mod2: - endpoints.append(create_endpoint(mod2, aid2)) - if len(endpoints) > 0: - connection["endpoints"] = endpoints - return connection - - # All arguments are mandatory - def create_connection(self, uid, mod1, aid1, mod2, aid2) -> Optional[str]: + return Constellation(constellations[0]) + + def get_transport_capacities(self) -> List[TransportCapacity]: + status_code, tc = self.__get_json("/api/v1/ns/transport-capacities?content=expanded") + if not tc or status_code != 200: + return [] + return [TransportCapacity(from_json=t) for t in tc] + + def get_transport_capacity_by_name(self, tc_name: str) -> Optional[Connection]: + qparams = [ + ('content', 'expanded'), + ('q', '{"state.name": "' + tc_name + '"}') + ] + r = self.__get_json("/api/v1/ns/transport-capacities?content=expanded", params=qparams) + if r and r[0] == 200 and len(r[1]) == 1: + return TransportCapacity(from_json=r[1][0]) + else: + return None + + def get_transport_capacity_by_teraflow_uuid(self, uuid: str) -> Optional[Connection]: + return self.get_transport_capacity_by_name(f"TF:{uuid}") + + def create_transport_capacity(self, tc: TransportCapacity) -> Optional[str]: + # Create wants a list, so wrap connection to list + tc_config = [tc.create_config()] + resp = self.__post("/api/v1/ns/transport-capacities", tc_config) + if resp and resp[0] == 202 and len(resp[1]) == 1 and "href" in resp[1][0]: + tc.href = resp[1][0]["href"] + LOGGER.info(f"Created transport-capcity {tc}") + #LOGGER.info(self.__get_json(f"/api/v1/ns/transport-capacities{tc.href}?content=expanded")) + return tc.href + else: + return None + + def delete_transport_capacity(self, href: str) -> bool: + resp = self.__delete(f"/api/v1/ns/transport-capacities{href}") + + # Returns empty body + if resp and resp[0] == 202: + LOGGER.info(f"Deleted transport-capacity {href=}") + return True + else: + LOGGER.info(f"Deleting transport-capacity {href=} failed, status {resp[0]}") + return False + + def create_connection(self, connection: Connection) -> Optional[str]: # Create wants a list, so wrap connection to list - connection = [CmConnection.create_connection_config(uid, "portMode", mod1, aid1, mod2, aid2)] - resp = self.__post("/api/v1/ncs/network-connections", connection) + cfg = [connection.create_config()] + + resp = self.__post("/api/v1/ncs/network-connections", cfg) if resp and resp[0] == 202 and len(resp[1]) == 1 and "href" in resp[1][0]: - created_resource = resp[1][0]["href"] - LOGGER.info(f"Created connection {created_resource} {uid=}, {mod1=}, {aid1=}, {mod2=}, {aid2=}") - # FIXME: remove - LOGGER.info(self.__get_json(f"/api/v1/ncs{created_resource}?content=expanded")) - return created_resource + connection.href = resp[1][0]["href"] + LOGGER.info(f"Created connection {connection}") + return connection.href else: + LOGGER.error(f"Create failure for connection {connection}, result {resp}") return None - # Modules and aids are optional. Uid is Teraflow UID, and is stored in mae field - def modify_connection(self, href: str, uid: str, service_mode: Optional[str], mod1: Optional[str]=None, aid1: Optional[str]=None, mod2: Optional[str]=None, aid2: Optional[str]=None) -> Optional[str]: - connection = CmConnection.create_connection_config(uid, service_mode, mod1, aid1, mod2, aid2) - resp = self.__put(f"/api/v1/ncs{href}", connection) + def update_connection(self, href: str, connection: Connection, existing_connection: Optional[Connection]=None) -> Optional[str]: + cfg = connection.create_config() + + # Endpoint updates + # Current CM implementation returns 501 (not implemented) for all of these actions + + # CM does not accept endpoint updates properly in same format that is used in initial creation. + # Instead we work around by using more granular APIs. + if "endpoints" in cfg: + del cfg["endpoints"] + if existing_connection is None: + existing_connection = self.get_connection_by_href(href) + print(existing_connection) + ep_deletes, ep_creates, ep_updates = connection.get_endpoint_updates(existing_connection) + #print(ep_deletes) + #print(ep_creates) + #print(ep_updates) + + # Perform deletes + for ep_href in ep_deletes: + resp = self.__delete(f"/api/v1/ncs{ep_href}") + if resp and resp[0] == 202: + LOGGER.info(f"update_connection: EP-UPDATE: Deleted connection endpoint {ep_href}") + else: + LOGGER.info(f"update_connection: EP-UPDATE: Failed to delete connection endpoint {ep_href}: {resp}") + + # Update capacities for otherwise similar endpoints + for ep_href, ep_cfg in ep_updates: + resp = self.__put(f"/api/v1/ncs{ep_href}", ep_cfg) + if resp and resp[0] == 202: + LOGGER.info(f"update_connection: EP-UPDATE: Updated connection endpoint {ep_href} with {ep_cfg}") + else: + LOGGER.info(f"update_connection: EP-UPDATE: Failed to update connection endpoint {ep_href} with {ep_cfg}: {resp}") + + # Perform adds + resp = self.__post(f"/api/v1/ncs{href}/endpoints", ep_creates) + if resp and resp[0] == 202 and len(resp[1]) == len(ep_creates): + LOGGER.info(f"update_connection: EP-UPDATE: Created connection endpoints {resp[1]} with {ep_creates}") + else: + LOGGER.info(f"update_connection: EP-UPDATE: Failed to create connection endpoints {resp[1]} with {ep_creates}: {resp}") + + # Connection update (excluding endpoints) + resp = self.__put(f"/api/v1/ncs{href}", cfg) # Returns empty body if resp and resp[0] == 202: - LOGGER.info(f"Updated connection {href=}, {uid=}, {service_mode=}, {mod1=}, {aid1=}, {mod2=}, {aid2=}") + LOGGER.info(f"update_connection: Updated connection {connection}") # Return href used for update to be consisten with create return href else: + LOGGER.error(f"update_connection: Update failure for connection {connection}, result {resp}") return None def delete_connection(self, href: str) -> bool: resp = self.__delete(f"/api/v1/ncs{href}") - print(resp) + #print(resp) # Returns empty body if resp and resp[0] == 202: LOGGER.info(f"Deleted connection {href=}") @@ -327,28 +302,13 @@ class CmConnection: else: return False - def create_connection_ifnames(self, uid: str, ifname1: str, ifname2: str): - module1, aid1 = ifname_to_module_and_aid(ifname1) - module2, aid2 = ifname_to_module_and_aid(ifname2) - return self.create_connection(uid, module1, aid1, module2, aid2) - - def modify_connection_ifnames(self, href: str, uid: str, ifname1: Optional[str], ifname2: Optional[str], service_mode: Optional[str] =None): - # Only uid and href are mandatory - module1, aid1 = ifname_to_module_and_aid(ifname1) if ifname1 else (None, None) - module2, aid2 = ifname_to_module_and_aid(ifname2) if ifname2 else (None, None) - return self.modify_connection(href, uid, service_mode, module1, aid1, module2, aid2) - # Always does the correct thing, that is update if present, otherwise create - def create_or_update_connection_ifnames(self, uid: str, ifname1: str, ifname2: str) -> Optional[str]: - module1, aid1 = ifname_to_module_and_aid(ifname1) - module2, aid2 = ifname_to_module_and_aid(ifname2) - - name = f"TF:{uid}" - existing_connection = self.get_connection_by_name(name) + def create_or_update_connection(self, connection: Connection) -> Optional[str]: + existing_connection = self.get_connection_by_name(connection.name) if existing_connection: - return self.modify_connection(existing_connection.href, uid, module1, aid1, module2, aid2) + return self.update_connection(existing_connection.href, connection) else: - return self.create_connection(uid, module1, aid1, module2, aid2) + return self.create_connection(connection) def get_connection_by_name(self, connection_name: str) -> Optional[Connection]: qparams = [ @@ -361,6 +321,16 @@ class CmConnection: else: return None + def get_connection_by_href(self, href: str) -> Optional[Connection]: + qparams = [ + ('content', 'expanded'), + ] + r = self.__get_json(f"/api/v1/ncs{href}", params=qparams) + if r and r[0] == 200: + return Connection(from_json=r[1]) + else: + return None + def get_connection_by_teraflow_uuid(self, uuid: str) -> Optional[Connection]: return self.get_connection_by_name(f"TF:{uuid}") diff --git a/src/device/service/drivers/xr/cm/connection.py b/src/device/service/drivers/xr/cm/connection.py new file mode 100644 index 000000000..78a853321 --- /dev/null +++ b/src/device/service/drivers/xr/cm/connection.py @@ -0,0 +1,165 @@ +#pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +from typing import Dict, Optional +from dataclasses import dataclass +from .tf_service import TFService +from .utils import make_selector, set_optional_parameter + +class InconsistentVlanConfiguration(Exception): + pass + +@dataclass +class CEndpoint: + module: str + port: str + # Emulated/translated VLAN. May also be a letter + # Only present on TF side, never on gets from CM. + # VLAN is never transmitted to wire on endpoint, it is purely an internal construct + # However VLAN is part of the whole connection + vlan: str + capacity: int + href: Optional[str] + + + def ifname(self) -> str: + if self.vlan is None: + return self.module + "|" + self.port + else: + return self.module + "|" + self.port + "." + self.vlan + + def portname(self) -> str: + return self.module + "|" + self.port + + def __str__(self): + return f"({self.ifname()}, {self.capacity})" + + def create_config(self) -> Dict[str, any]: + cfg = { + # VLAN is intentionally ignored here (None argument below) + "selector": make_selector(self.module, self.port, None) + } + if self.capacity > 0: + cfg["capacity"] = self.capacity + + return cfg + +class ConnectionDeserializationError(Exception): + pass + +class Connection: + def __init__(self, from_json: Optional[Dict[str, any]] = None, from_tf_service: Optional[TFService] = None): + def get_endpoint_mod_aid(endpoint: Dict[str, any]) -> Optional[str]: + try: + return (endpoint["state"]["moduleIf"]["moduleName"], endpoint["state"]["moduleIf"]["clientIfAid"]) + except KeyError: + return None + + def get_endpoint_capacity(endpoint: Dict[str, any]) -> int: + try: + return int(endpoint["state"]["capacity"]) + except KeyError: + return 0 + + if from_json: + try: + config = from_json["config"] + state = from_json["state"] + self.name = state["name"] if "name" in state else None #Name is optional + self.serviceMode = state["serviceMode"] + self.mc = config["mc"] if "mc" in config else None + self.vlan_filter = state["outerVID"] if "outerVID" in state else None + self.href = from_json["href"] + + self.endpoints = [] + for ep in from_json["endpoints"]: + ep_mod_aip = get_endpoint_mod_aid(ep) + if ep_mod_aip: + self.endpoints.append(CEndpoint(*ep_mod_aip, None, get_endpoint_capacity(ep), ep["href"])) + self.cm_data = from_json + except KeyError as e: + raise ConnectionDeserializationError(f"Missing mandatory key {str(e)}") from e + elif from_tf_service: + self.href = None + self.name = from_tf_service.name() + self.endpoints = [CEndpoint(mod, port, vlan, from_tf_service.capacity, None) for mod,port,vlan in from_tf_service.get_endpoints_mod_aid_vlan()] + # Service mode guessing has to be AFTER endpoint assigment. + # The heuristic used is perfectly valid in context of TF where we always encode + # VLANs to interface names. Correspondingly cm-cli user has to know + # to use VLANs on low level test APIs when using VTI mode. + self.serviceMode = self.__guess_service_mode_from_emulated_enpoints() + if self.serviceMode == "portMode": + self.vlan_filter = None + self.mc = None + else: + self.vlan_filter = str(self.__guess_vlan_id()) + " " # Needs to be in string format, can contain ranges, regexp is buggy, trailin space is needed for single VLAN + self.mc = "matchOuterVID" + + self.cm_data = None + else: + # May support other initializations in future + raise ConnectionDeserializationError("JSON dict missing") + + def __str__(self): + name = self.name if self.name else "" + endpoints = ", ".join((str(ep) for ep in self.endpoints)) + return f"name: {name}, id: {self.href}, service-mode: {self.serviceMode}, end-points: [{endpoints}]" + + def __guess_service_mode_from_emulated_enpoints(self): + for ep in self.endpoints: + if ep.vlan is not None: + return "vtiP2pSymmetric" + return "portMode" + + def __guess_vlan_id(self) -> int: + vlans = [] + for ep in self.endpoints: + if ep.vlan is not None and ep.vlan.isnumeric(): + vlans.append(int(ep.vlan)) + if not vlans: + raise InconsistentVlanConfiguration("VLAN ID is not encoded in TF interface names for VTI mode service") + else: + for vlan in vlans: + if vlan != vlans[0]: + raise InconsistentVlanConfiguration(f"VLAN configuration must match at both ends of the connection, {vlans[0]} != {vlan}") + return vlans[0] + + def create_config(self) -> Dict[str, any]: + cfg = {} + set_optional_parameter(cfg, "name", self.name) + cfg["serviceMode"] = self.serviceMode + if self.endpoints: + cfg["endpoints"] = [ep.create_config() for ep in self.endpoints] + set_optional_parameter(cfg, "outerVID", self.vlan_filter) + set_optional_parameter(cfg, "mc", self.mc) + #print(cfg) + return cfg + + def get_port_map(self) -> Dict[str, CEndpoint]: + return {ep.portname(): ep for ep in self.endpoints } + + # Type hint has to be string, because future annotations (enclosing class) + # is not yet widely available + def get_endpoint_updates(self, old: Optional['Connection']): # -> Tuple[List[str], List[Dict[str, any], List[Tuple[str, Dict[str, any]]]]]: + new_ports = self.get_port_map() + + if old is None: + return ([], [new_ep.create_config() for new_ep in new_ports.values()], []) + + # Can only compute difference against get from CM, as hrefs are needed + assert old.cm_data is not None + + old_ports = old.get_port_map() + + deletes = [] + creates = [] + updates = [] + for port, old_ep in old_ports.items(): + if port not in new_ports: + assert old_ep.href is not None + deletes.append(old_ep.href) + + for port, new_ep in new_ports.items(): + if port not in old_ports: + creates.append(new_ep.create_config()) + elif old_ports[port].capacity != new_ep.capacity: + updates.append((old_ports[port].href, {"capacity": new_ep.capacity})) + return deletes, creates, updates diff --git a/src/device/service/drivers/xr/cm/constellation.py b/src/device/service/drivers/xr/cm/constellation.py new file mode 100644 index 000000000..255d267d8 --- /dev/null +++ b/src/device/service/drivers/xr/cm/constellation.py @@ -0,0 +1,55 @@ +#pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring, wildcard-import, unused-wildcard-import +from typing import List +from .utils import * + +class ConstellationDeserializationError(Exception): + pass + +class Constellation: + def __init__(self, from_json=None): + if from_json: + try: + self.constellation_id = from_json["id"] + self.__hub_interfaces = [] + self.__leaf_interfaces = [] + self.__traffic_mode = None + # Intentional simplification for Teraflow. Constellation could have + # diverse traffic modes, however that does not occur in intended TF usage. + if "hubModule" in from_json: + hub = from_json["hubModule"] + self.traffic_mode = hub["state"]["module"]["trafficMode"] + self.__hub_interfaces.extend(get_constellation_module_ifnames(hub)) + if "leafModules" in from_json: + for leaf in from_json["leafModules"]: + if not self.__traffic_mode: + self.traffic_mode = leaf["state"]["module"]["trafficMode"] + self.__leaf_interfaces.extend(get_constellation_module_ifnames(leaf)) + except KeyError as e: + raise ConstellationDeserializationError(f"Missing mandatory key {str(e)}") from e + else: + # May support other initializations in future + raise ConstellationDeserializationError("JSON dict missing") + + def add_vlan_posfix(ifname, is_hub): + if is_hub: + # +100 so we don't need to worry about special meanings of VLANs 0 and 1 + return [f"{ifname}.{vlan+100}" for vlan in range(0,16)] + else: + return [f"{ifname}.{chr(ord('a') + vlan)}" for vlan in range(0,16)] + + self.__vti_hub_interfaces = [] + self.__vti_leaf_interfaces = [] + if self.is_vti_mode(): + for ifname in self.__hub_interfaces: + self.__vti_hub_interfaces.extend(add_vlan_posfix(ifname, True)) + for ifname in self.__leaf_interfaces: + self.__vti_leaf_interfaces.extend(add_vlan_posfix(ifname, False)) + + def ifnames(self) -> List[str]: + if self.is_vti_mode(): + return self.__vti_hub_interfaces + self.__vti_leaf_interfaces + else: + return self.__hub_interfaces + self.__leaf_interfaces + + def is_vti_mode(self) -> bool: + return self.traffic_mode != "L1Mode" diff --git a/src/device/service/drivers/xr/cm/tests/__init__.py b/src/device/service/drivers/xr/cm/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/device/service/drivers/xr/cm/tests/resources/connections-expanded.json b/src/device/service/drivers/xr/cm/tests/resources/connections-expanded.json new file mode 100644 index 000000000..f9f064ea2 --- /dev/null +++ b/src/device/service/drivers/xr/cm/tests/resources/connections-expanded.json @@ -0,0 +1,290 @@ +[ + { + "config": { + "name": "FooBar123", + "serviceMode": "portMode" + }, + "endpoints": [ + { + "acs": [], + "config": { + "selector": {} + }, + "href": "/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03/endpoints/230516d0-7e38-44b1-b174-1ba7d4454ee6", + "id": "230516d0-7e38-44b1-b174-1ba7d4454ee6", + "parentId": "4505d5d3-b2f3-40b8-8ec2-4a5b28523c03", + "rt": [ + "cm.network-connection.endpoint" + ], + "state": { + "hostPort": { + "chassisId": "192.168.101.1", + "chassisIdSubtype": "networkAddress", + "name": "", + "portDescr": "et-1/0/0:0", + "portId": "et-1/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:11:01", + "sysName": "PaloAlto" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100, + "currentRole": "leaf", + "macAddress": "00:0B:F8:00:01:01", + "moduleId": "555a0f6e-285d-4a97-70f2-9fa4201d422d", + "moduleName": "XR LEAF 1", + "serialNumber": "00000000B" + } + } + }, + { + "acs": [], + "config": { + "selector": {} + }, + "href": "/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03/endpoints/1d58ba8f-4d51-4213-83e1-97a0e0bdd388", + "id": "1d58ba8f-4d51-4213-83e1-97a0e0bdd388", + "parentId": "4505d5d3-b2f3-40b8-8ec2-4a5b28523c03", + "rt": [ + "cm.network-connection.endpoint" + ], + "state": { + "hostPort": { + "chassisId": "192.168.100.1", + "chassisIdSubtype": "networkAddress", + "name": "", + "portDescr": "et-1/0/0:0", + "portId": "et-1/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:00:11", + "sysName": "SanJose" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100, + "currentRole": "hub", + "macAddress": "00:0B:F8:00:00:01", + "moduleId": "e1d3a030-4f19-4efc-50c0-5a48609ad356", + "moduleName": "XR HUB 1", + "serialNumber": "000000009" + } + } + } + ], + "href": "/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03", + "id": "4505d5d3-b2f3-40b8-8ec2-4a5b28523c03", + "lcs": [ + { + "config": { + "clientAid": "XR-T1", + "direction": "txRx", + "dscgAid": "XR-L1-C1-DSCG1", + "moduleId": "555a0f6e-285d-4a97-70f2-9fa4201d422d" + }, + "href": "/lcs/5872e191-774f-4ae9-841a-ea743be01973", + "id": "5872e191-774f-4ae9-841a-ea743be01973", + "parentIds": [ + "4505d5d3-b2f3-40b8-8ec2-4a5b28523c03" + ], + "rt": [ + "cm.network-connection.local-connection" + ], + "state": { + "clientAid": "XR-T1", + "colId": 1, + "direction": "txRx", + "dscgAid": "XR-L1-C1-DSCG1", + "lcAid": "XR-T1,XR-L1-C1-DSCG1-1-ODUji-1", + "lineAid": "XR-L1-C1-DSCG1-1-ODUji-1", + "macAddress": "00:0B:F8:00:01:01", + "moduleId": "555a0f6e-285d-4a97-70f2-9fa4201d422d", + "remoteClientAid": "XR-T1", + "remoteModuleId": "00:0B:F8:00:00:01" + } + }, + { + "config": { + "clientAid": "XR-T1", + "direction": "txRx", + "dscgAid": "XR-L1-C1-DSCG1", + "moduleId": "e1d3a030-4f19-4efc-50c0-5a48609ad356" + }, + "href": "/lcs/c96a7954-2d12-48aa-8cfb-6a3cf5566cb0", + "id": "c96a7954-2d12-48aa-8cfb-6a3cf5566cb0", + "parentIds": [ + "4505d5d3-b2f3-40b8-8ec2-4a5b28523c03" + ], + "rt": [ + "cm.network-connection.local-connection" + ], + "state": { + "clientAid": "XR-T1", + "colId": 1, + "direction": "txRx", + "dscgAid": "XR-L1-C1-DSCG1", + "lcAid": "XR-T1,XR-L1-C1-DSCG1-1-ODUji-1", + "lineAid": "XR-L1-C1-DSCG1-1-ODUji-1", + "macAddress": "00:0B:F8:00:00:01", + "moduleId": "e1d3a030-4f19-4efc-50c0-5a48609ad356", + "remoteClientAid": "XR-T1", + "remoteModuleId": "00:0B:F8:00:01:01" + } + } + ], + "rt": [ + "cm.network-connection" + ], + "state": { + "createdBy": "host", + "lifecycleState": "configured", + "name": "FooBar123", + "serviceMode": "portMode" + } + }, + { + "config": { + "serviceMode": "portMode" + }, + "endpoints": [ + { + "acs": [], + "config": { + "selector": {} + }, + "href": "/network-connections/138f0cc0-3dc6-4195-97c0-2cbed5fd59ba/endpoints/59027aa4-858b-4d62-86b9-0f2d3738619c", + "id": "59027aa4-858b-4d62-86b9-0f2d3738619c", + "parentId": "138f0cc0-3dc6-4195-97c0-2cbed5fd59ba", + "rt": [ + "cm.network-connection.endpoint" + ], + "state": { + "hostPort": { + "chassisId": "192.168.100.1", + "chassisIdSubtype": "networkAddress", + "name": "", + "portDescr": "et-2/0/0:0", + "portId": "et-2/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:00:21", + "sysName": "SanJose" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100, + "currentRole": "hub", + "macAddress": "00:0B:F8:00:00:02", + "moduleId": "b6577725-9939-4b60-6be9-603bd210cde1", + "moduleName": "XR HUB 2", + "serialNumber": "00000000A" + } + } + }, + { + "acs": [], + "config": { + "selector": {} + }, + "href": "/network-connections/138f0cc0-3dc6-4195-97c0-2cbed5fd59ba/endpoints/b2fc53e2-41a1-4fe5-8f03-f91a11e52661", + "id": "b2fc53e2-41a1-4fe5-8f03-f91a11e52661", + "parentId": "138f0cc0-3dc6-4195-97c0-2cbed5fd59ba", + "rt": [ + "cm.network-connection.endpoint" + ], + "state": { + "hostPort": { + "chassisId": "192.168.101.3", + "chassisIdSubtype": "networkAddress", + "name": "", + "portDescr": "et-3/0/0:0", + "portId": "et-3/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:13:01", + "sysName": "Sunnnyvale" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100, + "currentRole": "leaf", + "macAddress": "00:0B:F8:00:01:03", + "moduleId": "00c6875e-21bf-4934-51c7-125ebd8d0559", + "moduleName": "XR LEAF 3", + "serialNumber": "00000000D" + } + } + } + ], + "href": "/network-connections/138f0cc0-3dc6-4195-97c0-2cbed5fd59ba", + "id": "138f0cc0-3dc6-4195-97c0-2cbed5fd59ba", + "lcs": [ + { + "config": { + "clientAid": "XR-T1", + "direction": "txRx", + "dscgAid": "XR-L1-C1-DSCG1", + "moduleId": "00c6875e-21bf-4934-51c7-125ebd8d0559" + }, + "href": "/lcs/0f8fe422-4f4e-4e78-8489-ee85031e083c", + "id": "0f8fe422-4f4e-4e78-8489-ee85031e083c", + "parentIds": [ + "138f0cc0-3dc6-4195-97c0-2cbed5fd59ba" + ], + "rt": [ + "cm.network-connection.local-connection" + ], + "state": { + "clientAid": "XR-T1", + "colId": 1, + "direction": "txRx", + "dscgAid": "XR-L1-C1-DSCG1", + "lcAid": "XR-T1,XR-L1-C1-DSCG1-1-ODUji-1", + "lineAid": "XR-L1-C1-DSCG1-1-ODUji-1", + "macAddress": "00:0B:F8:00:01:03", + "moduleId": "00c6875e-21bf-4934-51c7-125ebd8d0559", + "remoteClientAid": "XR-T1", + "remoteModuleId": "00:0B:F8:00:00:02" + } + }, + { + "config": { + "clientAid": "XR-T1", + "direction": "txRx", + "dscgAid": "XR-L1-C1-DSCG1", + "moduleId": "b6577725-9939-4b60-6be9-603bd210cde1" + }, + "href": "/lcs/7c769310-22b5-4d4c-8e9d-386a8083c611", + "id": "7c769310-22b5-4d4c-8e9d-386a8083c611", + "parentIds": [ + "138f0cc0-3dc6-4195-97c0-2cbed5fd59ba" + ], + "rt": [ + "cm.network-connection.local-connection" + ], + "state": { + "clientAid": "XR-T1", + "colId": 1, + "direction": "txRx", + "dscgAid": "XR-L1-C1-DSCG1", + "lcAid": "XR-T1,XR-L1-C1-DSCG1-1-ODUji-1", + "lineAid": "XR-L1-C1-DSCG1-1-ODUji-1", + "macAddress": "00:0B:F8:00:00:02", + "moduleId": "b6577725-9939-4b60-6be9-603bd210cde1", + "remoteClientAid": "XR-T1", + "remoteModuleId": "00:0B:F8:00:01:03" + } + } + ], + "rt": [ + "cm.network-connection" + ], + "state": { + "createdBy": "host", + "lifecycleState": "configured", + "serviceMode": "portMode" + } + } +] \ No newline at end of file diff --git a/src/device/service/drivers/xr/cm/tests/resources/constellations-expanded.json b/src/device/service/drivers/xr/cm/tests/resources/constellations-expanded.json new file mode 100644 index 000000000..c4aff84bf --- /dev/null +++ b/src/device/service/drivers/xr/cm/tests/resources/constellations-expanded.json @@ -0,0 +1,1768 @@ +[ + { + "config": { + "constellationFrequency": 191000000, + "modulation": "16QAM", + "name": "FooConstellation" + }, + "href": "/xr-networks/169860ee-c1b8-4ae1-8f6e-81920a6ee09d", + "hubModule": { + "config": { + "module": { + "fiberConnectionMode": "dual", + "maxAllowedDSCs": 16, + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + }, + "selector": { + "moduleSelectorByModuleName": { + "moduleName": "p2mp_HUB2" + } + } + }, + "href": "/xr-networks/169860ee-c1b8-4ae1-8f6e-81920a6ee09d/hubModule", + "id": "ec41b4b3-b8bf-4903-8054-8f85891dc057", + "parentId": "169860ee-c1b8-4ae1-8f6e-81920a6ee09d", + "rt": [ + "cm.xr-network.hubModule" + ], + "state": { + "lifecycleState": "pendingConfiguration", + "module": { + "baudRate": 50, + "capacity": 400, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 0, + "currentRole": "unknown", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:FF:00:01", + "modulation": "16QAM", + "moduleId": "d01099e6-fa38-40dd-5c0d-6e7097b3f691", + "moduleName": "p2mp_HUB2", + "ncoFrequency": 0, + "operatingFrequency": 0, + "roleStatus": "scanning", + "serialNumber": "000000002", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + "id": "169860ee-c1b8-4ae1-8f6e-81920a6ee09d", + "leafModules": [ + { + "config": { + "module": { + "fiberConnectionMode": "dual", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + }, + "selector": { + "moduleSelectorByModuleName": { + "moduleName": "p2mp_LEAF4" + } + } + }, + "href": "/xr-networks/169860ee-c1b8-4ae1-8f6e-81920a6ee09d/leafModules/169a434d-90c7-429a-928a-aef7013af715", + "id": "169a434d-90c7-429a-928a-aef7013af715", + "parentId": "169860ee-c1b8-4ae1-8f6e-81920a6ee09d", + "rt": [ + "cm.xr-network.leafModule" + ], + "state": { + "lifecycleState": "pendingConfiguration", + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 0, + "currentRole": "unknown", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:FF:00:06", + "modulation": "16QAM", + "moduleId": "d9580d90-1dbd-43d8-64d6-20afba0dbfe7", + "moduleName": "p2mp_LEAF4", + "ncoFrequency": 0, + "operatingFrequency": 0, + "roleStatus": "scanning", + "serialNumber": "000000006", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + } + ], + "rt": [ + "cm.xr-network" + ], + "state": { + "constellationFrequency": 0, + "lifecycleState": "pendingConfiguration", + "modulation": "16QAM", + "name": "FooConstellation" + } + }, + { + "href": "/xr-networks/84d9b09c-017d-4ac5-b8cd-d1102db1e070", + "hubModule": { + "href": "/xr-networks/84d9b09c-017d-4ac5-b8cd-d1102db1e070/hubModule", + "id": "921bdde3-88e3-40e6-8f6a-d5fffb9909fa", + "parentId": "84d9b09c-017d-4ac5-b8cd-d1102db1e070", + "rt": [ + "cm.xr-network.hubModule" + ], + "state": { + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.100.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-2/0/0:3", + "portId": "et-2/0/0:3", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:00:24" + }, + "moduleIf": { + "clientIfAid": "XR-T4", + "clientIfColId": 4, + "clientIfPortSpeed": 100 + } + }, + { + "hostPort": { + "chassisId": "192.168.100.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-2/0/0:0", + "portId": "et-2/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:00:21" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + }, + { + "hostPort": { + "chassisId": "192.168.100.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-2/0/0:1", + "portId": "et-2/0/0:1", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:00:22" + }, + "moduleIf": { + "clientIfAid": "XR-T2", + "clientIfColId": 2, + "clientIfPortSpeed": 100 + } + }, + { + "hostPort": { + "chassisId": "192.168.100.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-2/0/0:2", + "portId": "et-2/0/0:2", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:00:23" + }, + "moduleIf": { + "clientIfAid": "XR-T3", + "clientIfColId": 3, + "clientIfPortSpeed": 100 + } + } + ], + "lifecycleState": "configured", + "module": { + "baudRate": 50, + "capacity": 400, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 193000000, + "currentRole": "hub", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:00:02", + "modulation": "16QAM", + "moduleId": "3691e001-1e0c-44ae-5fd6-4f4acb8b945c", + "moduleName": "XR HUB 2", + "ncoFrequency": 0, + "operatingFrequency": 193000000, + "roleStatus": "ready", + "serialNumber": "00000000A", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + "id": "84d9b09c-017d-4ac5-b8cd-d1102db1e070", + "leafModules": [ + { + "href": "/xr-networks/84d9b09c-017d-4ac5-b8cd-d1102db1e070/leafModules/2a3503b0-4987-4720-8cc9-11a01183905c", + "id": "2a3503b0-4987-4720-8cc9-11a01183905c", + "parentId": "84d9b09c-017d-4ac5-b8cd-d1102db1e070", + "rt": [ + "cm.xr-network.leafModule" + ], + "state": { + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.3", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-3/0/0:0", + "portId": "et-3/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:13:01" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "lifecycleState": "configured", + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 193000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:03", + "maxAllowedDSCs": 4, + "modulation": "16QAM", + "moduleId": "1d23a10c-f5fb-4c2e-69ed-92608ce8e54c", + "moduleName": "XR LEAF 3", + "ncoFrequency": 0, + "operatingFrequency": 193000000, + "roleStatus": "ready", + "serialNumber": "00000000D", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + } + ], + "reachableModules": [ + { + "href": "/xr-networks/84d9b09c-017d-4ac5-b8cd-d1102db1e070/reachableModules/957dd5b2-2528-4d24-8d1f-1b8ca7fac33d", + "id": "957dd5b2-2528-4d24-8d1f-1b8ca7fac33d", + "parentId": "84d9b09c-017d-4ac5-b8cd-d1102db1e070", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:08Z", + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.3", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-3/0/0:0", + "portId": "et-3/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:13:01" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 193000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:03", + "modulation": "16QAM", + "moduleId": "1d23a10c-f5fb-4c2e-69ed-92608ce8e54c", + "moduleName": "XR LEAF 3", + "ncoFrequency": 0, + "operatingFrequency": 193000000, + "roleStatus": "ready", + "serialNumber": "00000000D", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + { + "href": "/xr-networks/84d9b09c-017d-4ac5-b8cd-d1102db1e070/reachableModules/d159d5e7-53d3-4770-94bf-4a53f22798e5", + "id": "d159d5e7-53d3-4770-94bf-4a53f22798e5", + "parentId": "84d9b09c-017d-4ac5-b8cd-d1102db1e070", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:05Z", + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-1/0/0:0", + "portId": "et-1/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:11:01" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:01", + "modulation": "16QAM", + "moduleId": "9b7fb759-c952-420a-7b1e-5b3b496e1f66", + "moduleName": "XR LEAF 1", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "00000000B", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + { + "href": "/xr-networks/84d9b09c-017d-4ac5-b8cd-d1102db1e070/reachableModules/d9172426-fbe8-4e1b-9c80-373f09219831", + "id": "d9172426-fbe8-4e1b-9c80-373f09219831", + "parentId": "84d9b09c-017d-4ac5-b8cd-d1102db1e070", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:08Z", + "endpoints": [ + { + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 193000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:03", + "modulation": "16QAM", + "moduleId": "9008a605-e6a1-4afd-5728-35dd711a4a15", + "moduleName": "XR LEAF 3", + "ncoFrequency": 0, + "operatingFrequency": 193000000, + "roleStatus": "ready", + "serialNumber": "00000000D", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + { + "href": "/xr-networks/84d9b09c-017d-4ac5-b8cd-d1102db1e070/reachableModules/0074af01-7f70-48ab-9d98-e810af9f278a", + "id": "0074af01-7f70-48ab-9d98-e810af9f278a", + "parentId": "84d9b09c-017d-4ac5-b8cd-d1102db1e070", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:05Z", + "endpoints": [ + { + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:01", + "modulation": "16QAM", + "moduleId": "4f44c0b2-3d63-465d-6413-5bc46dfa5fbf", + "moduleName": "XR LEAF 1", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "00000000B", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + } + ], + "rt": [ + "cm.xr-network" + ], + "state": { + "constellationFrequency": 193000000, + "controlLinks": [ + { + "conState": "active", + "destinationModuleId": "3691e001-1e0c-44ae-5fd6-4f4acb8b945c", + "lastConStateChange": "2022-06-28T09:04:07Z", + "sourceModuleId": "1d23a10c-f5fb-4c2e-69ed-92608ce8e54c" + }, + { + "conState": "active", + "destinationModuleId": "1d23a10c-f5fb-4c2e-69ed-92608ce8e54c", + "lastConStateChange": "2022-06-28T09:04:08Z", + "sourceModuleId": "3691e001-1e0c-44ae-5fd6-4f4acb8b945c" + }, + { + "conState": "active", + "destinationModuleId": "9008a605-e6a1-4afd-5728-35dd711a4a15", + "lastConStateChange": "2022-06-28T09:04:08Z", + "sourceModuleId": "3691e001-1e0c-44ae-5fd6-4f4acb8b945c" + }, + { + "conState": "active", + "destinationModuleId": "3691e001-1e0c-44ae-5fd6-4f4acb8b945c", + "lastConStateChange": "2022-06-28T09:04:07Z", + "sourceModuleId": "9008a605-e6a1-4afd-5728-35dd711a4a15" + } + ], + "lifecycleState": "configured", + "modulation": "16QAM" + } + }, + { + "href": "/xr-networks/b7d4bbac-8d87-4dfe-af52-be1ef0b905d8", + "hubModule": { + "href": "/xr-networks/b7d4bbac-8d87-4dfe-af52-be1ef0b905d8/hubModule", + "id": "975df7fe-dbd8-4681-83f3-2f2a57585b67", + "parentId": "b7d4bbac-8d87-4dfe-af52-be1ef0b905d8", + "rt": [ + "cm.xr-network.hubModule" + ], + "state": { + "endpoints": [ + { + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + }, + { + "moduleIf": { + "clientIfAid": "XR-T2", + "clientIfColId": 2, + "clientIfPortSpeed": 100 + } + }, + { + "moduleIf": { + "clientIfAid": "XR-T3", + "clientIfColId": 3, + "clientIfPortSpeed": 100 + } + }, + { + "moduleIf": { + "clientIfAid": "XR-T4", + "clientIfColId": 4, + "clientIfPortSpeed": 100 + } + } + ], + "lifecycleState": "configured", + "module": { + "baudRate": 50, + "capacity": 400, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "hub", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:00:01", + "modulation": "16QAM", + "moduleId": "fda2a49d-d23c-4b7a-4b30-1d2de15b26d0", + "moduleName": "XR HUB 1", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "000000009", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + "id": "b7d4bbac-8d87-4dfe-af52-be1ef0b905d8", + "leafModules": [ + { + "href": "/xr-networks/b7d4bbac-8d87-4dfe-af52-be1ef0b905d8/leafModules/55386ab9-d2e3-428d-82d1-d940c6017959", + "id": "55386ab9-d2e3-428d-82d1-d940c6017959", + "parentId": "b7d4bbac-8d87-4dfe-af52-be1ef0b905d8", + "rt": [ + "cm.xr-network.leafModule" + ], + "state": { + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-1/0/0:0", + "portId": "et-1/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:11:01" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "lifecycleState": "configured", + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:01", + "modulation": "16QAM", + "moduleId": "9b7fb759-c952-420a-7b1e-5b3b496e1f66", + "moduleName": "XR LEAF 1", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "00000000B", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + { + "href": "/xr-networks/b7d4bbac-8d87-4dfe-af52-be1ef0b905d8/leafModules/a0c8b77d-41e7-4c25-9c99-468dee383ec6", + "id": "a0c8b77d-41e7-4c25-9c99-468dee383ec6", + "parentId": "b7d4bbac-8d87-4dfe-af52-be1ef0b905d8", + "rt": [ + "cm.xr-network.leafModule" + ], + "state": { + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.2", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-2/0/0:0", + "portId": "et-2/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:12:01" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "lifecycleState": "configured", + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:02", + "modulation": "16QAM", + "moduleId": "50842917-0da4-4976-63de-706b62cefd7e", + "moduleName": "XR LEAF 2", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "00000000C", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + } + ], + "reachableModules": [ + { + "href": "/xr-networks/b7d4bbac-8d87-4dfe-af52-be1ef0b905d8/reachableModules/949d5565-eaf7-43a1-838e-c2d4fdbe4c77", + "id": "949d5565-eaf7-43a1-838e-c2d4fdbe4c77", + "parentId": "b7d4bbac-8d87-4dfe-af52-be1ef0b905d8", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:06Z", + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.2", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-2/0/0:0", + "portId": "et-2/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:12:01" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:02", + "modulation": "16QAM", + "moduleId": "50842917-0da4-4976-63de-706b62cefd7e", + "moduleName": "XR LEAF 2", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "00000000C", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + { + "href": "/xr-networks/b7d4bbac-8d87-4dfe-af52-be1ef0b905d8/reachableModules/d6ab2382-1095-4f98-bfb6-a0e85830c997", + "id": "d6ab2382-1095-4f98-bfb6-a0e85830c997", + "parentId": "b7d4bbac-8d87-4dfe-af52-be1ef0b905d8", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:08Z", + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.3", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-3/0/0:0", + "portId": "et-3/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:13:01" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 193000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:03", + "modulation": "16QAM", + "moduleId": "1d23a10c-f5fb-4c2e-69ed-92608ce8e54c", + "moduleName": "XR LEAF 3", + "ncoFrequency": 0, + "operatingFrequency": 193000000, + "roleStatus": "ready", + "serialNumber": "00000000D", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + { + "href": "/xr-networks/b7d4bbac-8d87-4dfe-af52-be1ef0b905d8/reachableModules/e02496f1-3aec-4cd0-810e-5ff79448b4c4", + "id": "e02496f1-3aec-4cd0-810e-5ff79448b4c4", + "parentId": "b7d4bbac-8d87-4dfe-af52-be1ef0b905d8", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:05Z", + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-1/0/0:0", + "portId": "et-1/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:11:01" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:01", + "modulation": "16QAM", + "moduleId": "9b7fb759-c952-420a-7b1e-5b3b496e1f66", + "moduleName": "XR LEAF 1", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "00000000B", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + { + "href": "/xr-networks/b7d4bbac-8d87-4dfe-af52-be1ef0b905d8/reachableModules/4021ad07-d20e-4149-a63c-a69c36197fba", + "id": "4021ad07-d20e-4149-a63c-a69c36197fba", + "parentId": "b7d4bbac-8d87-4dfe-af52-be1ef0b905d8", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:08Z", + "endpoints": [ + { + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 193000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:03", + "modulation": "16QAM", + "moduleId": "9008a605-e6a1-4afd-5728-35dd711a4a15", + "moduleName": "XR LEAF 3", + "ncoFrequency": 0, + "operatingFrequency": 193000000, + "roleStatus": "ready", + "serialNumber": "00000000D", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + { + "href": "/xr-networks/b7d4bbac-8d87-4dfe-af52-be1ef0b905d8/reachableModules/b376688f-21ef-4f3c-81da-15718b2bf9b4", + "id": "b376688f-21ef-4f3c-81da-15718b2bf9b4", + "parentId": "b7d4bbac-8d87-4dfe-af52-be1ef0b905d8", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:05Z", + "endpoints": [ + { + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:01", + "modulation": "16QAM", + "moduleId": "4f44c0b2-3d63-465d-6413-5bc46dfa5fbf", + "moduleName": "XR LEAF 1", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "00000000B", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + { + "href": "/xr-networks/b7d4bbac-8d87-4dfe-af52-be1ef0b905d8/reachableModules/37ac5291-bbf4-4c0b-ab0b-bdbe519f01ef", + "id": "37ac5291-bbf4-4c0b-ab0b-bdbe519f01ef", + "parentId": "b7d4bbac-8d87-4dfe-af52-be1ef0b905d8", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:06Z", + "endpoints": [ + { + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:02", + "modulation": "16QAM", + "moduleId": "c896fa4a-200f-40b1-449b-337c29c5e6e8", + "moduleName": "XR LEAF 2", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "00000000C", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + } + ], + "rt": [ + "cm.xr-network" + ], + "state": { + "constellationFrequency": 192000000, + "controlLinks": [ + { + "conState": "active", + "destinationModuleId": "fda2a49d-d23c-4b7a-4b30-1d2de15b26d0", + "lastConStateChange": "2022-06-28T09:04:05Z", + "sourceModuleId": "9b7fb759-c952-420a-7b1e-5b3b496e1f66" + }, + { + "conState": "active", + "destinationModuleId": "9b7fb759-c952-420a-7b1e-5b3b496e1f66", + "lastConStateChange": "2022-06-28T09:04:05Z", + "sourceModuleId": "fda2a49d-d23c-4b7a-4b30-1d2de15b26d0" + }, + { + "conState": "active", + "destinationModuleId": "fda2a49d-d23c-4b7a-4b30-1d2de15b26d0", + "lastConStateChange": "2022-06-28T09:04:05Z", + "sourceModuleId": "50842917-0da4-4976-63de-706b62cefd7e" + }, + { + "conState": "active", + "destinationModuleId": "50842917-0da4-4976-63de-706b62cefd7e", + "lastConStateChange": "2022-06-28T09:04:06Z", + "sourceModuleId": "fda2a49d-d23c-4b7a-4b30-1d2de15b26d0" + } + ], + "lifecycleState": "configured", + "modulation": "16QAM" + } + }, + { + "href": "/xr-networks/8f6ba601-6814-4fb7-acc9-c1999a9cd396", + "hubModule": { + "href": "/xr-networks/8f6ba601-6814-4fb7-acc9-c1999a9cd396/hubModule", + "id": "795fab13-e345-45a3-8225-e83720952ac7", + "parentId": "8f6ba601-6814-4fb7-acc9-c1999a9cd396", + "rt": [ + "cm.xr-network.hubModule" + ], + "state": { + "endpoints": [ + { + "moduleIf": { + "clientIfAid": "XR-T2", + "clientIfColId": 2, + "clientIfPortSpeed": 100 + } + }, + { + "moduleIf": { + "clientIfAid": "XR-T3", + "clientIfColId": 3, + "clientIfPortSpeed": 100 + } + }, + { + "moduleIf": { + "clientIfAid": "XR-T4", + "clientIfColId": 4, + "clientIfPortSpeed": 100 + } + }, + { + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "lifecycleState": "configured", + "module": { + "baudRate": 50, + "capacity": 400, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 193000000, + "currentRole": "hub", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:00:02", + "modulation": "16QAM", + "moduleId": "72415e29-6c91-40cc-588f-5ec5ee735123", + "moduleName": "XR HUB 2", + "ncoFrequency": 0, + "operatingFrequency": 193000000, + "roleStatus": "ready", + "serialNumber": "00000000A", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + "id": "8f6ba601-6814-4fb7-acc9-c1999a9cd396", + "leafModules": [ + { + "href": "/xr-networks/8f6ba601-6814-4fb7-acc9-c1999a9cd396/leafModules/2693aaf2-d66b-458c-95aa-149c11fb7915", + "id": "2693aaf2-d66b-458c-95aa-149c11fb7915", + "parentId": "8f6ba601-6814-4fb7-acc9-c1999a9cd396", + "rt": [ + "cm.xr-network.leafModule" + ], + "state": { + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.3", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-3/0/0:0", + "portId": "et-3/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:13:01" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "lifecycleState": "configured", + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 193000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:03", + "modulation": "16QAM", + "moduleId": "1d23a10c-f5fb-4c2e-69ed-92608ce8e54c", + "moduleName": "XR LEAF 3", + "ncoFrequency": 0, + "operatingFrequency": 193000000, + "roleStatus": "ready", + "serialNumber": "00000000D", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + } + ], + "reachableModules": [ + { + "href": "/xr-networks/8f6ba601-6814-4fb7-acc9-c1999a9cd396/reachableModules/e01f6d1c-09b2-4e64-bb5b-b2b3fa1d6d0c", + "id": "e01f6d1c-09b2-4e64-bb5b-b2b3fa1d6d0c", + "parentId": "8f6ba601-6814-4fb7-acc9-c1999a9cd396", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:08Z", + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.3", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-3/0/0:0", + "portId": "et-3/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:13:01" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 193000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:03", + "modulation": "16QAM", + "moduleId": "1d23a10c-f5fb-4c2e-69ed-92608ce8e54c", + "moduleName": "XR LEAF 3", + "ncoFrequency": 0, + "operatingFrequency": 193000000, + "roleStatus": "ready", + "serialNumber": "00000000D", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + { + "href": "/xr-networks/8f6ba601-6814-4fb7-acc9-c1999a9cd396/reachableModules/5ca5564c-5d6b-4d12-bc0e-daa552587b8c", + "id": "5ca5564c-5d6b-4d12-bc0e-daa552587b8c", + "parentId": "8f6ba601-6814-4fb7-acc9-c1999a9cd396", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:05Z", + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-1/0/0:0", + "portId": "et-1/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:11:01" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:01", + "modulation": "16QAM", + "moduleId": "9b7fb759-c952-420a-7b1e-5b3b496e1f66", + "moduleName": "XR LEAF 1", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "00000000B", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + { + "href": "/xr-networks/8f6ba601-6814-4fb7-acc9-c1999a9cd396/reachableModules/214932f0-a0bb-43a3-ab30-0568630d0929", + "id": "214932f0-a0bb-43a3-ab30-0568630d0929", + "parentId": "8f6ba601-6814-4fb7-acc9-c1999a9cd396", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:08Z", + "endpoints": [ + { + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 193000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:03", + "modulation": "16QAM", + "moduleId": "9008a605-e6a1-4afd-5728-35dd711a4a15", + "moduleName": "XR LEAF 3", + "ncoFrequency": 0, + "operatingFrequency": 193000000, + "roleStatus": "ready", + "serialNumber": "00000000D", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + { + "href": "/xr-networks/8f6ba601-6814-4fb7-acc9-c1999a9cd396/reachableModules/b2a3b179-b57e-4933-b9fb-e43f438bd17b", + "id": "b2a3b179-b57e-4933-b9fb-e43f438bd17b", + "parentId": "8f6ba601-6814-4fb7-acc9-c1999a9cd396", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:05Z", + "endpoints": [ + { + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:01", + "modulation": "16QAM", + "moduleId": "4f44c0b2-3d63-465d-6413-5bc46dfa5fbf", + "moduleName": "XR LEAF 1", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "00000000B", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + } + ], + "rt": [ + "cm.xr-network" + ], + "state": { + "constellationFrequency": 193000000, + "controlLinks": [ + { + "conState": "active", + "destinationModuleId": "72415e29-6c91-40cc-588f-5ec5ee735123", + "lastConStateChange": "2022-06-28T09:04:07Z", + "sourceModuleId": "1d23a10c-f5fb-4c2e-69ed-92608ce8e54c" + }, + { + "conState": "active", + "destinationModuleId": "1d23a10c-f5fb-4c2e-69ed-92608ce8e54c", + "lastConStateChange": "2022-06-28T09:04:08Z", + "sourceModuleId": "72415e29-6c91-40cc-588f-5ec5ee735123" + } + ], + "lifecycleState": "configured", + "modulation": "16QAM" + } + }, + { + "href": "/xr-networks/5456336f-24e4-4862-9fc0-81a843f07954", + "hubModule": { + "href": "/xr-networks/5456336f-24e4-4862-9fc0-81a843f07954/hubModule", + "id": "4760494d-0639-4a56-8045-a181f48d34a8", + "parentId": "5456336f-24e4-4862-9fc0-81a843f07954", + "rt": [ + "cm.xr-network.hubModule" + ], + "state": { + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.100.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-1/0/0:0", + "portId": "et-1/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:00:11" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + }, + { + "hostPort": { + "chassisId": "192.168.100.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-1/0/0:1", + "portId": "et-1/0/0:1", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:00:12" + }, + "moduleIf": { + "clientIfAid": "XR-T2", + "clientIfColId": 2, + "clientIfPortSpeed": 100 + } + }, + { + "hostPort": { + "chassisId": "192.168.100.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-1/0/0:2", + "portId": "et-1/0/0:2", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:00:13" + }, + "moduleIf": { + "clientIfAid": "XR-T3", + "clientIfColId": 3, + "clientIfPortSpeed": 100 + } + }, + { + "hostPort": { + "chassisId": "192.168.100.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-1/0/0:3", + "portId": "et-1/0/0:3", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:00:14" + }, + "moduleIf": { + "clientIfAid": "XR-T4", + "clientIfColId": 4, + "clientIfPortSpeed": 100 + } + } + ], + "lifecycleState": "configured", + "module": { + "baudRate": 50, + "capacity": 400, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "hub", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:00:01", + "modulation": "16QAM", + "moduleId": "841480aa-d5c4-4911-5088-0cfea7970ba7", + "moduleName": "XR HUB 1", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "000000009", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + "id": "5456336f-24e4-4862-9fc0-81a843f07954", + "leafModules": [ + { + "href": "/xr-networks/5456336f-24e4-4862-9fc0-81a843f07954/leafModules/ba954cda-485f-4b18-acdc-ba701f876ce3", + "id": "ba954cda-485f-4b18-acdc-ba701f876ce3", + "parentId": "5456336f-24e4-4862-9fc0-81a843f07954", + "rt": [ + "cm.xr-network.leafModule" + ], + "state": { + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-1/0/0:0", + "portId": "et-1/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:11:01" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "lifecycleState": "configured", + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:01", + "maxAllowedDSCs": 4, + "modulation": "16QAM", + "moduleId": "9b7fb759-c952-420a-7b1e-5b3b496e1f66", + "moduleName": "XR LEAF 1", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "00000000B", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + { + "href": "/xr-networks/5456336f-24e4-4862-9fc0-81a843f07954/leafModules/e9188272-71a6-4a03-96eb-9462eb16cf36", + "id": "e9188272-71a6-4a03-96eb-9462eb16cf36", + "parentId": "5456336f-24e4-4862-9fc0-81a843f07954", + "rt": [ + "cm.xr-network.leafModule" + ], + "state": { + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.2", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-2/0/0:0", + "portId": "et-2/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:12:01" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "lifecycleState": "configured", + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:02", + "maxAllowedDSCs": 4, + "modulation": "16QAM", + "moduleId": "50842917-0da4-4976-63de-706b62cefd7e", + "moduleName": "XR LEAF 2", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "00000000C", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + } + ], + "reachableModules": [ + { + "href": "/xr-networks/5456336f-24e4-4862-9fc0-81a843f07954/reachableModules/f8b554d3-4776-4260-8f20-eea0d0c92629", + "id": "f8b554d3-4776-4260-8f20-eea0d0c92629", + "parentId": "5456336f-24e4-4862-9fc0-81a843f07954", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:06Z", + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.2", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-2/0/0:0", + "portId": "et-2/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:12:01" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:02", + "modulation": "16QAM", + "moduleId": "50842917-0da4-4976-63de-706b62cefd7e", + "moduleName": "XR LEAF 2", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "00000000C", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + { + "href": "/xr-networks/5456336f-24e4-4862-9fc0-81a843f07954/reachableModules/3a45656a-6585-4ec6-a0be-365c9734827f", + "id": "3a45656a-6585-4ec6-a0be-365c9734827f", + "parentId": "5456336f-24e4-4862-9fc0-81a843f07954", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:08Z", + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.3", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-3/0/0:0", + "portId": "et-3/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:13:01" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 193000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:03", + "modulation": "16QAM", + "moduleId": "1d23a10c-f5fb-4c2e-69ed-92608ce8e54c", + "moduleName": "XR LEAF 3", + "ncoFrequency": 0, + "operatingFrequency": 193000000, + "roleStatus": "ready", + "serialNumber": "00000000D", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + { + "href": "/xr-networks/5456336f-24e4-4862-9fc0-81a843f07954/reachableModules/2b675693-a1b3-48d6-81d9-4b3a38c7a81e", + "id": "2b675693-a1b3-48d6-81d9-4b3a38c7a81e", + "parentId": "5456336f-24e4-4862-9fc0-81a843f07954", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:05Z", + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-1/0/0:0", + "portId": "et-1/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:11:01" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:01", + "modulation": "16QAM", + "moduleId": "9b7fb759-c952-420a-7b1e-5b3b496e1f66", + "moduleName": "XR LEAF 1", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "00000000B", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + { + "href": "/xr-networks/5456336f-24e4-4862-9fc0-81a843f07954/reachableModules/711f1c32-459c-4332-8722-244108210de2", + "id": "711f1c32-459c-4332-8722-244108210de2", + "parentId": "5456336f-24e4-4862-9fc0-81a843f07954", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:08Z", + "endpoints": [ + { + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 193000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:03", + "modulation": "16QAM", + "moduleId": "9008a605-e6a1-4afd-5728-35dd711a4a15", + "moduleName": "XR LEAF 3", + "ncoFrequency": 0, + "operatingFrequency": 193000000, + "roleStatus": "ready", + "serialNumber": "00000000D", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + { + "href": "/xr-networks/5456336f-24e4-4862-9fc0-81a843f07954/reachableModules/e62157e5-fd40-44a3-9ff6-7c5f4fe48089", + "id": "e62157e5-fd40-44a3-9ff6-7c5f4fe48089", + "parentId": "5456336f-24e4-4862-9fc0-81a843f07954", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:05Z", + "endpoints": [ + { + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:01", + "modulation": "16QAM", + "moduleId": "4f44c0b2-3d63-465d-6413-5bc46dfa5fbf", + "moduleName": "XR LEAF 1", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "00000000B", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + { + "href": "/xr-networks/5456336f-24e4-4862-9fc0-81a843f07954/reachableModules/8c344a79-1075-4d0f-90f1-cd8d4f030d64", + "id": "8c344a79-1075-4d0f-90f1-cd8d4f030d64", + "parentId": "5456336f-24e4-4862-9fc0-81a843f07954", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:06Z", + "endpoints": [ + { + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:02", + "modulation": "16QAM", + "moduleId": "c896fa4a-200f-40b1-449b-337c29c5e6e8", + "moduleName": "XR LEAF 2", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "00000000C", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + } + ], + "rt": [ + "cm.xr-network" + ], + "state": { + "constellationFrequency": 192000000, + "controlLinks": [ + { + "conState": "active", + "destinationModuleId": "841480aa-d5c4-4911-5088-0cfea7970ba7", + "lastConStateChange": "2022-06-28T09:04:05Z", + "sourceModuleId": "9b7fb759-c952-420a-7b1e-5b3b496e1f66" + }, + { + "conState": "active", + "destinationModuleId": "9b7fb759-c952-420a-7b1e-5b3b496e1f66", + "lastConStateChange": "2022-06-28T09:04:05Z", + "sourceModuleId": "841480aa-d5c4-4911-5088-0cfea7970ba7" + }, + { + "conState": "active", + "destinationModuleId": "841480aa-d5c4-4911-5088-0cfea7970ba7", + "lastConStateChange": "2022-06-28T09:04:05Z", + "sourceModuleId": "50842917-0da4-4976-63de-706b62cefd7e" + }, + { + "conState": "active", + "destinationModuleId": "50842917-0da4-4976-63de-706b62cefd7e", + "lastConStateChange": "2022-06-28T09:04:06Z", + "sourceModuleId": "841480aa-d5c4-4911-5088-0cfea7970ba7" + }, + { + "conState": "active", + "destinationModuleId": "4f44c0b2-3d63-465d-6413-5bc46dfa5fbf", + "lastConStateChange": "2022-06-28T09:04:05Z", + "sourceModuleId": "841480aa-d5c4-4911-5088-0cfea7970ba7" + }, + { + "conState": "active", + "destinationModuleId": "841480aa-d5c4-4911-5088-0cfea7970ba7", + "lastConStateChange": "2022-06-28T09:04:05Z", + "sourceModuleId": "4f44c0b2-3d63-465d-6413-5bc46dfa5fbf" + }, + { + "conState": "active", + "destinationModuleId": "c896fa4a-200f-40b1-449b-337c29c5e6e8", + "lastConStateChange": "2022-06-28T09:04:06Z", + "sourceModuleId": "841480aa-d5c4-4911-5088-0cfea7970ba7" + }, + { + "conState": "active", + "destinationModuleId": "841480aa-d5c4-4911-5088-0cfea7970ba7", + "lastConStateChange": "2022-06-28T09:04:05Z", + "sourceModuleId": "c896fa4a-200f-40b1-449b-337c29c5e6e8" + } + ], + "lifecycleState": "configured", + "modulation": "16QAM" + } + } +] \ No newline at end of file diff --git a/src/device/service/drivers/xr/cm/tests/resources/transport-capacities-swagger-example.json b/src/device/service/drivers/xr/cm/tests/resources/transport-capacities-swagger-example.json new file mode 100644 index 000000000..f4f1b00a3 --- /dev/null +++ b/src/device/service/drivers/xr/cm/tests/resources/transport-capacities-swagger-example.json @@ -0,0 +1,165 @@ +[ + { + "href": "/transport-capacities/6ce3aa86-2685-44b0-9f86-49e6a6c991a8", + "rt": [ + "cm.transport-capacity" + ], + "id": "6ce3aa86-2685-44b0-9f86-49e6a6c991a8", + "config": { + "name": "Transport capacity service example", + "capacityMode": "dedicatedDownlinkSymmetric" + }, + "state": { + "name": "Transport capacity service example", + "capacityMode": "dedicatedDownlinkSymmetric", + "lifecycleState": "configured", + "labels": [] + }, + "endpoints": [ + { + "href": "/transport-capacities/6ce3aa86-2685-44b0-9f86-49e6a6c991a8/endpoints/4511bc3d-617b-4757-9f4c-41bc7d8912eb", + "rt": [ + "cm.transport-capacity.hub" + ], + "id": "4511bc3d-617b-4757-9f4c-41bc7d8912eb", + "parentId": "6ce3aa86-2685-44b0-9f86-49e6a6c991a8", + "config": { + "capacity": 100, + "selector": { + "hostPortSelector": { + "chassisIdSubtype": "macAddress", + "chassisId": "28:c0:da:3e:3e:40", + "portIdSubtype": "interfaceName", + "portId": "et-1/0/1:2" + } + } + }, + "state": { + "capacity": 100, + "hostPort": { + "chassisIdSubtype": "macAddress", + "chassisId": "28:c0:da:3e:3e:40", + "portIdSubtype": "interfaceName", + "portId": "et-1/0/0:0", + "portSourceMAC": "da:3d:c2:4c:55:40", + "portDescr": "et-1/0/0:0" + }, + "moduleIf": { + "moduleId": "18e47620-8848-4c7e-710f-05c668478c57", + "moduleName": "XR Device", + "moduleMAC": "46:00:84:A0:0C:00", + "moduleSerialNumber": "12345678900", + "moduleCurrentRole": "hub", + "moduleClientIfColId": 1, + "clientIfAid": "XR T1", + "moduleClientIfPortSpeed": 100 + }, + "lifecycleState": "configured", + "labels": [] + } + }, + { + "href": "/transport-capacities/6ce3aa86-2685-44b0-9f86-49e6a6c991a8/endpoints/35e92b25-a682-4805-964a-6ce893a7aa56", + "rt": [ + "cm.transport-capacity.leaf" + ], + "id": "35e92b25-a682-4805-964a-6ce893a7aa56", + "parentId": "6ce3aa86-2685-44b0-9f86-49e6a6c991a8", + "config": { + "capacity": 100, + "selector": { + "hostPortSelector": { + "chassisIdSubtype": "macAddress", + "chassisId": "00:99:F8:2c:01:01", + "portIdSubtype": "interfaceName", + "portId": "et-1/0/0:0" + } + } + }, + "state": { + "capacity": 100, + "hostPort": { + "chassisIdSubtype": "macAddress", + "chassisId": "00:99:F8:2c:01:01", + "portIdSubtype": "interfaceName", + "portId": "et-1/0/0:0", + "portSourceMAC": "da:3d:c2:4c:55:40", + "portDescr": "et-1/0/0:0" + }, + "moduleIf": { + "moduleId": "23ffd75e-1a30-11ec-9621-0242ac130002", + "moduleName": "XR Device 2", + "moduleMAC": "46:00:84:A0:0C:02", + "moduleSerialNumber": "12345678902", + "moduleCurrentRole": "Leaf", + "moduleClientIfColId": 2, + "clientIfAid": "XR T1", + "moduleClientIfPortSpeed": 100 + }, + "lifecycleState": "configured", + "labels": [] + } + } + ], + "capacity-links": [ + { + "href": "/capacity-links/d9580972-7a72-43e7-91d9-5473251040ca", + "rt": [ + "cm.capacity-link" + ], + "id": "d9580972-7a72-43e7-91d9-5473251040ca", + "parentId": "6ce3aa86-2685-44b0-9f86-49e6a6c991a8", + "config": { + "directionality": "biDir", + "hubModule": { + "moduleId": "18e47620-8848-4c7e-710f-05c668478c57", + "dscgShared": false, + "dscs": [ + 7, + 5, + 3, + 1 + ] + }, + "leafModule": { + "moduleId": "23ffd75e-1a30-11ec-9621-0242ac130002", + "dscgShared": false, + "dscs": [ + 3, + 1, + 2, + 4 + ] + } + }, + "state": { + "directionality": "biDir", + "hubModule": { + "moduleId": "18e47620-8848-4c7e-710f-05c668478c57", + "dscgId": "552d4e35-c7fc-4fdf-bb31-1688f926582e", + "dscgShared": false, + "dscs": [ + 7, + 5, + 3, + 1 + ], + "lifecycleState": "configured" + }, + "leafModule": { + "moduleId": "23ffd75e-1a30-11ec-9621-0242ac130002", + "dscgId": "831884a0-fac7-4f1a-8c0d-74f82498921c", + "dscgShared": false, + "dscs": [ + 3, + 1, + 2, + 4 + ], + "lifecycleState": "configured" + } + } + } + ] + } + ] \ No newline at end of file diff --git a/src/device/service/drivers/xr/cm/tests/test_connection.py b/src/device/service/drivers/xr/cm/tests/test_connection.py new file mode 100644 index 000000000..6e5e77813 --- /dev/null +++ b/src/device/service/drivers/xr/cm/tests/test_connection.py @@ -0,0 +1,92 @@ +#pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +import inspect +import os +import json +import pytest + +from ..tf_service import TFService +from ..connection import Connection, InconsistentVlanConfiguration, ConnectionDeserializationError + +resources = os.path.join(os.path.dirname(os.path.abspath(inspect.stack()[0][1])), "resources") + +def test_connection_json(): + with open(os.path.join(resources, "connections-expanded.json"), "r", encoding="UTF-8") as f: + j = json.load(f) + connection = Connection(j[0]) + + assert connection.name == "FooBar123" + assert "name: FooBar123, id: /network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03, service-mode: portMode, end-points: [(XR LEAF 1|XR-T1, 0), (XR HUB 1|XR-T1, 0)]" == str(connection) + + config = connection.create_config() + expected_config = {'name': 'FooBar123', 'serviceMode': 'portMode', 'endpoints': [{'selector': {'ifSelectorByModuleName': {'moduleName': 'XR LEAF 1', 'moduleClientIfAid': 'XR-T1'}}}, {'selector': {'ifSelectorByModuleName': {'moduleName': 'XR HUB 1', 'moduleClientIfAid': 'XR-T1'}}}]} + assert config == expected_config + + # Remove mandatory key from leaf endpoint. It will not be parsed, but hub endpoint will + del j[0]["endpoints"][0]["state"]["moduleIf"]["clientIfAid"] + connection = Connection(j[0]) + assert "name: FooBar123, id: /network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03, service-mode: portMode, end-points: [(XR HUB 1|XR-T1, 0)]" == str(connection) + + # Remove Name, it is optional (although TF will always configure it) + del j[0]["state"]["name"] + connection = Connection(j[0]) + assert "name: , id: /network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03, service-mode: portMode, end-points: [(XR HUB 1|XR-T1, 0)]" == str(connection) + + # Remove mandatory key, will raise an exception + del j[0]["state"] + with pytest.raises(ConnectionDeserializationError, match=r"Missing mandatory key 'state'"): + _connection = Connection(j[0]) + +def test_connection_ep_change_compute(): + with open(os.path.join(resources, "connections-expanded.json"), "r", encoding="UTF-8") as f: + j = json.load(f) + existing_connection = Connection(j[0]) + + # Changing only capacity + new_connection = Connection(from_tf_service=TFService("FooBar123", "XR LEAF 1|XR-T1", "XR HUB 1|XR-T1", 25)) + ep_deletes, ep_creates, ep_updates = new_connection.get_endpoint_updates(existing_connection) + assert not ep_deletes + assert not ep_creates + assert ep_updates == [('/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03/endpoints/230516d0-7e38-44b1-b174-1ba7d4454ee6', {'capacity': 25}), ('/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03/endpoints/1d58ba8f-4d51-4213-83e1-97a0e0bdd388', {'capacity': 25})] + + # Change one of endpoints + new_connection = Connection(from_tf_service=TFService("FooBar123", "XR LEAF 1|XR-T1", "XR HUB 1|changed here", 0)) + ep_deletes, ep_creates, ep_updates = new_connection.get_endpoint_updates(existing_connection) + assert ep_deletes == ['/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03/endpoints/1d58ba8f-4d51-4213-83e1-97a0e0bdd388'] + assert ep_creates == [{'selector': {'ifSelectorByModuleName': {'moduleClientIfAid': 'changed here', 'moduleName': 'XR HUB 1'}}}] + assert not ep_updates + + # Change one of the endpoints and capacity + new_connection = Connection(from_tf_service=TFService("FooBar123", "XR LEAF 1|XR-T1", "XR HUB 1|changed here", 125)) + ep_deletes, ep_creates, ep_updates = new_connection.get_endpoint_updates(existing_connection) + assert ep_deletes == ['/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03/endpoints/1d58ba8f-4d51-4213-83e1-97a0e0bdd388'] + assert ep_creates == [{'selector': {'ifSelectorByModuleName': {'moduleClientIfAid': 'changed here', 'moduleName': 'XR HUB 1'}}, "capacity": 125}] + assert ep_updates == [('/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03/endpoints/230516d0-7e38-44b1-b174-1ba7d4454ee6', {'capacity': 125})] + + # No change at all + new_connection = Connection(from_tf_service=TFService("FooBar123", "XR LEAF 1|XR-T1", "XR HUB 1|XR-T1", 0)) + ep_deletes, ep_creates, ep_updates = new_connection.get_endpoint_updates(existing_connection) + assert not ep_deletes + assert not ep_creates + assert not ep_updates + + # Order of endpoints does not matter + new_connection = Connection(from_tf_service=TFService("FooBar123", "XR HUB 1|XR-T1", "XR LEAF 1|XR-T1", 0)) + ep_deletes, ep_creates, ep_updates = new_connection.get_endpoint_updates(existing_connection) + assert not ep_deletes + assert not ep_creates + assert not ep_updates + +def test_connection_from_service(): + # Port mode + connection = Connection(from_tf_service=TFService("FooBar123", "XR LEAF 1|XR-T1", "XR HUB 1|XR-T1", 0)) + assert connection.create_config() == {'name': 'TF:FooBar123', 'serviceMode': 'portMode', 'endpoints': [{'selector': {'ifSelectorByModuleName': {'moduleName': 'XR LEAF 1', 'moduleClientIfAid': 'XR-T1'}}}, {'selector': {'ifSelectorByModuleName': {'moduleName': 'XR HUB 1', 'moduleClientIfAid': 'XR-T1'}}}]} + + # VTI mode + connection = Connection(from_tf_service=TFService("FooBar123", "XR LEAF 1|XR-T1.A", "XR HUB 1|XR-T1.100", 0)) + # In endpoint selectors VLANs are note present (CM does not know about them, encoding them to aids is purely internal to Teraflow) + # However VLAN adds outerVID and some other fields + assert connection.create_config() == {'name': 'TF:FooBar123', 'serviceMode': 'vtiP2pSymmetric', 'endpoints': [{'selector': {'ifSelectorByModuleName': {'moduleName': 'XR LEAF 1', 'moduleClientIfAid': 'XR-T1'}}}, {'selector': {'ifSelectorByModuleName': {'moduleName': 'XR HUB 1', 'moduleClientIfAid': 'XR-T1'}}}], 'outerVID': '100 ', 'mc': 'matchOuterVID'} + + # Invalid configuration, differring VLANs on different sides + with pytest.raises(InconsistentVlanConfiguration) as _e_info: + Connection(from_tf_service=TFService("FooBar123", "XR LEAF 1|XR-T1.200", "XR HUB 1|XR-T1.100", 0)) diff --git a/src/device/service/drivers/xr/cm/tests/test_constellation.py b/src/device/service/drivers/xr/cm/tests/test_constellation.py new file mode 100644 index 000000000..6d40e2acb --- /dev/null +++ b/src/device/service/drivers/xr/cm/tests/test_constellation.py @@ -0,0 +1,31 @@ +#pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +import inspect +import os +import json +import pytest +from ..constellation import Constellation, ConstellationDeserializationError + +resources = os.path.join(os.path.dirname(os.path.abspath(inspect.stack()[0][1])), "resources") + +def test_constellation_json(): + # With a name + with open(os.path.join(resources, "constellations-expanded.json"), "r", encoding="UTF-8") as f: + j = json.load(f) + + # A pre-planned constellation without endpoints + constellation = Constellation(j[0]) + assert constellation.constellation_id == "169860ee-c1b8-4ae1-8f6e-81920a6ee09d" + assert not constellation.is_vti_mode() + assert not constellation.ifnames() + + # Proper constellation with endpoints + constellation = Constellation(j[1]) + assert constellation.constellation_id == "84d9b09c-017d-4ac5-b8cd-d1102db1e070" + assert not constellation.is_vti_mode() + print(constellation.ifnames()) + assert ['XR HUB 2|XR-T4', 'XR HUB 2|XR-T1', 'XR HUB 2|XR-T2', 'XR HUB 2|XR-T3', 'XR LEAF 3|XR-T1'] == constellation.ifnames() + + # Remove mandatory key, will raise an exception + del j[0]["hubModule"]["state"] + with pytest.raises(ConstellationDeserializationError, match=r"Missing mandatory key 'state'"): + _constellation = Constellation(j[0]) diff --git a/src/device/service/drivers/xr/cm/tests/test_transport_capacitity.py b/src/device/service/drivers/xr/cm/tests/test_transport_capacitity.py new file mode 100644 index 000000000..8d230b4b4 --- /dev/null +++ b/src/device/service/drivers/xr/cm/tests/test_transport_capacitity.py @@ -0,0 +1,45 @@ +#pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +import inspect +import os +import json + +from ..tf_service import TFService +from ..transport_capacity import TransportCapacity + +resources = os.path.join(os.path.dirname(os.path.abspath(inspect.stack()[0][1])), "resources") + +def test_transport_capacity_json(): + # Swagger example has been manually edited to match schema, that is moduleClientIfAid --> clientIfAid in state + # Also names of leafs have been fixed to be unique + # Once CM implementation is available, actual data obtained from CM should be used as a test vector + with open(os.path.join(resources, "transport-capacities-swagger-example.json"), "r", encoding="UTF-8") as f: + j = json.load(f) + + # A pre-planned constellation without endpoints + tc = TransportCapacity(j[0]) + assert str(tc) == "name: Transport capacity service example, id: /transport-capacities/6ce3aa86-2685-44b0-9f86-49e6a6c991a8, capacity-mode: dedicatedDownlinkSymmetric, end-points: [(XR Device|XR T1, 100), (XR Device 2|XR T1, 100)]" + + config = tc.create_config() + assert config == {'config': {'name': 'Transport capacity service example'}, 'endpoints': [{'capacity': 100, 'selector': {'ifSelectorByModuleName': {'moduleName': 'XR Device', 'moduleClientIfAid': 'XR T1'}}}, {'capacity': 100, 'selector': {'ifSelectorByModuleName': {'moduleName': 'XR Device 2', 'moduleClientIfAid': 'XR T1'}}}]} + +def test_transport_capacity_comparison(): + # Same content must compare same + t1=TransportCapacity(from_tf_service=TFService("foo", "Hub|T1", "Leaf 1|T2", 25)) + t2=TransportCapacity(from_tf_service=TFService("foo", "Hub|T1", "Leaf 1|T2", 25)) + assert t1 == t2 + + # Order of endpoints does not matter: + t2=TransportCapacity(from_tf_service=TFService("foo", "Leaf 1|T2", "Hub|T1", 25)) + assert t1 == t2 + + # Different bandwidth + t2=TransportCapacity(from_tf_service=TFService("foo", "Hub|T1", "Leaf 1|T2", 50)) + assert t1 != t2 + + # Different leaf module + t2=TransportCapacity(from_tf_service=TFService("foo", "Hub|T1", "Leaf 2|T2", 25)) + assert t1 != t2 + + # Different leaf interface + t2=TransportCapacity(from_tf_service=TFService("foo", "Hub|T1", "Leaf 1|T3", 25)) + assert t1 != t2 diff --git a/src/device/service/drivers/xr/cm/tf.py b/src/device/service/drivers/xr/cm/tf.py new file mode 100644 index 000000000..6bf7d58de --- /dev/null +++ b/src/device/service/drivers/xr/cm/tf.py @@ -0,0 +1,57 @@ +#pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +from typing import Dict, Union +import logging +from .cm_connection import CmConnection +from .constellation import Constellation +from .tf_service import TFService +from .transport_capacity import TransportCapacity +from .connection import Connection + +LOGGER = logging.getLogger(__name__) + +def _get_value_or_default(config: Dict[str, any], key: str, default_value: any) -> any: + if key not in config: + return default_value + else: + return config[key] + +def _get_capacity(config) -> int: + if "capacity_unit" not in config or "capacity_value" not in config: + return 0 + if config["capacity_unit"] != "gigabit": + return 0 + return config["capacity_value"] + +def set_config_for_service(cm_connection: CmConnection, constellation: Constellation, uuid: str, config: Dict[str, any]) -> Union[bool, Exception]: + try: + service = TFService(uuid, config["input_sip"], config["output_sip"], _get_capacity(config)) + if constellation.is_vti_mode(): + desired_tc = TransportCapacity(from_tf_service=service) + active_tc = cm_connection.get_transport_capacity_by_name(service.name()) + if desired_tc != active_tc: + if active_tc: + LOGGER.info(f"set_config_for_service: Transport Capacity change for {uuid}, ({active_tc=}, {desired_tc=}), performing service impacting update") + # Remove previous connection (if any) + active_connection = cm_connection.get_connection_by_name(service.name()) + if active_connection: + cm_connection.delete_connection(active_connection.href) + # Delete old TC + cm_connection.delete_transport_capacity(active_tc.href) + if desired_tc: + href = cm_connection.create_transport_capacity(desired_tc) + if not href: + LOGGER.error(f"set_config_for_service: Failed to create Transport Capacity ({desired_tc=})") + return False + connection = Connection(from_tf_service=service) + href = cm_connection.create_or_update_connection(connection) + if href: + LOGGER.info(f"set_config_for_service: Created service {uuid} as {href} ({connection=})") + return True + else: + LOGGER.error(f"set_config_for_service: Service creation failure for {uuid} ({connection=})") + return False + # Intentionally catching all exceptions, as they are stored in a list as return values + # by the caller + # pylint: disable=broad-except + except Exception as e: + return e diff --git a/src/device/service/drivers/xr/cm/tf_service.py b/src/device/service/drivers/xr/cm/tf_service.py new file mode 100644 index 000000000..a780edfa6 --- /dev/null +++ b/src/device/service/drivers/xr/cm/tf_service.py @@ -0,0 +1,53 @@ +#pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring, wildcard-import, unused-wildcard-import +import math +from dataclasses import dataclass +from typing import Tuple, Optional, Dict, List +from .utils import * + +@dataclass(init=False) +class TFService: + input_sip: str + output_sip: str + uuid: str + capacity: int + + def __init__(self, uuid, input_sip, output_sip, capacity): + self.uuid = uuid + self.input_sip = input_sip + self.output_sip = output_sip + # Capacity must be in multiples of 25 gigabits + if 0 == capacity: + self.capacity = 0 + else: + self.capacity = math.ceil(capacity/25) * 25 + + def __str__(self): + return f"({self.uuid}, {self.input_sip}, {self.output_sip}, {self.capacity})" + + def name(self) -> str: + return f"TF:{self.uuid}" + + def input_mod_aid_vlan(self) -> Tuple[str, str, Optional[str]]: + return ifname_to_module_aid_vlan(self.input_sip) + + def output_mod_aid_vlan(self) -> Tuple[str, str, Optional[str]]: + return ifname_to_module_aid_vlan(self.output_sip) + + # Return endpoints in a form suitable for selectors in various + # JSON constructs used by the CM API + def get_endpoint_selectors(self) -> List[Dict]: + return [make_selector(*self.input_mod_aid_vlan()), make_selector(*self.output_mod_aid_vlan())] + + # -> List[Tuple(str, str)] + def get_endpoints_mod_aid(self): + m1, a1, _ = self.input_mod_aid_vlan() + m2, a2, _ = self.output_mod_aid_vlan() + + return [(m1, a1), (m2, a2)] + + # -> List[Tuple(str, str)] + def get_endpoints_mod_aid_vlan(self): + m1, a1, v1 = self.input_mod_aid_vlan() + m2, a2, v2 = self.output_mod_aid_vlan() + + return [(m1, a1, v1), (m2, a2, v2)] diff --git a/src/device/service/drivers/xr/cm/transport_capacity.py b/src/device/service/drivers/xr/cm/transport_capacity.py new file mode 100644 index 000000000..873ae287a --- /dev/null +++ b/src/device/service/drivers/xr/cm/transport_capacity.py @@ -0,0 +1,96 @@ +#pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring + +from typing import Optional, Dict +from dataclasses import dataclass + +from .utils import make_selector + +from .tf_service import TFService + +@dataclass +class TCEndpoint: + module: str + port: str + capacity: int + + def ifname(self) -> str: + return self.module + "|" + self.port + + def __str__(self): + return f"({self.ifname()}, {self.capacity})" + + def create_config(self) -> Dict[str, any]: + cfg = { + "capacity": self.capacity, + "selector": make_selector(self.module, self.port, None) + } + return cfg + +class TransportCapacityDeserializationError(Exception): + pass + +class TransportCapacity: + def __init__(self, from_json=None, from_tf_service: Optional[TFService] = None): + def get_endpoint_from_json(endpoint: dict[str, any]) -> Optional[TCEndpoint]: + try: + return TCEndpoint(endpoint["state"]["moduleIf"]["moduleName"], endpoint["state"]["moduleIf"]["clientIfAid"], + endpoint["state"]["capacity"]) + except KeyError: + return None + + if from_json: + try: + self.href = from_json["href"] + + state = from_json["state"] + # Name is optional + self.name = state["name"] if "name" in state else None + self.capacity_mode = state["capacityMode"] + + self.endpoints = [] + for epj in from_json["endpoints"]: + ep = get_endpoint_from_json(epj) + if ep: + self.endpoints.append(ep) + + #self.__cm_data = from_json + except KeyError as e: + raise TransportCapacityDeserializationError(f"Missing mandatory key {str(e)}") from e + elif from_tf_service: + self.href = None + self.state = "tfInternalObject" + self.name = from_tf_service.name() + self.capacity_mode = "dedicatedDownlinkSymmetric" + self.endpoints = [TCEndpoint(mod, port, from_tf_service.capacity) for mod,port in from_tf_service.get_endpoints_mod_aid()] + #self.__cm_data = None + else: + # May support other initializations in future + raise TransportCapacityDeserializationError("Initializer missing") + + # Return suitable config for CM + def create_config(self) -> Dict[str, any]: + cfg = {} + if self.name is not None: + cfg["config"] = {"name": self.name } + cfg["endpoints"] = [ep.create_config() for ep in self.endpoints] + return cfg + + def __str__(self): + name = self.name if self.name else "" + endpoints = ", ".join((str(ep) for ep in self.endpoints)) + return f"name: {name}, id: {self.href}, capacity-mode: {self.capacity_mode}, end-points: [{endpoints}]" + + # Comparison for purpose of re-configuring + def __eq__(self, obj): + if not isinstance(obj, TransportCapacity): + return False + if self.name != obj.name: + return False + if self.capacity_mode != obj.capacity_mode: + return False + if sorted(self.endpoints, key=str) != sorted(obj.endpoints, key=str): + return False + return True + + def __ne__(self, obj): + return not self == obj diff --git a/src/device/service/drivers/xr/cm/utils.py b/src/device/service/drivers/xr/cm/utils.py new file mode 100644 index 000000000..09d98eb08 --- /dev/null +++ b/src/device/service/drivers/xr/cm/utils.py @@ -0,0 +1,57 @@ +#pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +from typing import Any, Tuple, Optional, Dict + +class InvalidIfnameError(Exception): + def __init__(self, ifname): + # Call the base class constructor with the parameters it needs + super().__init__(f"Invalid interface name {ifname}, expecting format \"MODULENAME|PORTNAME\" or \"MODULENAME|PORTNAME.VLAN\"") + +def ifname_to_module_and_aid(ifname: str) -> Tuple[str, str]: + a = ifname.split("|") + if len(a) != 2: + raise InvalidIfnameError(ifname) + return (a[0], a[1]) + +def virtual_aid_to_aid_and_vlan(ifname: str) -> Tuple[str, Optional[str]]: + a = ifname.split(".") + if len(a) == 1: + return (ifname, None) + if len(a) != 2: + raise InvalidIfnameError(ifname) + return (a[0], a[1]) + +def ifname_to_module_aid_vlan(ifname: str) -> Tuple[str, str, Optional[str]]: + module, aid = ifname_to_module_and_aid(ifname) + aid, vlan = virtual_aid_to_aid_and_vlan(aid) + return (module, aid, vlan) + +# For some reason when writing config, selector has moduleClientIfAid, when reading +# state it has clientIfAid... +def make_selector(mod, aid, _vlan) -> Dict[str, Any]: + selector = { + "ifSelectorByModuleName": { + "moduleName": mod, + "moduleClientIfAid": aid, + } + } + return selector + +def get_constellation_module_ifnames(module): + ifnames = [] + try: + module_state = module["state"] + module_name = module_state["module"]["moduleName"] + if "endpoints" in module_state: + for endpoint in module_state["endpoints"]: + try: + ifname = endpoint["moduleIf"]["clientIfAid"] + ifnames.append(f"{module_name}|{ifname}") + except KeyError: + pass + except KeyError: + pass + return ifnames + +def set_optional_parameter(container: Dict[str, any], key:str, value: Optional[any]): + if value is not None: + container[key] = value diff --git a/src/policy/target/generated-sources/grpc/context/ContextOuterClass.java b/src/policy/target/generated-sources/grpc/context/ContextOuterClass.java index 45a64fabb..1e9537414 100644 --- a/src/policy/target/generated-sources/grpc/context/ContextOuterClass.java +++ b/src/policy/target/generated-sources/grpc/context/ContextOuterClass.java @@ -173,6 +173,10 @@ public final class ContextOuterClass { * DEVICEDRIVER_ONF_TR_352 = 5; */ DEVICEDRIVER_ONF_TR_352(5), + /** + * DEVICEDRIVER_XR = 6; + */ + DEVICEDRIVER_XR(6), UNRECOGNIZED(-1), ; @@ -204,6 +208,10 @@ public final class ContextOuterClass { * DEVICEDRIVER_ONF_TR_352 = 5; */ public static final int DEVICEDRIVER_ONF_TR_352_VALUE = 5; + /** + * DEVICEDRIVER_XR = 6; + */ + public static final int DEVICEDRIVER_XR_VALUE = 6; public final int getNumber() { @@ -236,6 +244,7 @@ public final class ContextOuterClass { case 3: return DEVICEDRIVER_P4; case 4: return DEVICEDRIVER_IETF_NETWORK_TOPOLOGY; case 5: return DEVICEDRIVER_ONF_TR_352; + case 6: return DEVICEDRIVER_XR; default: return null; } } @@ -62114,97 +62123,98 @@ public final class ContextOuterClass { "ontextId\022\025\n\rauthenticated\030\002 \001(\010*j\n\rEvent" + "TypeEnum\022\027\n\023EVENTTYPE_UNDEFINED\020\000\022\024\n\020EVE" + "NTTYPE_CREATE\020\001\022\024\n\020EVENTTYPE_UPDATE\020\002\022\024\n" + - "\020EVENTTYPE_REMOVE\020\003*\305\001\n\020DeviceDriverEnum" + + "\020EVENTTYPE_REMOVE\020\003*\332\001\n\020DeviceDriverEnum" + "\022\032\n\026DEVICEDRIVER_UNDEFINED\020\000\022\033\n\027DEVICEDR" + "IVER_OPENCONFIG\020\001\022\036\n\032DEVICEDRIVER_TRANSP" + "ORT_API\020\002\022\023\n\017DEVICEDRIVER_P4\020\003\022&\n\"DEVICE" + "DRIVER_IETF_NETWORK_TOPOLOGY\020\004\022\033\n\027DEVICE" + - "DRIVER_ONF_TR_352\020\005*\217\001\n\033DeviceOperationa" + - "lStatusEnum\022%\n!DEVICEOPERATIONALSTATUS_U" + - "NDEFINED\020\000\022$\n DEVICEOPERATIONALSTATUS_DI" + - "SABLED\020\001\022#\n\037DEVICEOPERATIONALSTATUS_ENAB" + - "LED\020\002*\201\001\n\017ServiceTypeEnum\022\027\n\023SERVICETYPE" + - "_UNKNOWN\020\000\022\024\n\020SERVICETYPE_L3NM\020\001\022\024\n\020SERV" + - "ICETYPE_L2NM\020\002\022)\n%SERVICETYPE_TAPI_CONNE" + - "CTIVITY_SERVICE\020\003*\250\001\n\021ServiceStatusEnum\022" + - "\033\n\027SERVICESTATUS_UNDEFINED\020\000\022\031\n\025SERVICES" + - "TATUS_PLANNED\020\001\022\030\n\024SERVICESTATUS_ACTIVE\020" + - "\002\022!\n\035SERVICESTATUS_PENDING_REMOVAL\020\003\022\036\n\032" + - "SERVICESTATUS_SLA_VIOLATED\020\004*\251\001\n\017SliceSt" + - "atusEnum\022\031\n\025SLICESTATUS_UNDEFINED\020\000\022\027\n\023S" + - "LICESTATUS_PLANNED\020\001\022\024\n\020SLICESTATUS_INIT" + - "\020\002\022\026\n\022SLICESTATUS_ACTIVE\020\003\022\026\n\022SLICESTATU" + - "S_DEINIT\020\004\022\034\n\030SLICESTATUS_SLA_VIOLATED\020\005" + - "*]\n\020ConfigActionEnum\022\032\n\026CONFIGACTION_UND" + - "EFINED\020\000\022\024\n\020CONFIGACTION_SET\020\001\022\027\n\023CONFIG" + - "ACTION_DELETE\020\002*\203\002\n\022IsolationLevelEnum\022\020" + - "\n\014NO_ISOLATION\020\000\022\026\n\022PHYSICAL_ISOLATION\020\001" + - "\022\025\n\021LOGICAL_ISOLATION\020\002\022\025\n\021PROCESS_ISOLA" + - "TION\020\003\022\035\n\031PHYSICAL_MEMORY_ISOLATION\020\004\022\036\n" + - "\032PHYSICAL_NETWORK_ISOLATION\020\005\022\036\n\032VIRTUAL" + - "_RESOURCE_ISOLATION\020\006\022\037\n\033NETWORK_FUNCTIO" + - "NS_ISOLATION\020\007\022\025\n\021SERVICE_ISOLATION\020\0102\357\022" + - "\n\016ContextService\022:\n\016ListContextIds\022\016.con" + - "text.Empty\032\026.context.ContextIdList\"\000\0226\n\014" + - "ListContexts\022\016.context.Empty\032\024.context.C" + - "ontextList\"\000\0224\n\nGetContext\022\022.context.Con" + - "textId\032\020.context.Context\"\000\0224\n\nSetContext" + - "\022\020.context.Context\032\022.context.ContextId\"\000" + - "\0225\n\rRemoveContext\022\022.context.ContextId\032\016." + - "context.Empty\"\000\022=\n\020GetContextEvents\022\016.co" + - "ntext.Empty\032\025.context.ContextEvent\"\0000\001\022@" + - "\n\017ListTopologyIds\022\022.context.ContextId\032\027." + - "context.TopologyIdList\"\000\022=\n\016ListTopologi" + - "es\022\022.context.ContextId\032\025.context.Topolog" + - "yList\"\000\0227\n\013GetTopology\022\023.context.Topolog" + - "yId\032\021.context.Topology\"\000\0227\n\013SetTopology\022" + - "\021.context.Topology\032\023.context.TopologyId\"" + - "\000\0227\n\016RemoveTopology\022\023.context.TopologyId" + - "\032\016.context.Empty\"\000\022?\n\021GetTopologyEvents\022" + - "\016.context.Empty\032\026.context.TopologyEvent\"" + - "\0000\001\0228\n\rListDeviceIds\022\016.context.Empty\032\025.c" + - "ontext.DeviceIdList\"\000\0224\n\013ListDevices\022\016.c" + - "ontext.Empty\032\023.context.DeviceList\"\000\0221\n\tG" + - "etDevice\022\021.context.DeviceId\032\017.context.De" + - "vice\"\000\0221\n\tSetDevice\022\017.context.Device\032\021.c" + - "ontext.DeviceId\"\000\0223\n\014RemoveDevice\022\021.cont" + - "ext.DeviceId\032\016.context.Empty\"\000\022;\n\017GetDev" + - "iceEvents\022\016.context.Empty\032\024.context.Devi" + - "ceEvent\"\0000\001\0224\n\013ListLinkIds\022\016.context.Emp" + - "ty\032\023.context.LinkIdList\"\000\0220\n\tListLinks\022\016" + - ".context.Empty\032\021.context.LinkList\"\000\022+\n\007G" + - "etLink\022\017.context.LinkId\032\r.context.Link\"\000" + - "\022+\n\007SetLink\022\r.context.Link\032\017.context.Lin" + - "kId\"\000\022/\n\nRemoveLink\022\017.context.LinkId\032\016.c" + - "ontext.Empty\"\000\0227\n\rGetLinkEvents\022\016.contex" + - "t.Empty\032\022.context.LinkEvent\"\0000\001\022>\n\016ListS" + - "erviceIds\022\022.context.ContextId\032\026.context." + - "ServiceIdList\"\000\022:\n\014ListServices\022\022.contex" + - "t.ContextId\032\024.context.ServiceList\"\000\0224\n\nG" + - "etService\022\022.context.ServiceId\032\020.context." + - "Service\"\000\0224\n\nSetService\022\020.context.Servic" + - "e\032\022.context.ServiceId\"\000\0225\n\rRemoveService" + - "\022\022.context.ServiceId\032\016.context.Empty\"\000\022=" + - "\n\020GetServiceEvents\022\016.context.Empty\032\025.con" + - "text.ServiceEvent\"\0000\001\022:\n\014ListSliceIds\022\022." + - "context.ContextId\032\024.context.SliceIdList\"" + - "\000\0226\n\nListSlices\022\022.context.ContextId\032\022.co" + - "ntext.SliceList\"\000\022.\n\010GetSlice\022\020.context." + - "SliceId\032\016.context.Slice\"\000\022.\n\010SetSlice\022\016." + - "context.Slice\032\020.context.SliceId\"\000\0221\n\013Rem" + - "oveSlice\022\020.context.SliceId\032\016.context.Emp" + - "ty\"\000\0229\n\016GetSliceEvents\022\016.context.Empty\032\023" + - ".context.SliceEvent\"\0000\001\022D\n\021ListConnectio" + - "nIds\022\022.context.ServiceId\032\031.context.Conne" + - "ctionIdList\"\000\022@\n\017ListConnections\022\022.conte" + - "xt.ServiceId\032\027.context.ConnectionList\"\000\022" + - "=\n\rGetConnection\022\025.context.ConnectionId\032" + - "\023.context.Connection\"\000\022=\n\rSetConnection\022" + - "\023.context.Connection\032\025.context.Connectio" + - "nId\"\000\022;\n\020RemoveConnection\022\025.context.Conn" + - "ectionId\032\016.context.Empty\"\000\022C\n\023GetConnect" + - "ionEvents\022\016.context.Empty\032\030.context.Conn" + - "ectionEvent\"\0000\001b\006proto3" + "DRIVER_ONF_TR_352\020\005\022\023\n\017DEVICEDRIVER_XR\020\006" + + "*\217\001\n\033DeviceOperationalStatusEnum\022%\n!DEVI" + + "CEOPERATIONALSTATUS_UNDEFINED\020\000\022$\n DEVIC" + + "EOPERATIONALSTATUS_DISABLED\020\001\022#\n\037DEVICEO" + + "PERATIONALSTATUS_ENABLED\020\002*\201\001\n\017ServiceTy" + + "peEnum\022\027\n\023SERVICETYPE_UNKNOWN\020\000\022\024\n\020SERVI" + + "CETYPE_L3NM\020\001\022\024\n\020SERVICETYPE_L2NM\020\002\022)\n%S" + + "ERVICETYPE_TAPI_CONNECTIVITY_SERVICE\020\003*\250" + + "\001\n\021ServiceStatusEnum\022\033\n\027SERVICESTATUS_UN" + + "DEFINED\020\000\022\031\n\025SERVICESTATUS_PLANNED\020\001\022\030\n\024" + + "SERVICESTATUS_ACTIVE\020\002\022!\n\035SERVICESTATUS_" + + "PENDING_REMOVAL\020\003\022\036\n\032SERVICESTATUS_SLA_V" + + "IOLATED\020\004*\251\001\n\017SliceStatusEnum\022\031\n\025SLICEST" + + "ATUS_UNDEFINED\020\000\022\027\n\023SLICESTATUS_PLANNED\020" + + "\001\022\024\n\020SLICESTATUS_INIT\020\002\022\026\n\022SLICESTATUS_A" + + "CTIVE\020\003\022\026\n\022SLICESTATUS_DEINIT\020\004\022\034\n\030SLICE" + + "STATUS_SLA_VIOLATED\020\005*]\n\020ConfigActionEnu" + + "m\022\032\n\026CONFIGACTION_UNDEFINED\020\000\022\024\n\020CONFIGA" + + "CTION_SET\020\001\022\027\n\023CONFIGACTION_DELETE\020\002*\203\002\n" + + "\022IsolationLevelEnum\022\020\n\014NO_ISOLATION\020\000\022\026\n" + + "\022PHYSICAL_ISOLATION\020\001\022\025\n\021LOGICAL_ISOLATI" + + "ON\020\002\022\025\n\021PROCESS_ISOLATION\020\003\022\035\n\031PHYSICAL_" + + "MEMORY_ISOLATION\020\004\022\036\n\032PHYSICAL_NETWORK_I" + + "SOLATION\020\005\022\036\n\032VIRTUAL_RESOURCE_ISOLATION" + + "\020\006\022\037\n\033NETWORK_FUNCTIONS_ISOLATION\020\007\022\025\n\021S" + + "ERVICE_ISOLATION\020\0102\357\022\n\016ContextService\022:\n" + + "\016ListContextIds\022\016.context.Empty\032\026.contex" + + "t.ContextIdList\"\000\0226\n\014ListContexts\022\016.cont" + + "ext.Empty\032\024.context.ContextList\"\000\0224\n\nGet" + + "Context\022\022.context.ContextId\032\020.context.Co" + + "ntext\"\000\0224\n\nSetContext\022\020.context.Context\032" + + "\022.context.ContextId\"\000\0225\n\rRemoveContext\022\022" + + ".context.ContextId\032\016.context.Empty\"\000\022=\n\020" + + "GetContextEvents\022\016.context.Empty\032\025.conte" + + "xt.ContextEvent\"\0000\001\022@\n\017ListTopologyIds\022\022" + + ".context.ContextId\032\027.context.TopologyIdL" + + "ist\"\000\022=\n\016ListTopologies\022\022.context.Contex" + + "tId\032\025.context.TopologyList\"\000\0227\n\013GetTopol" + + "ogy\022\023.context.TopologyId\032\021.context.Topol" + + "ogy\"\000\0227\n\013SetTopology\022\021.context.Topology\032" + + "\023.context.TopologyId\"\000\0227\n\016RemoveTopology" + + "\022\023.context.TopologyId\032\016.context.Empty\"\000\022" + + "?\n\021GetTopologyEvents\022\016.context.Empty\032\026.c" + + "ontext.TopologyEvent\"\0000\001\0228\n\rListDeviceId" + + "s\022\016.context.Empty\032\025.context.DeviceIdList" + + "\"\000\0224\n\013ListDevices\022\016.context.Empty\032\023.cont" + + "ext.DeviceList\"\000\0221\n\tGetDevice\022\021.context." + + "DeviceId\032\017.context.Device\"\000\0221\n\tSetDevice" + + "\022\017.context.Device\032\021.context.DeviceId\"\000\0223" + + "\n\014RemoveDevice\022\021.context.DeviceId\032\016.cont" + + "ext.Empty\"\000\022;\n\017GetDeviceEvents\022\016.context" + + ".Empty\032\024.context.DeviceEvent\"\0000\001\0224\n\013List" + + "LinkIds\022\016.context.Empty\032\023.context.LinkId" + + "List\"\000\0220\n\tListLinks\022\016.context.Empty\032\021.co" + + "ntext.LinkList\"\000\022+\n\007GetLink\022\017.context.Li" + + "nkId\032\r.context.Link\"\000\022+\n\007SetLink\022\r.conte" + + "xt.Link\032\017.context.LinkId\"\000\022/\n\nRemoveLink" + + "\022\017.context.LinkId\032\016.context.Empty\"\000\0227\n\rG" + + "etLinkEvents\022\016.context.Empty\032\022.context.L" + + "inkEvent\"\0000\001\022>\n\016ListServiceIds\022\022.context" + + ".ContextId\032\026.context.ServiceIdList\"\000\022:\n\014" + + "ListServices\022\022.context.ContextId\032\024.conte" + + "xt.ServiceList\"\000\0224\n\nGetService\022\022.context" + + ".ServiceId\032\020.context.Service\"\000\0224\n\nSetSer" + + "vice\022\020.context.Service\032\022.context.Service" + + "Id\"\000\0225\n\rRemoveService\022\022.context.ServiceI" + + "d\032\016.context.Empty\"\000\022=\n\020GetServiceEvents\022" + + "\016.context.Empty\032\025.context.ServiceEvent\"\000" + + "0\001\022:\n\014ListSliceIds\022\022.context.ContextId\032\024" + + ".context.SliceIdList\"\000\0226\n\nListSlices\022\022.c" + + "ontext.ContextId\032\022.context.SliceList\"\000\022." + + "\n\010GetSlice\022\020.context.SliceId\032\016.context.S" + + "lice\"\000\022.\n\010SetSlice\022\016.context.Slice\032\020.con" + + "text.SliceId\"\000\0221\n\013RemoveSlice\022\020.context." + + "SliceId\032\016.context.Empty\"\000\0229\n\016GetSliceEve" + + "nts\022\016.context.Empty\032\023.context.SliceEvent" + + "\"\0000\001\022D\n\021ListConnectionIds\022\022.context.Serv" + + "iceId\032\031.context.ConnectionIdList\"\000\022@\n\017Li" + + "stConnections\022\022.context.ServiceId\032\027.cont" + + "ext.ConnectionList\"\000\022=\n\rGetConnection\022\025." + + "context.ConnectionId\032\023.context.Connectio" + + "n\"\000\022=\n\rSetConnection\022\023.context.Connectio" + + "n\032\025.context.ConnectionId\"\000\022;\n\020RemoveConn" + + "ection\022\025.context.ConnectionId\032\016.context." + + "Empty\"\000\022C\n\023GetConnectionEvents\022\016.context" + + ".Empty\032\030.context.ConnectionEvent\"\0000\001b\006pr" + + "oto3" }; descriptor = com.google.protobuf.Descriptors.FileDescriptor .internalBuildGeneratedFileFrom(descriptorData, -- GitLab From 47d1404d00174e93e97ccd10d1ae512505c6f0ba Mon Sep 17 00:00:00 2001 From: Ville Petteri Hallivuori Date: Tue, 30 Aug 2022 01:03:46 -0700 Subject: [PATCH 05/10] Pull request #5: vhallivu/xr_pytest_with_http_mock Merge in XRCA/teraflow from vhallivu/xr_pytest_with_http_mock to xr_development Squashed commit of the following: commit d0688826c369c9320027cc6b3eef34d82674f1ec Author: Ville Hallivuori Date: Tue Aug 30 07:29:30 2022 +0300 XR Teraflow driver logging cleanup commit 89922d37c7709b4fe5acb3b122f5d6aea023c956 Author: Ville Hallivuori Date: Tue Aug 30 07:04:15 2022 +0300 Improved HTTP request logging commit c50420a5358686dbafa1cf61acf35ac753a2dc5f Author: Ville Hallivuori Date: Mon Aug 29 14:33:28 2022 +0300 POST refactoring commit a133ed135816fd252bdc026e25598e8c2e65e8ec Author: Ville Hallivuori Date: Mon Aug 29 13:24:15 2022 +0300 More refactoring commit 202c9827f7373dabc98ac2e72464fca104d6ba64 Author: Ville Hallivuori Date: Mon Aug 29 08:16:07 2022 +0300 Start of HTTP refactoring commit 8595e63c2cc6fef51476315134c12f6f4e700a46 Author: Ville Hallivuori Date: Wed Aug 24 08:06:58 2022 +0300 Add missing mutexes to XrDriver commit d250437104322de12232320aa55e371f9777875a Author: Ville Hallivuori Date: Tue Aug 23 17:54:48 2022 +0300 Connection update unit test commit 5240a96941b43e2cc2488443bb927475fc96ac81 Author: Ville Hallivuori Date: Mon Aug 22 14:34:56 2022 +0300 Improved HTTP get error handling commit 10fa5132aa47db2a69345c5f69a60dd9e2c25c74 Author: Ville Hallivuori Date: Mon Aug 22 11:51:12 2022 +0300 Higher level unit test for XR commit 242f44363b8bdb63e02ec92c034aa3208abd7219 Author: Ville Hallivuori Date: Sat Aug 20 16:59:18 2022 +0300 Improved test data for constellation pytests commit 3f834ba6f7e9448cab77869789ed0ded6d838e70 Author: Ville Hallivuori Date: Sat Aug 20 16:32:48 2022 +0300 Initial prototyping of mock based testing for xr --- src/device/requirements.in | 1 + src/device/service/drivers/xr/XrDriver.py | 128 +- .../service/drivers/xr/cm/cm_connection.py | 272 ++-- .../resources/constellation-by-name-hub1.json | 388 +++++ .../resources/constellations-expanded.json | 1366 ++--------------- .../drivers/xr/cm/tests/test_cm_connection.py | 67 + .../drivers/xr/cm/tests/test_constellation.py | 10 +- .../xr/cm/tests/test_xr_service_set_config.py | 95 ++ src/device/service/drivers/xr/cm/tf.py | 4 +- 9 files changed, 904 insertions(+), 1427 deletions(-) create mode 100644 src/device/service/drivers/xr/cm/tests/resources/constellation-by-name-hub1.json create mode 100644 src/device/service/drivers/xr/cm/tests/test_cm_connection.py create mode 100644 src/device/service/drivers/xr/cm/tests/test_xr_service_set_config.py diff --git a/src/device/requirements.in b/src/device/requirements.in index e5aaddecb..0902a6579 100644 --- a/src/device/requirements.in +++ b/src/device/requirements.in @@ -10,6 +10,7 @@ python-json-logger==2.0.2 pytz==2021.3 redis==4.1.2 requests==2.27.1 +requests-mock==1.9.3 xmltodict==0.12.0 # pip's dependency resolver does not take into account installed packages. diff --git a/src/device/service/drivers/xr/XrDriver.py b/src/device/service/drivers/xr/XrDriver.py index c3325b14f..51fd29ad1 100644 --- a/src/device/service/drivers/xr/XrDriver.py +++ b/src/device/service/drivers/xr/XrDriver.py @@ -35,6 +35,7 @@ class XrDriver(_Driver): self.__started = threading.Event() self.__terminate = threading.Event() self.__timeout = int(settings.get('timeout', 120)) + self.__cm_address = address # Mandatory key, an exception will get thrown if missing self.__hub_module_name = settings["hub_module_name"] @@ -47,7 +48,11 @@ class XrDriver(_Driver): LOGGER.info(f"XrDriver instantiated, cm {address}:{port}, {settings=}") + def __str__(self): + return f"{self.__hub_module_name}@{self.__cm_address}" + def Connect(self) -> bool: + LOGGER.info(f"Connect[{self}]") with self.__lock: if self.__started.is_set(): return True @@ -58,91 +63,98 @@ class XrDriver(_Driver): return True def Disconnect(self) -> bool: + LOGGER.info(f"Disconnect[{self}]") with self.__lock: self.__terminate.set() return True def GetInitialConfig(self) -> List[Tuple[str, Any]]: + LOGGER.info(f"GetInitialConfig[{self}]") with self.__lock: return [] #pylint: disable=dangerous-default-value def GetConfig(self, resource_keys : List[str] = []) -> List[Tuple[str, Union[Any, None, Exception]]]: + LOGGER.info(f"GetConfig[{self}]: {resource_keys=}") chk_type('resources', resource_keys, list) - constellation = self.__cm_connection.get_constellation_by_hub_name(self.__hub_module_name) - if constellation: - self.__constellation = constellation - return [(f"/endpoints/endpoint[{ifname}]", {'uuid': ifname, 'type': 'optical', 'sample_types': {}}) for ifname in constellation.ifnames()] - else: - return [] + # Empty resource_keys means all resources. As we only have endpoints, we ignore parameter and always + # return everything. + with self.__lock: + constellation = self.__cm_connection.get_constellation_by_hub_name(self.__hub_module_name) + if constellation: + self.__constellation = constellation + return [(f"/endpoints/endpoint[{ifname}]", {'uuid': ifname, 'type': 'optical', 'sample_types': {}}) for ifname in constellation.ifnames()] + else: + return [] def SetConfig(self, resources: List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: - LOGGER.info(f"SetConfig {resources=}") - + LOGGER.info(f"SetConfig[{self}]: {resources=}") # Logged config seems like: - #[('/service[52ff5f0f-fda4-40bd-a0b1-066f4ff04079:optical]', '{"capacity_unit": "GHz", "capacity_value": 1, "direction": "UNIDIRECTIONAL", "input_sip": "XR HUB 1|XR-T4", "layer_protocol_name": "PHOTONIC_MEDIA", "layer_protocol_qualifier": "tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_NMC", "output_sip": "XR LEAF 1|XR-T1", "uuid": "52ff5f0f-fda4-40bd-a0b1-066f4ff04079:optical"}')] + #[('/service[52ff5f0f-fda4-40bd-a0b1-066f4ff04079:optical]', '{"capacity_unit": "GHz", "capacity_value": 1, "direction": "UNIDIRECTIONAL", "input_sip": "XR HUB 1|XR-T4", "layer_protocol_name": "PHOTONIC_MEDIA", "layer_protocol_qualifier": "tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_NMC", "output_sip": "XR LEAF 1|XR-T1", "uuid": "52ff5f0f-fda4-40bd-a0b1-066f4ff04079:optical"}')] - if self.__constellation is None: - self.__constellation = self.__cm_connection.get_constellation_by_hub_name(self.__hub_module_name) - - if self.__constellation is None: - LOGGER.error("SetConfig: no valid constellation") - return [False] * len(resources) + with self.__lock: + if self.__constellation is None: + self.__constellation = self.__cm_connection.get_constellation_by_hub_name(self.__hub_module_name) + + if self.__constellation is None: + LOGGER.error("SetConfig: no valid constellation") + return [False] * len(resources) + + results = [] + if len(resources) == 0: + return results + + for key, config in resources: + service_uuid = self.__cm_connection.service_uuid(key) + if service_uuid: + config = json.loads(config) + results.append(tf.set_config_for_service(self.__cm_connection, self.__constellation, service_uuid, config)) + else: + results.append(False) - results = [] - if len(resources) == 0: return results - for key, config in resources: - service_uuid = self.__cm_connection.service_uuid(key) - if service_uuid: - config = json.loads(config) - results.append(tf.set_config_for_service(self.__cm_connection, self.__constellation, service_uuid, config)) - else: - results.append(False) - - return results - def DeleteConfig(self, resources: List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: - LOGGER.info(f"DeleteConfig {resources=}") + LOGGER.info(f"DeleteConfig[{self}]: {resources=}") # Input looks like: # resources=[('/service[c8a35e81-88d8-4468-9afc-a8abd92a64d0:optical]', '{"uuid": "c8a35e81-88d8-4468-9afc-a8abd92a64d0:optical"}')] - results = [] - if len(resources) == 0: - return results - - # Temporary dummy version - for key, _config in resources: - service_uuid = self.__cm_connection.service_uuid(key) - if service_uuid: - connection = self.__cm_connection.get_connection_by_teraflow_uuid(service_uuid) - if connection is None: - LOGGER.info(f"DeleteConfig: Connection {service_uuid} does not exist, delete is no-op") - results.append(True) - else: - was_deleted = self.__cm_connection.delete_connection(connection.href) - if was_deleted: - LOGGER.info(f"DeleteConfig: Connection {service_uuid} deleted (was {str(connection)})") + with self.__lock: + results = [] + if len(resources) == 0: + return results + + # Temporary dummy version + for key, _config in resources: + service_uuid = self.__cm_connection.service_uuid(key) + if service_uuid: + connection = self.__cm_connection.get_connection_by_teraflow_uuid(service_uuid) + if connection is None: + LOGGER.info(f"DeleteConfig: Connection {service_uuid} does not exist, delete is no-op") + results.append(True) else: - LOGGER.info(f"DeleteConfig: Connection {service_uuid} delete failure (was {str(connection)})") - - if self.__constellation.is_vti_mode(): - active_tc = self.__cm_connection.get_transport_capacity_by_teraflow_uuid(service_uuid) - if active_tc is not None: - if self.__cm_connection.delete_transport_capacity(active_tc.href): - LOGGER.info(f"DeleteConfig: Transport Capacity {active_tc} deleted") - else: - LOGGER.error(f"DeleteConfig: Transport Capacity {active_tc} delete failure") - - results.append(was_deleted) - else: - results.append(False) + was_deleted = self.__cm_connection.delete_connection(connection.href) + if was_deleted: + LOGGER.info(f"DeleteConfig: Connection {service_uuid} deleted (was {str(connection)})") + else: + LOGGER.info(f"DeleteConfig: Connection {service_uuid} delete failure (was {str(connection)})") + + if self.__constellation.is_vti_mode(): + active_tc = self.__cm_connection.get_transport_capacity_by_teraflow_uuid(service_uuid) + if active_tc is not None: + if self.__cm_connection.delete_transport_capacity(active_tc.href): + LOGGER.info(f"DeleteConfig: Transport Capacity {active_tc} deleted") + else: + LOGGER.error(f"DeleteConfig: Transport Capacity {active_tc} delete failure") + + results.append(was_deleted) + else: + results.append(False) - return results + return results def SubscribeState(self, subscriptions : List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]: # Not supported diff --git a/src/device/service/drivers/xr/cm/cm_connection.py b/src/device/service/drivers/xr/cm/cm_connection.py index f86bb3179..1ea71cd13 100644 --- a/src/device/service/drivers/xr/cm/cm_connection.py +++ b/src/device/service/drivers/xr/cm/cm_connection.py @@ -1,8 +1,9 @@ #pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +import collections.abc import logging import json import time -from typing import Optional, List #Any, Iterator, , Union +from typing import Optional, List, Dict, Union import re import requests import urllib3 @@ -31,6 +32,69 @@ class ExpiringValue: else: return False +class UnexpectedEmptyBody(Exception): + pass + +class HttpResult: + def __init__(self, method: str, url: str, params: Dict[str, any] = None): + self.method = method + self.url = url + self.text = None + self.json = None + self.status_code = None + self.params = params + self.exception = None + + def __str__(self): + status_code = self.status_code if self.status_code is not None else "" + return f"{self.method} {self.url} {self.params}, status {status_code}" + + def process_http_response(self, response: requests.Response, permit_empty_body:bool = False): + LOGGER.info(f"process_http_response(): {self.method}: {self.url} qparams={self.params} ==> {response.status_code}") # FIXME: params + self.status_code = response.status_code + if response.content != b'null' and len(response.text): + self.text = response.text + + try: + r_json = json.loads(response.text) + self.json = r_json + except json.JSONDecodeError as json_err: + LOGGER.info(f"{self.method}: {self.url} ==> response json decode error: {str(json_err)}") + self.exception = json_err + elif not permit_empty_body: + raise UnexpectedEmptyBody(f"No body in HTTP response for {self.method} {self.url} (status code {response.status_code}") + + def __bool__(self): + # Error codes start at 400, codes below it are successes + return self.status_code is not None and self.text is not None and self.status_code < 400 and self.exception is None + + def is_valid_with_status_ignore_body(self, expected_status_code: int) -> bool: + return self.status_code is not None and self.status_code == expected_status_code and self.exception is None + + def is_valid_json_with_status(self, expected_status_code: int) -> bool: + return bool(self) and self.status_code == expected_status_code and self.json is not None + + def is_valid_json_list_with_status(self, expected_status_code: int, min_entries=-1, max_entries=-1) -> bool: + if not self.is_valid_json_with_status(expected_status_code): + return False + if not isinstance(self.json, collections.abc.Sequence): + return False + + if min_entries >=0 and len(self.json) < min_entries: + return False + + if max_entries >=0 and len(self.json) > max_entries: + return False + return True + + def is_valid_json_obj_with_status(self, expected_status_code: int) -> bool: + if not self.is_valid_json_with_status(expected_status_code): + return False + if not isinstance(self.json, collections.abc.Mapping): + return False + + return True + class CmConnection: def __init__(self, address: str, port: int, username: str, password: str, timeout=30, tls_verify=True) -> None: self.__tls_verify = tls_verify @@ -43,78 +107,51 @@ class CmConnection: self.__cm_root = 'https://' + address + ':' + str(port) self.__access_token = None - def __post_w_headers(self, path, data, headers, data_as_json=True): - url = self.__cm_root + path + def __perform_request(self, http_result: HttpResult, permit_empty_body: bool, fn, *args, **kwargs): try: - if data_as_json: - response = requests.post(url, headers=headers, json=data, timeout=self.__timeout, verify=self.__tls_verify) - else: - response = requests.post(url, headers=headers, data=data, timeout=self.__timeout, verify=self.__tls_verify) - - LOGGER.info(f"POST: {url} ==> {response.status_code=}, {response.text=}") - resp = json.loads(response.text) - return (response.status_code, resp) - except requests.exceptions.Timeout: - LOGGER.info(f"POST: {url} ==> timeout") - return None - except json.JSONDecodeError as json_err: - LOGGER.info(f"POST: {url} ==> response json decode error: {str(json_err)}") - return None + response = fn(*args, **kwargs) + http_result.process_http_response(response, permit_empty_body) + except requests.exceptions.Timeout as e: + LOGGER.info(f"{http_result} ==> timeout") + http_result.exception = e except Exception as e: # pylint: disable=broad-except es=str(e) - LOGGER.info(f"POST: {url} ==> unexpected exception: {es}") - return None + LOGGER.info(f"{http_result} ==> unexpected exception: {es}") + http_result.exception = e + return http_result + + def __post_w_headers(self, path, data, headers, data_as_json=True) -> HttpResult: + url = self.__cm_root + path + rv = HttpResult("POST", url) + if data_as_json: + self.__perform_request(rv, False, requests.post, url, headers=headers, json=data, timeout=self.__timeout, verify=self.__tls_verify) + else: + self.__perform_request(rv, False, requests.post, url, headers=headers, data=data, timeout=self.__timeout, verify=self.__tls_verify) + return rv - def __post(self, path, data, data_as_json=True): + def __post(self, path, data, data_as_json=True) -> HttpResult: return self.__post_w_headers(path, data, self.__http_headers(), data_as_json=data_as_json) - def __put(self, path, data, data_as_json=True): + def __put(self, path: str, data: Union[str,Dict[str, any]], data_as_json:bool =True, permit_empty_body:bool =True) -> HttpResult: url = self.__cm_root + path - headers = self.__http_headers() - try: - if data_as_json: - response = requests.put(url, headers=headers, json=data, timeout=self.__timeout, verify=self.__tls_verify) - else: - response = requests.put(url, headers=headers, data=data, timeout=self.__timeout, verify=self.__tls_verify) - - LOGGER.info(f"PUT: {url} ==> {response.status_code}") + rv = HttpResult("PUT", url) + if data_as_json: + self.__perform_request(rv, permit_empty_body, requests.put, url, headers=self.__http_headers(), json=data, timeout=self.__timeout, verify=self.__tls_verify) + else: + self.__perform_request(rv, permit_empty_body, requests.put, url, headers=self.__http_headers(), data=data, timeout=self.__timeout, verify=self.__tls_verify) + return rv - if response.content == b'null': - return (response.status_code, None) - resp = json.loads(response.text) - return (response.status_code, resp) - except requests.exceptions.Timeout: - LOGGER.info(f"PUT: {url} ==> timeout") - return None - except json.JSONDecodeError as json_err: - LOGGER.info(f"PUT: {url} ==> response json decode error: {str(json_err)}") - return None - except Exception as e: # pylint: disable=broad-except - es=str(e) - LOGGER.info(f"PUT: {url} ==> unexpected exception: {es}") - return None + def __get(self, path, params: Dict[str, any]=None) -> HttpResult: + url = self.__cm_root + path + rv = HttpResult("GET", url, params) + self.__perform_request(rv, False, requests.get, url, headers=self.__http_headers(), timeout=self.__timeout,verify=self.__tls_verify, params=params) + return rv - def __delete(self, path, data=None): + def __delete(self, path, data=None) -> HttpResult: url = self.__cm_root + path - headers = self.__http_headers() - try: - response = requests.delete(url, headers=headers, data=data, timeout=self.__timeout, verify=self.__tls_verify) - LOGGER.info(f"DELETE: {url} ==> {response.status_code}") - - if response.content == b'null': - return (response.status_code, None) - resp = json.loads(response.text) - return (response.status_code, resp) - except requests.exceptions.Timeout: - LOGGER.info(f"DELETE: {url} ==> timeout") - return None - except json.JSONDecodeError as json_err: - LOGGER.info(f"DELETE: {url} ==> response json decode error: {str(json_err)}") - return None - except Exception as e: # pylint: disable=broad-except - es=str(e) - LOGGER.info(f"DELETE: {url} ==> unexpected exception: {es}") - return None + rv = HttpResult("DELETE", url) + self.__perform_request(rv, True, requests.delete, url, headers=self.__http_headers(), data=data, timeout=self.__timeout, verify=self.__tls_verify) + return rv def __http_headers(self): self.__ensure_valid_access_token() @@ -123,24 +160,6 @@ class CmConnection: else: return {} - def __get_json(self, path, params=None): - url = self.__cm_root + path - try: - response = requests.get(url,headers=self.__http_headers(), timeout=self.__timeout,verify=self.__tls_verify, params=params) - LOGGER.info(f"GET: {url} {params=} ==> {response.status_code}") - resp = json.loads(response.text) - return (response.status_code, resp) - except requests.exceptions.Timeout: - LOGGER.info(f"GET: {url} {params=} ==> timeout") - return None - except json.JSONDecodeError as json_err: - LOGGER.info(f"GET: {url} {params=} ==> response json decode error: {str(json_err)}") - return None - except Exception as e: # pylint: disable=broad-except - es=str(e) - LOGGER.info(f"GET: {url} {params=} ==> unexpected exception: {es}") - return None - def __acquire_access_token(self): path = '/realms/xr-cm/protocol/openid-connect/token' req = { @@ -150,12 +169,20 @@ class CmConnection: "client_secret": "xr-web-client", "client_id": "xr-web-client" } - (status_code, response) = self.__post_w_headers(path, req, None, data_as_json=False) - if 200 != status_code or 'access_token' not in response: - LOGGER.error(f"Authentication failure, status code {status_code}, data {response}") + resp = self.__post_w_headers(path, req, None, data_as_json=False) + # Slightly more verbose check/logging of failures for authentication to help + # diagnose connectivity problems + if resp.status_code is None: + LOGGER.error("Failed to contact authentication API endpoint") + return False + if not resp.is_valid_json_obj_with_status(200): + LOGGER.error(f"Authentication failure, status code {resp.status_code}, data {resp.text}") + return False + if 'access_token' not in resp.json: + LOGGER.error(f"Authentication failure: missing access_token in JSON, status code {resp.status_code}, data {resp.text}") return False - access_token = response['access_token'] - expires = int(response["expires_in"]) if "expires_in" in response else 0 + access_token = resp.json['access_token'] + expires = int(resp.json["expires_in"]) if "expires_in" in resp.json else 0 LOGGER.info(f"Obtained access token {access_token}, expires in {expires}") self.__access_token = ExpiringValue(access_token, expires) return True @@ -168,10 +195,10 @@ class CmConnection: return self.__acquire_access_token() def list_constellations(self) -> List[Constellation]: - status_code, constellations = self.__get_json("/api/v1/ns/xr-networks?content=expanded") - if not constellations or status_code != 200: + r = self.__get("/api/v1/ns/xr-networks?content=expanded") + if not r.is_valid_json_list_with_status(200): return [] - return [Constellation(c) for c in constellations] + return [Constellation(c) for c in r.json] def get_constellation_by_hub_name(self, hub_module_name: str) -> Optional[Constellation]: @@ -179,25 +206,25 @@ class CmConnection: ('content', 'expanded'), ('q', '{"hubModule.state.module.moduleName": "' + hub_module_name + '"}') ] - status_code, constellations = self.__get_json("/api/v1/ns/xr-networks?content=expanded", params=qparams) - if not constellations or status_code != 200 or len(constellations) != 1: + r = self.__get("/api/v1/ns/xr-networks?content=expanded", params=qparams) + if not r.is_valid_json_list_with_status(200, 1, 1): return None - return Constellation(constellations[0]) + return Constellation(r.json[0]) def get_transport_capacities(self) -> List[TransportCapacity]: - status_code, tc = self.__get_json("/api/v1/ns/transport-capacities?content=expanded") - if not tc or status_code != 200: + r= self.__get("/api/v1/ns/transport-capacities?content=expanded") + if not r.is_valid_json_list_with_status(200): return [] - return [TransportCapacity(from_json=t) for t in tc] + return [TransportCapacity(from_json=t) for t in r.json] def get_transport_capacity_by_name(self, tc_name: str) -> Optional[Connection]: qparams = [ ('content', 'expanded'), ('q', '{"state.name": "' + tc_name + '"}') ] - r = self.__get_json("/api/v1/ns/transport-capacities?content=expanded", params=qparams) - if r and r[0] == 200 and len(r[1]) == 1: - return TransportCapacity(from_json=r[1][0]) + r = self.__get("/api/v1/ns/transport-capacities?content=expanded", params=qparams) + if not r.is_valid_json_list_with_status(200, 1, 1): + return TransportCapacity(from_json=r.json[0]) else: return None @@ -208,10 +235,10 @@ class CmConnection: # Create wants a list, so wrap connection to list tc_config = [tc.create_config()] resp = self.__post("/api/v1/ns/transport-capacities", tc_config) - if resp and resp[0] == 202 and len(resp[1]) == 1 and "href" in resp[1][0]: - tc.href = resp[1][0]["href"] + if resp.is_valid_json_list_with_status(202, 1, 1) and "href" in resp.json[0]: + tc.href = resp.json[0]["href"] LOGGER.info(f"Created transport-capcity {tc}") - #LOGGER.info(self.__get_json(f"/api/v1/ns/transport-capacities{tc.href}?content=expanded")) + #LOGGER.info(self.__get(f"/api/v1/ns/transport-capacities{tc.href}?content=expanded")) return tc.href else: return None @@ -220,11 +247,11 @@ class CmConnection: resp = self.__delete(f"/api/v1/ns/transport-capacities{href}") # Returns empty body - if resp and resp[0] == 202: + if resp.is_valid_with_status_ignore_body(202): LOGGER.info(f"Deleted transport-capacity {href=}") return True else: - LOGGER.info(f"Deleting transport-capacity {href=} failed, status {resp[0]}") + LOGGER.info(f"Deleting transport-capacity {href=} failed, status {resp.status_code}") return False def create_connection(self, connection: Connection) -> Optional[str]: @@ -232,8 +259,8 @@ class CmConnection: cfg = [connection.create_config()] resp = self.__post("/api/v1/ncs/network-connections", cfg) - if resp and resp[0] == 202 and len(resp[1]) == 1 and "href" in resp[1][0]: - connection.href = resp[1][0]["href"] + if resp.is_valid_json_list_with_status(202, 1, 1) and "href" in resp.json[0]: + connection.href = resp.json[0]["href"] LOGGER.info(f"Created connection {connection}") return connection.href else: @@ -252,7 +279,6 @@ class CmConnection: del cfg["endpoints"] if existing_connection is None: existing_connection = self.get_connection_by_href(href) - print(existing_connection) ep_deletes, ep_creates, ep_updates = connection.get_endpoint_updates(existing_connection) #print(ep_deletes) #print(ep_creates) @@ -261,7 +287,7 @@ class CmConnection: # Perform deletes for ep_href in ep_deletes: resp = self.__delete(f"/api/v1/ncs{ep_href}") - if resp and resp[0] == 202: + if resp.is_valid_with_status_ignore_body(202): LOGGER.info(f"update_connection: EP-UPDATE: Deleted connection endpoint {ep_href}") else: LOGGER.info(f"update_connection: EP-UPDATE: Failed to delete connection endpoint {ep_href}: {resp}") @@ -269,22 +295,22 @@ class CmConnection: # Update capacities for otherwise similar endpoints for ep_href, ep_cfg in ep_updates: resp = self.__put(f"/api/v1/ncs{ep_href}", ep_cfg) - if resp and resp[0] == 202: + if resp.is_valid_with_status_ignore_body(202): LOGGER.info(f"update_connection: EP-UPDATE: Updated connection endpoint {ep_href} with {ep_cfg}") else: LOGGER.info(f"update_connection: EP-UPDATE: Failed to update connection endpoint {ep_href} with {ep_cfg}: {resp}") # Perform adds resp = self.__post(f"/api/v1/ncs{href}/endpoints", ep_creates) - if resp and resp[0] == 202 and len(resp[1]) == len(ep_creates): - LOGGER.info(f"update_connection: EP-UPDATE: Created connection endpoints {resp[1]} with {ep_creates}") + if resp.is_valid_json_list_with_status(202, 1, 1) and "href" in resp.json[0]: + LOGGER.info(f"update_connection: EP-UPDATE: Created connection endpoints {resp.json[0]} with {ep_creates}") else: - LOGGER.info(f"update_connection: EP-UPDATE: Failed to create connection endpoints {resp[1]} with {ep_creates}: {resp}") + LOGGER.info(f"update_connection: EP-UPDATE: Failed to create connection endpoints {resp.json[0] if resp.json else None} with {ep_creates}: {resp}") # Connection update (excluding endpoints) resp = self.__put(f"/api/v1/ncs{href}", cfg) # Returns empty body - if resp and resp[0] == 202: + if resp.is_valid_with_status_ignore_body(202): LOGGER.info(f"update_connection: Updated connection {connection}") # Return href used for update to be consisten with create return href @@ -296,7 +322,7 @@ class CmConnection: resp = self.__delete(f"/api/v1/ncs{href}") #print(resp) # Returns empty body - if resp and resp[0] == 202: + if resp.is_valid_with_status_ignore_body(202): LOGGER.info(f"Deleted connection {href=}") return True else: @@ -306,7 +332,7 @@ class CmConnection: def create_or_update_connection(self, connection: Connection) -> Optional[str]: existing_connection = self.get_connection_by_name(connection.name) if existing_connection: - return self.update_connection(existing_connection.href, connection) + return self.update_connection(existing_connection.href, connection, existing_connection) else: return self.create_connection(connection) @@ -315,9 +341,9 @@ class CmConnection: ('content', 'expanded'), ('q', '{"state.name": "' + connection_name + '"}') ] - r = self.__get_json("/api/v1/ncs/network-connections", params=qparams) - if r and r[0] == 200 and len(r[1]) == 1: - return Connection(from_json=r[1][0]) + r = self.__get("/api/v1/ncs/network-connections", params=qparams) + if r.is_valid_json_list_with_status(200, 1, 1): + return Connection(from_json=r.json[0]) else: return None @@ -325,9 +351,9 @@ class CmConnection: qparams = [ ('content', 'expanded'), ] - r = self.__get_json(f"/api/v1/ncs{href}", params=qparams) - if r and r[0] == 200: - return Connection(from_json=r[1]) + r = self.__get(f"/api/v1/ncs{href}", params=qparams) + if r.is_valid_json_obj_with_status(200): + return Connection(from_json=r.json) else: return None @@ -335,9 +361,9 @@ class CmConnection: return self.get_connection_by_name(f"TF:{uuid}") def get_connections(self): - r = self.__get_json("/api/v1/ncs/network-connections?content=expanded") - if r and r[0] == 200: - return [Connection(from_json=c) for c in r[1]] + r = self.__get("/api/v1/ncs/network-connections?content=expanded") + if r.is_valid_json_list_with_status(200): + return [Connection(from_json=c) for c in r.json] else: return [] diff --git a/src/device/service/drivers/xr/cm/tests/resources/constellation-by-name-hub1.json b/src/device/service/drivers/xr/cm/tests/resources/constellation-by-name-hub1.json new file mode 100644 index 000000000..061d6453e --- /dev/null +++ b/src/device/service/drivers/xr/cm/tests/resources/constellation-by-name-hub1.json @@ -0,0 +1,388 @@ +[ + { + "href": "/xr-networks/233e169b-5d88-481d-bfe2-c909a2a859dd", + "hubModule": { + "href": "/xr-networks/233e169b-5d88-481d-bfe2-c909a2a859dd/hubModule", + "id": "519cc31f-b736-4e4c-b78d-600562d92911", + "parentId": "233e169b-5d88-481d-bfe2-c909a2a859dd", + "rt": [ + "cm.xr-network.hubModule" + ], + "state": { + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.100.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-1/0/0:0", + "portId": "et-1/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:00:11", + "sysName": "SanJose" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + }, + { + "hostPort": { + "chassisId": "192.168.100.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-1/0/0:1", + "portId": "et-1/0/0:1", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:00:12", + "sysName": "SanJose" + }, + "moduleIf": { + "clientIfAid": "XR-T2", + "clientIfColId": 2, + "clientIfPortSpeed": 100 + } + }, + { + "hostPort": { + "chassisId": "192.168.100.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-1/0/0:2", + "portId": "et-1/0/0:2", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:00:13", + "sysName": "SanJose" + }, + "moduleIf": { + "clientIfAid": "XR-T3", + "clientIfColId": 3, + "clientIfPortSpeed": 100 + } + }, + { + "hostPort": { + "chassisId": "192.168.100.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-1/0/0:3", + "portId": "et-1/0/0:3", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:00:14", + "sysName": "SanJose" + }, + "moduleIf": { + "clientIfAid": "XR-T4", + "clientIfColId": 4, + "clientIfPortSpeed": 100 + } + } + ], + "lifecycleState": "configured", + "module": { + "baudRate": 50, + "capacity": 400, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "hub", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:00:01", + "modulation": "16QAM", + "moduleId": "d859de3c-c463-4be5-7a8d-a198275f10f4", + "moduleName": "XR HUB 1", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "000000009", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + "id": "233e169b-5d88-481d-bfe2-c909a2a859dd", + "leafModules": [ + { + "href": "/xr-networks/233e169b-5d88-481d-bfe2-c909a2a859dd/leafModules/7e9da66b-8bf8-4eea-b4a7-045e5ba3bfd8", + "id": "7e9da66b-8bf8-4eea-b4a7-045e5ba3bfd8", + "parentId": "233e169b-5d88-481d-bfe2-c909a2a859dd", + "rt": [ + "cm.xr-network.leafModule" + ], + "state": { + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-1/0/0:0", + "portId": "et-1/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:11:01", + "sysName": "PaloAlto" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "lifecycleState": "configured", + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:01", + "maxAllowedDSCs": 4, + "modulation": "16QAM", + "moduleId": "7c33d7d0-4f7b-4525-5d57-58589adbd47c", + "moduleName": "XR LEAF 1", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "00000000B", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + { + "href": "/xr-networks/233e169b-5d88-481d-bfe2-c909a2a859dd/leafModules/7473b336-ef92-4508-b260-c096d05e4943", + "id": "7473b336-ef92-4508-b260-c096d05e4943", + "parentId": "233e169b-5d88-481d-bfe2-c909a2a859dd", + "rt": [ + "cm.xr-network.leafModule" + ], + "state": { + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.2", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-2/0/0:0", + "portId": "et-2/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:12:01", + "sysName": "Cupertino" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "lifecycleState": "configured", + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:02", + "maxAllowedDSCs": 4, + "modulation": "16QAM", + "moduleId": "d68a6b4e-03e4-4c89-5ad5-c5e782325e40", + "moduleName": "XR LEAF 2", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "00000000C", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + } + ], + "reachableModules": [ + { + "href": "/xr-networks/233e169b-5d88-481d-bfe2-c909a2a859dd/reachableModules/58785cd9-c642-43e4-a8b5-6d136acd8ae5", + "id": "58785cd9-c642-43e4-a8b5-6d136acd8ae5", + "parentId": "233e169b-5d88-481d-bfe2-c909a2a859dd", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:08Z", + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.3", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-3/0/0:0", + "portId": "et-3/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:13:01", + "sysName": "Sunnnyvale" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 193000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:03", + "modulation": "16QAM", + "moduleId": "572b2d8a-8d0b-40a0-5823-e53041ca2194", + "moduleName": "XR LEAF 3", + "ncoFrequency": 0, + "operatingFrequency": 193000000, + "roleStatus": "ready", + "serialNumber": "00000000D", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + { + "href": "/xr-networks/233e169b-5d88-481d-bfe2-c909a2a859dd/reachableModules/be85d276-6f30-4c7b-9f63-de8679dfab85", + "id": "be85d276-6f30-4c7b-9f63-de8679dfab85", + "parentId": "233e169b-5d88-481d-bfe2-c909a2a859dd", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:05Z", + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-1/0/0:0", + "portId": "et-1/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:11:01", + "sysName": "PaloAlto" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:01", + "modulation": "16QAM", + "moduleId": "7c33d7d0-4f7b-4525-5d57-58589adbd47c", + "moduleName": "XR LEAF 1", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "00000000B", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + { + "href": "/xr-networks/233e169b-5d88-481d-bfe2-c909a2a859dd/reachableModules/212cf331-c133-4321-8e74-023549b9afee", + "id": "212cf331-c133-4321-8e74-023549b9afee", + "parentId": "233e169b-5d88-481d-bfe2-c909a2a859dd", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:06Z", + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.2", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-2/0/0:0", + "portId": "et-2/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:12:01", + "sysName": "Cupertino" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:02", + "modulation": "16QAM", + "moduleId": "d68a6b4e-03e4-4c89-5ad5-c5e782325e40", + "moduleName": "XR LEAF 2", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "00000000C", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + } + ], + "rt": [ + "cm.xr-network" + ], + "state": { + "constellationFrequency": 192000000, + "controlLinks": [ + { + "conState": "active", + "destinationModuleId": "7c33d7d0-4f7b-4525-5d57-58589adbd47c", + "lastConStateChange": "2022-06-28T09:04:05Z", + "sourceModuleId": "d859de3c-c463-4be5-7a8d-a198275f10f4" + }, + { + "conState": "active", + "destinationModuleId": "d859de3c-c463-4be5-7a8d-a198275f10f4", + "lastConStateChange": "2022-06-28T09:04:05Z", + "sourceModuleId": "7c33d7d0-4f7b-4525-5d57-58589adbd47c" + }, + { + "conState": "active", + "destinationModuleId": "d68a6b4e-03e4-4c89-5ad5-c5e782325e40", + "lastConStateChange": "2022-06-28T09:04:06Z", + "sourceModuleId": "d859de3c-c463-4be5-7a8d-a198275f10f4" + }, + { + "conState": "active", + "destinationModuleId": "d859de3c-c463-4be5-7a8d-a198275f10f4", + "lastConStateChange": "2022-06-28T09:04:05Z", + "sourceModuleId": "d68a6b4e-03e4-4c89-5ad5-c5e782325e40" + } + ], + "lifecycleState": "configured", + "modulation": "16QAM" + } + } +] diff --git a/src/device/service/drivers/xr/cm/tests/resources/constellations-expanded.json b/src/device/service/drivers/xr/cm/tests/resources/constellations-expanded.json index c4aff84bf..cfe310f4b 100644 --- a/src/device/service/drivers/xr/cm/tests/resources/constellations-expanded.json +++ b/src/device/service/drivers/xr/cm/tests/resources/constellations-expanded.json @@ -1,946 +1,41 @@ [ { - "config": { - "constellationFrequency": 191000000, - "modulation": "16QAM", - "name": "FooConstellation" - }, - "href": "/xr-networks/169860ee-c1b8-4ae1-8f6e-81920a6ee09d", - "hubModule": { - "config": { - "module": { - "fiberConnectionMode": "dual", - "maxAllowedDSCs": 16, - "trafficMode": "L1Mode", - "txPowerTargetPerDsc": -6.4 - }, - "selector": { - "moduleSelectorByModuleName": { - "moduleName": "p2mp_HUB2" - } - } - }, - "href": "/xr-networks/169860ee-c1b8-4ae1-8f6e-81920a6ee09d/hubModule", - "id": "ec41b4b3-b8bf-4903-8054-8f85891dc057", - "parentId": "169860ee-c1b8-4ae1-8f6e-81920a6ee09d", - "rt": [ - "cm.xr-network.hubModule" - ], - "state": { - "lifecycleState": "pendingConfiguration", - "module": { - "baudRate": 50, - "capacity": 400, - "clientPortMode": "ethernet", - "configuredRole": "auto", - "constellationFrequency": 0, - "currentRole": "unknown", - "fiberConnectionMode": "dual", - "frequencyCtrl": "xr", - "macAddress": "00:0B:F8:FF:00:01", - "modulation": "16QAM", - "moduleId": "d01099e6-fa38-40dd-5c0d-6e7097b3f691", - "moduleName": "p2mp_HUB2", - "ncoFrequency": 0, - "operatingFrequency": 0, - "roleStatus": "scanning", - "serialNumber": "000000002", - "trafficMode": "L1Mode", - "txPowerTargetPerDsc": -6.4 - } - } - }, - "id": "169860ee-c1b8-4ae1-8f6e-81920a6ee09d", - "leafModules": [ - { - "config": { - "module": { - "fiberConnectionMode": "dual", - "trafficMode": "L1Mode", - "txPowerTargetPerDsc": -6.4 - }, - "selector": { - "moduleSelectorByModuleName": { - "moduleName": "p2mp_LEAF4" - } - } - }, - "href": "/xr-networks/169860ee-c1b8-4ae1-8f6e-81920a6ee09d/leafModules/169a434d-90c7-429a-928a-aef7013af715", - "id": "169a434d-90c7-429a-928a-aef7013af715", - "parentId": "169860ee-c1b8-4ae1-8f6e-81920a6ee09d", - "rt": [ - "cm.xr-network.leafModule" - ], - "state": { - "lifecycleState": "pendingConfiguration", - "module": { - "baudRate": 50, - "capacity": 100, - "clientPortMode": "ethernet", - "configuredRole": "auto", - "constellationFrequency": 0, - "currentRole": "unknown", - "fiberConnectionMode": "dual", - "frequencyCtrl": "xr", - "macAddress": "00:0B:F8:FF:00:06", - "modulation": "16QAM", - "moduleId": "d9580d90-1dbd-43d8-64d6-20afba0dbfe7", - "moduleName": "p2mp_LEAF4", - "ncoFrequency": 0, - "operatingFrequency": 0, - "roleStatus": "scanning", - "serialNumber": "000000006", - "trafficMode": "L1Mode", - "txPowerTargetPerDsc": -6.4 - } - } - } - ], - "rt": [ - "cm.xr-network" - ], - "state": { - "constellationFrequency": 0, - "lifecycleState": "pendingConfiguration", - "modulation": "16QAM", - "name": "FooConstellation" - } - }, - { - "href": "/xr-networks/84d9b09c-017d-4ac5-b8cd-d1102db1e070", - "hubModule": { - "href": "/xr-networks/84d9b09c-017d-4ac5-b8cd-d1102db1e070/hubModule", - "id": "921bdde3-88e3-40e6-8f6a-d5fffb9909fa", - "parentId": "84d9b09c-017d-4ac5-b8cd-d1102db1e070", - "rt": [ - "cm.xr-network.hubModule" - ], - "state": { - "endpoints": [ - { - "hostPort": { - "chassisId": "192.168.100.1", - "chassisIdSubtype": "networkAddress", - "portDescr": "et-2/0/0:3", - "portId": "et-2/0/0:3", - "portIdSubtype": "interfaceName", - "portSourceMAC": "58:00:BB:00:00:24" - }, - "moduleIf": { - "clientIfAid": "XR-T4", - "clientIfColId": 4, - "clientIfPortSpeed": 100 - } - }, - { - "hostPort": { - "chassisId": "192.168.100.1", - "chassisIdSubtype": "networkAddress", - "portDescr": "et-2/0/0:0", - "portId": "et-2/0/0:0", - "portIdSubtype": "interfaceName", - "portSourceMAC": "58:00:BB:00:00:21" - }, - "moduleIf": { - "clientIfAid": "XR-T1", - "clientIfColId": 1, - "clientIfPortSpeed": 100 - } - }, - { - "hostPort": { - "chassisId": "192.168.100.1", - "chassisIdSubtype": "networkAddress", - "portDescr": "et-2/0/0:1", - "portId": "et-2/0/0:1", - "portIdSubtype": "interfaceName", - "portSourceMAC": "58:00:BB:00:00:22" - }, - "moduleIf": { - "clientIfAid": "XR-T2", - "clientIfColId": 2, - "clientIfPortSpeed": 100 - } - }, - { - "hostPort": { - "chassisId": "192.168.100.1", - "chassisIdSubtype": "networkAddress", - "portDescr": "et-2/0/0:2", - "portId": "et-2/0/0:2", - "portIdSubtype": "interfaceName", - "portSourceMAC": "58:00:BB:00:00:23" - }, - "moduleIf": { - "clientIfAid": "XR-T3", - "clientIfColId": 3, - "clientIfPortSpeed": 100 - } - } - ], - "lifecycleState": "configured", - "module": { - "baudRate": 50, - "capacity": 400, - "clientPortMode": "ethernet", - "configuredRole": "auto", - "constellationFrequency": 193000000, - "currentRole": "hub", - "fiberConnectionMode": "dual", - "frequencyCtrl": "xr", - "macAddress": "00:0B:F8:00:00:02", - "modulation": "16QAM", - "moduleId": "3691e001-1e0c-44ae-5fd6-4f4acb8b945c", - "moduleName": "XR HUB 2", - "ncoFrequency": 0, - "operatingFrequency": 193000000, - "roleStatus": "ready", - "serialNumber": "00000000A", - "trafficMode": "L1Mode", - "txPowerTargetPerDsc": -6.4 - } - } - }, - "id": "84d9b09c-017d-4ac5-b8cd-d1102db1e070", - "leafModules": [ - { - "href": "/xr-networks/84d9b09c-017d-4ac5-b8cd-d1102db1e070/leafModules/2a3503b0-4987-4720-8cc9-11a01183905c", - "id": "2a3503b0-4987-4720-8cc9-11a01183905c", - "parentId": "84d9b09c-017d-4ac5-b8cd-d1102db1e070", - "rt": [ - "cm.xr-network.leafModule" - ], - "state": { - "endpoints": [ - { - "hostPort": { - "chassisId": "192.168.101.3", - "chassisIdSubtype": "networkAddress", - "portDescr": "et-3/0/0:0", - "portId": "et-3/0/0:0", - "portIdSubtype": "interfaceName", - "portSourceMAC": "58:00:BB:00:13:01" - }, - "moduleIf": { - "clientIfAid": "XR-T1", - "clientIfColId": 1, - "clientIfPortSpeed": 100 - } - } - ], - "lifecycleState": "configured", - "module": { - "baudRate": 50, - "capacity": 100, - "clientPortMode": "ethernet", - "configuredRole": "auto", - "constellationFrequency": 193000000, - "currentRole": "leaf", - "fiberConnectionMode": "dual", - "frequencyCtrl": "xr", - "macAddress": "00:0B:F8:00:01:03", - "maxAllowedDSCs": 4, - "modulation": "16QAM", - "moduleId": "1d23a10c-f5fb-4c2e-69ed-92608ce8e54c", - "moduleName": "XR LEAF 3", - "ncoFrequency": 0, - "operatingFrequency": 193000000, - "roleStatus": "ready", - "serialNumber": "00000000D", - "trafficMode": "L1Mode", - "txPowerTargetPerDsc": -6.4 - } - } - } - ], - "reachableModules": [ - { - "href": "/xr-networks/84d9b09c-017d-4ac5-b8cd-d1102db1e070/reachableModules/957dd5b2-2528-4d24-8d1f-1b8ca7fac33d", - "id": "957dd5b2-2528-4d24-8d1f-1b8ca7fac33d", - "parentId": "84d9b09c-017d-4ac5-b8cd-d1102db1e070", - "rt": [ - "cm.xr-network.reachableModule" - ], - "state": { - "discoveredTime": "2022-06-28T09:04:08Z", - "endpoints": [ - { - "hostPort": { - "chassisId": "192.168.101.3", - "chassisIdSubtype": "networkAddress", - "portDescr": "et-3/0/0:0", - "portId": "et-3/0/0:0", - "portIdSubtype": "interfaceName", - "portSourceMAC": "58:00:BB:00:13:01" - }, - "moduleIf": { - "clientIfAid": "XR-T1", - "clientIfColId": 1, - "clientIfPortSpeed": 100 - } - } - ], - "module": { - "baudRate": 50, - "capacity": 100, - "clientPortMode": "ethernet", - "configuredRole": "auto", - "constellationFrequency": 193000000, - "currentRole": "leaf", - "fiberConnectionMode": "dual", - "frequencyCtrl": "xr", - "macAddress": "00:0B:F8:00:01:03", - "modulation": "16QAM", - "moduleId": "1d23a10c-f5fb-4c2e-69ed-92608ce8e54c", - "moduleName": "XR LEAF 3", - "ncoFrequency": 0, - "operatingFrequency": 193000000, - "roleStatus": "ready", - "serialNumber": "00000000D", - "trafficMode": "L1Mode", - "txPowerTargetPerDsc": -6.4 - } - } - }, - { - "href": "/xr-networks/84d9b09c-017d-4ac5-b8cd-d1102db1e070/reachableModules/d159d5e7-53d3-4770-94bf-4a53f22798e5", - "id": "d159d5e7-53d3-4770-94bf-4a53f22798e5", - "parentId": "84d9b09c-017d-4ac5-b8cd-d1102db1e070", - "rt": [ - "cm.xr-network.reachableModule" - ], - "state": { - "discoveredTime": "2022-06-28T09:04:05Z", - "endpoints": [ - { - "hostPort": { - "chassisId": "192.168.101.1", - "chassisIdSubtype": "networkAddress", - "portDescr": "et-1/0/0:0", - "portId": "et-1/0/0:0", - "portIdSubtype": "interfaceName", - "portSourceMAC": "58:00:BB:00:11:01" - }, - "moduleIf": { - "clientIfAid": "XR-T1", - "clientIfColId": 1, - "clientIfPortSpeed": 100 - } - } - ], - "module": { - "baudRate": 50, - "capacity": 100, - "clientPortMode": "ethernet", - "configuredRole": "auto", - "constellationFrequency": 192000000, - "currentRole": "leaf", - "fiberConnectionMode": "dual", - "frequencyCtrl": "xr", - "macAddress": "00:0B:F8:00:01:01", - "modulation": "16QAM", - "moduleId": "9b7fb759-c952-420a-7b1e-5b3b496e1f66", - "moduleName": "XR LEAF 1", - "ncoFrequency": 0, - "operatingFrequency": 192000000, - "roleStatus": "ready", - "serialNumber": "00000000B", - "trafficMode": "L1Mode", - "txPowerTargetPerDsc": -6.4 - } - } - }, - { - "href": "/xr-networks/84d9b09c-017d-4ac5-b8cd-d1102db1e070/reachableModules/d9172426-fbe8-4e1b-9c80-373f09219831", - "id": "d9172426-fbe8-4e1b-9c80-373f09219831", - "parentId": "84d9b09c-017d-4ac5-b8cd-d1102db1e070", - "rt": [ - "cm.xr-network.reachableModule" - ], - "state": { - "discoveredTime": "2022-06-28T09:04:08Z", - "endpoints": [ - { - "moduleIf": { - "clientIfAid": "XR-T1", - "clientIfColId": 1, - "clientIfPortSpeed": 100 - } - } - ], - "module": { - "baudRate": 50, - "capacity": 100, - "clientPortMode": "ethernet", - "configuredRole": "auto", - "constellationFrequency": 193000000, - "currentRole": "leaf", - "fiberConnectionMode": "dual", - "frequencyCtrl": "xr", - "macAddress": "00:0B:F8:00:01:03", - "modulation": "16QAM", - "moduleId": "9008a605-e6a1-4afd-5728-35dd711a4a15", - "moduleName": "XR LEAF 3", - "ncoFrequency": 0, - "operatingFrequency": 193000000, - "roleStatus": "ready", - "serialNumber": "00000000D", - "trafficMode": "L1Mode", - "txPowerTargetPerDsc": -6.4 - } - } - }, - { - "href": "/xr-networks/84d9b09c-017d-4ac5-b8cd-d1102db1e070/reachableModules/0074af01-7f70-48ab-9d98-e810af9f278a", - "id": "0074af01-7f70-48ab-9d98-e810af9f278a", - "parentId": "84d9b09c-017d-4ac5-b8cd-d1102db1e070", - "rt": [ - "cm.xr-network.reachableModule" - ], - "state": { - "discoveredTime": "2022-06-28T09:04:05Z", - "endpoints": [ - { - "moduleIf": { - "clientIfAid": "XR-T1", - "clientIfColId": 1, - "clientIfPortSpeed": 100 - } - } - ], - "module": { - "baudRate": 50, - "capacity": 100, - "clientPortMode": "ethernet", - "configuredRole": "auto", - "constellationFrequency": 192000000, - "currentRole": "leaf", - "fiberConnectionMode": "dual", - "frequencyCtrl": "xr", - "macAddress": "00:0B:F8:00:01:01", - "modulation": "16QAM", - "moduleId": "4f44c0b2-3d63-465d-6413-5bc46dfa5fbf", - "moduleName": "XR LEAF 1", - "ncoFrequency": 0, - "operatingFrequency": 192000000, - "roleStatus": "ready", - "serialNumber": "00000000B", - "trafficMode": "L1Mode", - "txPowerTargetPerDsc": -6.4 - } - } - } - ], - "rt": [ - "cm.xr-network" - ], - "state": { - "constellationFrequency": 193000000, - "controlLinks": [ - { - "conState": "active", - "destinationModuleId": "3691e001-1e0c-44ae-5fd6-4f4acb8b945c", - "lastConStateChange": "2022-06-28T09:04:07Z", - "sourceModuleId": "1d23a10c-f5fb-4c2e-69ed-92608ce8e54c" - }, - { - "conState": "active", - "destinationModuleId": "1d23a10c-f5fb-4c2e-69ed-92608ce8e54c", - "lastConStateChange": "2022-06-28T09:04:08Z", - "sourceModuleId": "3691e001-1e0c-44ae-5fd6-4f4acb8b945c" - }, - { - "conState": "active", - "destinationModuleId": "9008a605-e6a1-4afd-5728-35dd711a4a15", - "lastConStateChange": "2022-06-28T09:04:08Z", - "sourceModuleId": "3691e001-1e0c-44ae-5fd6-4f4acb8b945c" - }, - { - "conState": "active", - "destinationModuleId": "3691e001-1e0c-44ae-5fd6-4f4acb8b945c", - "lastConStateChange": "2022-06-28T09:04:07Z", - "sourceModuleId": "9008a605-e6a1-4afd-5728-35dd711a4a15" - } - ], - "lifecycleState": "configured", - "modulation": "16QAM" - } - }, - { - "href": "/xr-networks/b7d4bbac-8d87-4dfe-af52-be1ef0b905d8", - "hubModule": { - "href": "/xr-networks/b7d4bbac-8d87-4dfe-af52-be1ef0b905d8/hubModule", - "id": "975df7fe-dbd8-4681-83f3-2f2a57585b67", - "parentId": "b7d4bbac-8d87-4dfe-af52-be1ef0b905d8", - "rt": [ - "cm.xr-network.hubModule" - ], - "state": { - "endpoints": [ - { - "moduleIf": { - "clientIfAid": "XR-T1", - "clientIfColId": 1, - "clientIfPortSpeed": 100 - } - }, - { - "moduleIf": { - "clientIfAid": "XR-T2", - "clientIfColId": 2, - "clientIfPortSpeed": 100 - } - }, - { - "moduleIf": { - "clientIfAid": "XR-T3", - "clientIfColId": 3, - "clientIfPortSpeed": 100 - } - }, - { - "moduleIf": { - "clientIfAid": "XR-T4", - "clientIfColId": 4, - "clientIfPortSpeed": 100 - } - } - ], - "lifecycleState": "configured", - "module": { - "baudRate": 50, - "capacity": 400, - "clientPortMode": "ethernet", - "configuredRole": "auto", - "constellationFrequency": 192000000, - "currentRole": "hub", - "fiberConnectionMode": "dual", - "frequencyCtrl": "xr", - "macAddress": "00:0B:F8:00:00:01", - "modulation": "16QAM", - "moduleId": "fda2a49d-d23c-4b7a-4b30-1d2de15b26d0", - "moduleName": "XR HUB 1", - "ncoFrequency": 0, - "operatingFrequency": 192000000, - "roleStatus": "ready", - "serialNumber": "000000009", - "trafficMode": "L1Mode", - "txPowerTargetPerDsc": -6.4 - } - } - }, - "id": "b7d4bbac-8d87-4dfe-af52-be1ef0b905d8", - "leafModules": [ - { - "href": "/xr-networks/b7d4bbac-8d87-4dfe-af52-be1ef0b905d8/leafModules/55386ab9-d2e3-428d-82d1-d940c6017959", - "id": "55386ab9-d2e3-428d-82d1-d940c6017959", - "parentId": "b7d4bbac-8d87-4dfe-af52-be1ef0b905d8", - "rt": [ - "cm.xr-network.leafModule" - ], - "state": { - "endpoints": [ - { - "hostPort": { - "chassisId": "192.168.101.1", - "chassisIdSubtype": "networkAddress", - "portDescr": "et-1/0/0:0", - "portId": "et-1/0/0:0", - "portIdSubtype": "interfaceName", - "portSourceMAC": "58:00:BB:00:11:01" - }, - "moduleIf": { - "clientIfAid": "XR-T1", - "clientIfColId": 1, - "clientIfPortSpeed": 100 - } - } - ], - "lifecycleState": "configured", - "module": { - "baudRate": 50, - "capacity": 100, - "clientPortMode": "ethernet", - "configuredRole": "auto", - "constellationFrequency": 192000000, - "currentRole": "leaf", - "fiberConnectionMode": "dual", - "frequencyCtrl": "xr", - "macAddress": "00:0B:F8:00:01:01", - "modulation": "16QAM", - "moduleId": "9b7fb759-c952-420a-7b1e-5b3b496e1f66", - "moduleName": "XR LEAF 1", - "ncoFrequency": 0, - "operatingFrequency": 192000000, - "roleStatus": "ready", - "serialNumber": "00000000B", - "trafficMode": "L1Mode", - "txPowerTargetPerDsc": -6.4 - } - } - }, - { - "href": "/xr-networks/b7d4bbac-8d87-4dfe-af52-be1ef0b905d8/leafModules/a0c8b77d-41e7-4c25-9c99-468dee383ec6", - "id": "a0c8b77d-41e7-4c25-9c99-468dee383ec6", - "parentId": "b7d4bbac-8d87-4dfe-af52-be1ef0b905d8", - "rt": [ - "cm.xr-network.leafModule" - ], - "state": { - "endpoints": [ - { - "hostPort": { - "chassisId": "192.168.101.2", - "chassisIdSubtype": "networkAddress", - "portDescr": "et-2/0/0:0", - "portId": "et-2/0/0:0", - "portIdSubtype": "interfaceName", - "portSourceMAC": "58:00:BB:00:12:01" - }, - "moduleIf": { - "clientIfAid": "XR-T1", - "clientIfColId": 1, - "clientIfPortSpeed": 100 - } - } - ], - "lifecycleState": "configured", - "module": { - "baudRate": 50, - "capacity": 100, - "clientPortMode": "ethernet", - "configuredRole": "auto", - "constellationFrequency": 192000000, - "currentRole": "leaf", - "fiberConnectionMode": "dual", - "frequencyCtrl": "xr", - "macAddress": "00:0B:F8:00:01:02", - "modulation": "16QAM", - "moduleId": "50842917-0da4-4976-63de-706b62cefd7e", - "moduleName": "XR LEAF 2", - "ncoFrequency": 0, - "operatingFrequency": 192000000, - "roleStatus": "ready", - "serialNumber": "00000000C", - "trafficMode": "L1Mode", - "txPowerTargetPerDsc": -6.4 - } - } - } - ], - "reachableModules": [ - { - "href": "/xr-networks/b7d4bbac-8d87-4dfe-af52-be1ef0b905d8/reachableModules/949d5565-eaf7-43a1-838e-c2d4fdbe4c77", - "id": "949d5565-eaf7-43a1-838e-c2d4fdbe4c77", - "parentId": "b7d4bbac-8d87-4dfe-af52-be1ef0b905d8", - "rt": [ - "cm.xr-network.reachableModule" - ], - "state": { - "discoveredTime": "2022-06-28T09:04:06Z", - "endpoints": [ - { - "hostPort": { - "chassisId": "192.168.101.2", - "chassisIdSubtype": "networkAddress", - "portDescr": "et-2/0/0:0", - "portId": "et-2/0/0:0", - "portIdSubtype": "interfaceName", - "portSourceMAC": "58:00:BB:00:12:01" - }, - "moduleIf": { - "clientIfAid": "XR-T1", - "clientIfColId": 1, - "clientIfPortSpeed": 100 - } - } - ], - "module": { - "baudRate": 50, - "capacity": 100, - "clientPortMode": "ethernet", - "configuredRole": "auto", - "constellationFrequency": 192000000, - "currentRole": "leaf", - "fiberConnectionMode": "dual", - "frequencyCtrl": "xr", - "macAddress": "00:0B:F8:00:01:02", - "modulation": "16QAM", - "moduleId": "50842917-0da4-4976-63de-706b62cefd7e", - "moduleName": "XR LEAF 2", - "ncoFrequency": 0, - "operatingFrequency": 192000000, - "roleStatus": "ready", - "serialNumber": "00000000C", - "trafficMode": "L1Mode", - "txPowerTargetPerDsc": -6.4 - } - } - }, - { - "href": "/xr-networks/b7d4bbac-8d87-4dfe-af52-be1ef0b905d8/reachableModules/d6ab2382-1095-4f98-bfb6-a0e85830c997", - "id": "d6ab2382-1095-4f98-bfb6-a0e85830c997", - "parentId": "b7d4bbac-8d87-4dfe-af52-be1ef0b905d8", - "rt": [ - "cm.xr-network.reachableModule" - ], - "state": { - "discoveredTime": "2022-06-28T09:04:08Z", - "endpoints": [ - { - "hostPort": { - "chassisId": "192.168.101.3", - "chassisIdSubtype": "networkAddress", - "portDescr": "et-3/0/0:0", - "portId": "et-3/0/0:0", - "portIdSubtype": "interfaceName", - "portSourceMAC": "58:00:BB:00:13:01" - }, - "moduleIf": { - "clientIfAid": "XR-T1", - "clientIfColId": 1, - "clientIfPortSpeed": 100 - } - } - ], - "module": { - "baudRate": 50, - "capacity": 100, - "clientPortMode": "ethernet", - "configuredRole": "auto", - "constellationFrequency": 193000000, - "currentRole": "leaf", - "fiberConnectionMode": "dual", - "frequencyCtrl": "xr", - "macAddress": "00:0B:F8:00:01:03", - "modulation": "16QAM", - "moduleId": "1d23a10c-f5fb-4c2e-69ed-92608ce8e54c", - "moduleName": "XR LEAF 3", - "ncoFrequency": 0, - "operatingFrequency": 193000000, - "roleStatus": "ready", - "serialNumber": "00000000D", - "trafficMode": "L1Mode", - "txPowerTargetPerDsc": -6.4 - } - } - }, - { - "href": "/xr-networks/b7d4bbac-8d87-4dfe-af52-be1ef0b905d8/reachableModules/e02496f1-3aec-4cd0-810e-5ff79448b4c4", - "id": "e02496f1-3aec-4cd0-810e-5ff79448b4c4", - "parentId": "b7d4bbac-8d87-4dfe-af52-be1ef0b905d8", - "rt": [ - "cm.xr-network.reachableModule" - ], - "state": { - "discoveredTime": "2022-06-28T09:04:05Z", - "endpoints": [ - { - "hostPort": { - "chassisId": "192.168.101.1", - "chassisIdSubtype": "networkAddress", - "portDescr": "et-1/0/0:0", - "portId": "et-1/0/0:0", - "portIdSubtype": "interfaceName", - "portSourceMAC": "58:00:BB:00:11:01" - }, - "moduleIf": { - "clientIfAid": "XR-T1", - "clientIfColId": 1, - "clientIfPortSpeed": 100 - } - } - ], - "module": { - "baudRate": 50, - "capacity": 100, - "clientPortMode": "ethernet", - "configuredRole": "auto", - "constellationFrequency": 192000000, - "currentRole": "leaf", - "fiberConnectionMode": "dual", - "frequencyCtrl": "xr", - "macAddress": "00:0B:F8:00:01:01", - "modulation": "16QAM", - "moduleId": "9b7fb759-c952-420a-7b1e-5b3b496e1f66", - "moduleName": "XR LEAF 1", - "ncoFrequency": 0, - "operatingFrequency": 192000000, - "roleStatus": "ready", - "serialNumber": "00000000B", - "trafficMode": "L1Mode", - "txPowerTargetPerDsc": -6.4 - } - } - }, - { - "href": "/xr-networks/b7d4bbac-8d87-4dfe-af52-be1ef0b905d8/reachableModules/4021ad07-d20e-4149-a63c-a69c36197fba", - "id": "4021ad07-d20e-4149-a63c-a69c36197fba", - "parentId": "b7d4bbac-8d87-4dfe-af52-be1ef0b905d8", - "rt": [ - "cm.xr-network.reachableModule" - ], - "state": { - "discoveredTime": "2022-06-28T09:04:08Z", - "endpoints": [ - { - "moduleIf": { - "clientIfAid": "XR-T1", - "clientIfColId": 1, - "clientIfPortSpeed": 100 - } - } - ], - "module": { - "baudRate": 50, - "capacity": 100, - "clientPortMode": "ethernet", - "configuredRole": "auto", - "constellationFrequency": 193000000, - "currentRole": "leaf", - "fiberConnectionMode": "dual", - "frequencyCtrl": "xr", - "macAddress": "00:0B:F8:00:01:03", - "modulation": "16QAM", - "moduleId": "9008a605-e6a1-4afd-5728-35dd711a4a15", - "moduleName": "XR LEAF 3", - "ncoFrequency": 0, - "operatingFrequency": 193000000, - "roleStatus": "ready", - "serialNumber": "00000000D", - "trafficMode": "L1Mode", - "txPowerTargetPerDsc": -6.4 - } - } - }, - { - "href": "/xr-networks/b7d4bbac-8d87-4dfe-af52-be1ef0b905d8/reachableModules/b376688f-21ef-4f3c-81da-15718b2bf9b4", - "id": "b376688f-21ef-4f3c-81da-15718b2bf9b4", - "parentId": "b7d4bbac-8d87-4dfe-af52-be1ef0b905d8", - "rt": [ - "cm.xr-network.reachableModule" - ], - "state": { - "discoveredTime": "2022-06-28T09:04:05Z", - "endpoints": [ - { - "moduleIf": { - "clientIfAid": "XR-T1", - "clientIfColId": 1, - "clientIfPortSpeed": 100 - } - } - ], - "module": { - "baudRate": 50, - "capacity": 100, - "clientPortMode": "ethernet", - "configuredRole": "auto", - "constellationFrequency": 192000000, - "currentRole": "leaf", - "fiberConnectionMode": "dual", - "frequencyCtrl": "xr", - "macAddress": "00:0B:F8:00:01:01", - "modulation": "16QAM", - "moduleId": "4f44c0b2-3d63-465d-6413-5bc46dfa5fbf", - "moduleName": "XR LEAF 1", - "ncoFrequency": 0, - "operatingFrequency": 192000000, - "roleStatus": "ready", - "serialNumber": "00000000B", - "trafficMode": "L1Mode", - "txPowerTargetPerDsc": -6.4 - } - } - }, - { - "href": "/xr-networks/b7d4bbac-8d87-4dfe-af52-be1ef0b905d8/reachableModules/37ac5291-bbf4-4c0b-ab0b-bdbe519f01ef", - "id": "37ac5291-bbf4-4c0b-ab0b-bdbe519f01ef", - "parentId": "b7d4bbac-8d87-4dfe-af52-be1ef0b905d8", - "rt": [ - "cm.xr-network.reachableModule" - ], - "state": { - "discoveredTime": "2022-06-28T09:04:06Z", - "endpoints": [ - { - "moduleIf": { - "clientIfAid": "XR-T1", - "clientIfColId": 1, - "clientIfPortSpeed": 100 - } - } - ], - "module": { - "baudRate": 50, - "capacity": 100, - "clientPortMode": "ethernet", - "configuredRole": "auto", - "constellationFrequency": 192000000, - "currentRole": "leaf", - "fiberConnectionMode": "dual", - "frequencyCtrl": "xr", - "macAddress": "00:0B:F8:00:01:02", - "modulation": "16QAM", - "moduleId": "c896fa4a-200f-40b1-449b-337c29c5e6e8", - "moduleName": "XR LEAF 2", - "ncoFrequency": 0, - "operatingFrequency": 192000000, - "roleStatus": "ready", - "serialNumber": "00000000C", - "trafficMode": "L1Mode", - "txPowerTargetPerDsc": -6.4 - } - } - } - ], - "rt": [ - "cm.xr-network" - ], - "state": { - "constellationFrequency": 192000000, - "controlLinks": [ - { - "conState": "active", - "destinationModuleId": "fda2a49d-d23c-4b7a-4b30-1d2de15b26d0", - "lastConStateChange": "2022-06-28T09:04:05Z", - "sourceModuleId": "9b7fb759-c952-420a-7b1e-5b3b496e1f66" - }, - { - "conState": "active", - "destinationModuleId": "9b7fb759-c952-420a-7b1e-5b3b496e1f66", - "lastConStateChange": "2022-06-28T09:04:05Z", - "sourceModuleId": "fda2a49d-d23c-4b7a-4b30-1d2de15b26d0" - }, - { - "conState": "active", - "destinationModuleId": "fda2a49d-d23c-4b7a-4b30-1d2de15b26d0", - "lastConStateChange": "2022-06-28T09:04:05Z", - "sourceModuleId": "50842917-0da4-4976-63de-706b62cefd7e" - }, - { - "conState": "active", - "destinationModuleId": "50842917-0da4-4976-63de-706b62cefd7e", - "lastConStateChange": "2022-06-28T09:04:06Z", - "sourceModuleId": "fda2a49d-d23c-4b7a-4b30-1d2de15b26d0" - } - ], - "lifecycleState": "configured", - "modulation": "16QAM" - } - }, - { - "href": "/xr-networks/8f6ba601-6814-4fb7-acc9-c1999a9cd396", + "href": "/xr-networks/6774cc4e-b0b1-43a1-923f-80fb1bec094b", "hubModule": { - "href": "/xr-networks/8f6ba601-6814-4fb7-acc9-c1999a9cd396/hubModule", - "id": "795fab13-e345-45a3-8225-e83720952ac7", - "parentId": "8f6ba601-6814-4fb7-acc9-c1999a9cd396", + "href": "/xr-networks/6774cc4e-b0b1-43a1-923f-80fb1bec094b/hubModule", + "id": "353563a1-895f-4110-abec-8f59ffb5ecc7", + "parentId": "6774cc4e-b0b1-43a1-923f-80fb1bec094b", "rt": [ "cm.xr-network.hubModule" ], "state": { "endpoints": [ { + "hostPort": { + "chassisId": "192.168.100.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-2/0/0:0", + "portId": "et-2/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:00:21", + "sysName": "SanJose" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + }, + { + "hostPort": { + "chassisId": "192.168.100.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-2/0/0:1", + "portId": "et-2/0/0:1", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:00:22", + "sysName": "SanJose" + }, "moduleIf": { "clientIfAid": "XR-T2", "clientIfColId": 2, @@ -948,6 +43,15 @@ } }, { + "hostPort": { + "chassisId": "192.168.100.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-2/0/0:2", + "portId": "et-2/0/0:2", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:00:23", + "sysName": "SanJose" + }, "moduleIf": { "clientIfAid": "XR-T3", "clientIfColId": 3, @@ -955,18 +59,20 @@ } }, { + "hostPort": { + "chassisId": "192.168.100.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-2/0/0:3", + "portId": "et-2/0/0:3", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:00:24", + "sysName": "SanJose" + }, "moduleIf": { "clientIfAid": "XR-T4", "clientIfColId": 4, "clientIfPortSpeed": 100 } - }, - { - "moduleIf": { - "clientIfAid": "XR-T1", - "clientIfColId": 1, - "clientIfPortSpeed": 100 - } } ], "lifecycleState": "configured", @@ -981,7 +87,7 @@ "frequencyCtrl": "xr", "macAddress": "00:0B:F8:00:00:02", "modulation": "16QAM", - "moduleId": "72415e29-6c91-40cc-588f-5ec5ee735123", + "moduleId": "3a2b5cfe-6265-4b68-549d-340c58363b85", "moduleName": "XR HUB 2", "ncoFrequency": 0, "operatingFrequency": 193000000, @@ -992,12 +98,12 @@ } } }, - "id": "8f6ba601-6814-4fb7-acc9-c1999a9cd396", + "id": "6774cc4e-b0b1-43a1-923f-80fb1bec094b", "leafModules": [ { - "href": "/xr-networks/8f6ba601-6814-4fb7-acc9-c1999a9cd396/leafModules/2693aaf2-d66b-458c-95aa-149c11fb7915", - "id": "2693aaf2-d66b-458c-95aa-149c11fb7915", - "parentId": "8f6ba601-6814-4fb7-acc9-c1999a9cd396", + "href": "/xr-networks/6774cc4e-b0b1-43a1-923f-80fb1bec094b/leafModules/e659ad54-9e6d-492c-ac56-09b3b681c5ed", + "id": "e659ad54-9e6d-492c-ac56-09b3b681c5ed", + "parentId": "6774cc4e-b0b1-43a1-923f-80fb1bec094b", "rt": [ "cm.xr-network.leafModule" ], @@ -1010,7 +116,8 @@ "portDescr": "et-3/0/0:0", "portId": "et-3/0/0:0", "portIdSubtype": "interfaceName", - "portSourceMAC": "58:00:BB:00:13:01" + "portSourceMAC": "58:00:BB:00:13:01", + "sysName": "Sunnnyvale" }, "moduleIf": { "clientIfAid": "XR-T1", @@ -1031,7 +138,7 @@ "frequencyCtrl": "xr", "macAddress": "00:0B:F8:00:01:03", "modulation": "16QAM", - "moduleId": "1d23a10c-f5fb-4c2e-69ed-92608ce8e54c", + "moduleId": "572b2d8a-8d0b-40a0-5823-e53041ca2194", "moduleName": "XR LEAF 3", "ncoFrequency": 0, "operatingFrequency": 193000000, @@ -1045,9 +152,9 @@ ], "reachableModules": [ { - "href": "/xr-networks/8f6ba601-6814-4fb7-acc9-c1999a9cd396/reachableModules/e01f6d1c-09b2-4e64-bb5b-b2b3fa1d6d0c", - "id": "e01f6d1c-09b2-4e64-bb5b-b2b3fa1d6d0c", - "parentId": "8f6ba601-6814-4fb7-acc9-c1999a9cd396", + "href": "/xr-networks/6774cc4e-b0b1-43a1-923f-80fb1bec094b/reachableModules/ede4b98b-a6a7-48fa-89c0-b981e7d4a98c", + "id": "ede4b98b-a6a7-48fa-89c0-b981e7d4a98c", + "parentId": "6774cc4e-b0b1-43a1-923f-80fb1bec094b", "rt": [ "cm.xr-network.reachableModule" ], @@ -1061,7 +168,8 @@ "portDescr": "et-3/0/0:0", "portId": "et-3/0/0:0", "portIdSubtype": "interfaceName", - "portSourceMAC": "58:00:BB:00:13:01" + "portSourceMAC": "58:00:BB:00:13:01", + "sysName": "Sunnnyvale" }, "moduleIf": { "clientIfAid": "XR-T1", @@ -1081,7 +189,7 @@ "frequencyCtrl": "xr", "macAddress": "00:0B:F8:00:01:03", "modulation": "16QAM", - "moduleId": "1d23a10c-f5fb-4c2e-69ed-92608ce8e54c", + "moduleId": "572b2d8a-8d0b-40a0-5823-e53041ca2194", "moduleName": "XR LEAF 3", "ncoFrequency": 0, "operatingFrequency": 193000000, @@ -1093,9 +201,9 @@ } }, { - "href": "/xr-networks/8f6ba601-6814-4fb7-acc9-c1999a9cd396/reachableModules/5ca5564c-5d6b-4d12-bc0e-daa552587b8c", - "id": "5ca5564c-5d6b-4d12-bc0e-daa552587b8c", - "parentId": "8f6ba601-6814-4fb7-acc9-c1999a9cd396", + "href": "/xr-networks/6774cc4e-b0b1-43a1-923f-80fb1bec094b/reachableModules/004ffffb-290f-45d8-90bf-4e0c914eb39c", + "id": "004ffffb-290f-45d8-90bf-4e0c914eb39c", + "parentId": "6774cc4e-b0b1-43a1-923f-80fb1bec094b", "rt": [ "cm.xr-network.reachableModule" ], @@ -1109,7 +217,8 @@ "portDescr": "et-1/0/0:0", "portId": "et-1/0/0:0", "portIdSubtype": "interfaceName", - "portSourceMAC": "58:00:BB:00:11:01" + "portSourceMAC": "58:00:BB:00:11:01", + "sysName": "PaloAlto" }, "moduleIf": { "clientIfAid": "XR-T1", @@ -1129,87 +238,7 @@ "frequencyCtrl": "xr", "macAddress": "00:0B:F8:00:01:01", "modulation": "16QAM", - "moduleId": "9b7fb759-c952-420a-7b1e-5b3b496e1f66", - "moduleName": "XR LEAF 1", - "ncoFrequency": 0, - "operatingFrequency": 192000000, - "roleStatus": "ready", - "serialNumber": "00000000B", - "trafficMode": "L1Mode", - "txPowerTargetPerDsc": -6.4 - } - } - }, - { - "href": "/xr-networks/8f6ba601-6814-4fb7-acc9-c1999a9cd396/reachableModules/214932f0-a0bb-43a3-ab30-0568630d0929", - "id": "214932f0-a0bb-43a3-ab30-0568630d0929", - "parentId": "8f6ba601-6814-4fb7-acc9-c1999a9cd396", - "rt": [ - "cm.xr-network.reachableModule" - ], - "state": { - "discoveredTime": "2022-06-28T09:04:08Z", - "endpoints": [ - { - "moduleIf": { - "clientIfAid": "XR-T1", - "clientIfColId": 1, - "clientIfPortSpeed": 100 - } - } - ], - "module": { - "baudRate": 50, - "capacity": 100, - "clientPortMode": "ethernet", - "configuredRole": "auto", - "constellationFrequency": 193000000, - "currentRole": "leaf", - "fiberConnectionMode": "dual", - "frequencyCtrl": "xr", - "macAddress": "00:0B:F8:00:01:03", - "modulation": "16QAM", - "moduleId": "9008a605-e6a1-4afd-5728-35dd711a4a15", - "moduleName": "XR LEAF 3", - "ncoFrequency": 0, - "operatingFrequency": 193000000, - "roleStatus": "ready", - "serialNumber": "00000000D", - "trafficMode": "L1Mode", - "txPowerTargetPerDsc": -6.4 - } - } - }, - { - "href": "/xr-networks/8f6ba601-6814-4fb7-acc9-c1999a9cd396/reachableModules/b2a3b179-b57e-4933-b9fb-e43f438bd17b", - "id": "b2a3b179-b57e-4933-b9fb-e43f438bd17b", - "parentId": "8f6ba601-6814-4fb7-acc9-c1999a9cd396", - "rt": [ - "cm.xr-network.reachableModule" - ], - "state": { - "discoveredTime": "2022-06-28T09:04:05Z", - "endpoints": [ - { - "moduleIf": { - "clientIfAid": "XR-T1", - "clientIfColId": 1, - "clientIfPortSpeed": 100 - } - } - ], - "module": { - "baudRate": 50, - "capacity": 100, - "clientPortMode": "ethernet", - "configuredRole": "auto", - "constellationFrequency": 192000000, - "currentRole": "leaf", - "fiberConnectionMode": "dual", - "frequencyCtrl": "xr", - "macAddress": "00:0B:F8:00:01:01", - "modulation": "16QAM", - "moduleId": "4f44c0b2-3d63-465d-6413-5bc46dfa5fbf", + "moduleId": "7c33d7d0-4f7b-4525-5d57-58589adbd47c", "moduleName": "XR LEAF 1", "ncoFrequency": 0, "operatingFrequency": 192000000, @@ -1229,15 +258,15 @@ "controlLinks": [ { "conState": "active", - "destinationModuleId": "72415e29-6c91-40cc-588f-5ec5ee735123", + "destinationModuleId": "3a2b5cfe-6265-4b68-549d-340c58363b85", "lastConStateChange": "2022-06-28T09:04:07Z", - "sourceModuleId": "1d23a10c-f5fb-4c2e-69ed-92608ce8e54c" + "sourceModuleId": "572b2d8a-8d0b-40a0-5823-e53041ca2194" }, { "conState": "active", - "destinationModuleId": "1d23a10c-f5fb-4c2e-69ed-92608ce8e54c", + "destinationModuleId": "572b2d8a-8d0b-40a0-5823-e53041ca2194", "lastConStateChange": "2022-06-28T09:04:08Z", - "sourceModuleId": "72415e29-6c91-40cc-588f-5ec5ee735123" + "sourceModuleId": "3a2b5cfe-6265-4b68-549d-340c58363b85" } ], "lifecycleState": "configured", @@ -1245,11 +274,11 @@ } }, { - "href": "/xr-networks/5456336f-24e4-4862-9fc0-81a843f07954", + "href": "/xr-networks/233e169b-5d88-481d-bfe2-c909a2a859dd", "hubModule": { - "href": "/xr-networks/5456336f-24e4-4862-9fc0-81a843f07954/hubModule", - "id": "4760494d-0639-4a56-8045-a181f48d34a8", - "parentId": "5456336f-24e4-4862-9fc0-81a843f07954", + "href": "/xr-networks/233e169b-5d88-481d-bfe2-c909a2a859dd/hubModule", + "id": "519cc31f-b736-4e4c-b78d-600562d92911", + "parentId": "233e169b-5d88-481d-bfe2-c909a2a859dd", "rt": [ "cm.xr-network.hubModule" ], @@ -1262,7 +291,8 @@ "portDescr": "et-1/0/0:0", "portId": "et-1/0/0:0", "portIdSubtype": "interfaceName", - "portSourceMAC": "58:00:BB:00:00:11" + "portSourceMAC": "58:00:BB:00:00:11", + "sysName": "SanJose" }, "moduleIf": { "clientIfAid": "XR-T1", @@ -1277,7 +307,8 @@ "portDescr": "et-1/0/0:1", "portId": "et-1/0/0:1", "portIdSubtype": "interfaceName", - "portSourceMAC": "58:00:BB:00:00:12" + "portSourceMAC": "58:00:BB:00:00:12", + "sysName": "SanJose" }, "moduleIf": { "clientIfAid": "XR-T2", @@ -1292,7 +323,8 @@ "portDescr": "et-1/0/0:2", "portId": "et-1/0/0:2", "portIdSubtype": "interfaceName", - "portSourceMAC": "58:00:BB:00:00:13" + "portSourceMAC": "58:00:BB:00:00:13", + "sysName": "SanJose" }, "moduleIf": { "clientIfAid": "XR-T3", @@ -1307,7 +339,8 @@ "portDescr": "et-1/0/0:3", "portId": "et-1/0/0:3", "portIdSubtype": "interfaceName", - "portSourceMAC": "58:00:BB:00:00:14" + "portSourceMAC": "58:00:BB:00:00:14", + "sysName": "SanJose" }, "moduleIf": { "clientIfAid": "XR-T4", @@ -1328,7 +361,7 @@ "frequencyCtrl": "xr", "macAddress": "00:0B:F8:00:00:01", "modulation": "16QAM", - "moduleId": "841480aa-d5c4-4911-5088-0cfea7970ba7", + "moduleId": "d859de3c-c463-4be5-7a8d-a198275f10f4", "moduleName": "XR HUB 1", "ncoFrequency": 0, "operatingFrequency": 192000000, @@ -1339,12 +372,12 @@ } } }, - "id": "5456336f-24e4-4862-9fc0-81a843f07954", + "id": "233e169b-5d88-481d-bfe2-c909a2a859dd", "leafModules": [ { - "href": "/xr-networks/5456336f-24e4-4862-9fc0-81a843f07954/leafModules/ba954cda-485f-4b18-acdc-ba701f876ce3", - "id": "ba954cda-485f-4b18-acdc-ba701f876ce3", - "parentId": "5456336f-24e4-4862-9fc0-81a843f07954", + "href": "/xr-networks/233e169b-5d88-481d-bfe2-c909a2a859dd/leafModules/7e9da66b-8bf8-4eea-b4a7-045e5ba3bfd8", + "id": "7e9da66b-8bf8-4eea-b4a7-045e5ba3bfd8", + "parentId": "233e169b-5d88-481d-bfe2-c909a2a859dd", "rt": [ "cm.xr-network.leafModule" ], @@ -1357,7 +390,8 @@ "portDescr": "et-1/0/0:0", "portId": "et-1/0/0:0", "portIdSubtype": "interfaceName", - "portSourceMAC": "58:00:BB:00:11:01" + "portSourceMAC": "58:00:BB:00:11:01", + "sysName": "PaloAlto" }, "moduleIf": { "clientIfAid": "XR-T1", @@ -1379,7 +413,7 @@ "macAddress": "00:0B:F8:00:01:01", "maxAllowedDSCs": 4, "modulation": "16QAM", - "moduleId": "9b7fb759-c952-420a-7b1e-5b3b496e1f66", + "moduleId": "7c33d7d0-4f7b-4525-5d57-58589adbd47c", "moduleName": "XR LEAF 1", "ncoFrequency": 0, "operatingFrequency": 192000000, @@ -1391,9 +425,9 @@ } }, { - "href": "/xr-networks/5456336f-24e4-4862-9fc0-81a843f07954/leafModules/e9188272-71a6-4a03-96eb-9462eb16cf36", - "id": "e9188272-71a6-4a03-96eb-9462eb16cf36", - "parentId": "5456336f-24e4-4862-9fc0-81a843f07954", + "href": "/xr-networks/233e169b-5d88-481d-bfe2-c909a2a859dd/leafModules/7473b336-ef92-4508-b260-c096d05e4943", + "id": "7473b336-ef92-4508-b260-c096d05e4943", + "parentId": "233e169b-5d88-481d-bfe2-c909a2a859dd", "rt": [ "cm.xr-network.leafModule" ], @@ -1406,7 +440,8 @@ "portDescr": "et-2/0/0:0", "portId": "et-2/0/0:0", "portIdSubtype": "interfaceName", - "portSourceMAC": "58:00:BB:00:12:01" + "portSourceMAC": "58:00:BB:00:12:01", + "sysName": "Cupertino" }, "moduleIf": { "clientIfAid": "XR-T1", @@ -1428,7 +463,7 @@ "macAddress": "00:0B:F8:00:01:02", "maxAllowedDSCs": 4, "modulation": "16QAM", - "moduleId": "50842917-0da4-4976-63de-706b62cefd7e", + "moduleId": "d68a6b4e-03e4-4c89-5ad5-c5e782325e40", "moduleName": "XR LEAF 2", "ncoFrequency": 0, "operatingFrequency": 192000000, @@ -1442,57 +477,9 @@ ], "reachableModules": [ { - "href": "/xr-networks/5456336f-24e4-4862-9fc0-81a843f07954/reachableModules/f8b554d3-4776-4260-8f20-eea0d0c92629", - "id": "f8b554d3-4776-4260-8f20-eea0d0c92629", - "parentId": "5456336f-24e4-4862-9fc0-81a843f07954", - "rt": [ - "cm.xr-network.reachableModule" - ], - "state": { - "discoveredTime": "2022-06-28T09:04:06Z", - "endpoints": [ - { - "hostPort": { - "chassisId": "192.168.101.2", - "chassisIdSubtype": "networkAddress", - "portDescr": "et-2/0/0:0", - "portId": "et-2/0/0:0", - "portIdSubtype": "interfaceName", - "portSourceMAC": "58:00:BB:00:12:01" - }, - "moduleIf": { - "clientIfAid": "XR-T1", - "clientIfColId": 1, - "clientIfPortSpeed": 100 - } - } - ], - "module": { - "baudRate": 50, - "capacity": 100, - "clientPortMode": "ethernet", - "configuredRole": "auto", - "constellationFrequency": 192000000, - "currentRole": "leaf", - "fiberConnectionMode": "dual", - "frequencyCtrl": "xr", - "macAddress": "00:0B:F8:00:01:02", - "modulation": "16QAM", - "moduleId": "50842917-0da4-4976-63de-706b62cefd7e", - "moduleName": "XR LEAF 2", - "ncoFrequency": 0, - "operatingFrequency": 192000000, - "roleStatus": "ready", - "serialNumber": "00000000C", - "trafficMode": "L1Mode", - "txPowerTargetPerDsc": -6.4 - } - } - }, - { - "href": "/xr-networks/5456336f-24e4-4862-9fc0-81a843f07954/reachableModules/3a45656a-6585-4ec6-a0be-365c9734827f", - "id": "3a45656a-6585-4ec6-a0be-365c9734827f", - "parentId": "5456336f-24e4-4862-9fc0-81a843f07954", + "href": "/xr-networks/233e169b-5d88-481d-bfe2-c909a2a859dd/reachableModules/58785cd9-c642-43e4-a8b5-6d136acd8ae5", + "id": "58785cd9-c642-43e4-a8b5-6d136acd8ae5", + "parentId": "233e169b-5d88-481d-bfe2-c909a2a859dd", "rt": [ "cm.xr-network.reachableModule" ], @@ -1506,7 +493,8 @@ "portDescr": "et-3/0/0:0", "portId": "et-3/0/0:0", "portIdSubtype": "interfaceName", - "portSourceMAC": "58:00:BB:00:13:01" + "portSourceMAC": "58:00:BB:00:13:01", + "sysName": "Sunnnyvale" }, "moduleIf": { "clientIfAid": "XR-T1", @@ -1526,7 +514,7 @@ "frequencyCtrl": "xr", "macAddress": "00:0B:F8:00:01:03", "modulation": "16QAM", - "moduleId": "1d23a10c-f5fb-4c2e-69ed-92608ce8e54c", + "moduleId": "572b2d8a-8d0b-40a0-5823-e53041ca2194", "moduleName": "XR LEAF 3", "ncoFrequency": 0, "operatingFrequency": 193000000, @@ -1538,9 +526,9 @@ } }, { - "href": "/xr-networks/5456336f-24e4-4862-9fc0-81a843f07954/reachableModules/2b675693-a1b3-48d6-81d9-4b3a38c7a81e", - "id": "2b675693-a1b3-48d6-81d9-4b3a38c7a81e", - "parentId": "5456336f-24e4-4862-9fc0-81a843f07954", + "href": "/xr-networks/233e169b-5d88-481d-bfe2-c909a2a859dd/reachableModules/be85d276-6f30-4c7b-9f63-de8679dfab85", + "id": "be85d276-6f30-4c7b-9f63-de8679dfab85", + "parentId": "233e169b-5d88-481d-bfe2-c909a2a859dd", "rt": [ "cm.xr-network.reachableModule" ], @@ -1554,7 +542,8 @@ "portDescr": "et-1/0/0:0", "portId": "et-1/0/0:0", "portIdSubtype": "interfaceName", - "portSourceMAC": "58:00:BB:00:11:01" + "portSourceMAC": "58:00:BB:00:11:01", + "sysName": "PaloAlto" }, "moduleIf": { "clientIfAid": "XR-T1", @@ -1574,87 +563,7 @@ "frequencyCtrl": "xr", "macAddress": "00:0B:F8:00:01:01", "modulation": "16QAM", - "moduleId": "9b7fb759-c952-420a-7b1e-5b3b496e1f66", - "moduleName": "XR LEAF 1", - "ncoFrequency": 0, - "operatingFrequency": 192000000, - "roleStatus": "ready", - "serialNumber": "00000000B", - "trafficMode": "L1Mode", - "txPowerTargetPerDsc": -6.4 - } - } - }, - { - "href": "/xr-networks/5456336f-24e4-4862-9fc0-81a843f07954/reachableModules/711f1c32-459c-4332-8722-244108210de2", - "id": "711f1c32-459c-4332-8722-244108210de2", - "parentId": "5456336f-24e4-4862-9fc0-81a843f07954", - "rt": [ - "cm.xr-network.reachableModule" - ], - "state": { - "discoveredTime": "2022-06-28T09:04:08Z", - "endpoints": [ - { - "moduleIf": { - "clientIfAid": "XR-T1", - "clientIfColId": 1, - "clientIfPortSpeed": 100 - } - } - ], - "module": { - "baudRate": 50, - "capacity": 100, - "clientPortMode": "ethernet", - "configuredRole": "auto", - "constellationFrequency": 193000000, - "currentRole": "leaf", - "fiberConnectionMode": "dual", - "frequencyCtrl": "xr", - "macAddress": "00:0B:F8:00:01:03", - "modulation": "16QAM", - "moduleId": "9008a605-e6a1-4afd-5728-35dd711a4a15", - "moduleName": "XR LEAF 3", - "ncoFrequency": 0, - "operatingFrequency": 193000000, - "roleStatus": "ready", - "serialNumber": "00000000D", - "trafficMode": "L1Mode", - "txPowerTargetPerDsc": -6.4 - } - } - }, - { - "href": "/xr-networks/5456336f-24e4-4862-9fc0-81a843f07954/reachableModules/e62157e5-fd40-44a3-9ff6-7c5f4fe48089", - "id": "e62157e5-fd40-44a3-9ff6-7c5f4fe48089", - "parentId": "5456336f-24e4-4862-9fc0-81a843f07954", - "rt": [ - "cm.xr-network.reachableModule" - ], - "state": { - "discoveredTime": "2022-06-28T09:04:05Z", - "endpoints": [ - { - "moduleIf": { - "clientIfAid": "XR-T1", - "clientIfColId": 1, - "clientIfPortSpeed": 100 - } - } - ], - "module": { - "baudRate": 50, - "capacity": 100, - "clientPortMode": "ethernet", - "configuredRole": "auto", - "constellationFrequency": 192000000, - "currentRole": "leaf", - "fiberConnectionMode": "dual", - "frequencyCtrl": "xr", - "macAddress": "00:0B:F8:00:01:01", - "modulation": "16QAM", - "moduleId": "4f44c0b2-3d63-465d-6413-5bc46dfa5fbf", + "moduleId": "7c33d7d0-4f7b-4525-5d57-58589adbd47c", "moduleName": "XR LEAF 1", "ncoFrequency": 0, "operatingFrequency": 192000000, @@ -1666,9 +575,9 @@ } }, { - "href": "/xr-networks/5456336f-24e4-4862-9fc0-81a843f07954/reachableModules/8c344a79-1075-4d0f-90f1-cd8d4f030d64", - "id": "8c344a79-1075-4d0f-90f1-cd8d4f030d64", - "parentId": "5456336f-24e4-4862-9fc0-81a843f07954", + "href": "/xr-networks/233e169b-5d88-481d-bfe2-c909a2a859dd/reachableModules/212cf331-c133-4321-8e74-023549b9afee", + "id": "212cf331-c133-4321-8e74-023549b9afee", + "parentId": "233e169b-5d88-481d-bfe2-c909a2a859dd", "rt": [ "cm.xr-network.reachableModule" ], @@ -1676,6 +585,15 @@ "discoveredTime": "2022-06-28T09:04:06Z", "endpoints": [ { + "hostPort": { + "chassisId": "192.168.101.2", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-2/0/0:0", + "portId": "et-2/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:12:01", + "sysName": "Cupertino" + }, "moduleIf": { "clientIfAid": "XR-T1", "clientIfColId": 1, @@ -1694,7 +612,7 @@ "frequencyCtrl": "xr", "macAddress": "00:0B:F8:00:01:02", "modulation": "16QAM", - "moduleId": "c896fa4a-200f-40b1-449b-337c29c5e6e8", + "moduleId": "d68a6b4e-03e4-4c89-5ad5-c5e782325e40", "moduleName": "XR LEAF 2", "ncoFrequency": 0, "operatingFrequency": 192000000, @@ -1714,51 +632,27 @@ "controlLinks": [ { "conState": "active", - "destinationModuleId": "841480aa-d5c4-4911-5088-0cfea7970ba7", - "lastConStateChange": "2022-06-28T09:04:05Z", - "sourceModuleId": "9b7fb759-c952-420a-7b1e-5b3b496e1f66" - }, - { - "conState": "active", - "destinationModuleId": "9b7fb759-c952-420a-7b1e-5b3b496e1f66", - "lastConStateChange": "2022-06-28T09:04:05Z", - "sourceModuleId": "841480aa-d5c4-4911-5088-0cfea7970ba7" - }, - { - "conState": "active", - "destinationModuleId": "841480aa-d5c4-4911-5088-0cfea7970ba7", - "lastConStateChange": "2022-06-28T09:04:05Z", - "sourceModuleId": "50842917-0da4-4976-63de-706b62cefd7e" - }, - { - "conState": "active", - "destinationModuleId": "50842917-0da4-4976-63de-706b62cefd7e", - "lastConStateChange": "2022-06-28T09:04:06Z", - "sourceModuleId": "841480aa-d5c4-4911-5088-0cfea7970ba7" - }, - { - "conState": "active", - "destinationModuleId": "4f44c0b2-3d63-465d-6413-5bc46dfa5fbf", + "destinationModuleId": "7c33d7d0-4f7b-4525-5d57-58589adbd47c", "lastConStateChange": "2022-06-28T09:04:05Z", - "sourceModuleId": "841480aa-d5c4-4911-5088-0cfea7970ba7" + "sourceModuleId": "d859de3c-c463-4be5-7a8d-a198275f10f4" }, { "conState": "active", - "destinationModuleId": "841480aa-d5c4-4911-5088-0cfea7970ba7", + "destinationModuleId": "d859de3c-c463-4be5-7a8d-a198275f10f4", "lastConStateChange": "2022-06-28T09:04:05Z", - "sourceModuleId": "4f44c0b2-3d63-465d-6413-5bc46dfa5fbf" + "sourceModuleId": "7c33d7d0-4f7b-4525-5d57-58589adbd47c" }, { "conState": "active", - "destinationModuleId": "c896fa4a-200f-40b1-449b-337c29c5e6e8", + "destinationModuleId": "d68a6b4e-03e4-4c89-5ad5-c5e782325e40", "lastConStateChange": "2022-06-28T09:04:06Z", - "sourceModuleId": "841480aa-d5c4-4911-5088-0cfea7970ba7" + "sourceModuleId": "d859de3c-c463-4be5-7a8d-a198275f10f4" }, { "conState": "active", - "destinationModuleId": "841480aa-d5c4-4911-5088-0cfea7970ba7", + "destinationModuleId": "d859de3c-c463-4be5-7a8d-a198275f10f4", "lastConStateChange": "2022-06-28T09:04:05Z", - "sourceModuleId": "c896fa4a-200f-40b1-449b-337c29c5e6e8" + "sourceModuleId": "d68a6b4e-03e4-4c89-5ad5-c5e782325e40" } ], "lifecycleState": "configured", diff --git a/src/device/service/drivers/xr/cm/tests/test_cm_connection.py b/src/device/service/drivers/xr/cm/tests/test_cm_connection.py new file mode 100644 index 000000000..0d52c71d6 --- /dev/null +++ b/src/device/service/drivers/xr/cm/tests/test_cm_connection.py @@ -0,0 +1,67 @@ +#pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +import inspect +import os +import requests_mock + +#from ..tf_service import TFService +from ..cm_connection import CmConnection + +access_token = r'{"access_token":"eyI3...","expires_in":3600,"refresh_expires_in":0,"refresh_token":"ey...","token_type":"Bearer","not-before-policy":0,"session_state":"f6e235c4-4ca4-4258-bede-4f2b7125adfb","scope":"profile email offline_access"}' + +resources = os.path.join(os.path.dirname(os.path.abspath(inspect.stack()[0][1])), "resources") +with open(os.path.join(resources, "constellations-expanded.json"), "r", encoding="UTF-8") as f: + res_constellations = f.read() +with open(os.path.join(resources, "constellation-by-name-hub1.json"), "r", encoding="UTF-8") as f: + res_constellation_by_name_hub1 = f.read() + +def mock_cm_connectivity(): + m = requests_mock.Mocker() + m.post('https://127.0.0.1:9999/realms/xr-cm/protocol/openid-connect/token', text=access_token) + return m + +def test_cmc_connect(): + # Valid access token + with requests_mock.Mocker() as m: + m.post('https://127.0.0.1:9999/realms/xr-cm/protocol/openid-connect/token', text=access_token) + cm = CmConnection("127.0.0.1", 9999, "xr-user", "xr-password", tls_verify=False) + assert cm.Connect() + + # Valid JSON but no access token + with requests_mock.Mocker() as m: + m.post('https://127.0.0.1:9999/realms/xr-cm/protocol/openid-connect/token', text=r'{"a": "b"}') + cm = CmConnection("127.0.0.1", 9999, "xr-user", "xr-password", tls_verify=False) + assert not cm.Connect() + + # Invalid JSON + with requests_mock.Mocker() as m: + m.post('https://127.0.0.1:9999/realms/xr-cm/protocol/openid-connect/token', text=r'}}}') + cm = CmConnection("127.0.0.1", 9999, "xr-user", "xr-password", tls_verify=False) + assert not cm.Connect() + + with requests_mock.Mocker() as m: + # No mock present for the destination + cm = CmConnection("127.0.0.1", 9999, "xr-user", "xr-password", tls_verify=False) + assert not cm.Connect() + +def test_cmc_get_constellations(): + with mock_cm_connectivity() as m: + m.get("https://127.0.0.1:9999/api/v1/ns/xr-networks?content=expanded", text=res_constellations) + cm = CmConnection("127.0.0.1", 9999, "xr-user", "xr-password", tls_verify=False) + assert cm.Connect() + + # List all constellations + constellations = cm.list_constellations() + assert len(constellations) == 2 + cids = [c.constellation_id for c in constellations] + assert cids == ["6774cc4e-b0b1-43a1-923f-80fb1bec094b", "233e169b-5d88-481d-bfe2-c909a2a859dd"] + ifnames = [c.ifnames() for c in constellations] + assert ifnames == [['XR HUB 2|XR-T1', 'XR HUB 2|XR-T2', 'XR HUB 2|XR-T3', 'XR HUB 2|XR-T4', 'XR LEAF 3|XR-T1'], + ['XR HUB 1|XR-T1', 'XR HUB 1|XR-T2', 'XR HUB 1|XR-T3', 'XR HUB 1|XR-T4', 'XR LEAF 1|XR-T1', 'XR LEAF 2|XR-T1']] + + # Get constellation by hub module name + m.get("https://127.0.0.1:9999/api/v1/ns/xr-networks?content=expanded&content=expanded&q=%7B%22hubModule.state.module.moduleName%22%3A+%22XR+HUB+1%22%7D", text=res_constellation_by_name_hub1) + constellation = cm.get_constellation_by_hub_name("XR HUB 1") + assert constellation + assert constellation.ifnames() == ['XR HUB 1|XR-T1', 'XR HUB 1|XR-T2', 'XR HUB 1|XR-T3', 'XR HUB 1|XR-T4', 'XR LEAF 1|XR-T1', 'XR LEAF 2|XR-T1'] + assert constellation.constellation_id == "233e169b-5d88-481d-bfe2-c909a2a859dd" + \ No newline at end of file diff --git a/src/device/service/drivers/xr/cm/tests/test_constellation.py b/src/device/service/drivers/xr/cm/tests/test_constellation.py index 6d40e2acb..89b449ec0 100644 --- a/src/device/service/drivers/xr/cm/tests/test_constellation.py +++ b/src/device/service/drivers/xr/cm/tests/test_constellation.py @@ -12,18 +12,12 @@ def test_constellation_json(): with open(os.path.join(resources, "constellations-expanded.json"), "r", encoding="UTF-8") as f: j = json.load(f) - # A pre-planned constellation without endpoints - constellation = Constellation(j[0]) - assert constellation.constellation_id == "169860ee-c1b8-4ae1-8f6e-81920a6ee09d" - assert not constellation.is_vti_mode() - assert not constellation.ifnames() - # Proper constellation with endpoints constellation = Constellation(j[1]) - assert constellation.constellation_id == "84d9b09c-017d-4ac5-b8cd-d1102db1e070" + assert constellation.constellation_id == "233e169b-5d88-481d-bfe2-c909a2a859dd" assert not constellation.is_vti_mode() print(constellation.ifnames()) - assert ['XR HUB 2|XR-T4', 'XR HUB 2|XR-T1', 'XR HUB 2|XR-T2', 'XR HUB 2|XR-T3', 'XR LEAF 3|XR-T1'] == constellation.ifnames() + assert ['XR HUB 1|XR-T1', 'XR HUB 1|XR-T2', 'XR HUB 1|XR-T3', 'XR HUB 1|XR-T4', 'XR LEAF 1|XR-T1', 'XR LEAF 2|XR-T1'] == constellation.ifnames() # Remove mandatory key, will raise an exception del j[0]["hubModule"]["state"] diff --git a/src/device/service/drivers/xr/cm/tests/test_xr_service_set_config.py b/src/device/service/drivers/xr/cm/tests/test_xr_service_set_config.py new file mode 100644 index 000000000..0565a8bc7 --- /dev/null +++ b/src/device/service/drivers/xr/cm/tests/test_xr_service_set_config.py @@ -0,0 +1,95 @@ +#pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +import inspect +import os +import json +import requests_mock +import traceback + +from ..cm_connection import CmConnection +from ..tf import set_config_for_service + +access_token = r'{"access_token":"eyI3...","expires_in":3600,"refresh_expires_in":0,"refresh_token":"ey...","token_type":"Bearer","not-before-policy":0,"session_state":"f6e235c4-4ca4-4258-bede-4f2b7125adfb","scope":"profile email offline_access"}' + +resources = os.path.join(os.path.dirname(os.path.abspath(inspect.stack()[0][1])), "resources") +with open(os.path.join(resources, "constellation-by-name-hub1.json"), "r", encoding="UTF-8") as f: + res_constellation_by_name_hub1 = f.read() +with open(os.path.join(resources, "connections-expanded.json"), "r", encoding="UTF-8") as f: + j = json.load(f) + # Fake reference data to have the name this test case needs for the given teraflow UUID + # (=no need for too large set of reference material) + j[0]["state"]["name"] = "TF:12345ABCDEFGHIJKLMN" + res_connection_by_name_json = [j[0]] # Single item list + +def mock_cm(): + m = requests_mock.Mocker() + m.post('https://127.0.0.1:9999/realms/xr-cm/protocol/openid-connect/token', text=access_token) + m.get("https://127.0.0.1:9999/api/v1/ns/xr-networks?content=expanded&content=expanded&q=%7B%22hubModule.state.module.moduleName%22%3A+%22XR+HUB+1%22%7D", text=res_constellation_by_name_hub1) + m.post("https://127.0.0.1:9999/api/v1/ncs/network-connections", text='[{"href":"/network-connections/c3b31608-0bb7-4a4f-9f9a-88b24a059432","rt":["cm.network-connection"]}]', status_code=202) + return m + +uuid = "12345ABCDEFGHIJKLMN" +config = { + "input_sip": "XR HUB 1|XR-T4;", + "output_sip": "XR LEAF 1|XR-T1", + "capacity_value": 125, + "capacity_unit": "gigabit" +} + +def _validate_result(result, expect): + if isinstance(result, Exception): + traceback.print_exception(result) + assert result is expect # Not, "is", not ==, we want type checking in this case, as also an exception can be returned (as return value) + +def test_xr_set_config(): + with mock_cm() as m: + cm = CmConnection("127.0.0.1", 9999, "xr-user", "xr-password", tls_verify=False) + assert cm.Connect() + + constellation = cm.get_constellation_by_hub_name("XR HUB 1") + assert constellation + + result = set_config_for_service(cm, constellation, uuid, config) + _validate_result(result, True) + + called_mocks = [(r._request.method, r._request.url) for r in m._adapter.request_history] + expected_mocks = [ + ('POST', 'https://127.0.0.1:9999/realms/xr-cm/protocol/openid-connect/token'), # Authentication + ('GET', 'https://127.0.0.1:9999/api/v1/ns/xr-networks?content=expanded&content=expanded&q=%7B%22hubModule.state.module.moduleName%22%3A+%22XR+HUB+1%22%7D'), # Hub module by name + ('GET', 'https://127.0.0.1:9999/api/v1/ncs/network-connections?content=expanded&q=%7B%22state.name%22%3A+%22TF%3A12345ABCDEFGHIJKLMN%22%7D'), # Get by name, determine update or create + ('POST', 'https://127.0.0.1:9999/api/v1/ncs/network-connections') # Create + ] + assert called_mocks == expected_mocks + +def test_xr_set_config_update_case(): + with mock_cm() as m: + cm = CmConnection("127.0.0.1", 9999, "xr-user", "xr-password", tls_verify=False) + assert cm.Connect() + + constellation = cm.get_constellation_by_hub_name("XR HUB 1") + assert constellation + + # Fake existing service (--> update path is taken) + m.get("https://127.0.0.1:9999/api/v1/ncs/network-connections?content=expanded&q=%7B%22state.name%22%3A+%22TF%3A12345ABCDEFGHIJKLMN%22%7D", json=res_connection_by_name_json) + # Delete endpoint that is no longer necessary + m.delete("https://127.0.0.1:9999/api/v1/ncs/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03/endpoints/1d58ba8f-4d51-4213-83e1-97a0e0bdd388", text="", status_code = 202) + # Update changed endpoint + m.put("https://127.0.0.1:9999/api/v1/ncs/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03/endpoints/230516d0-7e38-44b1-b174-1ba7d4454ee6", text="", status_code = 202) + # Create the newly added endpoint + m.post("https://127.0.0.1:9999/api/v1/ncs/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03/endpoints", json=[{"href":"/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03/endpoint/somethingplausible","rt":["plausible"]}], status_code=202) + # Update the connection itself + m.put("https://127.0.0.1:9999/api/v1/ncs/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03", text="", status_code=202) + + result = set_config_for_service(cm, constellation, uuid, config) + _validate_result(result, True) + + called_mocks = [(r._request.method, r._request.url) for r in m._adapter.request_history] + expected_mocks = [ + ('POST', 'https://127.0.0.1:9999/realms/xr-cm/protocol/openid-connect/token'), # Authentication + ('GET', 'https://127.0.0.1:9999/api/v1/ns/xr-networks?content=expanded&content=expanded&q=%7B%22hubModule.state.module.moduleName%22%3A+%22XR+HUB+1%22%7D'), # Hub module by name + ('GET', 'https://127.0.0.1:9999/api/v1/ncs/network-connections?content=expanded&q=%7B%22state.name%22%3A+%22TF%3A12345ABCDEFGHIJKLMN%22%7D'), # Get by name, determine update or create + ('DELETE', 'https://127.0.0.1:9999/api/v1/ncs/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03/endpoints/1d58ba8f-4d51-4213-83e1-97a0e0bdd388'), # Delete unnecessary endpoint + ('PUT', 'https://127.0.0.1:9999/api/v1/ncs/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03/endpoints/230516d0-7e38-44b1-b174-1ba7d4454ee6'), # Update changed endpoint + ('POST', 'https://127.0.0.1:9999/api/v1/ncs/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03/endpoints'), # Add new endpoint + ('PUT', 'https://127.0.0.1:9999/api/v1/ncs/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03') # Update the connection itself + ] + assert called_mocks == expected_mocks diff --git a/src/device/service/drivers/xr/cm/tf.py b/src/device/service/drivers/xr/cm/tf.py index 6bf7d58de..938dcfbb8 100644 --- a/src/device/service/drivers/xr/cm/tf.py +++ b/src/device/service/drivers/xr/cm/tf.py @@ -45,10 +45,10 @@ def set_config_for_service(cm_connection: CmConnection, constellation: Constella connection = Connection(from_tf_service=service) href = cm_connection.create_or_update_connection(connection) if href: - LOGGER.info(f"set_config_for_service: Created service {uuid} as {href} ({connection=})") + LOGGER.info(f"set_config_for_service: Created service {uuid} as {href} (connection={str(connection)})") return True else: - LOGGER.error(f"set_config_for_service: Service creation failure for {uuid} ({connection=})") + LOGGER.error(f"set_config_for_service: Service creation failure for {uuid} (connection={str(connection)})") return False # Intentionally catching all exceptions, as they are stored in a list as return values # by the caller -- GitLab From 941f83ccb2aa8f6f0d5bf96d141ed798d3f58eb6 Mon Sep 17 00:00:00 2001 From: Ville Hallivuori Date: Wed, 2 Nov 2022 14:35:55 +0200 Subject: [PATCH 06/10] Merged generated java file --- .../src/test/java/eu/teraflow/automation/SerializerTest.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/automation/src/test/java/eu/teraflow/automation/SerializerTest.java b/src/automation/src/test/java/eu/teraflow/automation/SerializerTest.java index efb5c97a6..1161d9552 100644 --- a/src/automation/src/test/java/eu/teraflow/automation/SerializerTest.java +++ b/src/automation/src/test/java/eu/teraflow/automation/SerializerTest.java @@ -1214,9 +1214,7 @@ class SerializerTest { Arguments.of( DeviceDriverEnum.ONF_TR_352, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352), - Arguments.of( - DeviceDriverEnum.XR, - ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_XR), + Arguments.of(DeviceDriverEnum.XR, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_XR), Arguments.of( DeviceDriverEnum.UNDEFINED, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED)); } -- GitLab From a2333e5842b5e1cdfc16b4091273e7c66f721389 Mon Sep 17 00:00:00 2001 From: Ville Hallivuori Date: Fri, 4 Nov 2022 09:58:29 +0200 Subject: [PATCH 07/10] Executed mvn spotless:apply to address build script failure --- .../src/test/java/eu/teraflow/policy/SerializerTest.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/policy/src/test/java/eu/teraflow/policy/SerializerTest.java b/src/policy/src/test/java/eu/teraflow/policy/SerializerTest.java index 130716733..156d313b2 100644 --- a/src/policy/src/test/java/eu/teraflow/policy/SerializerTest.java +++ b/src/policy/src/test/java/eu/teraflow/policy/SerializerTest.java @@ -3593,9 +3593,7 @@ class SerializerTest { Arguments.of( DeviceDriverEnum.ONF_TR_352, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352), - Arguments.of( - DeviceDriverEnum.XR, - ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_XR), + Arguments.of(DeviceDriverEnum.XR, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_XR), Arguments.of( DeviceDriverEnum.UNDEFINED, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED)); } -- GitLab From bde76f629ab3c72bbd4ec3935acf333bfc9e8fe9 Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Tue, 22 Nov 2022 18:23:02 +0100 Subject: [PATCH 08/10] Device - XR Device Driver: - updated repo URL in readme file --- src/device/service/drivers/xr/README_XR.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/device/service/drivers/xr/README_XR.md b/src/device/service/drivers/xr/README_XR.md index ac93321a3..f7c2316ce 100644 --- a/src/device/service/drivers/xr/README_XR.md +++ b/src/device/service/drivers/xr/README_XR.md @@ -1,6 +1,6 @@ # Infinera Readme -There are some instructions at https://gitlab.com/teraflow-h2020/controller/-/tree/develop/tutorial . They are not completely up to date and don't 100% work. +There are some instructions at https://labs.etsi.org/rep/tfs/controller/-/tree/develop/tutorial . They are not completely up to date and don't 100% work. Note that many of the scripts expect this and that K8s namespace being used, they are not consistent, so use manual kubectl commands where necessary. -- GitLab From 87da929415b6e860ab64d9efbfdcf3ede995b7ff Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Tue, 22 Nov 2022 18:38:19 +0100 Subject: [PATCH 09/10] Device - XR Device Driver: - added missing Python code file headers --- src/device/service/drivers/xr/__init__.py | 13 +++++++++++++ src/device/service/drivers/xr/cm-cli.py | 14 ++++++++++++++ src/device/service/drivers/xr/cm/__init__.py | 13 +++++++++++++ src/device/service/drivers/xr/cm/cm_connection.py | 14 ++++++++++++++ src/device/service/drivers/xr/cm/connection.py | 14 ++++++++++++++ src/device/service/drivers/xr/cm/constellation.py | 14 ++++++++++++++ src/device/service/drivers/xr/cm/tests/__init__.py | 13 +++++++++++++ .../drivers/xr/cm/tests/test_cm_connection.py | 14 ++++++++++++++ .../service/drivers/xr/cm/tests/test_connection.py | 14 ++++++++++++++ .../drivers/xr/cm/tests/test_constellation.py | 14 ++++++++++++++ .../xr/cm/tests/test_transport_capacitity.py | 14 ++++++++++++++ .../xr/cm/tests/test_xr_service_set_config.py | 14 ++++++++++++++ src/device/service/drivers/xr/cm/tf.py | 14 ++++++++++++++ src/device/service/drivers/xr/cm/tf_service.py | 14 ++++++++++++++ .../service/drivers/xr/cm/transport_capacity.py | 13 +++++++++++++ src/device/service/drivers/xr/cm/utils.py | 14 ++++++++++++++ 16 files changed, 220 insertions(+) diff --git a/src/device/service/drivers/xr/__init__.py b/src/device/service/drivers/xr/__init__.py index e69de29bb..9953c8205 100644 --- a/src/device/service/drivers/xr/__init__.py +++ b/src/device/service/drivers/xr/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/src/device/service/drivers/xr/cm-cli.py b/src/device/service/drivers/xr/cm-cli.py index 7cfc5b9ed..8b8fec59c 100755 --- a/src/device/service/drivers/xr/cm-cli.py +++ b/src/device/service/drivers/xr/cm-cli.py @@ -1,5 +1,19 @@ #!/usr/bin/env python3 #pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +# 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. + # Test program for CmConnection import argparse import logging diff --git a/src/device/service/drivers/xr/cm/__init__.py b/src/device/service/drivers/xr/cm/__init__.py index e69de29bb..9953c8205 100644 --- a/src/device/service/drivers/xr/cm/__init__.py +++ b/src/device/service/drivers/xr/cm/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/src/device/service/drivers/xr/cm/cm_connection.py b/src/device/service/drivers/xr/cm/cm_connection.py index 1ea71cd13..7e0fc61b7 100644 --- a/src/device/service/drivers/xr/cm/cm_connection.py +++ b/src/device/service/drivers/xr/cm/cm_connection.py @@ -1,4 +1,18 @@ #pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +# 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 collections.abc import logging import json diff --git a/src/device/service/drivers/xr/cm/connection.py b/src/device/service/drivers/xr/cm/connection.py index 78a853321..e88995842 100644 --- a/src/device/service/drivers/xr/cm/connection.py +++ b/src/device/service/drivers/xr/cm/connection.py @@ -1,4 +1,18 @@ #pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +# 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. + from typing import Dict, Optional from dataclasses import dataclass from .tf_service import TFService diff --git a/src/device/service/drivers/xr/cm/constellation.py b/src/device/service/drivers/xr/cm/constellation.py index 255d267d8..468cf70b6 100644 --- a/src/device/service/drivers/xr/cm/constellation.py +++ b/src/device/service/drivers/xr/cm/constellation.py @@ -1,4 +1,18 @@ #pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring, wildcard-import, unused-wildcard-import +# 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. + from typing import List from .utils import * diff --git a/src/device/service/drivers/xr/cm/tests/__init__.py b/src/device/service/drivers/xr/cm/tests/__init__.py index e69de29bb..9953c8205 100644 --- a/src/device/service/drivers/xr/cm/tests/__init__.py +++ b/src/device/service/drivers/xr/cm/tests/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/src/device/service/drivers/xr/cm/tests/test_cm_connection.py b/src/device/service/drivers/xr/cm/tests/test_cm_connection.py index 0d52c71d6..60cbeac06 100644 --- a/src/device/service/drivers/xr/cm/tests/test_cm_connection.py +++ b/src/device/service/drivers/xr/cm/tests/test_cm_connection.py @@ -1,4 +1,18 @@ #pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +# 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 inspect import os import requests_mock diff --git a/src/device/service/drivers/xr/cm/tests/test_connection.py b/src/device/service/drivers/xr/cm/tests/test_connection.py index 6e5e77813..0792033a3 100644 --- a/src/device/service/drivers/xr/cm/tests/test_connection.py +++ b/src/device/service/drivers/xr/cm/tests/test_connection.py @@ -1,4 +1,18 @@ #pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +# 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 inspect import os import json diff --git a/src/device/service/drivers/xr/cm/tests/test_constellation.py b/src/device/service/drivers/xr/cm/tests/test_constellation.py index 89b449ec0..82848b57e 100644 --- a/src/device/service/drivers/xr/cm/tests/test_constellation.py +++ b/src/device/service/drivers/xr/cm/tests/test_constellation.py @@ -1,4 +1,18 @@ #pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +# 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 inspect import os import json diff --git a/src/device/service/drivers/xr/cm/tests/test_transport_capacitity.py b/src/device/service/drivers/xr/cm/tests/test_transport_capacitity.py index 8d230b4b4..cfdadae6a 100644 --- a/src/device/service/drivers/xr/cm/tests/test_transport_capacitity.py +++ b/src/device/service/drivers/xr/cm/tests/test_transport_capacitity.py @@ -1,4 +1,18 @@ #pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +# 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 inspect import os import json diff --git a/src/device/service/drivers/xr/cm/tests/test_xr_service_set_config.py b/src/device/service/drivers/xr/cm/tests/test_xr_service_set_config.py index 0565a8bc7..5a97e6ee2 100644 --- a/src/device/service/drivers/xr/cm/tests/test_xr_service_set_config.py +++ b/src/device/service/drivers/xr/cm/tests/test_xr_service_set_config.py @@ -1,4 +1,18 @@ #pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +# 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 inspect import os import json diff --git a/src/device/service/drivers/xr/cm/tf.py b/src/device/service/drivers/xr/cm/tf.py index 938dcfbb8..1872bfe6c 100644 --- a/src/device/service/drivers/xr/cm/tf.py +++ b/src/device/service/drivers/xr/cm/tf.py @@ -1,4 +1,18 @@ #pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +# 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. + from typing import Dict, Union import logging from .cm_connection import CmConnection diff --git a/src/device/service/drivers/xr/cm/tf_service.py b/src/device/service/drivers/xr/cm/tf_service.py index a780edfa6..7ba8d9ee4 100644 --- a/src/device/service/drivers/xr/cm/tf_service.py +++ b/src/device/service/drivers/xr/cm/tf_service.py @@ -1,4 +1,18 @@ #pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring, wildcard-import, unused-wildcard-import +# 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 math from dataclasses import dataclass from typing import Tuple, Optional, Dict, List diff --git a/src/device/service/drivers/xr/cm/transport_capacity.py b/src/device/service/drivers/xr/cm/transport_capacity.py index 873ae287a..d28d5b137 100644 --- a/src/device/service/drivers/xr/cm/transport_capacity.py +++ b/src/device/service/drivers/xr/cm/transport_capacity.py @@ -1,4 +1,17 @@ #pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +# 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. from typing import Optional, Dict from dataclasses import dataclass diff --git a/src/device/service/drivers/xr/cm/utils.py b/src/device/service/drivers/xr/cm/utils.py index 09d98eb08..cdf9e58c3 100644 --- a/src/device/service/drivers/xr/cm/utils.py +++ b/src/device/service/drivers/xr/cm/utils.py @@ -1,4 +1,18 @@ #pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +# 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. + from typing import Any, Tuple, Optional, Dict class InvalidIfnameError(Exception): -- GitLab From e5205234adbdc97854fb3b00f792516763ec1eb8 Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Tue, 22 Nov 2022 18:48:05 +0100 Subject: [PATCH 10/10] Common: - cleanup .gitignore --- .gitignore | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.gitignore b/.gitignore index e238cd82d..7e3b0cd6a 100644 --- a/.gitignore +++ b/.gitignore @@ -169,11 +169,3 @@ local_k8s_deployment.sh # Other logs **/logs/*.log.* - -# Symlink for generated proto-files in host -src/context/proto - -# Generated files in dlt -src/dlt/.gradle -src/dlt/bin -src/dlt/gateway/bin -- GitLab