diff --git a/proto/context.proto b/proto/context.proto index f5dec30796a8426f512947d369b8db5f5889471a..5b49bd28866af919332ab7188bbf66203e8b766d 100644 --- a/proto/context.proto +++ b/proto/context.proto @@ -171,6 +171,7 @@ enum DeviceDriverEnum { DEVICEDRIVER_P4 = 3; DEVICEDRIVER_IETF_NETWORK_TOPOLOGY = 4; DEVICEDRIVER_ONF_TR_352 = 5; + DEVICEDRIVER_XR = 6; } enum DeviceOperationalStatusEnum { diff --git a/src/automation/src/main/java/eu/teraflow/automation/Serializer.java b/src/automation/src/main/java/eu/teraflow/automation/Serializer.java index 816500a57d8431b36f54a95ee714b59b5f984c62..445dea540b57717f1005d8b37269777f7e2147ee 100644 --- a/src/automation/src/main/java/eu/teraflow/automation/Serializer.java +++ b/src/automation/src/main/java/eu/teraflow/automation/Serializer.java @@ -851,6 +851,8 @@ public class Serializer { return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY; case ONF_TR_352: return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352; + case XR: + return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_XR; case UNDEFINED: default: return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED; @@ -870,6 +872,8 @@ public class Serializer { return DeviceDriverEnum.IETF_NETWORK_TOPOLOGY; case DEVICEDRIVER_ONF_TR_352: return DeviceDriverEnum.ONF_TR_352; + case DEVICEDRIVER_XR: + return DeviceDriverEnum.XR; case DEVICEDRIVER_UNDEFINED: case UNRECOGNIZED: default: diff --git a/src/automation/src/main/java/eu/teraflow/automation/context/model/DeviceDriverEnum.java b/src/automation/src/main/java/eu/teraflow/automation/context/model/DeviceDriverEnum.java index 8fc767ac2e5d7fed70f0375fcf8c820e30fbb149..fc0521927dfc695229016ad42bc612b27304d6eb 100644 --- a/src/automation/src/main/java/eu/teraflow/automation/context/model/DeviceDriverEnum.java +++ b/src/automation/src/main/java/eu/teraflow/automation/context/model/DeviceDriverEnum.java @@ -22,5 +22,6 @@ public enum DeviceDriverEnum { TRANSPORT_API, P4, IETF_NETWORK_TOPOLOGY, - ONF_TR_352 + ONF_TR_352, + XR } diff --git a/src/automation/src/test/java/eu/teraflow/automation/SerializerTest.java b/src/automation/src/test/java/eu/teraflow/automation/SerializerTest.java index a02fbbca49319feb93de85efbe759a30a4ed3aa9..1161d9552e9794412d6c1ee78b89d2e2404ea3d7 100644 --- a/src/automation/src/test/java/eu/teraflow/automation/SerializerTest.java +++ b/src/automation/src/test/java/eu/teraflow/automation/SerializerTest.java @@ -1214,6 +1214,7 @@ class SerializerTest { Arguments.of( DeviceDriverEnum.ONF_TR_352, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352), + Arguments.of(DeviceDriverEnum.XR, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_XR), Arguments.of( DeviceDriverEnum.UNDEFINED, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED)); } diff --git a/src/automation/target/generated-sources/grpc/context/ContextOuterClass.java b/src/automation/target/generated-sources/grpc/context/ContextOuterClass.java index 3c0d7ce36fcdc4e47697ba11a4ceb3d8e8cdea0c..fbbba62a2baa1c2fe2b3c3fe090883d6542996e4 100644 --- a/src/automation/target/generated-sources/grpc/context/ContextOuterClass.java +++ b/src/automation/target/generated-sources/grpc/context/ContextOuterClass.java @@ -173,6 +173,10 @@ public final class ContextOuterClass { * DEVICEDRIVER_ONF_TR_352 = 5; */ DEVICEDRIVER_ONF_TR_352(5), + /** + * DEVICEDRIVER_XR = 6; + */ + DEVICEDRIVER_XR(6), UNRECOGNIZED(-1), ; @@ -204,6 +208,10 @@ public final class ContextOuterClass { * DEVICEDRIVER_ONF_TR_352 = 5; */ public static final int DEVICEDRIVER_ONF_TR_352_VALUE = 5; + /** + * DEVICEDRIVER_XR = 6; + */ + public static final int DEVICEDRIVER_XR_VALUE = 6; public final int getNumber() { @@ -236,6 +244,7 @@ public final class ContextOuterClass { case 3: return DEVICEDRIVER_P4; case 4: return DEVICEDRIVER_IETF_NETWORK_TOPOLOGY; case 5: return DEVICEDRIVER_ONF_TR_352; + case 6: return DEVICEDRIVER_XR; default: return null; } } @@ -62318,100 +62327,100 @@ public final class ContextOuterClass { "ntext.ContextId\022\025\n\rauthenticated\030\002 \001(\010*j" + "\n\rEventTypeEnum\022\027\n\023EVENTTYPE_UNDEFINED\020\000" + "\022\024\n\020EVENTTYPE_CREATE\020\001\022\024\n\020EVENTTYPE_UPDA" + - "TE\020\002\022\024\n\020EVENTTYPE_REMOVE\020\003*\305\001\n\020DeviceDri" + + "TE\020\002\022\024\n\020EVENTTYPE_REMOVE\020\003*\332\001\n\020DeviceDri" + "verEnum\022\032\n\026DEVICEDRIVER_UNDEFINED\020\000\022\033\n\027D" + "EVICEDRIVER_OPENCONFIG\020\001\022\036\n\032DEVICEDRIVER" + "_TRANSPORT_API\020\002\022\023\n\017DEVICEDRIVER_P4\020\003\022&\n" + "\"DEVICEDRIVER_IETF_NETWORK_TOPOLOGY\020\004\022\033\n" + - "\027DEVICEDRIVER_ONF_TR_352\020\005*\217\001\n\033DeviceOpe" + - "rationalStatusEnum\022%\n!DEVICEOPERATIONALS" + - "TATUS_UNDEFINED\020\000\022$\n DEVICEOPERATIONALST" + - "ATUS_DISABLED\020\001\022#\n\037DEVICEOPERATIONALSTAT" + - "US_ENABLED\020\002*\201\001\n\017ServiceTypeEnum\022\027\n\023SERV" + - "ICETYPE_UNKNOWN\020\000\022\024\n\020SERVICETYPE_L3NM\020\001\022" + - "\024\n\020SERVICETYPE_L2NM\020\002\022)\n%SERVICETYPE_TAP" + - "I_CONNECTIVITY_SERVICE\020\003*\250\001\n\021ServiceStat" + - "usEnum\022\033\n\027SERVICESTATUS_UNDEFINED\020\000\022\031\n\025S" + - "ERVICESTATUS_PLANNED\020\001\022\030\n\024SERVICESTATUS_" + - "ACTIVE\020\002\022!\n\035SERVICESTATUS_PENDING_REMOVA" + - "L\020\003\022\036\n\032SERVICESTATUS_SLA_VIOLATED\020\004*\251\001\n\017" + - "SliceStatusEnum\022\031\n\025SLICESTATUS_UNDEFINED" + - "\020\000\022\027\n\023SLICESTATUS_PLANNED\020\001\022\024\n\020SLICESTAT" + - "US_INIT\020\002\022\026\n\022SLICESTATUS_ACTIVE\020\003\022\026\n\022SLI" + - "CESTATUS_DEINIT\020\004\022\034\n\030SLICESTATUS_SLA_VIO" + - "LATED\020\005*]\n\020ConfigActionEnum\022\032\n\026CONFIGACT" + - "ION_UNDEFINED\020\000\022\024\n\020CONFIGACTION_SET\020\001\022\027\n" + - "\023CONFIGACTION_DELETE\020\002*\203\002\n\022IsolationLeve" + - "lEnum\022\020\n\014NO_ISOLATION\020\000\022\026\n\022PHYSICAL_ISOL" + - "ATION\020\001\022\025\n\021LOGICAL_ISOLATION\020\002\022\025\n\021PROCES" + - "S_ISOLATION\020\003\022\035\n\031PHYSICAL_MEMORY_ISOLATI" + - "ON\020\004\022\036\n\032PHYSICAL_NETWORK_ISOLATION\020\005\022\036\n\032" + - "VIRTUAL_RESOURCE_ISOLATION\020\006\022\037\n\033NETWORK_" + - "FUNCTIONS_ISOLATION\020\007\022\025\n\021SERVICE_ISOLATI" + - "ON\020\0102\331\023\n\016ContextService\022:\n\016ListContextId" + - "s\022\016.context.Empty\032\026.context.ContextIdLis" + - "t\"\000\0226\n\014ListContexts\022\016.context.Empty\032\024.co" + - "ntext.ContextList\"\000\0224\n\nGetContext\022\022.cont" + - "ext.ContextId\032\020.context.Context\"\000\0224\n\nSet" + - "Context\022\020.context.Context\032\022.context.Cont" + - "extId\"\000\0225\n\rRemoveContext\022\022.context.Conte" + - "xtId\032\016.context.Empty\"\000\022=\n\020GetContextEven" + - "ts\022\016.context.Empty\032\025.context.ContextEven" + - "t\"\0000\001\022@\n\017ListTopologyIds\022\022.context.Conte" + - "xtId\032\027.context.TopologyIdList\"\000\022=\n\016ListT" + - "opologies\022\022.context.ContextId\032\025.context." + - "TopologyList\"\000\0227\n\013GetTopology\022\023.context." + - "TopologyId\032\021.context.Topology\"\000\0227\n\013SetTo" + - "pology\022\021.context.Topology\032\023.context.Topo" + - "logyId\"\000\0227\n\016RemoveTopology\022\023.context.Top" + - "ologyId\032\016.context.Empty\"\000\022?\n\021GetTopology" + - "Events\022\016.context.Empty\032\026.context.Topolog" + - "yEvent\"\0000\001\0228\n\rListDeviceIds\022\016.context.Em" + - "pty\032\025.context.DeviceIdList\"\000\0224\n\013ListDevi" + - "ces\022\016.context.Empty\032\023.context.DeviceList" + - "\"\000\0221\n\tGetDevice\022\021.context.DeviceId\032\017.con" + - "text.Device\"\000\0221\n\tSetDevice\022\017.context.Dev" + - "ice\032\021.context.DeviceId\"\000\0223\n\014RemoveDevice" + - "\022\021.context.DeviceId\032\016.context.Empty\"\000\022;\n" + - "\017GetDeviceEvents\022\016.context.Empty\032\024.conte" + - "xt.DeviceEvent\"\0000\001\0224\n\013ListLinkIds\022\016.cont" + - "ext.Empty\032\023.context.LinkIdList\"\000\0220\n\tList" + - "Links\022\016.context.Empty\032\021.context.LinkList" + - "\"\000\022+\n\007GetLink\022\017.context.LinkId\032\r.context" + - ".Link\"\000\022+\n\007SetLink\022\r.context.Link\032\017.cont" + - "ext.LinkId\"\000\022/\n\nRemoveLink\022\017.context.Lin" + - "kId\032\016.context.Empty\"\000\0227\n\rGetLinkEvents\022\016" + - ".context.Empty\032\022.context.LinkEvent\"\0000\001\022>" + - "\n\016ListServiceIds\022\022.context.ContextId\032\026.c" + - "ontext.ServiceIdList\"\000\022:\n\014ListServices\022\022" + - ".context.ContextId\032\024.context.ServiceList" + - "\"\000\0224\n\nGetService\022\022.context.ServiceId\032\020.c" + - "ontext.Service\"\000\0224\n\nSetService\022\020.context" + - ".Service\032\022.context.ServiceId\"\000\0226\n\014UnsetS" + - "ervice\022\020.context.Service\032\022.context.Servi" + - "ceId\"\000\0225\n\rRemoveService\022\022.context.Servic" + - "eId\032\016.context.Empty\"\000\022=\n\020GetServiceEvent" + - "s\022\016.context.Empty\032\025.context.ServiceEvent" + - "\"\0000\001\022:\n\014ListSliceIds\022\022.context.ContextId" + - "\032\024.context.SliceIdList\"\000\0226\n\nListSlices\022\022" + - ".context.ContextId\032\022.context.SliceList\"\000" + - "\022.\n\010GetSlice\022\020.context.SliceId\032\016.context" + - ".Slice\"\000\022.\n\010SetSlice\022\016.context.Slice\032\020.c" + - "ontext.SliceId\"\000\0220\n\nUnsetSlice\022\016.context" + - ".Slice\032\020.context.SliceId\"\000\0221\n\013RemoveSlic" + - "e\022\020.context.SliceId\032\016.context.Empty\"\000\0229\n" + - "\016GetSliceEvents\022\016.context.Empty\032\023.contex" + - "t.SliceEvent\"\0000\001\022D\n\021ListConnectionIds\022\022." + - "context.ServiceId\032\031.context.ConnectionId" + - "List\"\000\022@\n\017ListConnections\022\022.context.Serv" + - "iceId\032\027.context.ConnectionList\"\000\022=\n\rGetC" + - "onnection\022\025.context.ConnectionId\032\023.conte" + - "xt.Connection\"\000\022=\n\rSetConnection\022\023.conte" + - "xt.Connection\032\025.context.ConnectionId\"\000\022;" + - "\n\020RemoveConnection\022\025.context.ConnectionI" + - "d\032\016.context.Empty\"\000\022C\n\023GetConnectionEven" + - "ts\022\016.context.Empty\032\030.context.ConnectionE" + - "vent\"\0000\001b\006proto3" + "\027DEVICEDRIVER_ONF_TR_352\020\005\022\023\n\017DEVICEDRIV" + + "ER_XR\020\006*\217\001\n\033DeviceOperationalStatusEnum\022" + + "%\n!DEVICEOPERATIONALSTATUS_UNDEFINED\020\000\022$" + + "\n DEVICEOPERATIONALSTATUS_DISABLED\020\001\022#\n\037" + + "DEVICEOPERATIONALSTATUS_ENABLED\020\002*\201\001\n\017Se" + + "rviceTypeEnum\022\027\n\023SERVICETYPE_UNKNOWN\020\000\022\024" + + "\n\020SERVICETYPE_L3NM\020\001\022\024\n\020SERVICETYPE_L2NM" + + "\020\002\022)\n%SERVICETYPE_TAPI_CONNECTIVITY_SERV" + + "ICE\020\003*\250\001\n\021ServiceStatusEnum\022\033\n\027SERVICEST" + + "ATUS_UNDEFINED\020\000\022\031\n\025SERVICESTATUS_PLANNE" + + "D\020\001\022\030\n\024SERVICESTATUS_ACTIVE\020\002\022!\n\035SERVICE" + + "STATUS_PENDING_REMOVAL\020\003\022\036\n\032SERVICESTATU" + + "S_SLA_VIOLATED\020\004*\251\001\n\017SliceStatusEnum\022\031\n\025" + + "SLICESTATUS_UNDEFINED\020\000\022\027\n\023SLICESTATUS_P" + + "LANNED\020\001\022\024\n\020SLICESTATUS_INIT\020\002\022\026\n\022SLICES" + + "TATUS_ACTIVE\020\003\022\026\n\022SLICESTATUS_DEINIT\020\004\022\034" + + "\n\030SLICESTATUS_SLA_VIOLATED\020\005*]\n\020ConfigAc" + + "tionEnum\022\032\n\026CONFIGACTION_UNDEFINED\020\000\022\024\n\020" + + "CONFIGACTION_SET\020\001\022\027\n\023CONFIGACTION_DELET" + + "E\020\002*\203\002\n\022IsolationLevelEnum\022\020\n\014NO_ISOLATI" + + "ON\020\000\022\026\n\022PHYSICAL_ISOLATION\020\001\022\025\n\021LOGICAL_" + + "ISOLATION\020\002\022\025\n\021PROCESS_ISOLATION\020\003\022\035\n\031PH" + + "YSICAL_MEMORY_ISOLATION\020\004\022\036\n\032PHYSICAL_NE" + + "TWORK_ISOLATION\020\005\022\036\n\032VIRTUAL_RESOURCE_IS" + + "OLATION\020\006\022\037\n\033NETWORK_FUNCTIONS_ISOLATION" + + "\020\007\022\025\n\021SERVICE_ISOLATION\020\0102\331\023\n\016ContextSer" + + "vice\022:\n\016ListContextIds\022\016.context.Empty\032\026" + + ".context.ContextIdList\"\000\0226\n\014ListContexts" + + "\022\016.context.Empty\032\024.context.ContextList\"\000" + + "\0224\n\nGetContext\022\022.context.ContextId\032\020.con" + + "text.Context\"\000\0224\n\nSetContext\022\020.context.C" + + "ontext\032\022.context.ContextId\"\000\0225\n\rRemoveCo" + + "ntext\022\022.context.ContextId\032\016.context.Empt" + + "y\"\000\022=\n\020GetContextEvents\022\016.context.Empty\032" + + "\025.context.ContextEvent\"\0000\001\022@\n\017ListTopolo" + + "gyIds\022\022.context.ContextId\032\027.context.Topo" + + "logyIdList\"\000\022=\n\016ListTopologies\022\022.context" + + ".ContextId\032\025.context.TopologyList\"\000\0227\n\013G" + + "etTopology\022\023.context.TopologyId\032\021.contex" + + "t.Topology\"\000\0227\n\013SetTopology\022\021.context.To" + + "pology\032\023.context.TopologyId\"\000\0227\n\016RemoveT" + + "opology\022\023.context.TopologyId\032\016.context.E" + + "mpty\"\000\022?\n\021GetTopologyEvents\022\016.context.Em" + + "pty\032\026.context.TopologyEvent\"\0000\001\0228\n\rListD" + + "eviceIds\022\016.context.Empty\032\025.context.Devic" + + "eIdList\"\000\0224\n\013ListDevices\022\016.context.Empty" + + "\032\023.context.DeviceList\"\000\0221\n\tGetDevice\022\021.c" + + "ontext.DeviceId\032\017.context.Device\"\000\0221\n\tSe" + + "tDevice\022\017.context.Device\032\021.context.Devic" + + "eId\"\000\0223\n\014RemoveDevice\022\021.context.DeviceId" + + "\032\016.context.Empty\"\000\022;\n\017GetDeviceEvents\022\016." + + "context.Empty\032\024.context.DeviceEvent\"\0000\001\022" + + "4\n\013ListLinkIds\022\016.context.Empty\032\023.context" + + ".LinkIdList\"\000\0220\n\tListLinks\022\016.context.Emp" + + "ty\032\021.context.LinkList\"\000\022+\n\007GetLink\022\017.con" + + "text.LinkId\032\r.context.Link\"\000\022+\n\007SetLink\022" + + "\r.context.Link\032\017.context.LinkId\"\000\022/\n\nRem" + + "oveLink\022\017.context.LinkId\032\016.context.Empty" + + "\"\000\0227\n\rGetLinkEvents\022\016.context.Empty\032\022.co" + + "ntext.LinkEvent\"\0000\001\022>\n\016ListServiceIds\022\022." + + "context.ContextId\032\026.context.ServiceIdLis" + + "t\"\000\022:\n\014ListServices\022\022.context.ContextId\032" + + "\024.context.ServiceList\"\000\0224\n\nGetService\022\022." + + "context.ServiceId\032\020.context.Service\"\000\0224\n" + + "\nSetService\022\020.context.Service\032\022.context." + + "ServiceId\"\000\0226\n\014UnsetService\022\020.context.Se" + + "rvice\032\022.context.ServiceId\"\000\0225\n\rRemoveSer" + + "vice\022\022.context.ServiceId\032\016.context.Empty" + + "\"\000\022=\n\020GetServiceEvents\022\016.context.Empty\032\025" + + ".context.ServiceEvent\"\0000\001\022:\n\014ListSliceId" + + "s\022\022.context.ContextId\032\024.context.SliceIdL" + + "ist\"\000\0226\n\nListSlices\022\022.context.ContextId\032" + + "\022.context.SliceList\"\000\022.\n\010GetSlice\022\020.cont" + + "ext.SliceId\032\016.context.Slice\"\000\022.\n\010SetSlic" + + "e\022\016.context.Slice\032\020.context.SliceId\"\000\0220\n" + + "\nUnsetSlice\022\016.context.Slice\032\020.context.Sl" + + "iceId\"\000\0221\n\013RemoveSlice\022\020.context.SliceId" + + "\032\016.context.Empty\"\000\0229\n\016GetSliceEvents\022\016.c" + + "ontext.Empty\032\023.context.SliceEvent\"\0000\001\022D\n" + + "\021ListConnectionIds\022\022.context.ServiceId\032\031" + + ".context.ConnectionIdList\"\000\022@\n\017ListConne" + + "ctions\022\022.context.ServiceId\032\027.context.Con" + + "nectionList\"\000\022=\n\rGetConnection\022\025.context" + + ".ConnectionId\032\023.context.Connection\"\000\022=\n\r" + + "SetConnection\022\023.context.Connection\032\025.con" + + "text.ConnectionId\"\000\022;\n\020RemoveConnection\022" + + "\025.context.ConnectionId\032\016.context.Empty\"\000" + + "\022C\n\023GetConnectionEvents\022\016.context.Empty\032" + + "\030.context.ConnectionEvent\"\0000\001b\006proto3" }; descriptor = com.google.protobuf.Descriptors.FileDescriptor .internalBuildGeneratedFileFrom(descriptorData, diff --git a/src/common/DeviceTypes.py b/src/common/DeviceTypes.py index 5bc16dd3e6012d0bea42f8837f7f43781a28044f..98c96d6831ca7381c70975fd60335e8cecfc6e1b 100644 --- a/src/common/DeviceTypes.py +++ b/src/common/DeviceTypes.py @@ -35,3 +35,4 @@ class DeviceTypeEnum(Enum): P4_SWITCH = 'p4-switch' PACKET_ROUTER = 'packet-router' PACKET_SWITCH = 'packet-switch' + XR_CONSTELLATION = 'xr-constellation' \ No newline at end of file diff --git a/src/common/tools/object_factory/Device.py b/src/common/tools/object_factory/Device.py index 406af80a82081cb60a3d9fd9c0bcb01ecaa78d30..666d65f1e739b4cca8b665846f1775dcd6130e1c 100644 --- a/src/common/tools/object_factory/Device.py +++ b/src/common/tools/object_factory/Device.py @@ -33,6 +33,10 @@ DEVICE_PR_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG] DEVICE_TAPI_TYPE = DeviceTypeEnum.OPEN_LINE_SYSTEM.value DEVICE_TAPI_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_TRANSPORT_API] +DEVICE_XR_CONSTELLATION_TYPE = DeviceTypeEnum.XR_CONSTELLATION.value +DEVICE_XR_CONSTELLATION_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_XR] + +# check which enum type and value assign to microwave device DEVICE_MICROWAVE_TYPE = DeviceTypeEnum.MICROWAVE_RADIO_SYSTEM.value DEVICE_MICROWAVE_DRIVERS = [DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY] @@ -93,6 +97,14 @@ def json_device_tapi_disabled( return json_device( device_uuid, DEVICE_TAPI_TYPE, DEVICE_DISABLED, endpoints=endpoints, config_rules=config_rules, drivers=drivers) +def json_device_xr_constellation_disabled( + device_uuid : str, endpoints : List[Dict] = [], config_rules : List[Dict] = [], + drivers : List[Dict] = DEVICE_XR_CONSTELLATION_DRIVERS + ): + return json_device( + device_uuid, DEVICE_XR_CONSTELLATION_TYPE, DEVICE_DISABLED, endpoints=endpoints, config_rules=config_rules, + drivers=drivers) + def json_device_microwave_disabled( device_uuid : str, endpoints : List[Dict] = [], config_rules : List[Dict] = [], drivers : List[Dict] = DEVICE_MICROWAVE_DRIVERS diff --git a/src/common/type_checkers/Assertions.py b/src/common/type_checkers/Assertions.py index 20ffa9ad619a40d6da4f3830c202d1a545545b51..aa9ede33303fca73d033ee2a40dec587882a5bb1 100644 --- a/src/common/type_checkers/Assertions.py +++ b/src/common/type_checkers/Assertions.py @@ -32,6 +32,7 @@ def validate_device_driver_enum(message): 'DEVICEDRIVER_P4', 'DEVICEDRIVER_IETF_NETWORK_TOPOLOGY', 'DEVICEDRIVER_ONF_TR_352', + 'DEVICEDRIVER_XR', ] def validate_device_operational_status_enum(message): diff --git a/src/context/service/database/DeviceModel.py b/src/context/service/database/DeviceModel.py index 0d42326793b44473d8aef3da2c3e9ce8464bd1c4..0ffb97fee51da62802a1f7eb730380ba7a89dc0f 100644 --- a/src/context/service/database/DeviceModel.py +++ b/src/context/service/database/DeviceModel.py @@ -35,6 +35,7 @@ class ORM_DeviceDriverEnum(Enum): P4 = DeviceDriverEnum.DEVICEDRIVER_P4 IETF_NETWORK_TOPOLOGY = DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY ONF_TR_352 = DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352 + XR = DeviceDriverEnum.DEVICEDRIVER_XR grpc_to_enum__device_driver = functools.partial( grpc_to_enum, DeviceDriverEnum, ORM_DeviceDriverEnum) diff --git a/src/device/requirements.in b/src/device/requirements.in index 9c8c0ef18f3bcd4a92180465d11cd465c4336d44..2b9c199c86a580b72190a9d0e74a161e567abed2 100644 --- a/src/device/requirements.in +++ b/src/device/requirements.in @@ -9,6 +9,7 @@ python-json-logger==2.0.2 pytz==2021.3 redis==4.1.2 requests==2.27.1 +requests-mock==1.9.3 xmltodict==0.12.0 tabulate ipaddress diff --git a/src/device/service/database/DeviceModel.py b/src/device/service/database/DeviceModel.py index 7a0a2325928ed7312063eb66d629a08cc7591b7b..9dd63d36efebf135b7bb38845d917bc9e03dc100 100644 --- a/src/device/service/database/DeviceModel.py +++ b/src/device/service/database/DeviceModel.py @@ -35,6 +35,7 @@ class ORM_DeviceDriverEnum(Enum): P4 = DeviceDriverEnum.DEVICEDRIVER_P4 IETF_NETWORK_TOPOLOGY = DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY ONF_TR_352 = DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352 + XR = DeviceDriverEnum.DEVICEDRIVER_XR grpc_to_enum__device_driver = functools.partial( grpc_to_enum, DeviceDriverEnum, ORM_DeviceDriverEnum) diff --git a/src/device/service/drivers/__init__.py b/src/device/service/drivers/__init__.py index 3a56420c9b222f4cbbc13b78692fd895916aedeb..4e4a9ac11363958fb4609976ce8609745bb97c01 100644 --- a/src/device/service/drivers/__init__.py +++ b/src/device/service/drivers/__init__.py @@ -115,3 +115,14 @@ if LOAD_ALL_DEVICE_DRIVERS: FilterFieldEnum.DRIVER : ORM_DeviceDriverEnum.IETF_NETWORK_TOPOLOGY, } ])) + +if LOAD_ALL_DEVICE_DRIVERS: + from .xr.XrDriver import XrDriver # pylint: disable=wrong-import-position + DRIVERS.append( + (XrDriver, [ + { + # Close enough, it does optical switching + FilterFieldEnum.DEVICE_TYPE: DeviceTypeEnum.XR_CONSTELLATION, + FilterFieldEnum.DRIVER : ORM_DeviceDriverEnum.XR, + } + ])) diff --git a/src/device/service/drivers/xr/README_XR.md b/src/device/service/drivers/xr/README_XR.md new file mode 100644 index 0000000000000000000000000000000000000000..f7c2316ce5fa810969d373e1fad7bc5ca83b9e49 --- /dev/null +++ b/src/device/service/drivers/xr/README_XR.md @@ -0,0 +1,149 @@ +# Infinera Readme + +There are some instructions at https://labs.etsi.org/rep/tfs/controller/-/tree/develop/tutorial . They are not completely up to date and don't 100% work. + +Note that many of the scripts expect this and that K8s namespace being used, they are not consistent, so use manual kubectl commands where necessary. + +Infinera repo (cloned from upstream) is https://bitbucket.infinera.com/projects/XRCA/repos/teraflow/browse . The main development branch for us is xr-development (branched of origin/develop). + +## Preliminaries + +Kubernetes must be installed and configured. + +Note that if runninc MicroK8s (I would highly recommend it), then install also regular kubectl so that scripts work. That is, download the kubectl, and also export credidentials to standard location. + +```bash +# As a root +su - +cd /usr/local/bin +curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" +chmod 755 kubectl +exit + +# As your local user +cd ~/.kube +microk8s config > config +``` + +Local Docker registry is needed for build results. Use the following command to start local registry (docker will pull necessary images from Internet) + +```bash +docker run -d -p 32000:5000 --restart=always --name registry registry:2 +``` + +Setup mydeploy script outside the git repo. E.g. following will do. SOURCE IT ON ALL SHELLS. + +IMPORTANT: September 2022 version of controller has a bug where any update to device trigger update to device +until GRPC endpoints are so loaded that K8s kills device service. XR does not need automation service, so it can +be left out. + +```bash +export TFS_REGISTRY_IMAGE="http://localhost:32000/tfs/" +# Without automation service (see note above) +export TFS_COMPONENTS="context device pathcomp service slice compute monitoring webui" +# Correct setting +# export TFS_COMPONENTS="context device automation pathcomp service slice compute monitoring webui" +# Pre-rebase +#export TFS_COMPONENTS="context device automation service compute monitoring webui" +export TFS_IMAGE_TAG="dev" +export TFS_K8S_NAMESPACE="tfs" +export TFS_EXTRA_MANIFESTS="manifests/nginx_ingress_http.yaml" +export TFS_GRAFANA_PASSWORD="admin123+" +``` + +Build is containerized, pytest used for setup is not. Teraflow has some third party venv suggestion in docs. However standard venv works. Create: + +```bash +python -m venv .venv +source .venv/bin/activate +./install_requirements.sh +``` + +SOURCE VENV ACTIVATE ON ANY SHELL USED FOR PYTHON RELATED WORK (e.g. pytest). + +Use apt-get to install any missing tools (e.g. jq is required). + +For host based Python development (e.g. VS Code) and test script execution, generate protobuf stubs: + +```bash +cd proto +./generate_code_python.sh +cd ../src/context +ln -s ../../proto/src/python proto +``` + +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! + +```bash +./deploy.sh +``` + +If protobuf definitions have changed, regenerate version controlled Java files manually +(it is a horrifying bug in build system that this is not automated!). +``` +cd automation +# In case Java is not already installed +sudo apt-get install openjdk-11-jdk -y +export MAVEN_OPTS='--add-exports=java.base/jdk.internal.module=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED' +cd src/policy +./mvnw compile +cd - +cd src/automation +./mvnw compile +``` + +Compilation fails but does update the protobuf generated files. + +## Testing + +Upload descriptors_emulatex_xr.json via WEB UI to setup fake topology. + +Setup service by following commands in src directory. Kubernetes endpoins change on every build, so setup script is mandatory. + +```bash + source tests/ofc22/setup_test_env.sh + python -m pytest --verbose tests/ofc22/tests/test_functional_create_service_xr.py +``` + +Good logs to check are: + +* kubectl logs service/deviceservice --namespace tfs +* kubectl logs service/webuiservice --namespace tfs + +## 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: + +```bash +./cm-cli.py 172.19.219.44 443 xr-user-1 xr-user-1 --show-constellation-by-hub-name="XR HUB 1" +./cm-cli.py 172.19.219.44 443 xr-user-1 xr-user-1 --list-constellations +./cm-cli.py 172.19.219.44 443 xr-user-1 xr-user-1 --create-connection="FOO;XR HUB 1|XR-T4;XR LEAF 1|XR-T1" +./cm-cli.py 172.19.219.44 443 xr-user-1 xr-user-1 --show-connection-by-name="FooBar123" +./cm-cli.py 172.19.219.44 443 xr-user-1 xr-user-1 --list-connections +# Modify argumens: href;uuid;ifname;ifname +# uuid translates to name TF:uuid +./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 new file mode 100644 index 0000000000000000000000000000000000000000..51fd29ad11af5ccdad7e5c49e7d069a1bf2e8ffb --- /dev/null +++ b/src/device/service/drivers/xr/XrDriver.py @@ -0,0 +1,171 @@ +# 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. +#pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring + +import logging +import threading +import json +from typing import Any, Iterator, List, Optional, Tuple, Union +import urllib3 +from common.type_checkers.Checkers import chk_type +from device.service.driver_api._Driver import _Driver +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. +urllib3.disable_warnings() + +LOGGER = logging.getLogger(__name__) + +class XrDriver(_Driver): + def __init__(self, address: str, port: int, **settings) -> None: # pylint: disable=super-init-not-called + self.__lock = threading.Lock() + self.__started = threading.Event() + self.__terminate = threading.Event() + self.__timeout = int(settings.get('timeout', 120)) + self.__cm_address = address + # Mandatory key, an exception will get thrown if missing + self.__hub_module_name = settings["hub_module_name"] + + tls_verify = False # Currently using self signed certificates + username = settings["username"] if "username" in settings else "xr-user-1" + password = settings["password"] if "password" in settings else "xr-user-1" + + self.__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 __str__(self): + return f"{self.__hub_module_name}@{self.__cm_address}" + + def Connect(self) -> bool: + LOGGER.info(f"Connect[{self}]") + with self.__lock: + if self.__started.is_set(): + return True + if not self.__cm_connection.Connect(): + return False + else: + self.__started.set() + return True + + def Disconnect(self) -> bool: + LOGGER.info(f"Disconnect[{self}]") + with self.__lock: + self.__terminate.set() + return True + + def GetInitialConfig(self) -> List[Tuple[str, Any]]: + LOGGER.info(f"GetInitialConfig[{self}]") + with self.__lock: + return [] + + #pylint: disable=dangerous-default-value + def GetConfig(self, resource_keys : List[str] = []) -> List[Tuple[str, Union[Any, None, Exception]]]: + LOGGER.info(f"GetConfig[{self}]: {resource_keys=}") + chk_type('resources', resource_keys, list) + + # Empty resource_keys means all resources. As we only have endpoints, we ignore parameter and always + # return everything. + + with self.__lock: + constellation = self.__cm_connection.get_constellation_by_hub_name(self.__hub_module_name) + if constellation: + self.__constellation = constellation + return [(f"/endpoints/endpoint[{ifname}]", {'uuid': ifname, 'type': 'optical', 'sample_types': {}}) for ifname in constellation.ifnames()] + else: + return [] + + def SetConfig(self, resources: List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: + LOGGER.info(f"SetConfig[{self}]: {resources=}") + # Logged config seems like: + #[('/service[52ff5f0f-fda4-40bd-a0b1-066f4ff04079:optical]', '{"capacity_unit": "GHz", "capacity_value": 1, "direction": "UNIDIRECTIONAL", "input_sip": "XR HUB 1|XR-T4", "layer_protocol_name": "PHOTONIC_MEDIA", "layer_protocol_qualifier": "tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_NMC", "output_sip": "XR LEAF 1|XR-T1", "uuid": "52ff5f0f-fda4-40bd-a0b1-066f4ff04079:optical"}')] + + with self.__lock: + if self.__constellation is None: + self.__constellation = self.__cm_connection.get_constellation_by_hub_name(self.__hub_module_name) + + if self.__constellation is None: + LOGGER.error("SetConfig: no valid constellation") + return [False] * len(resources) + + results = [] + if len(resources) == 0: + return results + + for key, config in resources: + service_uuid = self.__cm_connection.service_uuid(key) + if service_uuid: + config = json.loads(config) + results.append(tf.set_config_for_service(self.__cm_connection, self.__constellation, service_uuid, config)) + else: + results.append(False) + + return results + + def DeleteConfig(self, resources: List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: + LOGGER.info(f"DeleteConfig[{self}]: {resources=}") + + # Input looks like: + # resources=[('/service[c8a35e81-88d8-4468-9afc-a8abd92a64d0:optical]', '{"uuid": "c8a35e81-88d8-4468-9afc-a8abd92a64d0:optical"}')] + + with self.__lock: + results = [] + if len(resources) == 0: + return results + + # Temporary dummy version + for key, _config in resources: + service_uuid = self.__cm_connection.service_uuid(key) + if service_uuid: + connection = self.__cm_connection.get_connection_by_teraflow_uuid(service_uuid) + if connection is None: + LOGGER.info(f"DeleteConfig: Connection {service_uuid} does not exist, delete is no-op") + results.append(True) + else: + was_deleted = self.__cm_connection.delete_connection(connection.href) + if was_deleted: + LOGGER.info(f"DeleteConfig: Connection {service_uuid} deleted (was {str(connection)})") + else: + LOGGER.info(f"DeleteConfig: Connection {service_uuid} delete failure (was {str(connection)})") + + if self.__constellation.is_vti_mode(): + active_tc = self.__cm_connection.get_transport_capacity_by_teraflow_uuid(service_uuid) + if active_tc is not None: + if self.__cm_connection.delete_transport_capacity(active_tc.href): + LOGGER.info(f"DeleteConfig: Transport Capacity {active_tc} deleted") + else: + LOGGER.error(f"DeleteConfig: Transport Capacity {active_tc} delete failure") + + results.append(was_deleted) + else: + results.append(False) + + return results + + def SubscribeState(self, subscriptions : List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]: + # Not supported + return [False for _ in subscriptions] + + def UnsubscribeState(self, subscriptions : List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]: + # Not supported + return [False for _ in subscriptions] + + def GetState( + self, blocking=False, terminate : Optional[threading.Event] = None + ) -> Iterator[Tuple[float, str, Any]]: + # Not supported + return [] diff --git a/src/device/service/drivers/xr/__init__.py b/src/device/service/drivers/xr/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..9953c820575d42fa88351cc8de022d880ba96e6a --- /dev/null +++ b/src/device/service/drivers/xr/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/src/device/service/drivers/xr/cm-cli.py b/src/device/service/drivers/xr/cm-cli.py new file mode 100755 index 0000000000000000000000000000000000000000..8b8fec59c45f458d802a9ff609c345f55948626e --- /dev/null +++ b/src/device/service/drivers/xr/cm-cli.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 +#pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Test program for CmConnection +import argparse +import logging +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) + +parser = argparse.ArgumentParser(description='CM Connectin Test Utility') +parser.add_argument('ip', help='CM IP address or domain name') +parser.add_argument('port', help='CM port', type=int) +parser.add_argument('username', help='Username') +parser.add_argument('password', help='Password') + +parser.add_argument('--list-constellations', action='store_true') +parser.add_argument('--show-constellation-by-hub-name', nargs='?', type=str) +parser.add_argument('--create-connection', nargs='?', type=str, help="uuid;ifname;ifname;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() + +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 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: + 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: + 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(";") + connection = Connection(from_tf_service=tf_service) + result = cm.update_connection(href, connection) + if result: + print(f"Updated {href} for {connection}") + else: + 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) + if connection: + print(str(connection)) + +if args.list_connections: + connections = cm.get_connections() + for c in connections: + print(str(c)) + +if args.delete_connection: + was_deleted = cm.delete_connection(args.delete_connection) + if was_deleted: + print(f"Successfully deleted {args.delete_connection}") + else: + print(f"Failed to delete {args.delete_connection}") + +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..9953c820575d42fa88351cc8de022d880ba96e6a --- /dev/null +++ b/src/device/service/drivers/xr/cm/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/src/device/service/drivers/xr/cm/cm_connection.py b/src/device/service/drivers/xr/cm/cm_connection.py new file mode 100644 index 0000000000000000000000000000000000000000..7e0fc61b72e7028fae00886cea4dcb2f922bfbf4 --- /dev/null +++ b/src/device/service/drivers/xr/cm/cm_connection.py @@ -0,0 +1,389 @@ +#pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import collections.abc +import logging +import json +import time +from typing import Optional, List, Dict, Union +import re +import requests +import urllib3 +from .connection import Connection +from .transport_capacity import TransportCapacity +from .constellation import Constellation + +# 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 + +LOGGER = logging.getLogger(__name__) + +class ExpiringValue: + def __init__(self, value, expiry): + self.__value = value + self.__expiry = expiry + self.__created = time.monotonic() + + def get_value(self): + return self.__value + + def is_valid_for(self, duration): + if self.__created + self.__expiry >= time.monotonic()+duration: + return True + else: + return False + +class UnexpectedEmptyBody(Exception): + pass + +class HttpResult: + def __init__(self, method: str, url: str, params: Dict[str, any] = None): + self.method = method + self.url = url + self.text = None + self.json = None + self.status_code = None + self.params = params + self.exception = None + + def __str__(self): + status_code = self.status_code if self.status_code is not None else "" + return f"{self.method} {self.url} {self.params}, status {status_code}" + + def process_http_response(self, response: requests.Response, permit_empty_body:bool = False): + LOGGER.info(f"process_http_response(): {self.method}: {self.url} qparams={self.params} ==> {response.status_code}") # FIXME: params + self.status_code = response.status_code + if response.content != b'null' and len(response.text): + self.text = response.text + + try: + r_json = json.loads(response.text) + self.json = r_json + except json.JSONDecodeError as json_err: + LOGGER.info(f"{self.method}: {self.url} ==> response json decode error: {str(json_err)}") + self.exception = json_err + elif not permit_empty_body: + raise UnexpectedEmptyBody(f"No body in HTTP response for {self.method} {self.url} (status code {response.status_code}") + + def __bool__(self): + # Error codes start at 400, codes below it are successes + return self.status_code is not None and self.text is not None and self.status_code < 400 and self.exception is None + + def is_valid_with_status_ignore_body(self, expected_status_code: int) -> bool: + return self.status_code is not None and self.status_code == expected_status_code and self.exception is None + + def is_valid_json_with_status(self, expected_status_code: int) -> bool: + return bool(self) and self.status_code == expected_status_code and self.json is not None + + def is_valid_json_list_with_status(self, expected_status_code: int, min_entries=-1, max_entries=-1) -> bool: + if not self.is_valid_json_with_status(expected_status_code): + return False + if not isinstance(self.json, collections.abc.Sequence): + return False + + if min_entries >=0 and len(self.json) < min_entries: + return False + + if max_entries >=0 and len(self.json) > max_entries: + return False + return True + + def is_valid_json_obj_with_status(self, expected_status_code: int) -> bool: + if not self.is_valid_json_with_status(expected_status_code): + return False + if not isinstance(self.json, collections.abc.Mapping): + return False + + return True + +class CmConnection: + def __init__(self, address: str, port: int, username: str, password: str, timeout=30, tls_verify=True) -> None: + self.__tls_verify = tls_verify + if not tls_verify: + urllib3.disable_warnings() + + self.__timeout = timeout + self.__username = username + self.__password = password + self.__cm_root = 'https://' + address + ':' + str(port) + self.__access_token = None + + def __perform_request(self, http_result: HttpResult, permit_empty_body: bool, fn, *args, **kwargs): + try: + response = fn(*args, **kwargs) + http_result.process_http_response(response, permit_empty_body) + except requests.exceptions.Timeout as e: + LOGGER.info(f"{http_result} ==> timeout") + http_result.exception = e + except Exception as e: # pylint: disable=broad-except + es=str(e) + LOGGER.info(f"{http_result} ==> unexpected exception: {es}") + http_result.exception = e + return http_result + + def __post_w_headers(self, path, data, headers, data_as_json=True) -> HttpResult: + url = self.__cm_root + path + rv = HttpResult("POST", url) + if data_as_json: + self.__perform_request(rv, False, requests.post, url, headers=headers, json=data, timeout=self.__timeout, verify=self.__tls_verify) + else: + self.__perform_request(rv, False, requests.post, url, headers=headers, data=data, timeout=self.__timeout, verify=self.__tls_verify) + return rv + + def __post(self, path, data, data_as_json=True) -> HttpResult: + return self.__post_w_headers(path, data, self.__http_headers(), data_as_json=data_as_json) + + def __put(self, path: str, data: Union[str,Dict[str, any]], data_as_json:bool =True, permit_empty_body:bool =True) -> HttpResult: + url = self.__cm_root + path + rv = HttpResult("PUT", url) + if data_as_json: + self.__perform_request(rv, permit_empty_body, requests.put, url, headers=self.__http_headers(), json=data, timeout=self.__timeout, verify=self.__tls_verify) + else: + self.__perform_request(rv, permit_empty_body, requests.put, url, headers=self.__http_headers(), data=data, timeout=self.__timeout, verify=self.__tls_verify) + return rv + + def __get(self, path, params: Dict[str, any]=None) -> HttpResult: + url = self.__cm_root + path + rv = HttpResult("GET", url, params) + self.__perform_request(rv, False, requests.get, url, headers=self.__http_headers(), timeout=self.__timeout,verify=self.__tls_verify, params=params) + return rv + + def __delete(self, path, data=None) -> HttpResult: + url = self.__cm_root + path + rv = HttpResult("DELETE", url) + self.__perform_request(rv, True, requests.delete, url, headers=self.__http_headers(), data=data, timeout=self.__timeout, verify=self.__tls_verify) + return rv + + def __http_headers(self): + self.__ensure_valid_access_token() + if self.__access_token: + return {'Authorization': 'Bearer '+ self.__access_token.get_value()} + else: + return {} + + def __acquire_access_token(self): + path = '/realms/xr-cm/protocol/openid-connect/token' + req = { + "username": self.__username, + "password": self.__password, + "grant_type": "password", + "client_secret": "xr-web-client", + "client_id": "xr-web-client" + } + resp = self.__post_w_headers(path, req, None, data_as_json=False) + # Slightly more verbose check/logging of failures for authentication to help + # diagnose connectivity problems + if resp.status_code is None: + LOGGER.error("Failed to contact authentication API endpoint") + return False + if not resp.is_valid_json_obj_with_status(200): + LOGGER.error(f"Authentication failure, status code {resp.status_code}, data {resp.text}") + return False + if 'access_token' not in resp.json: + LOGGER.error(f"Authentication failure: missing access_token in JSON, status code {resp.status_code}, data {resp.text}") + return False + access_token = resp.json['access_token'] + expires = int(resp.json["expires_in"]) if "expires_in" in resp.json else 0 + LOGGER.info(f"Obtained access token {access_token}, expires in {expires}") + self.__access_token = ExpiringValue(access_token, expires) + return True + + def __ensure_valid_access_token(self): + if not self.__access_token or not self.__access_token.is_valid_for(60): + self.__acquire_access_token() + + def Connect(self) -> bool: + return self.__acquire_access_token() + + def list_constellations(self) -> List[Constellation]: + r = self.__get("/api/v1/ns/xr-networks?content=expanded") + if not r.is_valid_json_list_with_status(200): + return [] + return [Constellation(c) for c in r.json] + + + def get_constellation_by_hub_name(self, hub_module_name: str) -> Optional[Constellation]: + qparams = [ + ('content', 'expanded'), + ('q', '{"hubModule.state.module.moduleName": "' + hub_module_name + '"}') + ] + r = self.__get("/api/v1/ns/xr-networks?content=expanded", params=qparams) + if not r.is_valid_json_list_with_status(200, 1, 1): + return None + return Constellation(r.json[0]) + + def get_transport_capacities(self) -> List[TransportCapacity]: + r= self.__get("/api/v1/ns/transport-capacities?content=expanded") + if not r.is_valid_json_list_with_status(200): + return [] + return [TransportCapacity(from_json=t) for t in r.json] + + def get_transport_capacity_by_name(self, tc_name: str) -> Optional[Connection]: + qparams = [ + ('content', 'expanded'), + ('q', '{"state.name": "' + tc_name + '"}') + ] + r = self.__get("/api/v1/ns/transport-capacities?content=expanded", params=qparams) + if not r.is_valid_json_list_with_status(200, 1, 1): + return TransportCapacity(from_json=r.json[0]) + else: + return None + + 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.is_valid_json_list_with_status(202, 1, 1) and "href" in resp.json[0]: + tc.href = resp.json[0]["href"] + LOGGER.info(f"Created transport-capcity {tc}") + #LOGGER.info(self.__get(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.is_valid_with_status_ignore_body(202): + LOGGER.info(f"Deleted transport-capacity {href=}") + return True + else: + LOGGER.info(f"Deleting transport-capacity {href=} failed, status {resp.status_code}") + return False + + def create_connection(self, connection: Connection) -> Optional[str]: + # Create wants a list, so wrap connection to list + cfg = [connection.create_config()] + + resp = self.__post("/api/v1/ncs/network-connections", cfg) + if resp.is_valid_json_list_with_status(202, 1, 1) and "href" in resp.json[0]: + connection.href = resp.json[0]["href"] + LOGGER.info(f"Created connection {connection}") + return connection.href + else: + LOGGER.error(f"Create failure for connection {connection}, result {resp}") + return None + + 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) + 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.is_valid_with_status_ignore_body(202): + LOGGER.info(f"update_connection: EP-UPDATE: Deleted connection endpoint {ep_href}") + else: + LOGGER.info(f"update_connection: EP-UPDATE: Failed to delete connection endpoint {ep_href}: {resp}") + + # 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.is_valid_with_status_ignore_body(202): + LOGGER.info(f"update_connection: EP-UPDATE: Updated connection endpoint {ep_href} with {ep_cfg}") + else: + LOGGER.info(f"update_connection: EP-UPDATE: Failed to update connection endpoint {ep_href} with {ep_cfg}: {resp}") + + # Perform adds + resp = self.__post(f"/api/v1/ncs{href}/endpoints", ep_creates) + if resp.is_valid_json_list_with_status(202, 1, 1) and "href" in resp.json[0]: + LOGGER.info(f"update_connection: EP-UPDATE: Created connection endpoints {resp.json[0]} with {ep_creates}") + else: + LOGGER.info(f"update_connection: EP-UPDATE: Failed to create connection endpoints {resp.json[0] if resp.json else None} with {ep_creates}: {resp}") + + # Connection update (excluding endpoints) + resp = self.__put(f"/api/v1/ncs{href}", cfg) + # Returns empty body + if resp.is_valid_with_status_ignore_body(202): + LOGGER.info(f"update_connection: Updated connection {connection}") + # Return href used for update to be consisten with create + return href + 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) + # Returns empty body + if resp.is_valid_with_status_ignore_body(202): + LOGGER.info(f"Deleted connection {href=}") + return True + else: + return False + + # Always does the correct thing, that is update if present, otherwise create + def create_or_update_connection(self, connection: Connection) -> Optional[str]: + existing_connection = self.get_connection_by_name(connection.name) + if existing_connection: + return self.update_connection(existing_connection.href, connection, existing_connection) + else: + return self.create_connection(connection) + + def get_connection_by_name(self, connection_name: str) -> Optional[Connection]: + qparams = [ + ('content', 'expanded'), + ('q', '{"state.name": "' + connection_name + '"}') + ] + r = self.__get("/api/v1/ncs/network-connections", params=qparams) + if r.is_valid_json_list_with_status(200, 1, 1): + return Connection(from_json=r.json[0]) + else: + return None + + def get_connection_by_href(self, href: str) -> Optional[Connection]: + qparams = [ + ('content', 'expanded'), + ] + r = self.__get(f"/api/v1/ncs{href}", params=qparams) + if r.is_valid_json_obj_with_status(200): + return Connection(from_json=r.json) + else: + return None + + def get_connection_by_teraflow_uuid(self, uuid: str) -> Optional[Connection]: + return self.get_connection_by_name(f"TF:{uuid}") + + def get_connections(self): + r = self.__get("/api/v1/ncs/network-connections?content=expanded") + if r.is_valid_json_list_with_status(200): + return [Connection(from_json=c) for c in r.json] + else: + return [] + + def service_uuid(self, key: str) -> Optional[str]: + service = re.match(r"^/service\[(.+)\]$", key) + if service: + return service.group(1) + else: + return None diff --git a/src/device/service/drivers/xr/cm/connection.py b/src/device/service/drivers/xr/cm/connection.py new file mode 100644 index 0000000000000000000000000000000000000000..e88995842eb0b6266d4d8eb42e2cc3197d89bea1 --- /dev/null +++ b/src/device/service/drivers/xr/cm/connection.py @@ -0,0 +1,179 @@ +#pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Dict, Optional +from dataclasses import dataclass +from .tf_service import TFService +from .utils import make_selector, set_optional_parameter + +class InconsistentVlanConfiguration(Exception): + pass + +@dataclass +class CEndpoint: + module: str + port: str + # Emulated/translated VLAN. May also be a letter + # Only present on TF side, never on gets from CM. + # VLAN is never transmitted to wire on endpoint, it is purely an internal construct + # However VLAN is part of the whole connection + vlan: str + capacity: int + href: Optional[str] + + + def ifname(self) -> str: + if self.vlan is None: + return self.module + "|" + self.port + else: + return self.module + "|" + self.port + "." + self.vlan + + def portname(self) -> str: + return self.module + "|" + self.port + + def __str__(self): + return f"({self.ifname()}, {self.capacity})" + + def create_config(self) -> Dict[str, any]: + cfg = { + # VLAN is intentionally ignored here (None argument below) + "selector": make_selector(self.module, self.port, None) + } + if self.capacity > 0: + cfg["capacity"] = self.capacity + + return cfg + +class ConnectionDeserializationError(Exception): + pass + +class Connection: + def __init__(self, from_json: Optional[Dict[str, any]] = None, from_tf_service: Optional[TFService] = None): + def get_endpoint_mod_aid(endpoint: Dict[str, any]) -> Optional[str]: + try: + return (endpoint["state"]["moduleIf"]["moduleName"], endpoint["state"]["moduleIf"]["clientIfAid"]) + except KeyError: + return None + + def get_endpoint_capacity(endpoint: Dict[str, any]) -> int: + try: + return int(endpoint["state"]["capacity"]) + except KeyError: + return 0 + + if from_json: + try: + config = from_json["config"] + state = from_json["state"] + self.name = state["name"] if "name" in state else None #Name is optional + self.serviceMode = state["serviceMode"] + self.mc = config["mc"] if "mc" in config else None + self.vlan_filter = state["outerVID"] if "outerVID" in state else None + self.href = from_json["href"] + + self.endpoints = [] + for ep in from_json["endpoints"]: + ep_mod_aip = get_endpoint_mod_aid(ep) + if ep_mod_aip: + self.endpoints.append(CEndpoint(*ep_mod_aip, None, get_endpoint_capacity(ep), ep["href"])) + self.cm_data = from_json + except KeyError as e: + raise ConnectionDeserializationError(f"Missing mandatory key {str(e)}") from e + elif from_tf_service: + self.href = None + self.name = from_tf_service.name() + self.endpoints = [CEndpoint(mod, port, vlan, from_tf_service.capacity, None) for mod,port,vlan in from_tf_service.get_endpoints_mod_aid_vlan()] + # Service mode guessing has to be AFTER endpoint assigment. + # The heuristic used is perfectly valid in context of TF where we always encode + # VLANs to interface names. Correspondingly cm-cli user has to know + # to use VLANs on low level test APIs when using VTI mode. + self.serviceMode = self.__guess_service_mode_from_emulated_enpoints() + if self.serviceMode == "portMode": + self.vlan_filter = None + self.mc = None + else: + self.vlan_filter = str(self.__guess_vlan_id()) + " " # Needs to be in string format, can contain ranges, regexp is buggy, trailin space is needed for single VLAN + self.mc = "matchOuterVID" + + self.cm_data = None + else: + # May support other initializations in future + raise ConnectionDeserializationError("JSON dict missing") + + def __str__(self): + name = self.name if self.name else "" + endpoints = ", ".join((str(ep) for ep in self.endpoints)) + return f"name: {name}, id: {self.href}, service-mode: {self.serviceMode}, end-points: [{endpoints}]" + + def __guess_service_mode_from_emulated_enpoints(self): + for ep in self.endpoints: + if ep.vlan is not None: + return "vtiP2pSymmetric" + return "portMode" + + def __guess_vlan_id(self) -> int: + vlans = [] + for ep in self.endpoints: + if ep.vlan is not None and ep.vlan.isnumeric(): + vlans.append(int(ep.vlan)) + if not vlans: + raise InconsistentVlanConfiguration("VLAN ID is not encoded in TF interface names for VTI mode service") + else: + for vlan in vlans: + if vlan != vlans[0]: + raise InconsistentVlanConfiguration(f"VLAN configuration must match at both ends of the connection, {vlans[0]} != {vlan}") + return vlans[0] + + def create_config(self) -> Dict[str, any]: + cfg = {} + set_optional_parameter(cfg, "name", self.name) + cfg["serviceMode"] = self.serviceMode + if self.endpoints: + cfg["endpoints"] = [ep.create_config() for ep in self.endpoints] + set_optional_parameter(cfg, "outerVID", self.vlan_filter) + set_optional_parameter(cfg, "mc", self.mc) + #print(cfg) + return cfg + + def get_port_map(self) -> Dict[str, CEndpoint]: + return {ep.portname(): ep for ep in self.endpoints } + + # Type hint has to be string, because future annotations (enclosing class) + # is not yet widely available + def get_endpoint_updates(self, old: Optional['Connection']): # -> Tuple[List[str], List[Dict[str, any], List[Tuple[str, Dict[str, any]]]]]: + new_ports = self.get_port_map() + + if old is None: + return ([], [new_ep.create_config() for new_ep in new_ports.values()], []) + + # Can only compute difference against get from CM, as hrefs are needed + assert old.cm_data is not None + + old_ports = old.get_port_map() + + deletes = [] + creates = [] + updates = [] + for port, old_ep in old_ports.items(): + if port not in new_ports: + assert old_ep.href is not None + deletes.append(old_ep.href) + + for port, new_ep in new_ports.items(): + if port not in old_ports: + creates.append(new_ep.create_config()) + elif old_ports[port].capacity != new_ep.capacity: + updates.append((old_ports[port].href, {"capacity": new_ep.capacity})) + return deletes, creates, updates diff --git a/src/device/service/drivers/xr/cm/constellation.py b/src/device/service/drivers/xr/cm/constellation.py new file mode 100644 index 0000000000000000000000000000000000000000..468cf70b6180080bfa11ea3321aca1af623b73fc --- /dev/null +++ b/src/device/service/drivers/xr/cm/constellation.py @@ -0,0 +1,69 @@ +#pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring, wildcard-import, unused-wildcard-import +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import List +from .utils import * + +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..9953c820575d42fa88351cc8de022d880ba96e6a --- /dev/null +++ b/src/device/service/drivers/xr/cm/tests/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/src/device/service/drivers/xr/cm/tests/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/constellation-by-name-hub1.json b/src/device/service/drivers/xr/cm/tests/resources/constellation-by-name-hub1.json new file mode 100644 index 0000000000000000000000000000000000000000..061d6453edebc764a96fe3eaeace1168200b1a20 --- /dev/null +++ b/src/device/service/drivers/xr/cm/tests/resources/constellation-by-name-hub1.json @@ -0,0 +1,388 @@ +[ + { + "href": "/xr-networks/233e169b-5d88-481d-bfe2-c909a2a859dd", + "hubModule": { + "href": "/xr-networks/233e169b-5d88-481d-bfe2-c909a2a859dd/hubModule", + "id": "519cc31f-b736-4e4c-b78d-600562d92911", + "parentId": "233e169b-5d88-481d-bfe2-c909a2a859dd", + "rt": [ + "cm.xr-network.hubModule" + ], + "state": { + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.100.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-1/0/0:0", + "portId": "et-1/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:00:11", + "sysName": "SanJose" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + }, + { + "hostPort": { + "chassisId": "192.168.100.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-1/0/0:1", + "portId": "et-1/0/0:1", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:00:12", + "sysName": "SanJose" + }, + "moduleIf": { + "clientIfAid": "XR-T2", + "clientIfColId": 2, + "clientIfPortSpeed": 100 + } + }, + { + "hostPort": { + "chassisId": "192.168.100.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-1/0/0:2", + "portId": "et-1/0/0:2", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:00:13", + "sysName": "SanJose" + }, + "moduleIf": { + "clientIfAid": "XR-T3", + "clientIfColId": 3, + "clientIfPortSpeed": 100 + } + }, + { + "hostPort": { + "chassisId": "192.168.100.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-1/0/0:3", + "portId": "et-1/0/0:3", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:00:14", + "sysName": "SanJose" + }, + "moduleIf": { + "clientIfAid": "XR-T4", + "clientIfColId": 4, + "clientIfPortSpeed": 100 + } + } + ], + "lifecycleState": "configured", + "module": { + "baudRate": 50, + "capacity": 400, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "hub", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:00:01", + "modulation": "16QAM", + "moduleId": "d859de3c-c463-4be5-7a8d-a198275f10f4", + "moduleName": "XR HUB 1", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "000000009", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + "id": "233e169b-5d88-481d-bfe2-c909a2a859dd", + "leafModules": [ + { + "href": "/xr-networks/233e169b-5d88-481d-bfe2-c909a2a859dd/leafModules/7e9da66b-8bf8-4eea-b4a7-045e5ba3bfd8", + "id": "7e9da66b-8bf8-4eea-b4a7-045e5ba3bfd8", + "parentId": "233e169b-5d88-481d-bfe2-c909a2a859dd", + "rt": [ + "cm.xr-network.leafModule" + ], + "state": { + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-1/0/0:0", + "portId": "et-1/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:11:01", + "sysName": "PaloAlto" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "lifecycleState": "configured", + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:01", + "maxAllowedDSCs": 4, + "modulation": "16QAM", + "moduleId": "7c33d7d0-4f7b-4525-5d57-58589adbd47c", + "moduleName": "XR LEAF 1", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "00000000B", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + { + "href": "/xr-networks/233e169b-5d88-481d-bfe2-c909a2a859dd/leafModules/7473b336-ef92-4508-b260-c096d05e4943", + "id": "7473b336-ef92-4508-b260-c096d05e4943", + "parentId": "233e169b-5d88-481d-bfe2-c909a2a859dd", + "rt": [ + "cm.xr-network.leafModule" + ], + "state": { + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.2", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-2/0/0:0", + "portId": "et-2/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:12:01", + "sysName": "Cupertino" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "lifecycleState": "configured", + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:02", + "maxAllowedDSCs": 4, + "modulation": "16QAM", + "moduleId": "d68a6b4e-03e4-4c89-5ad5-c5e782325e40", + "moduleName": "XR LEAF 2", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "00000000C", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + } + ], + "reachableModules": [ + { + "href": "/xr-networks/233e169b-5d88-481d-bfe2-c909a2a859dd/reachableModules/58785cd9-c642-43e4-a8b5-6d136acd8ae5", + "id": "58785cd9-c642-43e4-a8b5-6d136acd8ae5", + "parentId": "233e169b-5d88-481d-bfe2-c909a2a859dd", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:08Z", + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.3", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-3/0/0:0", + "portId": "et-3/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:13:01", + "sysName": "Sunnnyvale" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 193000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:03", + "modulation": "16QAM", + "moduleId": "572b2d8a-8d0b-40a0-5823-e53041ca2194", + "moduleName": "XR LEAF 3", + "ncoFrequency": 0, + "operatingFrequency": 193000000, + "roleStatus": "ready", + "serialNumber": "00000000D", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + { + "href": "/xr-networks/233e169b-5d88-481d-bfe2-c909a2a859dd/reachableModules/be85d276-6f30-4c7b-9f63-de8679dfab85", + "id": "be85d276-6f30-4c7b-9f63-de8679dfab85", + "parentId": "233e169b-5d88-481d-bfe2-c909a2a859dd", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:05Z", + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-1/0/0:0", + "portId": "et-1/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:11:01", + "sysName": "PaloAlto" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:01", + "modulation": "16QAM", + "moduleId": "7c33d7d0-4f7b-4525-5d57-58589adbd47c", + "moduleName": "XR LEAF 1", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "00000000B", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + { + "href": "/xr-networks/233e169b-5d88-481d-bfe2-c909a2a859dd/reachableModules/212cf331-c133-4321-8e74-023549b9afee", + "id": "212cf331-c133-4321-8e74-023549b9afee", + "parentId": "233e169b-5d88-481d-bfe2-c909a2a859dd", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:06Z", + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.2", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-2/0/0:0", + "portId": "et-2/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:12:01", + "sysName": "Cupertino" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:02", + "modulation": "16QAM", + "moduleId": "d68a6b4e-03e4-4c89-5ad5-c5e782325e40", + "moduleName": "XR LEAF 2", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "00000000C", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + } + ], + "rt": [ + "cm.xr-network" + ], + "state": { + "constellationFrequency": 192000000, + "controlLinks": [ + { + "conState": "active", + "destinationModuleId": "7c33d7d0-4f7b-4525-5d57-58589adbd47c", + "lastConStateChange": "2022-06-28T09:04:05Z", + "sourceModuleId": "d859de3c-c463-4be5-7a8d-a198275f10f4" + }, + { + "conState": "active", + "destinationModuleId": "d859de3c-c463-4be5-7a8d-a198275f10f4", + "lastConStateChange": "2022-06-28T09:04:05Z", + "sourceModuleId": "7c33d7d0-4f7b-4525-5d57-58589adbd47c" + }, + { + "conState": "active", + "destinationModuleId": "d68a6b4e-03e4-4c89-5ad5-c5e782325e40", + "lastConStateChange": "2022-06-28T09:04:06Z", + "sourceModuleId": "d859de3c-c463-4be5-7a8d-a198275f10f4" + }, + { + "conState": "active", + "destinationModuleId": "d859de3c-c463-4be5-7a8d-a198275f10f4", + "lastConStateChange": "2022-06-28T09:04:05Z", + "sourceModuleId": "d68a6b4e-03e4-4c89-5ad5-c5e782325e40" + } + ], + "lifecycleState": "configured", + "modulation": "16QAM" + } + } +] diff --git a/src/device/service/drivers/xr/cm/tests/resources/constellations-expanded.json b/src/device/service/drivers/xr/cm/tests/resources/constellations-expanded.json new file mode 100644 index 0000000000000000000000000000000000000000..cfe310f4b1340395d660f775d34086591a8e8827 --- /dev/null +++ b/src/device/service/drivers/xr/cm/tests/resources/constellations-expanded.json @@ -0,0 +1,662 @@ +[ + { + "href": "/xr-networks/6774cc4e-b0b1-43a1-923f-80fb1bec094b", + "hubModule": { + "href": "/xr-networks/6774cc4e-b0b1-43a1-923f-80fb1bec094b/hubModule", + "id": "353563a1-895f-4110-abec-8f59ffb5ecc7", + "parentId": "6774cc4e-b0b1-43a1-923f-80fb1bec094b", + "rt": [ + "cm.xr-network.hubModule" + ], + "state": { + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.100.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-2/0/0:0", + "portId": "et-2/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:00:21", + "sysName": "SanJose" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + }, + { + "hostPort": { + "chassisId": "192.168.100.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-2/0/0:1", + "portId": "et-2/0/0:1", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:00:22", + "sysName": "SanJose" + }, + "moduleIf": { + "clientIfAid": "XR-T2", + "clientIfColId": 2, + "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", + "sysName": "SanJose" + }, + "moduleIf": { + "clientIfAid": "XR-T3", + "clientIfColId": 3, + "clientIfPortSpeed": 100 + } + }, + { + "hostPort": { + "chassisId": "192.168.100.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-2/0/0:3", + "portId": "et-2/0/0:3", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:00:24", + "sysName": "SanJose" + }, + "moduleIf": { + "clientIfAid": "XR-T4", + "clientIfColId": 4, + "clientIfPortSpeed": 100 + } + } + ], + "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": "3a2b5cfe-6265-4b68-549d-340c58363b85", + "moduleName": "XR HUB 2", + "ncoFrequency": 0, + "operatingFrequency": 193000000, + "roleStatus": "ready", + "serialNumber": "00000000A", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + "id": "6774cc4e-b0b1-43a1-923f-80fb1bec094b", + "leafModules": [ + { + "href": "/xr-networks/6774cc4e-b0b1-43a1-923f-80fb1bec094b/leafModules/e659ad54-9e6d-492c-ac56-09b3b681c5ed", + "id": "e659ad54-9e6d-492c-ac56-09b3b681c5ed", + "parentId": "6774cc4e-b0b1-43a1-923f-80fb1bec094b", + "rt": [ + "cm.xr-network.leafModule" + ], + "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", + "sysName": "Sunnnyvale" + }, + "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": "572b2d8a-8d0b-40a0-5823-e53041ca2194", + "moduleName": "XR LEAF 3", + "ncoFrequency": 0, + "operatingFrequency": 193000000, + "roleStatus": "ready", + "serialNumber": "00000000D", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + } + ], + "reachableModules": [ + { + "href": "/xr-networks/6774cc4e-b0b1-43a1-923f-80fb1bec094b/reachableModules/ede4b98b-a6a7-48fa-89c0-b981e7d4a98c", + "id": "ede4b98b-a6a7-48fa-89c0-b981e7d4a98c", + "parentId": "6774cc4e-b0b1-43a1-923f-80fb1bec094b", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:08Z", + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.3", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-3/0/0:0", + "portId": "et-3/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:13:01", + "sysName": "Sunnnyvale" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 193000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:03", + "modulation": "16QAM", + "moduleId": "572b2d8a-8d0b-40a0-5823-e53041ca2194", + "moduleName": "XR LEAF 3", + "ncoFrequency": 0, + "operatingFrequency": 193000000, + "roleStatus": "ready", + "serialNumber": "00000000D", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + { + "href": "/xr-networks/6774cc4e-b0b1-43a1-923f-80fb1bec094b/reachableModules/004ffffb-290f-45d8-90bf-4e0c914eb39c", + "id": "004ffffb-290f-45d8-90bf-4e0c914eb39c", + "parentId": "6774cc4e-b0b1-43a1-923f-80fb1bec094b", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:05Z", + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-1/0/0:0", + "portId": "et-1/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:11:01", + "sysName": "PaloAlto" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:01", + "modulation": "16QAM", + "moduleId": "7c33d7d0-4f7b-4525-5d57-58589adbd47c", + "moduleName": "XR LEAF 1", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "00000000B", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + } + ], + "rt": [ + "cm.xr-network" + ], + "state": { + "constellationFrequency": 193000000, + "controlLinks": [ + { + "conState": "active", + "destinationModuleId": "3a2b5cfe-6265-4b68-549d-340c58363b85", + "lastConStateChange": "2022-06-28T09:04:07Z", + "sourceModuleId": "572b2d8a-8d0b-40a0-5823-e53041ca2194" + }, + { + "conState": "active", + "destinationModuleId": "572b2d8a-8d0b-40a0-5823-e53041ca2194", + "lastConStateChange": "2022-06-28T09:04:08Z", + "sourceModuleId": "3a2b5cfe-6265-4b68-549d-340c58363b85" + } + ], + "lifecycleState": "configured", + "modulation": "16QAM" + } + }, + { + "href": "/xr-networks/233e169b-5d88-481d-bfe2-c909a2a859dd", + "hubModule": { + "href": "/xr-networks/233e169b-5d88-481d-bfe2-c909a2a859dd/hubModule", + "id": "519cc31f-b736-4e4c-b78d-600562d92911", + "parentId": "233e169b-5d88-481d-bfe2-c909a2a859dd", + "rt": [ + "cm.xr-network.hubModule" + ], + "state": { + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.100.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-1/0/0:0", + "portId": "et-1/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:00:11", + "sysName": "SanJose" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + }, + { + "hostPort": { + "chassisId": "192.168.100.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-1/0/0:1", + "portId": "et-1/0/0:1", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:00:12", + "sysName": "SanJose" + }, + "moduleIf": { + "clientIfAid": "XR-T2", + "clientIfColId": 2, + "clientIfPortSpeed": 100 + } + }, + { + "hostPort": { + "chassisId": "192.168.100.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-1/0/0:2", + "portId": "et-1/0/0:2", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:00:13", + "sysName": "SanJose" + }, + "moduleIf": { + "clientIfAid": "XR-T3", + "clientIfColId": 3, + "clientIfPortSpeed": 100 + } + }, + { + "hostPort": { + "chassisId": "192.168.100.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-1/0/0:3", + "portId": "et-1/0/0:3", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:00:14", + "sysName": "SanJose" + }, + "moduleIf": { + "clientIfAid": "XR-T4", + "clientIfColId": 4, + "clientIfPortSpeed": 100 + } + } + ], + "lifecycleState": "configured", + "module": { + "baudRate": 50, + "capacity": 400, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "hub", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:00:01", + "modulation": "16QAM", + "moduleId": "d859de3c-c463-4be5-7a8d-a198275f10f4", + "moduleName": "XR HUB 1", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "000000009", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + "id": "233e169b-5d88-481d-bfe2-c909a2a859dd", + "leafModules": [ + { + "href": "/xr-networks/233e169b-5d88-481d-bfe2-c909a2a859dd/leafModules/7e9da66b-8bf8-4eea-b4a7-045e5ba3bfd8", + "id": "7e9da66b-8bf8-4eea-b4a7-045e5ba3bfd8", + "parentId": "233e169b-5d88-481d-bfe2-c909a2a859dd", + "rt": [ + "cm.xr-network.leafModule" + ], + "state": { + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-1/0/0:0", + "portId": "et-1/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:11:01", + "sysName": "PaloAlto" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "lifecycleState": "configured", + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:01", + "maxAllowedDSCs": 4, + "modulation": "16QAM", + "moduleId": "7c33d7d0-4f7b-4525-5d57-58589adbd47c", + "moduleName": "XR LEAF 1", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "00000000B", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + { + "href": "/xr-networks/233e169b-5d88-481d-bfe2-c909a2a859dd/leafModules/7473b336-ef92-4508-b260-c096d05e4943", + "id": "7473b336-ef92-4508-b260-c096d05e4943", + "parentId": "233e169b-5d88-481d-bfe2-c909a2a859dd", + "rt": [ + "cm.xr-network.leafModule" + ], + "state": { + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.2", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-2/0/0:0", + "portId": "et-2/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:12:01", + "sysName": "Cupertino" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "lifecycleState": "configured", + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:02", + "maxAllowedDSCs": 4, + "modulation": "16QAM", + "moduleId": "d68a6b4e-03e4-4c89-5ad5-c5e782325e40", + "moduleName": "XR LEAF 2", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "00000000C", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + } + ], + "reachableModules": [ + { + "href": "/xr-networks/233e169b-5d88-481d-bfe2-c909a2a859dd/reachableModules/58785cd9-c642-43e4-a8b5-6d136acd8ae5", + "id": "58785cd9-c642-43e4-a8b5-6d136acd8ae5", + "parentId": "233e169b-5d88-481d-bfe2-c909a2a859dd", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:08Z", + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.3", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-3/0/0:0", + "portId": "et-3/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:13:01", + "sysName": "Sunnnyvale" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 193000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:03", + "modulation": "16QAM", + "moduleId": "572b2d8a-8d0b-40a0-5823-e53041ca2194", + "moduleName": "XR LEAF 3", + "ncoFrequency": 0, + "operatingFrequency": 193000000, + "roleStatus": "ready", + "serialNumber": "00000000D", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + { + "href": "/xr-networks/233e169b-5d88-481d-bfe2-c909a2a859dd/reachableModules/be85d276-6f30-4c7b-9f63-de8679dfab85", + "id": "be85d276-6f30-4c7b-9f63-de8679dfab85", + "parentId": "233e169b-5d88-481d-bfe2-c909a2a859dd", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:05Z", + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.1", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-1/0/0:0", + "portId": "et-1/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:11:01", + "sysName": "PaloAlto" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:01", + "modulation": "16QAM", + "moduleId": "7c33d7d0-4f7b-4525-5d57-58589adbd47c", + "moduleName": "XR LEAF 1", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "00000000B", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + }, + { + "href": "/xr-networks/233e169b-5d88-481d-bfe2-c909a2a859dd/reachableModules/212cf331-c133-4321-8e74-023549b9afee", + "id": "212cf331-c133-4321-8e74-023549b9afee", + "parentId": "233e169b-5d88-481d-bfe2-c909a2a859dd", + "rt": [ + "cm.xr-network.reachableModule" + ], + "state": { + "discoveredTime": "2022-06-28T09:04:06Z", + "endpoints": [ + { + "hostPort": { + "chassisId": "192.168.101.2", + "chassisIdSubtype": "networkAddress", + "portDescr": "et-2/0/0:0", + "portId": "et-2/0/0:0", + "portIdSubtype": "interfaceName", + "portSourceMAC": "58:00:BB:00:12:01", + "sysName": "Cupertino" + }, + "moduleIf": { + "clientIfAid": "XR-T1", + "clientIfColId": 1, + "clientIfPortSpeed": 100 + } + } + ], + "module": { + "baudRate": 50, + "capacity": 100, + "clientPortMode": "ethernet", + "configuredRole": "auto", + "constellationFrequency": 192000000, + "currentRole": "leaf", + "fiberConnectionMode": "dual", + "frequencyCtrl": "xr", + "macAddress": "00:0B:F8:00:01:02", + "modulation": "16QAM", + "moduleId": "d68a6b4e-03e4-4c89-5ad5-c5e782325e40", + "moduleName": "XR LEAF 2", + "ncoFrequency": 0, + "operatingFrequency": 192000000, + "roleStatus": "ready", + "serialNumber": "00000000C", + "trafficMode": "L1Mode", + "txPowerTargetPerDsc": -6.4 + } + } + } + ], + "rt": [ + "cm.xr-network" + ], + "state": { + "constellationFrequency": 192000000, + "controlLinks": [ + { + "conState": "active", + "destinationModuleId": "7c33d7d0-4f7b-4525-5d57-58589adbd47c", + "lastConStateChange": "2022-06-28T09:04:05Z", + "sourceModuleId": "d859de3c-c463-4be5-7a8d-a198275f10f4" + }, + { + "conState": "active", + "destinationModuleId": "d859de3c-c463-4be5-7a8d-a198275f10f4", + "lastConStateChange": "2022-06-28T09:04:05Z", + "sourceModuleId": "7c33d7d0-4f7b-4525-5d57-58589adbd47c" + }, + { + "conState": "active", + "destinationModuleId": "d68a6b4e-03e4-4c89-5ad5-c5e782325e40", + "lastConStateChange": "2022-06-28T09:04:06Z", + "sourceModuleId": "d859de3c-c463-4be5-7a8d-a198275f10f4" + }, + { + "conState": "active", + "destinationModuleId": "d859de3c-c463-4be5-7a8d-a198275f10f4", + "lastConStateChange": "2022-06-28T09:04:05Z", + "sourceModuleId": "d68a6b4e-03e4-4c89-5ad5-c5e782325e40" + } + ], + "lifecycleState": "configured", + "modulation": "16QAM" + } + } +] \ 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_cm_connection.py b/src/device/service/drivers/xr/cm/tests/test_cm_connection.py new file mode 100644 index 0000000000000000000000000000000000000000..60cbeac06abf312335abb693f4cd190281fffa7b --- /dev/null +++ b/src/device/service/drivers/xr/cm/tests/test_cm_connection.py @@ -0,0 +1,81 @@ +#pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import inspect +import os +import requests_mock + +#from ..tf_service import TFService +from ..cm_connection import CmConnection + +access_token = r'{"access_token":"eyI3...","expires_in":3600,"refresh_expires_in":0,"refresh_token":"ey...","token_type":"Bearer","not-before-policy":0,"session_state":"f6e235c4-4ca4-4258-bede-4f2b7125adfb","scope":"profile email offline_access"}' + +resources = os.path.join(os.path.dirname(os.path.abspath(inspect.stack()[0][1])), "resources") +with open(os.path.join(resources, "constellations-expanded.json"), "r", encoding="UTF-8") as f: + res_constellations = f.read() +with open(os.path.join(resources, "constellation-by-name-hub1.json"), "r", encoding="UTF-8") as f: + res_constellation_by_name_hub1 = f.read() + +def mock_cm_connectivity(): + m = requests_mock.Mocker() + m.post('https://127.0.0.1:9999/realms/xr-cm/protocol/openid-connect/token', text=access_token) + return m + +def test_cmc_connect(): + # Valid access token + with requests_mock.Mocker() as m: + m.post('https://127.0.0.1:9999/realms/xr-cm/protocol/openid-connect/token', text=access_token) + cm = CmConnection("127.0.0.1", 9999, "xr-user", "xr-password", tls_verify=False) + assert cm.Connect() + + # Valid JSON but no access token + with requests_mock.Mocker() as m: + m.post('https://127.0.0.1:9999/realms/xr-cm/protocol/openid-connect/token', text=r'{"a": "b"}') + cm = CmConnection("127.0.0.1", 9999, "xr-user", "xr-password", tls_verify=False) + assert not cm.Connect() + + # Invalid JSON + with requests_mock.Mocker() as m: + m.post('https://127.0.0.1:9999/realms/xr-cm/protocol/openid-connect/token', text=r'}}}') + cm = CmConnection("127.0.0.1", 9999, "xr-user", "xr-password", tls_verify=False) + assert not cm.Connect() + + with requests_mock.Mocker() as m: + # No mock present for the destination + cm = CmConnection("127.0.0.1", 9999, "xr-user", "xr-password", tls_verify=False) + assert not cm.Connect() + +def test_cmc_get_constellations(): + with mock_cm_connectivity() as m: + m.get("https://127.0.0.1:9999/api/v1/ns/xr-networks?content=expanded", text=res_constellations) + cm = CmConnection("127.0.0.1", 9999, "xr-user", "xr-password", tls_verify=False) + assert cm.Connect() + + # List all constellations + constellations = cm.list_constellations() + assert len(constellations) == 2 + cids = [c.constellation_id for c in constellations] + assert cids == ["6774cc4e-b0b1-43a1-923f-80fb1bec094b", "233e169b-5d88-481d-bfe2-c909a2a859dd"] + ifnames = [c.ifnames() for c in constellations] + assert ifnames == [['XR HUB 2|XR-T1', 'XR HUB 2|XR-T2', 'XR HUB 2|XR-T3', 'XR HUB 2|XR-T4', 'XR LEAF 3|XR-T1'], + ['XR HUB 1|XR-T1', 'XR HUB 1|XR-T2', 'XR HUB 1|XR-T3', 'XR HUB 1|XR-T4', 'XR LEAF 1|XR-T1', 'XR LEAF 2|XR-T1']] + + # Get constellation by hub module name + m.get("https://127.0.0.1:9999/api/v1/ns/xr-networks?content=expanded&content=expanded&q=%7B%22hubModule.state.module.moduleName%22%3A+%22XR+HUB+1%22%7D", text=res_constellation_by_name_hub1) + constellation = cm.get_constellation_by_hub_name("XR HUB 1") + assert constellation + assert constellation.ifnames() == ['XR HUB 1|XR-T1', 'XR HUB 1|XR-T2', 'XR HUB 1|XR-T3', 'XR HUB 1|XR-T4', 'XR LEAF 1|XR-T1', 'XR LEAF 2|XR-T1'] + assert constellation.constellation_id == "233e169b-5d88-481d-bfe2-c909a2a859dd" + \ No newline at end of file diff --git a/src/device/service/drivers/xr/cm/tests/test_connection.py b/src/device/service/drivers/xr/cm/tests/test_connection.py new file mode 100644 index 0000000000000000000000000000000000000000..0792033a34d029628a853b9383af8c8a2c6272ad --- /dev/null +++ b/src/device/service/drivers/xr/cm/tests/test_connection.py @@ -0,0 +1,106 @@ +#pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import inspect +import os +import json +import pytest + +from ..tf_service import TFService +from ..connection import Connection, InconsistentVlanConfiguration, ConnectionDeserializationError + +resources = os.path.join(os.path.dirname(os.path.abspath(inspect.stack()[0][1])), "resources") + +def test_connection_json(): + with open(os.path.join(resources, "connections-expanded.json"), "r", encoding="UTF-8") as f: + j = json.load(f) + connection = Connection(j[0]) + + assert connection.name == "FooBar123" + assert "name: FooBar123, id: /network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03, service-mode: portMode, end-points: [(XR LEAF 1|XR-T1, 0), (XR HUB 1|XR-T1, 0)]" == str(connection) + + config = connection.create_config() + expected_config = {'name': 'FooBar123', 'serviceMode': 'portMode', 'endpoints': [{'selector': {'ifSelectorByModuleName': {'moduleName': 'XR LEAF 1', 'moduleClientIfAid': 'XR-T1'}}}, {'selector': {'ifSelectorByModuleName': {'moduleName': 'XR HUB 1', 'moduleClientIfAid': 'XR-T1'}}}]} + assert config == expected_config + + # Remove mandatory key from leaf endpoint. It will not be parsed, but hub endpoint will + del j[0]["endpoints"][0]["state"]["moduleIf"]["clientIfAid"] + connection = Connection(j[0]) + assert "name: FooBar123, id: /network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03, service-mode: portMode, end-points: [(XR HUB 1|XR-T1, 0)]" == str(connection) + + # Remove Name, it is optional (although TF will always configure it) + del j[0]["state"]["name"] + connection = Connection(j[0]) + assert "name: , id: /network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03, service-mode: portMode, end-points: [(XR HUB 1|XR-T1, 0)]" == str(connection) + + # Remove mandatory key, will raise an exception + del j[0]["state"] + with pytest.raises(ConnectionDeserializationError, match=r"Missing mandatory key 'state'"): + _connection = Connection(j[0]) + +def test_connection_ep_change_compute(): + with open(os.path.join(resources, "connections-expanded.json"), "r", encoding="UTF-8") as f: + j = json.load(f) + existing_connection = Connection(j[0]) + + # Changing only capacity + new_connection = Connection(from_tf_service=TFService("FooBar123", "XR LEAF 1|XR-T1", "XR HUB 1|XR-T1", 25)) + ep_deletes, ep_creates, ep_updates = new_connection.get_endpoint_updates(existing_connection) + assert not ep_deletes + assert not ep_creates + assert ep_updates == [('/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03/endpoints/230516d0-7e38-44b1-b174-1ba7d4454ee6', {'capacity': 25}), ('/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03/endpoints/1d58ba8f-4d51-4213-83e1-97a0e0bdd388', {'capacity': 25})] + + # Change one of endpoints + new_connection = Connection(from_tf_service=TFService("FooBar123", "XR LEAF 1|XR-T1", "XR HUB 1|changed here", 0)) + ep_deletes, ep_creates, ep_updates = new_connection.get_endpoint_updates(existing_connection) + assert ep_deletes == ['/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03/endpoints/1d58ba8f-4d51-4213-83e1-97a0e0bdd388'] + assert ep_creates == [{'selector': {'ifSelectorByModuleName': {'moduleClientIfAid': 'changed here', 'moduleName': 'XR HUB 1'}}}] + assert not ep_updates + + # Change one of the endpoints and capacity + new_connection = Connection(from_tf_service=TFService("FooBar123", "XR LEAF 1|XR-T1", "XR HUB 1|changed here", 125)) + ep_deletes, ep_creates, ep_updates = new_connection.get_endpoint_updates(existing_connection) + assert ep_deletes == ['/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03/endpoints/1d58ba8f-4d51-4213-83e1-97a0e0bdd388'] + assert ep_creates == [{'selector': {'ifSelectorByModuleName': {'moduleClientIfAid': 'changed here', 'moduleName': 'XR HUB 1'}}, "capacity": 125}] + assert ep_updates == [('/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03/endpoints/230516d0-7e38-44b1-b174-1ba7d4454ee6', {'capacity': 125})] + + # No change at all + new_connection = Connection(from_tf_service=TFService("FooBar123", "XR LEAF 1|XR-T1", "XR HUB 1|XR-T1", 0)) + ep_deletes, ep_creates, ep_updates = new_connection.get_endpoint_updates(existing_connection) + assert not ep_deletes + assert not ep_creates + assert not ep_updates + + # Order of endpoints does not matter + new_connection = Connection(from_tf_service=TFService("FooBar123", "XR HUB 1|XR-T1", "XR LEAF 1|XR-T1", 0)) + ep_deletes, ep_creates, ep_updates = new_connection.get_endpoint_updates(existing_connection) + assert not ep_deletes + assert not ep_creates + assert not ep_updates + +def test_connection_from_service(): + # Port mode + connection = Connection(from_tf_service=TFService("FooBar123", "XR LEAF 1|XR-T1", "XR HUB 1|XR-T1", 0)) + assert connection.create_config() == {'name': 'TF:FooBar123', 'serviceMode': 'portMode', 'endpoints': [{'selector': {'ifSelectorByModuleName': {'moduleName': 'XR LEAF 1', 'moduleClientIfAid': 'XR-T1'}}}, {'selector': {'ifSelectorByModuleName': {'moduleName': 'XR HUB 1', 'moduleClientIfAid': 'XR-T1'}}}]} + + # VTI mode + connection = Connection(from_tf_service=TFService("FooBar123", "XR LEAF 1|XR-T1.A", "XR HUB 1|XR-T1.100", 0)) + # In endpoint selectors VLANs are note present (CM does not know about them, encoding them to aids is purely internal to Teraflow) + # However VLAN adds outerVID and some other fields + assert connection.create_config() == {'name': 'TF:FooBar123', 'serviceMode': 'vtiP2pSymmetric', 'endpoints': [{'selector': {'ifSelectorByModuleName': {'moduleName': 'XR LEAF 1', 'moduleClientIfAid': 'XR-T1'}}}, {'selector': {'ifSelectorByModuleName': {'moduleName': 'XR HUB 1', 'moduleClientIfAid': 'XR-T1'}}}], 'outerVID': '100 ', 'mc': 'matchOuterVID'} + + # Invalid configuration, differring VLANs on different sides + with pytest.raises(InconsistentVlanConfiguration) as _e_info: + Connection(from_tf_service=TFService("FooBar123", "XR LEAF 1|XR-T1.200", "XR HUB 1|XR-T1.100", 0)) diff --git a/src/device/service/drivers/xr/cm/tests/test_constellation.py b/src/device/service/drivers/xr/cm/tests/test_constellation.py new file mode 100644 index 0000000000000000000000000000000000000000..82848b57e87884826e42234124aade4f447003fe --- /dev/null +++ b/src/device/service/drivers/xr/cm/tests/test_constellation.py @@ -0,0 +1,39 @@ +#pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import inspect +import os +import json +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) + + # Proper constellation with endpoints + constellation = Constellation(j[1]) + assert constellation.constellation_id == "233e169b-5d88-481d-bfe2-c909a2a859dd" + assert not constellation.is_vti_mode() + print(constellation.ifnames()) + assert ['XR HUB 1|XR-T1', 'XR HUB 1|XR-T2', 'XR HUB 1|XR-T3', 'XR HUB 1|XR-T4', 'XR LEAF 1|XR-T1', 'XR LEAF 2|XR-T1'] == constellation.ifnames() + + # Remove mandatory key, will raise an exception + del j[0]["hubModule"]["state"] + 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..cfdadae6a5e150e9890076dba0e657aea6fa3b1e --- /dev/null +++ b/src/device/service/drivers/xr/cm/tests/test_transport_capacitity.py @@ -0,0 +1,59 @@ +#pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import inspect +import os +import json + +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/tests/test_xr_service_set_config.py b/src/device/service/drivers/xr/cm/tests/test_xr_service_set_config.py new file mode 100644 index 0000000000000000000000000000000000000000..5a97e6ee2ee5d2ca119f2f8c3ffb776f34d8c1bc --- /dev/null +++ b/src/device/service/drivers/xr/cm/tests/test_xr_service_set_config.py @@ -0,0 +1,109 @@ +#pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import inspect +import os +import json +import requests_mock +import traceback + +from ..cm_connection import CmConnection +from ..tf import set_config_for_service + +access_token = r'{"access_token":"eyI3...","expires_in":3600,"refresh_expires_in":0,"refresh_token":"ey...","token_type":"Bearer","not-before-policy":0,"session_state":"f6e235c4-4ca4-4258-bede-4f2b7125adfb","scope":"profile email offline_access"}' + +resources = os.path.join(os.path.dirname(os.path.abspath(inspect.stack()[0][1])), "resources") +with open(os.path.join(resources, "constellation-by-name-hub1.json"), "r", encoding="UTF-8") as f: + res_constellation_by_name_hub1 = f.read() +with open(os.path.join(resources, "connections-expanded.json"), "r", encoding="UTF-8") as f: + j = json.load(f) + # Fake reference data to have the name this test case needs for the given teraflow UUID + # (=no need for too large set of reference material) + j[0]["state"]["name"] = "TF:12345ABCDEFGHIJKLMN" + res_connection_by_name_json = [j[0]] # Single item list + +def mock_cm(): + m = requests_mock.Mocker() + m.post('https://127.0.0.1:9999/realms/xr-cm/protocol/openid-connect/token', text=access_token) + m.get("https://127.0.0.1:9999/api/v1/ns/xr-networks?content=expanded&content=expanded&q=%7B%22hubModule.state.module.moduleName%22%3A+%22XR+HUB+1%22%7D", text=res_constellation_by_name_hub1) + m.post("https://127.0.0.1:9999/api/v1/ncs/network-connections", text='[{"href":"/network-connections/c3b31608-0bb7-4a4f-9f9a-88b24a059432","rt":["cm.network-connection"]}]', status_code=202) + return m + +uuid = "12345ABCDEFGHIJKLMN" +config = { + "input_sip": "XR HUB 1|XR-T4;", + "output_sip": "XR LEAF 1|XR-T1", + "capacity_value": 125, + "capacity_unit": "gigabit" +} + +def _validate_result(result, expect): + if isinstance(result, Exception): + traceback.print_exception(result) + assert result is expect # Not, "is", not ==, we want type checking in this case, as also an exception can be returned (as return value) + +def test_xr_set_config(): + with mock_cm() as m: + cm = CmConnection("127.0.0.1", 9999, "xr-user", "xr-password", tls_verify=False) + assert cm.Connect() + + constellation = cm.get_constellation_by_hub_name("XR HUB 1") + assert constellation + + result = set_config_for_service(cm, constellation, uuid, config) + _validate_result(result, True) + + called_mocks = [(r._request.method, r._request.url) for r in m._adapter.request_history] + expected_mocks = [ + ('POST', 'https://127.0.0.1:9999/realms/xr-cm/protocol/openid-connect/token'), # Authentication + ('GET', 'https://127.0.0.1:9999/api/v1/ns/xr-networks?content=expanded&content=expanded&q=%7B%22hubModule.state.module.moduleName%22%3A+%22XR+HUB+1%22%7D'), # Hub module by name + ('GET', 'https://127.0.0.1:9999/api/v1/ncs/network-connections?content=expanded&q=%7B%22state.name%22%3A+%22TF%3A12345ABCDEFGHIJKLMN%22%7D'), # Get by name, determine update or create + ('POST', 'https://127.0.0.1:9999/api/v1/ncs/network-connections') # Create + ] + assert called_mocks == expected_mocks + +def test_xr_set_config_update_case(): + with mock_cm() as m: + cm = CmConnection("127.0.0.1", 9999, "xr-user", "xr-password", tls_verify=False) + assert cm.Connect() + + constellation = cm.get_constellation_by_hub_name("XR HUB 1") + assert constellation + + # Fake existing service (--> update path is taken) + m.get("https://127.0.0.1:9999/api/v1/ncs/network-connections?content=expanded&q=%7B%22state.name%22%3A+%22TF%3A12345ABCDEFGHIJKLMN%22%7D", json=res_connection_by_name_json) + # Delete endpoint that is no longer necessary + m.delete("https://127.0.0.1:9999/api/v1/ncs/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03/endpoints/1d58ba8f-4d51-4213-83e1-97a0e0bdd388", text="", status_code = 202) + # Update changed endpoint + m.put("https://127.0.0.1:9999/api/v1/ncs/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03/endpoints/230516d0-7e38-44b1-b174-1ba7d4454ee6", text="", status_code = 202) + # Create the newly added endpoint + m.post("https://127.0.0.1:9999/api/v1/ncs/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03/endpoints", json=[{"href":"/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03/endpoint/somethingplausible","rt":["plausible"]}], status_code=202) + # Update the connection itself + m.put("https://127.0.0.1:9999/api/v1/ncs/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03", text="", status_code=202) + + result = set_config_for_service(cm, constellation, uuid, config) + _validate_result(result, True) + + called_mocks = [(r._request.method, r._request.url) for r in m._adapter.request_history] + expected_mocks = [ + ('POST', 'https://127.0.0.1:9999/realms/xr-cm/protocol/openid-connect/token'), # Authentication + ('GET', 'https://127.0.0.1:9999/api/v1/ns/xr-networks?content=expanded&content=expanded&q=%7B%22hubModule.state.module.moduleName%22%3A+%22XR+HUB+1%22%7D'), # Hub module by name + ('GET', 'https://127.0.0.1:9999/api/v1/ncs/network-connections?content=expanded&q=%7B%22state.name%22%3A+%22TF%3A12345ABCDEFGHIJKLMN%22%7D'), # Get by name, determine update or create + ('DELETE', 'https://127.0.0.1:9999/api/v1/ncs/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03/endpoints/1d58ba8f-4d51-4213-83e1-97a0e0bdd388'), # Delete unnecessary endpoint + ('PUT', 'https://127.0.0.1:9999/api/v1/ncs/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03/endpoints/230516d0-7e38-44b1-b174-1ba7d4454ee6'), # Update changed endpoint + ('POST', 'https://127.0.0.1:9999/api/v1/ncs/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03/endpoints'), # Add new endpoint + ('PUT', 'https://127.0.0.1:9999/api/v1/ncs/network-connections/4505d5d3-b2f3-40b8-8ec2-4a5b28523c03') # Update the connection itself + ] + assert called_mocks == expected_mocks diff --git a/src/device/service/drivers/xr/cm/tf.py b/src/device/service/drivers/xr/cm/tf.py new file mode 100644 index 0000000000000000000000000000000000000000..1872bfe6c374c9e295b71c8f9673689c67202cd9 --- /dev/null +++ b/src/device/service/drivers/xr/cm/tf.py @@ -0,0 +1,71 @@ +#pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Dict, Union +import logging +from .cm_connection import CmConnection +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={str(connection)})") + return True + else: + LOGGER.error(f"set_config_for_service: Service creation failure for {uuid} (connection={str(connection)})") + return False + # Intentionally catching all exceptions, as they are stored in a list as return values + # by the caller + # 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..7ba8d9ee44bca7820857c50ff5894099c75e8c5b --- /dev/null +++ b/src/device/service/drivers/xr/cm/tf_service.py @@ -0,0 +1,67 @@ +#pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring, wildcard-import, unused-wildcard-import +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math +from dataclasses import dataclass +from typing import Tuple, Optional, Dict, List +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..d28d5b13707249a60fde04ccf4a1f1d35cc45cc8 --- /dev/null +++ b/src/device/service/drivers/xr/cm/transport_capacity.py @@ -0,0 +1,109 @@ +#pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Optional, Dict +from dataclasses import dataclass + +from .utils import make_selector + +from .tf_service import TFService + +@dataclass +class TCEndpoint: + module: str + port: str + capacity: int + + def ifname(self) -> str: + return self.module + "|" + self.port + + def __str__(self): + return f"({self.ifname()}, {self.capacity})" + + def create_config(self) -> Dict[str, any]: + cfg = { + "capacity": self.capacity, + "selector": make_selector(self.module, self.port, None) + } + return cfg + +class TransportCapacityDeserializationError(Exception): + pass + +class TransportCapacity: + def __init__(self, from_json=None, from_tf_service: Optional[TFService] = None): + def get_endpoint_from_json(endpoint: dict[str, any]) -> Optional[TCEndpoint]: + try: + return TCEndpoint(endpoint["state"]["moduleIf"]["moduleName"], endpoint["state"]["moduleIf"]["clientIfAid"], + endpoint["state"]["capacity"]) + except KeyError: + return None + + if from_json: + try: + self.href = from_json["href"] + + state = from_json["state"] + # Name is optional + self.name = state["name"] if "name" in state else None + self.capacity_mode = state["capacityMode"] + + self.endpoints = [] + for epj in from_json["endpoints"]: + ep = get_endpoint_from_json(epj) + if ep: + self.endpoints.append(ep) + + #self.__cm_data = from_json + except KeyError as e: + raise TransportCapacityDeserializationError(f"Missing mandatory key {str(e)}") from e + elif from_tf_service: + self.href = None + self.state = "tfInternalObject" + self.name = from_tf_service.name() + self.capacity_mode = "dedicatedDownlinkSymmetric" + self.endpoints = [TCEndpoint(mod, port, from_tf_service.capacity) for mod,port in from_tf_service.get_endpoints_mod_aid()] + #self.__cm_data = None + else: + # May support other initializations in future + raise TransportCapacityDeserializationError("Initializer missing") + + # Return suitable config for CM + def create_config(self) -> Dict[str, any]: + cfg = {} + if self.name is not None: + cfg["config"] = {"name": self.name } + cfg["endpoints"] = [ep.create_config() for ep in self.endpoints] + return cfg + + def __str__(self): + name = self.name if self.name else "" + endpoints = ", ".join((str(ep) for ep in self.endpoints)) + return f"name: {name}, id: {self.href}, capacity-mode: {self.capacity_mode}, end-points: [{endpoints}]" + + # Comparison for purpose of re-configuring + def __eq__(self, obj): + if not isinstance(obj, TransportCapacity): + return False + if self.name != obj.name: + return False + if self.capacity_mode != obj.capacity_mode: + return False + if sorted(self.endpoints, key=str) != sorted(obj.endpoints, key=str): + return False + return True + + def __ne__(self, obj): + return not self == obj diff --git a/src/device/service/drivers/xr/cm/utils.py b/src/device/service/drivers/xr/cm/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..cdf9e58c348f572c1547bb392a8cddba7669d0b0 --- /dev/null +++ b/src/device/service/drivers/xr/cm/utils.py @@ -0,0 +1,71 @@ +#pylint: disable=invalid-name, missing-function-docstring, line-too-long, logging-fstring-interpolation, missing-class-docstring, missing-module-docstring +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Any, Tuple, Optional, Dict + +class InvalidIfnameError(Exception): + 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/pathcomp/frontend/service/algorithms/tools/ConstantsMappings.py b/src/pathcomp/frontend/service/algorithms/tools/ConstantsMappings.py index 5e9b9a893039e96174ba7797d5aa09a2d8b152fe..7d61e1ef8f78950ec6f9bd0878de136d4a01b554 100644 --- a/src/pathcomp/frontend/service/algorithms/tools/ConstantsMappings.py +++ b/src/pathcomp/frontend/service/algorithms/tools/ConstantsMappings.py @@ -85,6 +85,7 @@ DEVICE_TYPE_TO_LAYER = { DeviceTypeEnum.PACKET_ROUTER.value : DeviceLayerEnum.PACKET_DEVICE, DeviceTypeEnum.EMULATED_PACKET_SWITCH.value : DeviceLayerEnum.MAC_LAYER_DEVICE, DeviceTypeEnum.PACKET_SWITCH.value : DeviceLayerEnum.MAC_LAYER_DEVICE, + DeviceTypeEnum.EMULATED_P4_SWITCH.value : DeviceLayerEnum.MAC_LAYER_DEVICE, DeviceTypeEnum.P4_SWITCH.value : DeviceLayerEnum.MAC_LAYER_DEVICE, @@ -93,6 +94,7 @@ DEVICE_TYPE_TO_LAYER = { DeviceTypeEnum.EMULATED_OPEN_LINE_SYSTEM.value : DeviceLayerEnum.OPTICAL_CONTROLLER, DeviceTypeEnum.OPEN_LINE_SYSTEM.value : DeviceLayerEnum.OPTICAL_CONTROLLER, + DeviceTypeEnum.XR_CONSTELLATION.value : DeviceLayerEnum.OPTICAL_CONTROLLER, DeviceTypeEnum.EMULATED_OPTICAL_ROADM.value : DeviceLayerEnum.OPTICAL_DEVICE, DeviceTypeEnum.OPTICAL_ROADM.value : DeviceLayerEnum.OPTICAL_DEVICE, diff --git a/src/policy/src/main/java/eu/teraflow/policy/Serializer.java b/src/policy/src/main/java/eu/teraflow/policy/Serializer.java index 88ebd332c719f42e1345e3c0f7fbbb734cdf42ba..4c43f39006227065d33a2f9db496013f503695fd 100644 --- a/src/policy/src/main/java/eu/teraflow/policy/Serializer.java +++ b/src/policy/src/main/java/eu/teraflow/policy/Serializer.java @@ -2243,6 +2243,8 @@ public class Serializer { return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY; case ONF_TR_352: return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352; + case XR: + return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_XR; case UNDEFINED: default: return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED; @@ -2262,6 +2264,8 @@ public class Serializer { return DeviceDriverEnum.IETF_NETWORK_TOPOLOGY; case DEVICEDRIVER_ONF_TR_352: return DeviceDriverEnum.ONF_TR_352; + case DEVICEDRIVER_XR: + return DeviceDriverEnum.XR; case DEVICEDRIVER_UNDEFINED: case UNRECOGNIZED: default: diff --git a/src/policy/src/main/java/eu/teraflow/policy/context/model/DeviceDriverEnum.java b/src/policy/src/main/java/eu/teraflow/policy/context/model/DeviceDriverEnum.java index c98fc1fce545974a8067db2667ec1e519a058ae2..ee1ebcbcf96962d06be6915f40acfd8230483655 100644 --- a/src/policy/src/main/java/eu/teraflow/policy/context/model/DeviceDriverEnum.java +++ b/src/policy/src/main/java/eu/teraflow/policy/context/model/DeviceDriverEnum.java @@ -22,5 +22,6 @@ public enum DeviceDriverEnum { TRANSPORT_API, P4, IETF_NETWORK_TOPOLOGY, - ONF_TR_352 + ONF_TR_352, + XR } diff --git a/src/policy/src/test/java/eu/teraflow/policy/SerializerTest.java b/src/policy/src/test/java/eu/teraflow/policy/SerializerTest.java index 0f27fe20c93fd133cf24c7f6c0d8ce5baa0b7d37..7e6967e0c22c2aa4b88d4b8a77c87f20023d3c2a 100644 --- a/src/policy/src/test/java/eu/teraflow/policy/SerializerTest.java +++ b/src/policy/src/test/java/eu/teraflow/policy/SerializerTest.java @@ -3599,6 +3599,7 @@ class SerializerTest { Arguments.of( DeviceDriverEnum.ONF_TR_352, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352), + Arguments.of(DeviceDriverEnum.XR, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_XR), Arguments.of( DeviceDriverEnum.UNDEFINED, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED)); } diff --git a/src/policy/target/generated-sources/grpc/context/ContextOuterClass.java b/src/policy/target/generated-sources/grpc/context/ContextOuterClass.java index 3c0d7ce36fcdc4e47697ba11a4ceb3d8e8cdea0c..fbbba62a2baa1c2fe2b3c3fe090883d6542996e4 100644 --- a/src/policy/target/generated-sources/grpc/context/ContextOuterClass.java +++ b/src/policy/target/generated-sources/grpc/context/ContextOuterClass.java @@ -173,6 +173,10 @@ public final class ContextOuterClass { * DEVICEDRIVER_ONF_TR_352 = 5; */ DEVICEDRIVER_ONF_TR_352(5), + /** + * DEVICEDRIVER_XR = 6; + */ + DEVICEDRIVER_XR(6), UNRECOGNIZED(-1), ; @@ -204,6 +208,10 @@ public final class ContextOuterClass { * DEVICEDRIVER_ONF_TR_352 = 5; */ public static final int DEVICEDRIVER_ONF_TR_352_VALUE = 5; + /** + * DEVICEDRIVER_XR = 6; + */ + public static final int DEVICEDRIVER_XR_VALUE = 6; public final int getNumber() { @@ -236,6 +244,7 @@ public final class ContextOuterClass { case 3: return DEVICEDRIVER_P4; case 4: return DEVICEDRIVER_IETF_NETWORK_TOPOLOGY; case 5: return DEVICEDRIVER_ONF_TR_352; + case 6: return DEVICEDRIVER_XR; default: return null; } } @@ -62318,100 +62327,100 @@ public final class ContextOuterClass { "ntext.ContextId\022\025\n\rauthenticated\030\002 \001(\010*j" + "\n\rEventTypeEnum\022\027\n\023EVENTTYPE_UNDEFINED\020\000" + "\022\024\n\020EVENTTYPE_CREATE\020\001\022\024\n\020EVENTTYPE_UPDA" + - "TE\020\002\022\024\n\020EVENTTYPE_REMOVE\020\003*\305\001\n\020DeviceDri" + + "TE\020\002\022\024\n\020EVENTTYPE_REMOVE\020\003*\332\001\n\020DeviceDri" + "verEnum\022\032\n\026DEVICEDRIVER_UNDEFINED\020\000\022\033\n\027D" + "EVICEDRIVER_OPENCONFIG\020\001\022\036\n\032DEVICEDRIVER" + "_TRANSPORT_API\020\002\022\023\n\017DEVICEDRIVER_P4\020\003\022&\n" + "\"DEVICEDRIVER_IETF_NETWORK_TOPOLOGY\020\004\022\033\n" + - "\027DEVICEDRIVER_ONF_TR_352\020\005*\217\001\n\033DeviceOpe" + - "rationalStatusEnum\022%\n!DEVICEOPERATIONALS" + - "TATUS_UNDEFINED\020\000\022$\n DEVICEOPERATIONALST" + - "ATUS_DISABLED\020\001\022#\n\037DEVICEOPERATIONALSTAT" + - "US_ENABLED\020\002*\201\001\n\017ServiceTypeEnum\022\027\n\023SERV" + - "ICETYPE_UNKNOWN\020\000\022\024\n\020SERVICETYPE_L3NM\020\001\022" + - "\024\n\020SERVICETYPE_L2NM\020\002\022)\n%SERVICETYPE_TAP" + - "I_CONNECTIVITY_SERVICE\020\003*\250\001\n\021ServiceStat" + - "usEnum\022\033\n\027SERVICESTATUS_UNDEFINED\020\000\022\031\n\025S" + - "ERVICESTATUS_PLANNED\020\001\022\030\n\024SERVICESTATUS_" + - "ACTIVE\020\002\022!\n\035SERVICESTATUS_PENDING_REMOVA" + - "L\020\003\022\036\n\032SERVICESTATUS_SLA_VIOLATED\020\004*\251\001\n\017" + - "SliceStatusEnum\022\031\n\025SLICESTATUS_UNDEFINED" + - "\020\000\022\027\n\023SLICESTATUS_PLANNED\020\001\022\024\n\020SLICESTAT" + - "US_INIT\020\002\022\026\n\022SLICESTATUS_ACTIVE\020\003\022\026\n\022SLI" + - "CESTATUS_DEINIT\020\004\022\034\n\030SLICESTATUS_SLA_VIO" + - "LATED\020\005*]\n\020ConfigActionEnum\022\032\n\026CONFIGACT" + - "ION_UNDEFINED\020\000\022\024\n\020CONFIGACTION_SET\020\001\022\027\n" + - "\023CONFIGACTION_DELETE\020\002*\203\002\n\022IsolationLeve" + - "lEnum\022\020\n\014NO_ISOLATION\020\000\022\026\n\022PHYSICAL_ISOL" + - "ATION\020\001\022\025\n\021LOGICAL_ISOLATION\020\002\022\025\n\021PROCES" + - "S_ISOLATION\020\003\022\035\n\031PHYSICAL_MEMORY_ISOLATI" + - "ON\020\004\022\036\n\032PHYSICAL_NETWORK_ISOLATION\020\005\022\036\n\032" + - "VIRTUAL_RESOURCE_ISOLATION\020\006\022\037\n\033NETWORK_" + - "FUNCTIONS_ISOLATION\020\007\022\025\n\021SERVICE_ISOLATI" + - "ON\020\0102\331\023\n\016ContextService\022:\n\016ListContextId" + - "s\022\016.context.Empty\032\026.context.ContextIdLis" + - "t\"\000\0226\n\014ListContexts\022\016.context.Empty\032\024.co" + - "ntext.ContextList\"\000\0224\n\nGetContext\022\022.cont" + - "ext.ContextId\032\020.context.Context\"\000\0224\n\nSet" + - "Context\022\020.context.Context\032\022.context.Cont" + - "extId\"\000\0225\n\rRemoveContext\022\022.context.Conte" + - "xtId\032\016.context.Empty\"\000\022=\n\020GetContextEven" + - "ts\022\016.context.Empty\032\025.context.ContextEven" + - "t\"\0000\001\022@\n\017ListTopologyIds\022\022.context.Conte" + - "xtId\032\027.context.TopologyIdList\"\000\022=\n\016ListT" + - "opologies\022\022.context.ContextId\032\025.context." + - "TopologyList\"\000\0227\n\013GetTopology\022\023.context." + - "TopologyId\032\021.context.Topology\"\000\0227\n\013SetTo" + - "pology\022\021.context.Topology\032\023.context.Topo" + - "logyId\"\000\0227\n\016RemoveTopology\022\023.context.Top" + - "ologyId\032\016.context.Empty\"\000\022?\n\021GetTopology" + - "Events\022\016.context.Empty\032\026.context.Topolog" + - "yEvent\"\0000\001\0228\n\rListDeviceIds\022\016.context.Em" + - "pty\032\025.context.DeviceIdList\"\000\0224\n\013ListDevi" + - "ces\022\016.context.Empty\032\023.context.DeviceList" + - "\"\000\0221\n\tGetDevice\022\021.context.DeviceId\032\017.con" + - "text.Device\"\000\0221\n\tSetDevice\022\017.context.Dev" + - "ice\032\021.context.DeviceId\"\000\0223\n\014RemoveDevice" + - "\022\021.context.DeviceId\032\016.context.Empty\"\000\022;\n" + - "\017GetDeviceEvents\022\016.context.Empty\032\024.conte" + - "xt.DeviceEvent\"\0000\001\0224\n\013ListLinkIds\022\016.cont" + - "ext.Empty\032\023.context.LinkIdList\"\000\0220\n\tList" + - "Links\022\016.context.Empty\032\021.context.LinkList" + - "\"\000\022+\n\007GetLink\022\017.context.LinkId\032\r.context" + - ".Link\"\000\022+\n\007SetLink\022\r.context.Link\032\017.cont" + - "ext.LinkId\"\000\022/\n\nRemoveLink\022\017.context.Lin" + - "kId\032\016.context.Empty\"\000\0227\n\rGetLinkEvents\022\016" + - ".context.Empty\032\022.context.LinkEvent\"\0000\001\022>" + - "\n\016ListServiceIds\022\022.context.ContextId\032\026.c" + - "ontext.ServiceIdList\"\000\022:\n\014ListServices\022\022" + - ".context.ContextId\032\024.context.ServiceList" + - "\"\000\0224\n\nGetService\022\022.context.ServiceId\032\020.c" + - "ontext.Service\"\000\0224\n\nSetService\022\020.context" + - ".Service\032\022.context.ServiceId\"\000\0226\n\014UnsetS" + - "ervice\022\020.context.Service\032\022.context.Servi" + - "ceId\"\000\0225\n\rRemoveService\022\022.context.Servic" + - "eId\032\016.context.Empty\"\000\022=\n\020GetServiceEvent" + - "s\022\016.context.Empty\032\025.context.ServiceEvent" + - "\"\0000\001\022:\n\014ListSliceIds\022\022.context.ContextId" + - "\032\024.context.SliceIdList\"\000\0226\n\nListSlices\022\022" + - ".context.ContextId\032\022.context.SliceList\"\000" + - "\022.\n\010GetSlice\022\020.context.SliceId\032\016.context" + - ".Slice\"\000\022.\n\010SetSlice\022\016.context.Slice\032\020.c" + - "ontext.SliceId\"\000\0220\n\nUnsetSlice\022\016.context" + - ".Slice\032\020.context.SliceId\"\000\0221\n\013RemoveSlic" + - "e\022\020.context.SliceId\032\016.context.Empty\"\000\0229\n" + - "\016GetSliceEvents\022\016.context.Empty\032\023.contex" + - "t.SliceEvent\"\0000\001\022D\n\021ListConnectionIds\022\022." + - "context.ServiceId\032\031.context.ConnectionId" + - "List\"\000\022@\n\017ListConnections\022\022.context.Serv" + - "iceId\032\027.context.ConnectionList\"\000\022=\n\rGetC" + - "onnection\022\025.context.ConnectionId\032\023.conte" + - "xt.Connection\"\000\022=\n\rSetConnection\022\023.conte" + - "xt.Connection\032\025.context.ConnectionId\"\000\022;" + - "\n\020RemoveConnection\022\025.context.ConnectionI" + - "d\032\016.context.Empty\"\000\022C\n\023GetConnectionEven" + - "ts\022\016.context.Empty\032\030.context.ConnectionE" + - "vent\"\0000\001b\006proto3" + "\027DEVICEDRIVER_ONF_TR_352\020\005\022\023\n\017DEVICEDRIV" + + "ER_XR\020\006*\217\001\n\033DeviceOperationalStatusEnum\022" + + "%\n!DEVICEOPERATIONALSTATUS_UNDEFINED\020\000\022$" + + "\n DEVICEOPERATIONALSTATUS_DISABLED\020\001\022#\n\037" + + "DEVICEOPERATIONALSTATUS_ENABLED\020\002*\201\001\n\017Se" + + "rviceTypeEnum\022\027\n\023SERVICETYPE_UNKNOWN\020\000\022\024" + + "\n\020SERVICETYPE_L3NM\020\001\022\024\n\020SERVICETYPE_L2NM" + + "\020\002\022)\n%SERVICETYPE_TAPI_CONNECTIVITY_SERV" + + "ICE\020\003*\250\001\n\021ServiceStatusEnum\022\033\n\027SERVICEST" + + "ATUS_UNDEFINED\020\000\022\031\n\025SERVICESTATUS_PLANNE" + + "D\020\001\022\030\n\024SERVICESTATUS_ACTIVE\020\002\022!\n\035SERVICE" + + "STATUS_PENDING_REMOVAL\020\003\022\036\n\032SERVICESTATU" + + "S_SLA_VIOLATED\020\004*\251\001\n\017SliceStatusEnum\022\031\n\025" + + "SLICESTATUS_UNDEFINED\020\000\022\027\n\023SLICESTATUS_P" + + "LANNED\020\001\022\024\n\020SLICESTATUS_INIT\020\002\022\026\n\022SLICES" + + "TATUS_ACTIVE\020\003\022\026\n\022SLICESTATUS_DEINIT\020\004\022\034" + + "\n\030SLICESTATUS_SLA_VIOLATED\020\005*]\n\020ConfigAc" + + "tionEnum\022\032\n\026CONFIGACTION_UNDEFINED\020\000\022\024\n\020" + + "CONFIGACTION_SET\020\001\022\027\n\023CONFIGACTION_DELET" + + "E\020\002*\203\002\n\022IsolationLevelEnum\022\020\n\014NO_ISOLATI" + + "ON\020\000\022\026\n\022PHYSICAL_ISOLATION\020\001\022\025\n\021LOGICAL_" + + "ISOLATION\020\002\022\025\n\021PROCESS_ISOLATION\020\003\022\035\n\031PH" + + "YSICAL_MEMORY_ISOLATION\020\004\022\036\n\032PHYSICAL_NE" + + "TWORK_ISOLATION\020\005\022\036\n\032VIRTUAL_RESOURCE_IS" + + "OLATION\020\006\022\037\n\033NETWORK_FUNCTIONS_ISOLATION" + + "\020\007\022\025\n\021SERVICE_ISOLATION\020\0102\331\023\n\016ContextSer" + + "vice\022:\n\016ListContextIds\022\016.context.Empty\032\026" + + ".context.ContextIdList\"\000\0226\n\014ListContexts" + + "\022\016.context.Empty\032\024.context.ContextList\"\000" + + "\0224\n\nGetContext\022\022.context.ContextId\032\020.con" + + "text.Context\"\000\0224\n\nSetContext\022\020.context.C" + + "ontext\032\022.context.ContextId\"\000\0225\n\rRemoveCo" + + "ntext\022\022.context.ContextId\032\016.context.Empt" + + "y\"\000\022=\n\020GetContextEvents\022\016.context.Empty\032" + + "\025.context.ContextEvent\"\0000\001\022@\n\017ListTopolo" + + "gyIds\022\022.context.ContextId\032\027.context.Topo" + + "logyIdList\"\000\022=\n\016ListTopologies\022\022.context" + + ".ContextId\032\025.context.TopologyList\"\000\0227\n\013G" + + "etTopology\022\023.context.TopologyId\032\021.contex" + + "t.Topology\"\000\0227\n\013SetTopology\022\021.context.To" + + "pology\032\023.context.TopologyId\"\000\0227\n\016RemoveT" + + "opology\022\023.context.TopologyId\032\016.context.E" + + "mpty\"\000\022?\n\021GetTopologyEvents\022\016.context.Em" + + "pty\032\026.context.TopologyEvent\"\0000\001\0228\n\rListD" + + "eviceIds\022\016.context.Empty\032\025.context.Devic" + + "eIdList\"\000\0224\n\013ListDevices\022\016.context.Empty" + + "\032\023.context.DeviceList\"\000\0221\n\tGetDevice\022\021.c" + + "ontext.DeviceId\032\017.context.Device\"\000\0221\n\tSe" + + "tDevice\022\017.context.Device\032\021.context.Devic" + + "eId\"\000\0223\n\014RemoveDevice\022\021.context.DeviceId" + + "\032\016.context.Empty\"\000\022;\n\017GetDeviceEvents\022\016." + + "context.Empty\032\024.context.DeviceEvent\"\0000\001\022" + + "4\n\013ListLinkIds\022\016.context.Empty\032\023.context" + + ".LinkIdList\"\000\0220\n\tListLinks\022\016.context.Emp" + + "ty\032\021.context.LinkList\"\000\022+\n\007GetLink\022\017.con" + + "text.LinkId\032\r.context.Link\"\000\022+\n\007SetLink\022" + + "\r.context.Link\032\017.context.LinkId\"\000\022/\n\nRem" + + "oveLink\022\017.context.LinkId\032\016.context.Empty" + + "\"\000\0227\n\rGetLinkEvents\022\016.context.Empty\032\022.co" + + "ntext.LinkEvent\"\0000\001\022>\n\016ListServiceIds\022\022." + + "context.ContextId\032\026.context.ServiceIdLis" + + "t\"\000\022:\n\014ListServices\022\022.context.ContextId\032" + + "\024.context.ServiceList\"\000\0224\n\nGetService\022\022." + + "context.ServiceId\032\020.context.Service\"\000\0224\n" + + "\nSetService\022\020.context.Service\032\022.context." + + "ServiceId\"\000\0226\n\014UnsetService\022\020.context.Se" + + "rvice\032\022.context.ServiceId\"\000\0225\n\rRemoveSer" + + "vice\022\022.context.ServiceId\032\016.context.Empty" + + "\"\000\022=\n\020GetServiceEvents\022\016.context.Empty\032\025" + + ".context.ServiceEvent\"\0000\001\022:\n\014ListSliceId" + + "s\022\022.context.ContextId\032\024.context.SliceIdL" + + "ist\"\000\0226\n\nListSlices\022\022.context.ContextId\032" + + "\022.context.SliceList\"\000\022.\n\010GetSlice\022\020.cont" + + "ext.SliceId\032\016.context.Slice\"\000\022.\n\010SetSlic" + + "e\022\016.context.Slice\032\020.context.SliceId\"\000\0220\n" + + "\nUnsetSlice\022\016.context.Slice\032\020.context.Sl" + + "iceId\"\000\0221\n\013RemoveSlice\022\020.context.SliceId" + + "\032\016.context.Empty\"\000\0229\n\016GetSliceEvents\022\016.c" + + "ontext.Empty\032\023.context.SliceEvent\"\0000\001\022D\n" + + "\021ListConnectionIds\022\022.context.ServiceId\032\031" + + ".context.ConnectionIdList\"\000\022@\n\017ListConne" + + "ctions\022\022.context.ServiceId\032\027.context.Con" + + "nectionList\"\000\022=\n\rGetConnection\022\025.context" + + ".ConnectionId\032\023.context.Connection\"\000\022=\n\r" + + "SetConnection\022\023.context.Connection\032\025.con" + + "text.ConnectionId\"\000\022;\n\020RemoveConnection\022" + + "\025.context.ConnectionId\032\016.context.Empty\"\000" + + "\022C\n\023GetConnectionEvents\022\016.context.Empty\032" + + "\030.context.ConnectionEvent\"\0000\001b\006proto3" }; descriptor = com.google.protobuf.Descriptors.FileDescriptor .internalBuildGeneratedFileFrom(descriptorData, diff --git a/src/service/service/service_handler_api/FilterFields.py b/src/service/service/service_handler_api/FilterFields.py index 98113ba30fb095a29a2142e592b7759d2634eab9..0f21812089e2af8271884ef7539f979ff0426a5a 100644 --- a/src/service/service/service_handler_api/FilterFields.py +++ b/src/service/service/service_handler_api/FilterFields.py @@ -33,6 +33,7 @@ DEVICE_DRIVER_VALUES = { DeviceDriverEnum.DEVICEDRIVER_P4, DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY, DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352, + DeviceDriverEnum.DEVICEDRIVER_XR } # Map allowed filter fields to allowed values per Filter field. If no restriction (free text) None is specified diff --git a/src/service/service/service_handlers/__init__.py b/src/service/service/service_handlers/__init__.py index c6cb589d5864a3c59d158a2f0e03c5718f21ae87..34689ca1136c68611a098115b5acf5b74a788372 100644 --- a/src/service/service/service_handlers/__init__.py +++ b/src/service/service/service_handlers/__init__.py @@ -42,7 +42,7 @@ SERVICE_HANDLERS = [ (TapiServiceHandler, [ { FilterFieldEnum.SERVICE_TYPE : ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE, - FilterFieldEnum.DEVICE_DRIVER : DeviceDriverEnum.DEVICEDRIVER_TRANSPORT_API, + FilterFieldEnum.DEVICE_DRIVER : [DeviceDriverEnum.DEVICEDRIVER_TRANSPORT_API, DeviceDriverEnum.DEVICEDRIVER_XR], } ]), (MicrowaveServiceHandler, [ @@ -51,4 +51,4 @@ SERVICE_HANDLERS = [ FilterFieldEnum.DEVICE_DRIVER : DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY, } ]), -] +] \ No newline at end of file diff --git a/src/tests/ofc22/descriptors_emulated_xr.json b/src/tests/ofc22/descriptors_emulated_xr.json new file mode 100644 index 0000000000000000000000000000000000000000..30bd97dddeb94f836d3fe66e51fce729c34ceced --- /dev/null +++ b/src/tests/ofc22/descriptors_emulated_xr.json @@ -0,0 +1,108 @@ +{ + "contexts": [ + { + "context_id": {"context_uuid": {"uuid": "admin"}}, + "topology_ids": [], + "service_ids": [] + } + ], + "topologies": [ + { + "topology_id": {"topology_uuid": {"uuid": "admin"}, "context_id": {"context_uuid": {"uuid": "admin"}}}, + "device_ids": [], + "link_ids": [] + } + ], + "devices": [ + { + "device_id": {"device_uuid": {"uuid": "R1-EMU"}}, + "device_type": "emu-packet-router", + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": "{\"endpoints\": [{\"sample_types\": [], \"type\": \"optical\", \"uuid\": \"13/0/0\"}, {\"sample_types\": [101, 102, 201, 202], \"type\": \"copper\", \"uuid\": \"13/1/2\"}]}"}} + ]}, + "device_operational_status": 1, + "device_drivers": [0], + "device_endpoints": [] + }, + { + "device_id": {"device_uuid": {"uuid": "R2-EMU"}}, + "device_type": "emu-packet-router", + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": "{\"endpoints\": [{\"sample_types\": [], \"type\": \"optical\", \"uuid\": \"13/0/0\"}, {\"sample_types\": [101, 102, 201, 202], \"type\": \"copper\", \"uuid\": \"13/1/2\"}]}"}} + ]}, + "device_operational_status": 1, + "device_drivers": [0], + "device_endpoints": [] + }, + { + "device_id": {"device_uuid": {"uuid": "R3-EMU"}}, + "device_type": "emu-packet-router", + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": "{\"endpoints\": [{\"sample_types\": [], \"type\": \"optical\", \"uuid\": \"13/0/0\"}, {\"sample_types\": [101, 102, 201, 202], \"type\": \"copper\", \"uuid\": \"13/1/2\"}]}"}} + ]}, + "device_operational_status": 1, + "device_drivers": [0], + "device_endpoints": [] + }, + { + "device_id": {"device_uuid": {"uuid": "R4-EMU"}}, + "device_type": "emu-packet-router", + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": "{\"endpoints\": [{\"sample_types\": [], \"type\": \"optical\", \"uuid\": \"13/0/0\"}, {\"sample_types\": [101, 102, 201, 202], \"type\": \"copper\", \"uuid\": \"13/1/2\"}]}"}} + ]}, + "device_operational_status": 1, + "device_drivers": [0], + "device_endpoints": [] + }, + { + "device_id": {"device_uuid": {"uuid": "X1-XR-CONSTELLATION"}}, + "device_type": "xr-constellation", + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "172.19.219.44"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "443"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": "{\"username\": \"xr-user-1\", \"password\": \"xr-user-1\", \"hub_module_name\": \"XR HUB 1\"}"}} + ]}, + "device_operational_status": 1, + "device_drivers": [6], + "device_endpoints": [] + } + ], + "links": [ + { + "link_id": {"link_uuid": {"uuid": "R1-EMU/13/0/0==XR HUB 1|XR-T4"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R1-EMU"}}, "endpoint_uuid": {"uuid": "13/0/0"}}, + {"device_id": {"device_uuid": {"uuid": "X1-XR-CONSTELLATION"}}, "endpoint_uuid": {"uuid": "XR HUB 1|XR-T4"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R2-EMU/13/0/0==XR HUB 1|XR-T3"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R2-EMU"}}, "endpoint_uuid": {"uuid": "13/0/0"}}, + {"device_id": {"device_uuid": {"uuid": "X1-XR-CONSTELLATION"}}, "endpoint_uuid": {"uuid": "XR HUB 1|XR-T3"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R3-EMU/13/0/0==XR1-XR LEAF 1|XR-T1"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R3-EMU"}}, "endpoint_uuid": {"uuid": "13/0/0"}}, + {"device_id": {"device_uuid": {"uuid": "X1-XR-CONSTELLATION"}}, "endpoint_uuid": {"uuid": "XR LEAF 1|XR-T1"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R4-EMU/13/0/0==XR LEAF 2|XR-T1"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R4-EMU"}}, "endpoint_uuid": {"uuid": "13/0/0"}}, + {"device_id": {"device_uuid": {"uuid": "X1-XR-CONSTELLATION"}}, "endpoint_uuid": {"uuid": "XR LEAF 2|XR-T1"}} + ] + } + ] +} diff --git a/src/tests/ofc22/setup_test_env.sh b/src/tests/ofc22/setup_test_env.sh new file mode 100755 index 0000000000000000000000000000000000000000..1f8b0a5a7a8dc986715c6f54a62151f6afa4ad80 --- /dev/null +++ b/src/tests/ofc22/setup_test_env.sh @@ -0,0 +1,9 @@ +#!/bin/sh +export CONTEXTSERVICE_SERVICE_HOST=$(kubectl get service/contextservice --namespace tfs --template '{{.spec.clusterIP}}') +export CONTEXTSERVICE_SERVICE_PORT_GRPC=$(kubectl get service/contextservice --namespace tfs -o jsonpath='{.spec.ports[?(@.name=="grpc")].port}') +export COMPUTESERVICE_SERVICE_HOST=$(kubectl get service/computeservice --namespace tfs --template '{{.spec.clusterIP}}') +export COMPUTESERVICE_SERVICE_PORT_HTTP=$(kubectl get service/computeservice --namespace tfs -o jsonpath='{.spec.ports[?(@.name=="http")].port}') +echo "CONTEXTSERVICE_SERVICE_HOST=$CONTEXTSERVICE_SERVICE_HOST" +echo "CONTEXTSERVICE_SERVICE_PORT_GRPC=$CONTEXTSERVICE_SERVICE_PORT_GRPC" +echo "COMPUTESERVICE_SERVICE_HOST=$COMPUTESERVICE_SERVICE_HOST" +echo "COMPUTESERVICE_SERVICE_PORT_HTTP=$COMPUTESERVICE_SERVICE_PORT_HTTP" diff --git a/src/tests/ofc22/tests/ObjectsXr.py b/src/tests/ofc22/tests/ObjectsXr.py new file mode 100644 index 0000000000000000000000000000000000000000..0cb223de2ede509443275496ba9ca57158335036 --- /dev/null +++ b/src/tests/ofc22/tests/ObjectsXr.py @@ -0,0 +1,238 @@ +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Dict, List, Tuple +from common.Constants import DEFAULT_CONTEXT_UUID, DEFAULT_TOPOLOGY_UUID +from common.tools.object_factory.Context import json_context, json_context_id +from common.tools.object_factory.Device import ( + json_device_connect_rules, json_device_emulated_connect_rules, json_device_emulated_packet_router_disabled, + json_device_emulated_tapi_disabled, json_device_id, json_device_packetrouter_disabled, json_device_tapi_disabled) +from common.tools.object_factory.EndPoint import json_endpoint, json_endpoint_id +from common.tools.object_factory.Link import json_link, json_link_id +from common.tools.object_factory.Topology import json_topology, json_topology_id +from common.proto.kpi_sample_types_pb2 import KpiSampleType + +# ----- Context -------------------------------------------------------------------------------------------------------- +CONTEXT_ID = json_context_id(DEFAULT_CONTEXT_UUID) +CONTEXT = json_context(DEFAULT_CONTEXT_UUID) + +# ----- Topology ------------------------------------------------------------------------------------------------------- +TOPOLOGY_ID = json_topology_id(DEFAULT_TOPOLOGY_UUID, context_id=CONTEXT_ID) +TOPOLOGY = json_topology(DEFAULT_TOPOLOGY_UUID, context_id=CONTEXT_ID) + +# ----- Monitoring Samples --------------------------------------------------------------------------------------------- +PACKET_PORT_SAMPLE_TYPES = [ + KpiSampleType.KPISAMPLETYPE_PACKETS_TRANSMITTED, + KpiSampleType.KPISAMPLETYPE_PACKETS_RECEIVED, + KpiSampleType.KPISAMPLETYPE_BYTES_TRANSMITTED, + KpiSampleType.KPISAMPLETYPE_BYTES_RECEIVED, +] + +# ----- Device Credentials and Settings -------------------------------------------------------------------------------- +try: + from .Credentials import DEVICE_R1_ADDRESS, DEVICE_R1_PORT, DEVICE_R1_USERNAME, DEVICE_R1_PASSWORD + from .Credentials import DEVICE_R3_ADDRESS, DEVICE_R3_PORT, DEVICE_R3_USERNAME, DEVICE_R3_PASSWORD + #from .Credentials import DEVICE_O1_ADDRESS, DEVICE_O1_PORT + USE_REAL_DEVICES = True # Use real devices +except ImportError: + USE_REAL_DEVICES = False # Use emulated devices + + DEVICE_R1_ADDRESS = '0.0.0.0' + DEVICE_R1_PORT = 830 + DEVICE_R1_USERNAME = 'admin' + DEVICE_R1_PASSWORD = 'admin' + + DEVICE_R3_ADDRESS = '0.0.0.0' + DEVICE_R3_PORT = 830 + DEVICE_R3_USERNAME = 'admin' + DEVICE_R3_PASSWORD = 'admin' + +DEVICE_X1_ADDRESS = '172.19.219.44' +DEVICE_X1_PORT = 443 + +#USE_REAL_DEVICES = False # Uncomment to force to use emulated devices + +def json_endpoint_ids(device_id : Dict, endpoint_descriptors : List[Tuple[str, str, List[int]]]): + return [ + json_endpoint_id(device_id, ep_uuid, topology_id=None) + for ep_uuid, _, _ in endpoint_descriptors + ] + +def json_endpoints(device_id : Dict, endpoint_descriptors : List[Tuple[str, str, List[int]]]): + return [ + json_endpoint(device_id, ep_uuid, ep_type, topology_id=None, kpi_sample_types=ep_sample_types) + for ep_uuid, ep_type, ep_sample_types in endpoint_descriptors + ] + +def get_link_uuid(a_device_id : Dict, a_endpoint_id : Dict, z_device_id : Dict, z_endpoint_id : Dict) -> str: + return '{:s}/{:s}=={:s}/{:s}'.format( + a_device_id['device_uuid']['uuid'], a_endpoint_id['endpoint_uuid']['uuid'], + z_device_id['device_uuid']['uuid'], z_endpoint_id['endpoint_uuid']['uuid']) + + +# ----- Devices -------------------------------------------------------------------------------------------------------- +if not USE_REAL_DEVICES: + json_device_packetrouter_disabled = json_device_emulated_packet_router_disabled + json_device_tapi_disabled = json_device_emulated_tapi_disabled + +DEVICE_R1_UUID = 'R1-EMU' +DEVICE_R1_TIMEOUT = 120 +DEVICE_R1_ENDPOINT_DEFS = [('13/0/0', 'optical', []), ('13/1/2', 'copper', PACKET_PORT_SAMPLE_TYPES)] +DEVICE_R1_ID = json_device_id(DEVICE_R1_UUID) +#DEVICE_R1_ENDPOINTS = json_endpoints(DEVICE_R1_ID, DEVICE_R1_ENDPOINT_DEFS) +DEVICE_R1_ENDPOINT_IDS = json_endpoint_ids(DEVICE_R1_ID, DEVICE_R1_ENDPOINT_DEFS) +DEVICE_R1 = json_device_packetrouter_disabled(DEVICE_R1_UUID) +ENDPOINT_ID_R1_13_0_0 = DEVICE_R1_ENDPOINT_IDS[0] +ENDPOINT_ID_R1_13_1_2 = DEVICE_R1_ENDPOINT_IDS[1] +DEVICE_R1_CONNECT_RULES = json_device_connect_rules(DEVICE_R1_ADDRESS, DEVICE_R1_PORT, { + 'username': DEVICE_R1_USERNAME, + 'password': DEVICE_R1_PASSWORD, + 'timeout' : DEVICE_R1_TIMEOUT, +}) if USE_REAL_DEVICES else json_device_emulated_connect_rules(DEVICE_R1_ENDPOINT_DEFS) + + +DEVICE_R2_UUID = 'R2-EMU' +DEVICE_R2_ENDPOINT_DEFS = [('13/0/0', 'optical', []), ('13/1/2', 'copper', PACKET_PORT_SAMPLE_TYPES)] +DEVICE_R2_ID = json_device_id(DEVICE_R2_UUID) +#DEVICE_R2_ENDPOINTS = json_endpoints(DEVICE_R2_ID, DEVICE_R2_ENDPOINT_DEFS) +DEVICE_R2_ENDPOINT_IDS = json_endpoint_ids(DEVICE_R2_ID, DEVICE_R2_ENDPOINT_DEFS) +DEVICE_R2 = json_device_emulated_packet_router_disabled(DEVICE_R2_UUID) +ENDPOINT_ID_R2_13_0_0 = DEVICE_R2_ENDPOINT_IDS[0] +ENDPOINT_ID_R2_13_1_2 = DEVICE_R2_ENDPOINT_IDS[1] +DEVICE_R2_CONNECT_RULES = json_device_emulated_connect_rules(DEVICE_R2_ENDPOINT_DEFS) + + +DEVICE_R3_UUID = 'R3-EMU' +DEVICE_R3_TIMEOUT = 120 +DEVICE_R3_ENDPOINT_DEFS = [('13/0/0', 'optical', []), ('13/1/2', 'copper', PACKET_PORT_SAMPLE_TYPES)] +DEVICE_R3_ID = json_device_id(DEVICE_R3_UUID) +#DEVICE_R3_ENDPOINTS = json_endpoints(DEVICE_R3_ID, DEVICE_R3_ENDPOINT_DEFS) +DEVICE_R3_ENDPOINT_IDS = json_endpoint_ids(DEVICE_R3_ID, DEVICE_R3_ENDPOINT_DEFS) +DEVICE_R3 = json_device_packetrouter_disabled(DEVICE_R3_UUID) +ENDPOINT_ID_R3_13_0_0 = DEVICE_R3_ENDPOINT_IDS[0] +ENDPOINT_ID_R3_13_1_2 = DEVICE_R3_ENDPOINT_IDS[1] +DEVICE_R3_CONNECT_RULES = json_device_connect_rules(DEVICE_R3_ADDRESS, DEVICE_R3_PORT, { + 'username': DEVICE_R3_USERNAME, + 'password': DEVICE_R3_PASSWORD, + 'timeout' : DEVICE_R3_TIMEOUT, +}) if USE_REAL_DEVICES else json_device_emulated_connect_rules(DEVICE_R3_ENDPOINT_DEFS) + + +DEVICE_R4_UUID = 'R4-EMU' +DEVICE_R4_ENDPOINT_DEFS = [('13/0/0', 'optical', []), ('13/1/2', 'copper', PACKET_PORT_SAMPLE_TYPES)] +DEVICE_R4_ID = json_device_id(DEVICE_R4_UUID) +#DEVICE_R4_ENDPOINTS = json_endpoints(DEVICE_R4_ID, DEVICE_R4_ENDPOINT_DEFS) +DEVICE_R4_ENDPOINT_IDS = json_endpoint_ids(DEVICE_R4_ID, DEVICE_R4_ENDPOINT_DEFS) +DEVICE_R4 = json_device_emulated_packet_router_disabled(DEVICE_R4_UUID) +ENDPOINT_ID_R4_13_0_0 = DEVICE_R4_ENDPOINT_IDS[0] +ENDPOINT_ID_R4_13_1_2 = DEVICE_R4_ENDPOINT_IDS[1] +DEVICE_R4_CONNECT_RULES = json_device_emulated_connect_rules(DEVICE_R4_ENDPOINT_DEFS) + + +DEVICE_X1_UUID = 'X1-XR-CONSTELLATION' +DEVICE_X1_TIMEOUT = 120 +DEVICE_X1_ENDPOINT_DEFS = [ + ('XR HUB 1|XR-T1', 'optical', []), + ('XR HUB 1|XR-T2', 'optical', []), + ('XR HUB 1|XR-T3', 'optical', []), + ('XR HUB 1|XR-T4', 'optical', []), + ('XR LEAF 1|XR-T1', 'optical', []), + ('XR LEAF 2|XR-T1', 'optical', []), +] +DEVICE_X1_ID = json_device_id(DEVICE_X1_UUID) +DEVICE_X1 = json_device_tapi_disabled(DEVICE_X1_UUID) +DEVICE_X1_ENDPOINT_IDS = json_endpoint_ids(DEVICE_X1_ID, DEVICE_X1_ENDPOINT_DEFS) +# These match JSON, hence indexes are what theyt are +ENDPOINT_ID_X1_EP1 = DEVICE_X1_ENDPOINT_IDS[3] +ENDPOINT_ID_X1_EP2 = DEVICE_X1_ENDPOINT_IDS[2] +ENDPOINT_ID_X1_EP3 = DEVICE_X1_ENDPOINT_IDS[4] +ENDPOINT_ID_X1_EP4 = DEVICE_X1_ENDPOINT_IDS[5] +DEVICE_X1_CONNECT_RULES = json_device_connect_rules(DEVICE_X1_ADDRESS, DEVICE_X1_PORT, { + 'timeout' : DEVICE_X1_TIMEOUT, + "username": "xr-user-1", + "password": "xr-user-1", + "hub_module_name": "XR HUB 1" +}) +# Always using real device (CM, whether CM has emulated backend is another story) +#if USE_REAL_DEVICES else json_device_emulated_connect_rules(DEVICE_X1_ENDPOINT_DEFS) + + +# ----- Links ---------------------------------------------------------------------------------------------------------- +LINK_R1_X1_UUID = get_link_uuid(DEVICE_R1_ID, ENDPOINT_ID_R1_13_0_0, DEVICE_X1_ID, ENDPOINT_ID_X1_EP1) +LINK_R1_X1_ID = json_link_id(LINK_R1_X1_UUID) +LINK_R1_X1 = json_link(LINK_R1_X1_UUID, [ENDPOINT_ID_R1_13_0_0, ENDPOINT_ID_X1_EP1]) + +LINK_R2_X1_UUID = get_link_uuid(DEVICE_R2_ID, ENDPOINT_ID_R2_13_0_0, DEVICE_X1_ID, ENDPOINT_ID_X1_EP2) +LINK_R2_X1_ID = json_link_id(LINK_R2_X1_UUID) +LINK_R2_X1 = json_link(LINK_R2_X1_UUID, [ENDPOINT_ID_R2_13_0_0, ENDPOINT_ID_X1_EP2]) + +LINK_R3_X1_UUID = get_link_uuid(DEVICE_R3_ID, ENDPOINT_ID_R3_13_0_0, DEVICE_X1_ID, ENDPOINT_ID_X1_EP3) +LINK_R3_X1_ID = json_link_id(LINK_R3_X1_UUID) +LINK_R3_X1 = json_link(LINK_R3_X1_UUID, [ENDPOINT_ID_R3_13_0_0, ENDPOINT_ID_X1_EP3]) + +LINK_R4_X1_UUID = get_link_uuid(DEVICE_R4_ID, ENDPOINT_ID_R4_13_0_0, DEVICE_X1_ID, ENDPOINT_ID_X1_EP4) +LINK_R4_X1_ID = json_link_id(LINK_R4_X1_UUID) +LINK_R4_X1 = json_link(LINK_R4_X1_UUID, [ENDPOINT_ID_R4_13_0_0, ENDPOINT_ID_X1_EP4]) + + +# ----- WIM Service Settings ------------------------------------------------------------------------------------------- + +def compose_service_endpoint_id(endpoint_id): + device_uuid = endpoint_id['device_id']['device_uuid']['uuid'] + endpoint_uuid = endpoint_id['endpoint_uuid']['uuid'] + return ':'.join([device_uuid, endpoint_uuid]) + +WIM_SEP_R1_ID = compose_service_endpoint_id(ENDPOINT_ID_R1_13_1_2) +WIM_SEP_R1_SITE_ID = '1' +WIM_SEP_R1_BEARER = WIM_SEP_R1_ID +WIM_SRV_R1_VLAN_ID = 400 + +WIM_SEP_R3_ID = compose_service_endpoint_id(ENDPOINT_ID_R3_13_1_2) +WIM_SEP_R3_SITE_ID = '2' +WIM_SEP_R3_BEARER = WIM_SEP_R3_ID +WIM_SRV_R3_VLAN_ID = 500 + +WIM_USERNAME = 'admin' +WIM_PASSWORD = 'admin' + +WIM_MAPPING = [ + {'device-id': DEVICE_R1_UUID, 'service_endpoint_id': WIM_SEP_R1_ID, + 'service_mapping_info': {'bearer': {'bearer-reference': WIM_SEP_R1_BEARER}, 'site-id': WIM_SEP_R1_SITE_ID}}, + {'device-id': DEVICE_R3_UUID, 'service_endpoint_id': WIM_SEP_R3_ID, + 'service_mapping_info': {'bearer': {'bearer-reference': WIM_SEP_R3_BEARER}, 'site-id': WIM_SEP_R3_SITE_ID}}, +] +WIM_SERVICE_TYPE = 'ELINE' +WIM_SERVICE_CONNECTION_POINTS = [ + {'service_endpoint_id': WIM_SEP_R1_ID, + 'service_endpoint_encapsulation_type': 'dot1q', + 'service_endpoint_encapsulation_info': {'vlan': WIM_SRV_R1_VLAN_ID}}, + {'service_endpoint_id': WIM_SEP_R3_ID, + 'service_endpoint_encapsulation_type': 'dot1q', + 'service_endpoint_encapsulation_info': {'vlan': WIM_SRV_R3_VLAN_ID}}, +] + +# ----- Object Collections --------------------------------------------------------------------------------------------- + +CONTEXTS = [CONTEXT] +TOPOLOGIES = [TOPOLOGY] + +DEVICES = [ + (DEVICE_R1, DEVICE_R1_CONNECT_RULES), + (DEVICE_R2, DEVICE_R2_CONNECT_RULES), + (DEVICE_R3, DEVICE_R3_CONNECT_RULES), + (DEVICE_R4, DEVICE_R4_CONNECT_RULES), + (DEVICE_X1, DEVICE_X1_CONNECT_RULES), +] + +LINKS = [LINK_R1_X1, LINK_R2_X1, LINK_R3_X1, LINK_R4_X1] diff --git a/src/tests/ofc22/tests/test_functional_create_service_xr.py b/src/tests/ofc22/tests/test_functional_create_service_xr.py new file mode 100644 index 0000000000000000000000000000000000000000..bb78abc1efe7701308448ad4b83ef2a6e32079c4 --- /dev/null +++ b/src/tests/ofc22/tests/test_functional_create_service_xr.py @@ -0,0 +1,129 @@ +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging, pytest +from common.DeviceTypes import DeviceTypeEnum +from common.Settings import get_setting +from common.tests.EventTools import EVENT_CREATE, EVENT_UPDATE, check_events +from common.tools.object_factory.Connection import json_connection_id +from common.tools.object_factory.Device import json_device_id +from common.tools.object_factory.Service import json_service_id +from common.tools.grpc.Tools import grpc_message_to_json_string +from compute.tests.mock_osm.MockOSM import MockOSM +from context.client.ContextClient import ContextClient +from context.client.EventsCollector import EventsCollector +from common.proto.context_pb2 import ContextId, Empty +from .ObjectsXr import ( + CONTEXT_ID, CONTEXTS, DEVICE_X1_UUID, DEVICE_R1_UUID, DEVICE_R3_UUID, DEVICES, LINKS, TOPOLOGIES, + WIM_MAPPING, WIM_PASSWORD, WIM_SERVICE_CONNECTION_POINTS, WIM_SERVICE_TYPE, WIM_USERNAME) + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +DEVTYPE_EMU_PR = DeviceTypeEnum.EMULATED_PACKET_ROUTER.value +DEVTYPE_XR_CONSTELLATION = DeviceTypeEnum.XR_CONSTELLATION.value + + +@pytest.fixture(scope='session') +def context_client(): + _client = ContextClient(get_setting('CONTEXTSERVICE_SERVICE_HOST'), get_setting('CONTEXTSERVICE_SERVICE_PORT_GRPC')) + yield _client + _client.close() + + +@pytest.fixture(scope='session') +def osm_wim(): + wim_url = 'http://{:s}:{:s}'.format( + get_setting('COMPUTESERVICE_SERVICE_HOST'), str(get_setting('COMPUTESERVICE_SERVICE_PORT_HTTP'))) + return MockOSM(wim_url, WIM_MAPPING, WIM_USERNAME, WIM_PASSWORD) + + +def test_scenario_is_correct(context_client : ContextClient): # pylint: disable=redefined-outer-name + # ----- List entities - Ensure links are created ------------------------------------------------------------------- + response = context_client.ListContexts(Empty()) + assert len(response.contexts) == len(CONTEXTS) + + response = context_client.ListTopologies(ContextId(**CONTEXT_ID)) + assert len(response.topologies) == len(TOPOLOGIES) + + response = context_client.ListDevices(Empty()) + assert len(response.devices) == len(DEVICES) + + response = context_client.ListLinks(Empty()) + assert len(response.links) == len(LINKS) + + response = context_client.ListServices(ContextId(**CONTEXT_ID)) + assert len(response.services) == 0 + + +def test_service_creation(context_client : ContextClient, osm_wim : MockOSM): # pylint: disable=redefined-outer-name + # ----- Start the EventsCollector ---------------------------------------------------------------------------------- + # events_collector = EventsCollector(context_client, log_events_received=True) + # events_collector.start() + + # ----- Create Service --------------------------------------------------------------------------------------------- + service_uuid = osm_wim.create_connectivity_service(WIM_SERVICE_TYPE, WIM_SERVICE_CONNECTION_POINTS) + osm_wim.get_connectivity_service_status(service_uuid) + + # ----- Validate collected events ---------------------------------------------------------------------------------- + + # packet_connection_uuid = '{:s}:{:s}'.format(service_uuid, DEVTYPE_EMU_PR) + # optical_connection_uuid = '{:s}:optical:{:s}'.format(service_uuid, DEVTYPE_XR_CONSTELLATION) + # optical_service_uuid = '{:s}:optical'.format(service_uuid) + + # expected_events = [ + # # Create packet service and add first endpoint + # ('ServiceEvent', EVENT_CREATE, json_service_id(service_uuid, context_id=CONTEXT_ID)), + # ('ServiceEvent', EVENT_UPDATE, json_service_id(service_uuid, context_id=CONTEXT_ID)), + + # # Configure OLS controller, create optical service, create optical connection + # ('DeviceEvent', EVENT_UPDATE, json_device_id(DEVICE_X1_UUID)), + # ('ServiceEvent', EVENT_CREATE, json_service_id(optical_service_uuid, context_id=CONTEXT_ID)), + # ('ConnectionEvent', EVENT_CREATE, json_connection_id(optical_connection_uuid)), + + # # Configure endpoint packet devices, add second endpoint to service, create connection + # ('DeviceEvent', EVENT_UPDATE, json_device_id(DEVICE_R1_UUID)), + # ('DeviceEvent', EVENT_UPDATE, json_device_id(DEVICE_R3_UUID)), + # ('ServiceEvent', EVENT_UPDATE, json_service_id(service_uuid, context_id=CONTEXT_ID)), + # ('ConnectionEvent', EVENT_CREATE, json_connection_id(packet_connection_uuid)), + # ] + # check_events(events_collector, expected_events) + + # ----- Stop the EventsCollector ----------------------------------------------------------------------------------- + # events_collector.stop() + + +def test_scenario_service_created(context_client : ContextClient): # pylint: disable=redefined-outer-name + # ----- List entities - Ensure service is created ------------------------------------------------------------------ + response = context_client.ListContexts(Empty()) + assert len(response.contexts) == len(CONTEXTS) + + response = context_client.ListTopologies(ContextId(**CONTEXT_ID)) + assert len(response.topologies) == len(TOPOLOGIES) + + response = context_client.ListDevices(Empty()) + assert len(response.devices) == len(DEVICES) + + response = context_client.ListLinks(Empty()) + assert len(response.links) == len(LINKS) + + response = context_client.ListServices(ContextId(**CONTEXT_ID)) + LOGGER.info('Services[{:d}] = {:s}'.format(len(response.services), grpc_message_to_json_string(response))) + assert len(response.services) == 2 # L3NM + TAPI + for service in response.services: + service_id = service.service_id + response = context_client.ListConnections(service_id) + LOGGER.info(' ServiceId[{:s}] => Connections[{:d}] = {:s}'.format( + grpc_message_to_json_string(service_id), len(response.connections), grpc_message_to_json_string(response))) + assert len(response.connections) == 1 # one connection per service diff --git a/src/tests/ofc22/tests/test_functional_delete_service_xr.py b/src/tests/ofc22/tests/test_functional_delete_service_xr.py new file mode 100644 index 0000000000000000000000000000000000000000..f28828be056e755058a0f6b15bd8ea3e9acbbdeb --- /dev/null +++ b/src/tests/ofc22/tests/test_functional_delete_service_xr.py @@ -0,0 +1,133 @@ +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging, pytest +from common.DeviceTypes import DeviceTypeEnum +from common.Settings import get_setting +from common.tests.EventTools import EVENT_REMOVE, EVENT_UPDATE, check_events +from common.tools.object_factory.Connection import json_connection_id +from common.tools.object_factory.Device import json_device_id +from common.tools.object_factory.Service import json_service_id +from common.tools.grpc.Tools import grpc_message_to_json_string +from compute.tests.mock_osm.MockOSM import MockOSM +from context.client.ContextClient import ContextClient +from context.client.EventsCollector import EventsCollector +from common.proto.context_pb2 import ContextId, Empty, ServiceTypeEnum +from .ObjectsXr import ( + CONTEXT_ID, CONTEXTS, DEVICE_X1_UUID, DEVICE_R1_UUID, DEVICE_R3_UUID, DEVICES, LINKS, TOPOLOGIES, WIM_MAPPING, + WIM_PASSWORD, WIM_USERNAME) + + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +DEVTYPE_EMU_PR = DeviceTypeEnum.EMULATED_PACKET_ROUTER.value +DEVTYPE_XR_CONSTELLATION = DeviceTypeEnum.XR_CONSTELLATION.value + +@pytest.fixture(scope='session') +def context_client(): + _client = ContextClient(get_setting('CONTEXTSERVICE_SERVICE_HOST'), get_setting('CONTEXTSERVICE_SERVICE_PORT_GRPC')) + yield _client + _client.close() + + +@pytest.fixture(scope='session') +def osm_wim(): + wim_url = 'http://{:s}:{:s}'.format( + get_setting('COMPUTESERVICE_SERVICE_HOST'), str(get_setting('COMPUTESERVICE_SERVICE_PORT_HTTP'))) + return MockOSM(wim_url, WIM_MAPPING, WIM_USERNAME, WIM_PASSWORD) + + +def test_scenario_is_correct(context_client : ContextClient): # pylint: disable=redefined-outer-name + # ----- List entities - Ensure service is created ------------------------------------------------------------------ + response = context_client.ListContexts(Empty()) + assert len(response.contexts) == len(CONTEXTS) + + response = context_client.ListTopologies(ContextId(**CONTEXT_ID)) + assert len(response.topologies) == len(TOPOLOGIES) + + response = context_client.ListDevices(Empty()) + assert len(response.devices) == len(DEVICES) + + response = context_client.ListLinks(Empty()) + assert len(response.links) == len(LINKS) + + response = context_client.ListServices(ContextId(**CONTEXT_ID)) + LOGGER.info('Services[{:d}] = {:s}'.format(len(response.services), grpc_message_to_json_string(response))) + assert len(response.services) == 2 # L3NM + TAPI + for service in response.services: + service_id = service.service_id + response = context_client.ListConnections(service_id) + LOGGER.info(' ServiceId[{:s}] => Connections[{:d}] = {:s}'.format( + grpc_message_to_json_string(service_id), len(response.connections), grpc_message_to_json_string(response))) + assert len(response.connections) == 1 # one connection per service + + +def test_service_removal(context_client : ContextClient, osm_wim : MockOSM): # pylint: disable=redefined-outer-name + # ----- Start the EventsCollector ---------------------------------------------------------------------------------- + events_collector = EventsCollector(context_client, log_events_received=True) + events_collector.start() + + # ----- Delete Service --------------------------------------------------------------------------------------------- + response = context_client.ListServices(ContextId(**CONTEXT_ID)) + LOGGER.info('Services[{:d}] = {:s}'.format(len(response.services), grpc_message_to_json_string(response))) + assert len(response.services) == 2 # L3NM + TAPI + service_uuids = set() + for service in response.services: + if service.service_type != ServiceTypeEnum.SERVICETYPE_L3NM: continue + service_uuid = service.service_id.service_uuid.uuid + service_uuids.add(service_uuid) + osm_wim.conn_info[service_uuid] = {} + + assert len(service_uuids) == 1 # assume a single service has been created + service_uuid = set(service_uuids).pop() + + osm_wim.delete_connectivity_service(service_uuid) + + # ----- Validate collected events ---------------------------------------------------------------------------------- + # packet_connection_uuid = '{:s}:{:s}'.format(service_uuid, DEVTYPE_EMU_PR) + # optical_connection_uuid = '{:s}:optical:{:s}'.format(service_uuid, DEVTYPE_XR_CONSTELLATION) + # optical_service_uuid = '{:s}:optical'.format(service_uuid) + + # expected_events = [ + # ('ConnectionEvent', EVENT_REMOVE, json_connection_id(packet_connection_uuid)), + # ('DeviceEvent', EVENT_UPDATE, json_device_id(DEVICE_R1_UUID)), + # ('DeviceEvent', EVENT_UPDATE, json_device_id(DEVICE_R3_UUID)), + # ('ServiceEvent', EVENT_REMOVE, json_service_id(service_uuid, context_id=CONTEXT_ID)), + # ('ConnectionEvent', EVENT_REMOVE, json_connection_id(optical_connection_uuid)), + # ('DeviceEvent', EVENT_UPDATE, json_device_id(DEVICE_X1_UUID)), + # ('ServiceEvent', EVENT_REMOVE, json_service_id(optical_service_uuid, context_id=CONTEXT_ID)), + # ] + # check_events(events_collector, expected_events) + + # ----- Stop the EventsCollector ----------------------------------------------------------------------------------- + # events_collector.stop() + + +def test_services_removed(context_client : ContextClient): # pylint: disable=redefined-outer-name + # ----- List entities - Ensure service is removed ------------------------------------------------------------------ + response = context_client.ListContexts(Empty()) + assert len(response.contexts) == len(CONTEXTS) + + response = context_client.ListTopologies(ContextId(**CONTEXT_ID)) + assert len(response.topologies) == len(TOPOLOGIES) + + response = context_client.ListDevices(Empty()) + assert len(response.devices) == len(DEVICES) + + response = context_client.ListLinks(Empty()) + assert len(response.links) == len(LINKS) + + response = context_client.ListServices(ContextId(**CONTEXT_ID)) + assert len(response.services) == 0 diff --git a/src/webui/service/static/topology_icons/xr-constellation.png b/src/webui/service/static/topology_icons/xr-constellation.png new file mode 100644 index 0000000000000000000000000000000000000000..518ca5a60b1d6b9c674783873189566430adccf9 Binary files /dev/null and b/src/webui/service/static/topology_icons/xr-constellation.png differ