diff --git a/.gitignore b/.gitignore index 71b77da25c4d53db49b24642d88062906e7db219..00cedaff9e97e5fa3dfe53f7a5297d472bbb6265 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 45a64fabb43bab645e97e9d80bc1825242006dce..1e953741471dbe872e491ad8ab836e1d7653a05e 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 { * <code>DEVICEDRIVER_ONF_TR_352 = 5;</code> */ DEVICEDRIVER_ONF_TR_352(5), + /** + * <code>DEVICEDRIVER_XR = 6;</code> + */ + DEVICEDRIVER_XR(6), UNRECOGNIZED(-1), ; @@ -204,6 +208,10 @@ public final class ContextOuterClass { * <code>DEVICEDRIVER_ONF_TR_352 = 5;</code> */ public static final int DEVICEDRIVER_ONF_TR_352_VALUE = 5; + /** + * <code>DEVICEDRIVER_XR = 6;</code> + */ + 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 339c516ae93f2a72910ae1fc5ccabdeff10ca997..234261124fc70059ac14e947274a3a4e95648192 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 c40224cb4e07e9346874b5f6064c2abacd51155e..c3325b14f3732483a675d6e58768f9b01aaa2d6d 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 6c466ff9849537949120f492df1aa18261572da0..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 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 2bd23fdb842989574824e8423a90e65b758081b6..7cfc5b9ed33317e867d286da275e9c1f9497efae 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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 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 490597860ff1df2883bd0d64f7e7da0347b4805e..f86bb3179261309c2f263957a50a89c9d352195f 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 "<NO NAME>" - 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 0000000000000000000000000000000000000000..78a853321d30a637b40e4de1056125dd1d170fd2 --- /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 "<NO NAME>" + 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 0000000000000000000000000000000000000000..255d267d8061399a9cbca51551ecc65814bf6ac5 --- /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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 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 0000000000000000000000000000000000000000..f9f064ea20c3764ad0a5e4d0d3dfb60b468c2556 --- /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 0000000000000000000000000000000000000000..c4aff84bf75fc48cd30ba0dd8180b030a35dd78e --- /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 0000000000000000000000000000000000000000..f4f1b00a30aa60d15962eee5fd7471a978c0ee67 --- /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 0000000000000000000000000000000000000000..6e5e77813945608f7ecfb4f973d64069286375bc --- /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: <NO 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 0000000000000000000000000000000000000000..6d40e2acb929b7b044dbafa987db85fcf849dca8 --- /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 0000000000000000000000000000000000000000..8d230b4b40f85215406ac1e2f8e4e3c94b7939a7 --- /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 0000000000000000000000000000000000000000..6bf7d58de80a41e2495b33b8f62b3553bccc5a61 --- /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 0000000000000000000000000000000000000000..a780edfa6b447bbc851e83b328a5cc5e1cc382e3 --- /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 0000000000000000000000000000000000000000..873ae287a7bd957979c61ec18d1deb9321abede5 --- /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 "<NO NAME>" + 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 0000000000000000000000000000000000000000..09d98eb083c218768971d349cafa4108dd05dcd3 --- /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 45a64fabb43bab645e97e9d80bc1825242006dce..1e953741471dbe872e491ad8ab836e1d7653a05e 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 { * <code>DEVICEDRIVER_ONF_TR_352 = 5;</code> */ DEVICEDRIVER_ONF_TR_352(5), + /** + * <code>DEVICEDRIVER_XR = 6;</code> + */ + DEVICEDRIVER_XR(6), UNRECOGNIZED(-1), ; @@ -204,6 +208,10 @@ public final class ContextOuterClass { * <code>DEVICEDRIVER_ONF_TR_352 = 5;</code> */ public static final int DEVICEDRIVER_ONF_TR_352_VALUE = 5; + /** + * <code>DEVICEDRIVER_XR = 6;</code> + */ + 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,