diff --git a/.gitignore b/.gitignore index a0ac78095a9f275ae35060a584c5df2151aa7d0e..db47387c8c8ff9900a59107642221960134aa1f1 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ MANIFEST # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest +.manifest/ *.spec # Installer logs diff --git a/proto/context.proto b/proto/context.proto index d3888e77b0256f63fb4b4eb10b987137057a0aa7..bb769a5a2cc37c7137be1da5f4e47edf41628f5e 100644 --- a/proto/context.proto +++ b/proto/context.proto @@ -77,13 +77,19 @@ service ContextService { // ------------------------------ Experimental ----------------------------- - rpc GetOpticalConfig (Empty ) returns (OpticalConfigList ) {} - rpc SetOpticalConfig (OpticalConfig ) returns (OpticalConfigId ) {} - rpc SelectOpticalConfig(OpticalConfigId) returns (OpticalConfig ) {} + rpc GetOpticalConfig (Empty ) returns (OpticalConfigList) {} + rpc SetOpticalConfig (OpticalConfig ) returns (OpticalConfigId ) {} + rpc UpdateOpticalConfig (OpticalConfig ) returns (OpticalConfigId ) {} + rpc SelectOpticalConfig (OpticalConfigId ) returns (OpticalConfig ) {} + rpc DeleteOpticalConfig (OpticalConfigId ) returns (Empty ) {} + rpc DeleteOpticalChannel (OpticalConfig ) returns (Empty ) {} - rpc SetOpticalLink (OpticalLink ) returns (Empty ) {} - rpc GetOpticalLink (OpticalLinkId ) returns (OpticalLink ) {} - rpc GetFiber (FiberId ) returns (Fiber ) {} + rpc SetOpticalLink (OpticalLink ) returns (Empty ) {} + rpc GetOpticalLink (LinkId ) returns (OpticalLink ) {} + rpc DeleteOpticalLink (LinkId ) returns (Empty ) {} + rpc GetOpticalLinkList (Empty ) returns (OpticalLinkList ) {} + + rpc DeleteServiceConfigRule(ServiceConfigRule) returns (Empty ) {} } // ----- Generic ------------------------------------------------------------------------------------------------------- @@ -148,6 +154,7 @@ message Topology { string name = 2; repeated DeviceId device_ids = 3; repeated LinkId link_ids = 4; + repeated LinkId optical_link_ids = 5; } message TopologyDetails { @@ -155,6 +162,7 @@ message TopologyDetails { string name = 2; repeated Device devices = 3; repeated Link links = 4; + repeated OpticalLink optical_links = 5; } message TopologyIdList { @@ -653,45 +661,57 @@ message OpticalConfigId { message OpticalConfig { OpticalConfigId opticalconfig_id = 1; string config = 2; + DeviceId device_id = 3; } message OpticalConfigList { repeated OpticalConfig opticalconfigs = 1; } +message OpticalConfigEvent { + Event event = 1; + OpticalConfigId opticalconfig_id = 2; +} + + // ---- Optical Link ---- -message OpticalLinkId { - Uuid optical_link_uuid = 1; +message OpticalEndPointId { + DeviceId device_id = 2; + Uuid endpoint_uuid = 3; } -message FiberId { - Uuid fiber_uuid = 1; -} -message Fiber { - string ID = 10; - string src_port = 1; - string dst_port = 2; - string local_peer_port = 3; - string remote_peer_port = 4; - repeated int32 c_slots = 5; - repeated int32 l_slots = 6; - repeated int32 s_slots = 7; - float length = 8; - bool used = 9; - FiberId fiber_uuid = 11; - +message OpticalLinkList { + repeated OpticalLink optical_links = 1; } + + + message OpticalLinkDetails { float length = 1; - string source = 2; - string target = 3; - repeated Fiber fibers = 4; + string src_port = 2; + string dst_port = 3; + string local_peer_port = 4; + string remote_peer_port = 5 ; + bool used = 6 ; + map<string, int32> c_slots = 7; + map<string, int32> l_slots = 8; + map<string, int32> s_slots = 9; } message OpticalLink { string name = 1; - OpticalLinkDetails details = 2; - OpticalLinkId optical_link_uuid = 3; + OpticalLinkDetails optical_details = 2; + LinkId link_id = 3; + repeated EndPointId link_endpoint_ids=4; +} + + + +////////////////// Config Rule Delete //////////// + +message ServiceConfigRule { + ServiceId service_id =1; + ConfigRule_Custom configrule_custom =2; } diff --git a/proto/openconfig_device.proto b/proto/optical_device.proto similarity index 64% rename from proto/openconfig_device.proto rename to proto/optical_device.proto index 8ed3a900b6b157e096308c9f476e3c0ab1f575c1..39eb92e4420bf28979b5da9d93ec9264a07dacb3 100644 --- a/proto/openconfig_device.proto +++ b/proto/optical_device.proto @@ -13,11 +13,13 @@ // limitations under the License. syntax = "proto3"; -package openconfig_device; +package optical_device; import "context.proto"; service OpenConfigService { - rpc AddOpenConfigDevice (context.OpticalConfig) returns (context.OpticalConfigId) {} - rpc ConfigureOpticalDevice(context.OpticalConfig) returns (context.Empty ) {} + rpc AddOpenConfigDevice (context.OpticalConfig ) returns (context.OpticalConfigId) {} + rpc ConfigureOpticalDevice(context.OpticalConfig ) returns (context.Empty ) {} + rpc DisableOpticalDevice (context.OpticalConfig ) returns (context.Empty ) {} + rpc GetDeviceConfiguration(context.OpticalConfigList) returns (context.Empty ) {} } diff --git a/scripts/run_tests_locally-device-gnmi-openconfig.sh b/scripts/run_tests_locally-device-gnmi-openconfig.sh new file mode 100755 index 0000000000000000000000000000000000000000..92e8448126f53a7f34b97c480585fea25f2b0411 --- /dev/null +++ b/scripts/run_tests_locally-device-gnmi-openconfig.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +PROJECTDIR=`pwd` + +cd $PROJECTDIR/src +RCFILE=$PROJECTDIR/coverage/.coveragerc + +# Run unitary tests and analyze coverage of code at same time +# helpful pytest flags: --log-level=INFO -o log_cli=true --verbose --maxfail=1 --durations=0 +coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO --verbose \ + device/tests/gnmi_openconfig/test_unitary_gnmi_openconfig.py diff --git a/scripts/run_tests_locally-device-openconfig-arista-l2vpn.sh b/scripts/run_tests_locally-device-openconfig-arista-l2vpn.sh new file mode 100755 index 0000000000000000000000000000000000000000..9e151dd3e4c482d90342af91de0cbb018fb1bf78 --- /dev/null +++ b/scripts/run_tests_locally-device-openconfig-arista-l2vpn.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +PROJECTDIR=`pwd` + +cd $PROJECTDIR/src +RCFILE=$PROJECTDIR/coverage/.coveragerc + +# Run unitary tests and analyze coverage of code at same time +# helpful pytest flags: --log-level=INFO -o log_cli=true --verbose --maxfail=1 --durations=0 +coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO -o log_cli=true --verbose \ + device/tests/test_unitary_openconfig_arista_l2vpn.py diff --git a/src/automation/client/PolicyClient.py b/src/automation/client/PolicyClient.py index f2b25f2429d790c9cb25b5d70e1e737594586133..f7b6412a62670d0be2caa1460f7fab5ffec6fccd 100644 --- a/src/automation/client/PolicyClient.py +++ b/src/automation/client/PolicyClient.py @@ -19,7 +19,7 @@ from common.proto.policy_pb2 import PolicyRuleService, PolicyRuleState from common.proto.policy_pb2_grpc import PolicyServiceStub from common.tools.client.RetryDecorator import retry, delay_exponential from common.tools.grpc.Tools import grpc_message_to_json_string -from common.proto.openconfig_device_pb2_grpc import OpenConfigServiceStub + LOGGER = logging.getLogger(__name__) MAX_RETRIES = 15 DELAY_FUNCTION = delay_exponential(initial=0.01, increment=2.0, maximum=5.0) @@ -40,7 +40,6 @@ class PolicyClient: def connect(self): self.channel = grpc.insecure_channel(self.endpoint) self.stub = PolicyServiceStub(self.channel) - self.openconfig_stub=OpenConfigServiceStub(self.channel) def close(self): if self.channel is not None: self.channel.close() diff --git a/src/common/tools/context_queries/OpticalConfig.py b/src/common/tools/context_queries/OpticalConfig.py new file mode 100644 index 0000000000000000000000000000000000000000..bd697599cdd674ad2dca9ac8d38100333e315ea3 --- /dev/null +++ b/src/common/tools/context_queries/OpticalConfig.py @@ -0,0 +1,66 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from common.method_wrappers.ServiceExceptions import InvalidArgumentsException +from typing import Optional, Union +from uuid import UUID, uuid4, uuid5 + +# Generate a UUIDv5-like from the SHA-1 of "TFS" and no namespace to be used as the NAMESPACE for all +# the context UUIDs generated. For efficiency purposes, the UUID is hardcoded; however, it is produced +# using the following code: +# from hashlib import sha1 +# from uuid import UUID +# hash = sha1(bytes('TFS', 'utf-8')).digest() +# NAMESPACE_TFS = UUID(bytes=hash[:16], version=5) +NAMESPACE_TFS = UUID('200e3a1f-2223-534f-a100-758e29c37f40') + +def get_uuid_from_string(str_uuid_or_name : Union[str, UUID], prefix_for_name : Optional[str] = None) -> str: + # if UUID given, assume it is already a valid UUID + if isinstance(str_uuid_or_name, UUID): return str_uuid_or_name + if not isinstance(str_uuid_or_name, str): + MSG = 'Parameter({:s}) cannot be used to produce a UUID' + raise Exception(MSG.format(str(repr(str_uuid_or_name)))) + try: + # try to parse as UUID + return str(UUID(str_uuid_or_name)) + except: # pylint: disable=bare-except + # produce a UUID within TFS namespace from parameter + if prefix_for_name is not None: + str_uuid_or_name = '{:s}/{:s}'.format(prefix_for_name, str_uuid_or_name) + return str(uuid5(NAMESPACE_TFS, str_uuid_or_name)) + +def get_uuid_random() -> str: + # Generate random UUID. No need to use namespace since "namespace + random = random". + return str(uuid4()) + +def device_get_uuid (device_name) : + if (len(device_name)> 0): + return get_uuid_from_string(device_name) + raise InvalidArgumentsException([ + ('name', device_name), + ], extra_details=['Device Name is required to produce Device UUID']) + + +def opticalconfig_get_uuid( + device_name : str = '', allow_random : bool = False +) -> str: + if len(device_name) > 0: + device_uuid= device_get_uuid(device_name=device_name) + return get_uuid_from_string(f"{device_uuid}_opticalconfig") + if allow_random: return get_uuid_random() + + raise InvalidArgumentsException([ + ('name', device_name), + ], extra_details=['At least one is required to produce a OpticalConfig UUID']) diff --git a/src/common/tools/descriptor/Loader.py b/src/common/tools/descriptor/Loader.py index 2fcc5b63a66ae3c3e96ab774d34958a110f0454a..958536f26c3c63468f1af4a6d14130b6e12fd08f 100644 --- a/src/common/tools/descriptor/Loader.py +++ b/src/common/tools/descriptor/Loader.py @@ -38,7 +38,7 @@ from typing import Any, Dict, List, Optional, Tuple, Union from common.proto.context_pb2 import ( Connection, Context, ContextId, Device, DeviceId, Empty, Link, LinkId, Service, ServiceId, Slice, SliceId, - Topology, TopologyId + Topology, TopologyId , OpticalLink ) from common.tools.object_factory.Context import json_context_id from context.client.ContextClient import ContextClient @@ -128,15 +128,16 @@ class DescriptorLoader: self.__num_workers = num_workers - self.__dummy_mode = self.__descriptors.get('dummy_mode' , False) - self.__contexts = self.__descriptors.get('contexts' , []) - self.__topologies = self.__descriptors.get('topologies' , []) - self.__devices = self.__descriptors.get('devices' , []) - self.__links = self.__descriptors.get('links' , []) - self.__services = self.__descriptors.get('services' , []) - self.__slices = self.__descriptors.get('slices' , []) - self.__ietf_slices = self.__descriptors.get('ietf-network-slice-service:network-slice-services', {}) - self.__connections = self.__descriptors.get('connections', []) + self.__dummy_mode = self.__descriptors.get('dummy_mode' , False) + self.__contexts = self.__descriptors.get('contexts' , []) + self.__topologies = self.__descriptors.get('topologies' , []) + self.__devices = self.__descriptors.get('devices' , []) + self.__links = self.__descriptors.get('links' , []) + self.__services = self.__descriptors.get('services' , []) + self.__slices = self.__descriptors.get('slices' , []) + self.__ietf_slices = self.__descriptors.get('ietf-network-slice-service:network-slice-services', {}) + self.__connections = self.__descriptors.get('connections', []) + self.__optical_links = self.__descriptors.get('optical_links',[]) if len(self.__ietf_slices) > 0: for slice_service in self.__ietf_slices["slice-service"]: @@ -285,6 +286,9 @@ class DescriptorLoader: @property def num_connections(self) -> int: return len(self.__connections) + + @property + def optical_links(self) -> List[Dict]: return self.__optical_links def process(self) -> TypeResults: # Format CustomConfigRules in Devices, Services and Slices provided in JSON format @@ -348,16 +352,17 @@ class DescriptorLoader: self.__svc_cli.connect() self.__slc_cli.connect() - self._process_descr('context', 'add', self.__ctx_cli.SetContext, Context, self.__contexts_add ) - self._process_descr('topology', 'add', self.__ctx_cli.SetTopology, Topology, self.__topologies_add) - self._process_descr('controller', 'add', self.__dev_cli.AddDevice, Device, controllers_add ) - self._process_descr('device', 'add', self.__dev_cli.AddDevice, Device, network_devices_add ) - self._process_descr('device', 'config', self.__dev_cli.ConfigureDevice, Device, self.__devices_config) - self._process_descr('link', 'add', self.__ctx_cli.SetLink, Link, self.__links ) - self._process_descr('service', 'add', self.__svc_cli.CreateService, Service, self.__services_add ) - self._process_descr('service', 'update', self.__svc_cli.UpdateService, Service, self.__services ) - self._process_descr('slice', 'add', self.__slc_cli.CreateSlice, Slice, self.__slices_add ) - self._process_descr('slice', 'update', self.__slc_cli.UpdateSlice, Slice, self.__slices ) + self._process_descr('context', 'add', self.__ctx_cli.SetContext, Context, self.__contexts_add ) + self._process_descr('topology', 'add', self.__ctx_cli.SetTopology, Topology, self.__topologies_add) + self._process_descr('controller', 'add', self.__dev_cli.AddDevice, Device, controllers_add ) + self._process_descr('device', 'add', self.__dev_cli.AddDevice, Device, network_devices_add ) + self._process_descr('device', 'config', self.__dev_cli.ConfigureDevice, Device, self.__devices_config) + self._process_descr('link', 'add', self.__ctx_cli.SetLink, Link, self.__links ) + self._process_descr('service', 'add', self.__svc_cli.CreateService, Service, self.__services_add ) + self._process_descr('service', 'update', self.__svc_cli.UpdateService, Service, self.__services ) + self._process_descr('slice', 'add', self.__slc_cli.CreateSlice, Slice, self.__slices_add ) + self._process_descr('slice', 'update', self.__slc_cli.UpdateSlice, Slice, self.__slices ) + self._process_descr('link', 'add', self.__ctx_cli.SetOpticalLink, OpticalLink, self.__optical_links ) # By default the Context component automatically assigns devices and links to topologies based on their # endpoints, and assigns topologies, services, and slices to contexts based on their identifiers. diff --git a/src/common/tools/object_factory/EndPoint.py b/src/common/tools/object_factory/EndPoint.py index 85a5d4494184567447d6d16fa7df2c530106c6ba..faf1accd37ebdc32b382a1cca16ccbeb510099f9 100644 --- a/src/common/tools/object_factory/EndPoint.py +++ b/src/common/tools/object_factory/EndPoint.py @@ -43,13 +43,14 @@ def json_endpoint_ids( def json_endpoint( device_id : Dict, endpoint_uuid : str, endpoint_type : str, topology_id : Optional[Dict] = None, - kpi_sample_types : List[int] = [], location : Optional[Dict] = None + name : Optional[str] = None, kpi_sample_types : List[int] = [], location : Optional[Dict] = None ): result = { 'endpoint_id': json_endpoint_id(device_id, endpoint_uuid, topology_id=topology_id), 'endpoint_type': endpoint_type, } + if name is not None: result['name'] = name if kpi_sample_types is not None and len(kpi_sample_types) > 0: result['kpi_sample_types'] = copy.deepcopy(kpi_sample_types) if location is not None: diff --git a/src/common/tools/object_factory/OpticalLink.py b/src/common/tools/object_factory/OpticalLink.py new file mode 100644 index 0000000000000000000000000000000000000000..765cfac22d2aa6a168e01e41df393cc2b693a32f --- /dev/null +++ b/src/common/tools/object_factory/OpticalLink.py @@ -0,0 +1,40 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import copy + +def convert_to_dict(single_val:int)->dict: + slot= dict() + bin_num = bin(single_val) + sliced_num=bin_num[2:] + for i in range(len(sliced_num)): + slot[str(i+1)]=int(sliced_num[i]) + return slot + +def correct_slot(dic: dict) -> dict: + _dict = copy.deepcopy(dic) + keys_list = list(_dict.keys()) + if len(keys_list) < 20: + num_keys = [int(i) for i in keys_list] + if num_keys[-1] != 20: + missed_keys = [] + diff = 20 - len(num_keys) + #print(f"diff {diff}") + for i in range(diff+1): + missed_keys.append(num_keys[-1]+i) + #print(f"missed_keys {missed_keys}") + for key in missed_keys : + _dict[key]=1 + #print(f"result {_dict}") + return _dict diff --git a/src/common/type_checkers/Assertions.py b/src/common/type_checkers/Assertions.py index 3934c9d89568be90129bb766c61b55a532d2f0b3..498a85349c4c9d68bc8c64c5d174595cb98c1cef 100644 --- a/src/common/type_checkers/Assertions.py +++ b/src/common/type_checkers/Assertions.py @@ -451,7 +451,7 @@ def validate_slice(message): def validate_topology(message, num_devices=None, num_links=None): assert isinstance(message, dict) - assert len(message.keys()) == 4 + assert len(message.keys()) == 5 assert 'topology_id' in message validate_topology_id(message['topology_id']) assert 'name' in message @@ -464,6 +464,10 @@ def validate_topology(message, num_devices=None, num_links=None): assert isinstance(message['link_ids'], list) if num_links is not None: assert len(message['link_ids']) == num_links for link_id in message['link_ids']: validate_link_id(link_id) + assert 'optical_link_ids' in message + assert isinstance(message['optical_link_ids'], list) + #if num_links is not None: assert len(message['optical_link_ids']) == num_links + for link_id in message['optical_link_ids']: validate_link_id(link_id) def validate_endpoint(message): assert isinstance(message, dict) diff --git a/src/context/client/ContextClient.py b/src/context/client/ContextClient.py index 2776a0d294e9a9ee7b00e46bfd3fbb068133741f..35f9121b33f60034340cbe2492e1bd26a622f6e7 100644 --- a/src/context/client/ContextClient.py +++ b/src/context/client/ContextClient.py @@ -24,10 +24,10 @@ from common.proto.context_pb2 import ( Device, DeviceEvent, DeviceFilter, DeviceId, DeviceIdList, DeviceList, Empty, EndPointIdList, EndPointNameList, Link, LinkEvent, LinkId, LinkIdList, LinkList, - Service, ServiceEvent, ServiceFilter, ServiceId, ServiceIdList, ServiceList, + OpticalConfig, OpticalConfigId, OpticalConfigList , OpticalLink, OpticalLinkList, + Service, ServiceConfigRule, ServiceEvent, ServiceFilter, ServiceId, ServiceIdList, ServiceList, Slice, SliceEvent, SliceFilter, SliceId, SliceIdList, SliceList, Topology, TopologyDetails, TopologyEvent, TopologyId, TopologyIdList, TopologyList, - OpticalConfig, OpticalConfigId, OpticalConfigList ) from common.proto.context_pb2_grpc import ContextServiceStub from common.proto.context_policy_pb2_grpc import ContextPolicyServiceStub @@ -447,6 +447,14 @@ class ContextClient: response = self.stub.SetOpticalConfig(request) LOGGER.debug('SetOpticalConfig result: {:s}'.format(grpc_message_to_json_string(response))) return response + + @RETRY_DECORATOR + def UpdateOpticalConfig(self, request : OpticalConfig) -> OpticalConfigId: + LOGGER.debug('SetOpticalConfig request: {:s}'.format(grpc_message_to_json_string(request))) + response_future = self.stub.UpdateOpticalConfig.future(request) + response = response_future.result() + LOGGER.debug('SetOpticalConfig result: {:s}'.format(grpc_message_to_json_string(response))) + return response @RETRY_DECORATOR def GetOpticalConfig(self, request : Empty) -> OpticalConfigList: @@ -461,3 +469,55 @@ class ContextClient: response = self.stub.SelectOpticalConfig(request) LOGGER.debug('SelectOpticalConfig result: {:s}'.format(grpc_message_to_json_string(response))) return response + + @RETRY_DECORATOR + def DeleteOpticalConfig(self,request : OpticalConfigId) -> Empty: + LOGGER.debug('DeleteOpticalConfig request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.DeleteOpticalConfig(request) + LOGGER.debug('DeleteOpticalConfig result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + def DeleteOpticalChannel(self,request : OpticalConfig) -> Empty: + LOGGER.debug('DeleteOpticalChannel request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.DeleteOpticalChannel(request) + LOGGER.debug('DeleteOpticalChannel result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + #--------------------------- Optical Link ------------------------ + def GetOpticalLinkList(self, request: Empty) -> OpticalLinkList: + LOGGER.debug('ListOpticalLinks request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.GetOpticalLinkList(request) + LOGGER.debug('ListOpticalLinks result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + def GetOpticalLink(self, request: LinkId) -> OpticalLink: + LOGGER.debug('GetOpticalLink request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.GetOpticalLink(request) + LOGGER.debug('GetOpticalLink result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + def SetOpticalLink(self, request: OpticalLink) -> LinkId: + LOGGER.debug('SetOpticalLink request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.SetOpticalLink(request) + LOGGER.debug('SetOpticalLink result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + def DeleteOpticalLink(self, request: LinkId) -> Empty: + LOGGER.debug('RemoveOpticalLink request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.DeleteOpticalLink(request) + LOGGER.debug('RemoveOpticalLink result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + + # --------------------------------- Service ConfigRule Deletion ------------------ + + @RETRY_DECORATOR + def DeleteServiceConfigRule(self, request: ServiceConfigRule) -> Empty: + LOGGER.debug('ServiceConfigRule Delete request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.DeleteServiceConfigRule(request) + LOGGER.debug('ServiceConfigRule Delete result: {:s}'.format(grpc_message_to_json_string(response))) + return response diff --git a/src/context/service/ContextServiceServicerImpl.py b/src/context/service/ContextServiceServicerImpl.py index be32372108e059625801d14c660d18cbe0df677f..09d16b0f8a80f956a32f7e074e3bbe5582b3b26c 100644 --- a/src/context/service/ContextServiceServicerImpl.py +++ b/src/context/service/ContextServiceServicerImpl.py @@ -24,29 +24,51 @@ from common.proto.context_pb2 import ( Service, ServiceEvent, ServiceFilter, ServiceId, ServiceIdList, ServiceList, Slice, SliceEvent, SliceFilter, SliceId, SliceIdList, SliceList, Topology, TopologyDetails, TopologyEvent, TopologyId, TopologyIdList, TopologyList, - OpticalConfigList, OpticalConfigId, OpticalConfig + OpticalConfigList, OpticalConfigId, OpticalConfig, OpticalLink, OpticalLinkList, + ServiceConfigRule ) from common.proto.policy_pb2 import PolicyRuleIdList, PolicyRuleId, PolicyRuleList, PolicyRule from common.proto.context_pb2_grpc import ContextServiceServicer from common.proto.context_policy_pb2_grpc import ContextPolicyServiceServicer from common.method_wrappers.Decorator import MetricsPool, safe_and_metered_rpc_method from .database.Connection import ( - connection_delete, connection_get, connection_list_ids, connection_list_objs, connection_set) -from .database.Context import context_delete, context_get, context_list_ids, context_list_objs, context_set -from .database.Device import device_delete, device_get, device_list_ids, device_list_objs, device_select, device_set + connection_delete, connection_get, connection_list_ids, connection_list_objs, connection_set +) +from .database.Context import ( + context_delete, context_get, context_list_ids, context_list_objs, context_set +) +from .database.Device import ( + device_delete, device_get, device_list_ids, device_list_objs, device_select, device_set +) from .database.EndPoint import endpoint_list_names from .database.Events import EventTopicEnum, consume_events -from .database.Link import link_delete, link_get, link_list_ids, link_list_objs, link_set +from .database.Link import ( + link_delete, link_get, link_list_ids, link_list_objs, link_set +) from .database.PolicyRule import ( - policyrule_delete, policyrule_get, policyrule_list_ids, policyrule_list_objs, policyrule_set) + policyrule_delete, policyrule_get, policyrule_list_ids, policyrule_list_objs, + policyrule_set +) from .database.Service import ( - service_delete, service_get, service_list_ids, service_list_objs, service_select, service_set, service_unset) + service_delete, service_get, service_list_ids, service_list_objs, service_select, + service_set, service_unset +) from .database.Slice import ( - slice_delete, slice_get, slice_list_ids, slice_list_objs, slice_select, slice_set, slice_unset) + slice_delete, slice_get, slice_list_ids, slice_list_objs, slice_select, + slice_set, slice_unset +) from .database.Topology import ( - topology_delete, topology_get, topology_get_details, topology_list_ids, topology_list_objs, topology_set) -from .database.OpticalConfig import set_opticalconfig, select_opticalconfig, get_opticalconfig - + topology_delete, topology_get, topology_get_details, topology_list_ids, + topology_list_objs, topology_set +) +from .database.OpticalConfig import ( + set_opticalconfig, select_opticalconfig, get_opticalconfig, delete_opticalconfig, + update_opticalconfig, delete_opticalchannel +) +from .database.OpticalLink import ( + optical_link_delete, optical_link_get, optical_link_list_objs, optical_link_set +) +from .database.ConfigRule import delete_config_rule LOGGER = logging.getLogger(__name__) METRICS_POOL = MetricsPool('Context', 'RPC') @@ -312,7 +334,48 @@ class ContextServiceServicerImpl(ContextServiceServicer, ContextPolicyServiceSer result = set_opticalconfig(self.db_engine, request) return OpticalConfigId(**result) + @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) + def UpdateOpticalConfig(self, request : OpticalConfig, context : grpc.ServicerContext) -> OpticalConfigId: + result = update_opticalconfig(self.db_engine, request) + return OpticalConfigId(**result) + @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def SelectOpticalConfig(self, request : OpticalConfigId, context : grpc.ServicerContext) -> OpticalConfig: result = select_opticalconfig(self.db_engine, request) - return OpticalConfig(config=result.config, opticalconfig_id=result.opticalconfig_id) + optical_config_id = OpticalConfigId() + device_id = DeviceId() + optical_config_id.CopyFrom(result.opticalconfig_id) + device_id.CopyFrom(result.device_id) + return OpticalConfig(config=result.config, opticalconfig_id=optical_config_id , device_id=device_id) + + @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) + def DeleteOpticalConfig(self, request : OpticalConfigId, context : grpc.ServicerContext) -> Empty: + delete_opticalconfig(self.db_engine, self.messagebroker, request) + return Empty() + + @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) + def DeleteOpticalChannel(self, request : OpticalConfig, context : grpc.ServicerContext) -> Empty: + delete_opticalchannel(self.db_engine, self.messagebroker, request) + return Empty() + + #--------------------- Experimental Optical Link ------------------- + + @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) + def GetOpticalLinkList(self, request : Empty, context : grpc.ServicerContext) -> OpticalLinkList: + return optical_link_list_objs(self.db_engine) + + @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) + def GetOpticalLink(self, request : LinkId, context : grpc.ServicerContext) -> OpticalLink: + return optical_link_get(self.db_engine, request) + + @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) + def SetOpticalLink(self, request : Link, context : grpc.ServicerContext) -> LinkId: + return optical_link_set(self.db_engine, self.messagebroker, request) + + @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) + def DeleteOpticalLink(self, request : LinkId, context : grpc.ServicerContext) -> Empty: + return optical_link_delete(self.db_engine, self.messagebroker, request) + + @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) + def DeleteServiceConfigRule(self, request : ServiceConfigRule, context : grpc.ServicerContext) -> Empty: + return delete_config_rule(self.db_engine, request) diff --git a/src/context/service/database/ConfigRule.py b/src/context/service/database/ConfigRule.py index 7d816b3e87803f71678511f4fadc6bbe7eba548e..0201d2672e0cd47f208e7569f9d34eae6e4601a8 100644 --- a/src/context/service/database/ConfigRule.py +++ b/src/context/service/database/ConfigRule.py @@ -16,15 +16,18 @@ import datetime, json, logging from sqlalchemy import delete #from sqlalchemy.dialects import postgresql from sqlalchemy.dialects.postgresql import insert +from sqlalchemy.engine import Engine from sqlalchemy.orm import Session from typing import Dict, List, Optional, Set -from common.proto.context_pb2 import ConfigRule +from common.proto.context_pb2 import ConfigRule, ServiceConfigRule, Empty from common.tools.grpc.Tools import grpc_message_to_json_string from .models.enums.ConfigAction import ORM_ConfigActionEnum, grpc_to_enum__config_action from .models.ConfigRuleModel import ( ConfigRuleKindEnum, DeviceConfigRuleModel, ServiceConfigRuleModel, SliceConfigRuleModel) from .uuids._Builder import get_uuid_from_string from .uuids.EndPoint import endpoint_get_uuid +from sqlalchemy_cockroachdb import run_transaction +from sqlalchemy.orm import Session, sessionmaker LOGGER = logging.getLogger(__name__) @@ -149,3 +152,16 @@ def upsert_config_rules( upsert_affected = any([(updated_at > created_at) for created_at,updated_at in configrule_updates]) return delete_affected or upsert_affected + + +def delete_config_rule(db_engine : Engine, request : ServiceConfigRule): + config_rule = request.configrule_custom + service_id = request.service_id + parent_uuid = service_id.service_uuid.uuid + configrule_name = 'service:custom:{:s}'.format( config_rule.resource_key) + configrule_uuid = get_uuid_from_string(configrule_name, prefix_for_name=parent_uuid) + def callback(session : Session) -> bool: + num_deleted = session.query(ServiceConfigRuleModel).filter_by(configrule_uuid=configrule_uuid).delete() + return num_deleted > 0 + deleted = run_transaction(sessionmaker(bind=db_engine), callback) + return Empty() diff --git a/src/context/service/database/Device.py b/src/context/service/database/Device.py index beeae59c5967723f2cccfa365fca67dd3acd0096..4713b8e0744df39e6d9227e02f909571bd159a37 100644 --- a/src/context/service/database/Device.py +++ b/src/context/service/database/Device.py @@ -106,6 +106,7 @@ def device_set(db_engine : Engine, messagebroker : MessageBroker, request : Devi topology_uuids.add(topology_uuid) is_oc_driver = DeviceDriverEnum.DEVICEDRIVER_OC in set(request.device_drivers) + #optical_endpoints_data : List[Dict] = list() endpoints_data : List[Dict] = list() for i, endpoint in enumerate(request.device_endpoints): @@ -136,6 +137,18 @@ def device_set(db_engine : Engine, messagebroker : MessageBroker, request : Devi 'created_at' : now, 'updated_at' : now, }) + # # ------------------- Experimental ----------------------- + + # if is_oc_driver: + + # optical_endpoints_data.append({ + # 'endpoint_uuid' : endpoint_uuid, + # 'device_uuid' : endpoint_device_uuid, + # 'name' : endpoint_name, + # 'endpoint_type' : endpoint.endpoint_type, + # 'created_at' : now, + # 'updated_at' : now, + # }) if endpoint_topology_uuid not in topology_uuids: related_topologies.append({ @@ -191,6 +204,23 @@ def device_set(db_engine : Engine, messagebroker : MessageBroker, request : Devi stmt = stmt.returning(EndPointModel.created_at, EndPointModel.updated_at) endpoint_updates = session.execute(stmt).fetchall() updated_endpoints = any([(updated_at > created_at) for created_at,updated_at in endpoint_updates]) + + #---------------------- Experimental --------------------------------- + + # if len(optical_endpoints_data) > 0: + # LOGGER.info(f"Optical endpoint data_ device_model {optical_endpoints_data}") + # stmt = insert(OpticalEndPointModel).values(optical_endpoints_data) + # stmt = stmt.on_conflict_do_update( + # index_elements=[OpticalEndPointModel.endpoint_uuid], + # set_=dict( + # name = stmt.excluded.name, + # endpoint_type = stmt.excluded.endpoint_type, + # updated_at = stmt.excluded.updated_at, + # ) + # ) + # stmt = stmt.returning(OpticalEndPointModel.created_at, OpticalEndPointModel.updated_at) + # optical_endpoint_updates = session.execute(stmt).fetchall() + # updated_optical_endpoints = any([(updated_at > created_at) for created_at,updated_at in endpoint_updates]) device_topology_ids = [] if not updated or len(related_topologies) > 1: diff --git a/src/context/service/database/Events.py b/src/context/service/database/Events.py index a88a4656a8cf078d2b90fe3a0c0ea22a4ec15a75..46688775eaabd5ab428ff9f0e77de52d25c9af41 100644 --- a/src/context/service/database/Events.py +++ b/src/context/service/database/Events.py @@ -17,27 +17,32 @@ from typing import Dict, Iterator, Set from common.message_broker.Message import Message from common.message_broker.MessageBroker import MessageBroker from common.proto.context_pb2 import ( - ConnectionEvent, ContextEvent, DeviceEvent, EventTypeEnum, LinkEvent, ServiceEvent, SliceEvent, TopologyEvent) + ConnectionEvent, ContextEvent, DeviceEvent, EventTypeEnum, LinkEvent, + ServiceEvent, SliceEvent, TopologyEvent, OpticalConfigEvent +) class EventTopicEnum(enum.Enum): - CONNECTION = 'connection' - CONTEXT = 'context' - DEVICE = 'device' - LINK = 'link' - POLICY_RULE = 'policy-rule' - SERVICE = 'service' - SLICE = 'slice' - TOPOLOGY = 'topology' + CONNECTION = 'connection' + CONTEXT = 'context' + DEVICE = 'device' + LINK = 'link' + POLICY_RULE = 'policy-rule' + SERVICE = 'service' + SLICE = 'slice' + TOPOLOGY = 'topology' + OPTICALCONFIG = 'optical-config' + TOPIC_TO_EVENTCLASS = { - EventTopicEnum.CONNECTION.value : ConnectionEvent, - EventTopicEnum.CONTEXT.value : ContextEvent, - EventTopicEnum.DEVICE.value : DeviceEvent, - EventTopicEnum.LINK.value : LinkEvent, - #EventTopicEnum.POLICY_RULE.value : PolicyRuleEvent, # Not defined in proto files - EventTopicEnum.SERVICE.value : ServiceEvent, - EventTopicEnum.SLICE.value : SliceEvent, - EventTopicEnum.TOPOLOGY.value : TopologyEvent, + EventTopicEnum.CONNECTION.value : ConnectionEvent, + EventTopicEnum.CONTEXT.value : ContextEvent, + EventTopicEnum.DEVICE.value : DeviceEvent, + EventTopicEnum.LINK.value : LinkEvent, + #EventTopicEnum.POLICY_RULE.value : PolicyRuleEvent, # Not defined in proto files + EventTopicEnum.SERVICE.value : ServiceEvent, + EventTopicEnum.SLICE.value : SliceEvent, + EventTopicEnum.TOPOLOGY.value : TopologyEvent, + EventTopicEnum.OPTICALCONFIG.value : OpticalConfigEvent, } CONSUME_TIMEOUT = 0.5 # seconds @@ -61,6 +66,9 @@ def notify_event_topology(messagebroker : MessageBroker, event_type : EventTypeE def notify_event_device(messagebroker : MessageBroker, event_type : EventTypeEnum, device_id : Dict) -> None: notify_event(messagebroker, EventTopicEnum.DEVICE, event_type, {'device_id': device_id}) +def notify_event_opticalconfig(messagebroker : MessageBroker, event_type : EventTypeEnum, opticalconfig_id : Dict) -> None: + notify_event(messagebroker, EventTopicEnum.DEVICE, event_type, {'opticalconfig_id': opticalconfig_id}) + def notify_event_link(messagebroker : MessageBroker, event_type : EventTypeEnum, link_id : Dict) -> None: notify_event(messagebroker, EventTopicEnum.LINK, event_type, {'link_id': link_id}) diff --git a/src/context/service/database/OpticalConfig.py b/src/context/service/database/OpticalConfig.py index 663c3bd0b16ac8af20cb1acf9176764d719b3046..ad38751eef9c73b734c13feeeabbc3329d58df0d 100644 --- a/src/context/service/database/OpticalConfig.py +++ b/src/context/service/database/OpticalConfig.py @@ -14,11 +14,19 @@ import json, logging from sqlalchemy.dialects.postgresql import insert +from common.message_broker.MessageBroker import MessageBroker +from common.DeviceTypes import DeviceTypeEnum from sqlalchemy.engine import Engine from sqlalchemy.orm import Session, sessionmaker from sqlalchemy_cockroachdb import run_transaction -from common.proto.context_pb2 import OpticalConfig, OpticalConfigId -from .models.OpticalConfigModel import OpticalConfigModel +from common.proto.context_pb2 import OpticalConfig, OpticalConfigId, Empty, EventTypeEnum +from .models.OpticalConfig.OpticalConfigModel import OpticalConfigModel +from .models.OpticalConfig.TransponderModel import TransponderTypeModel, OpticalChannelModel +from .models.OpticalConfig.RoadmModel import RoadmTypeModel, ChannelModel +from .uuids.OpticalConfig import ( + channel_get_uuid, opticalconfig_get_uuid, transponder_get_uuid, roadm_get_uuid +) +from .Events import notify_event_opticalconfig LOGGER = logging.getLogger(__name__) @@ -28,8 +36,10 @@ def get_opticalconfig(db_engine : Engine): results = session.query(OpticalConfigModel).all() for obj in results: optical_config = OpticalConfig() - optical_config.config = json.dump(obj.config) - optical_config.opticalconfig_id.opticalconfig_uuid = obj.opticalconfig_uuid + optical_config.config = json.dumps(obj.dump()) + ids_obj = obj.dump_id() + optical_config.opticalconfig_id.opticalconfig_uuid = ids_obj["opticalconfig_uuid"] + optical_config.device_id.device_uuid.uuid=ids_obj["device_uuid"] optical_configs.append(optical_config) return optical_configs obj = run_transaction(sessionmaker(bind=db_engine), callback) @@ -37,33 +47,323 @@ def get_opticalconfig(db_engine : Engine): def set_opticalconfig(db_engine : Engine, request : OpticalConfig): opticalconfig_id = OpticalConfigId() - opticalconfig_id.opticalconfig_uuid = request.opticalconfig_id.opticalconfig_uuid - my_config_data = [] + device_id = request.device_id + device_uuid = request.device_id.device_uuid.uuid + channels = [] + transponder = [] + roadms = [] + channel_namespace = None + OpticalConfig_data = [] + config_type=None + #is_transpondre=False + opticalconfig_uuid = opticalconfig_get_uuid(device_id) + if request.config: - channels = [] - transceivers = [] config = json.loads(request.config) - if 'channels' in config and len(config['channels']) > 0: - channels = [channel['name']['index'] for channel in config['channels']] - if 'transceivers' in config and len(config['transceivers']['transceiver']) > 0: - transceivers = [transceiver for transceiver in config['transceivers']['transceiver']] - - my_config_data = [ - { - "opticalconfig_uuid": request.opticalconfig_id.opticalconfig_uuid, - "channels" : channels, - "transcievers" : transceivers, - "interfaces" : json.dumps(config["interfaces"]["interface"]), - "channel_namespace" : config["channel_namespace"], - "endpoints" : [json.dumps(endpoint) for endpoint in config["endpoints"]], - "frequency" : config["frequency"] if "frequency" in config else 0, - "operational_mode" : config["operational_mode"] if "operational_mode" in config else 0, - "output_power" : config["output_power"] if "output_power" in config else '', - } - ] + if 'channel_namespace' in config: + channel_namespace=config['channel_namespace'] + if "type" in config: + config_type= config["type"] + if config_type == DeviceTypeEnum.OPTICAL_TRANSPONDER._value_: + is_transpondre = True + transceivers = [] + if channel_namespace is None and 'channel_namespace' in config: + channel_namespace=config['channel_namespace'] + if 'transceivers' in config and len(config['transceivers']['transceiver']) > 0: + transceivers = [transceiver for transceiver in config ['transceivers']['transceiver']] + if 'channels' in config and len(config['channels']) > 0: + #channels = [channel['name']['index'] for channel in config['channels']] + for channel_params in config['channels']: + channels.append({ + # "opticalconfig_uuid":opticalconfig_uuid, + "transponder_uuid" : transponder_get_uuid(device_id), + "channel_uuid" : channel_get_uuid(channel_params['name']['index'],device_uuid), + "channel_name" : channel_params['name']['index'], + "frequency" : int(channel_params["frequency"]) if "frequency" in channel_params else 0, + "operational_mode" : int(channel_params["operational-mode"]) if "operational-mode" in channel_params else 0, + "target_output_power": channel_params["target-output-power"] if "target-output-power" in channel_params else '', + "status" : channel_params["status"] if "status" in channel_params else "" + }) + + transponder.append({ + "transponder_uuid" : transponder_get_uuid(device_id), + "transcievers" : transceivers, + "interfaces" : None, + "opticalconfig_uuid": opticalconfig_uuid, + }) + + if config_type == DeviceTypeEnum.OPTICAL_ROADM._value_: + if channel_namespace is None and 'channel_namespace' in config: + channel_namespace=config['channel_namespace'] + if 'media_channels' in config and len(config['media_channels']) > 0: + #channels = [channel['name']['index'] for channel in config['channels']] + channel_num = 0 + for channel_params in config['media_channels']: + channel_index = channel_params['channel_index'] if channel_params['channel_index'] is not None else None + channels.append({ + # "opticalconfig_uuid":opticalconfig_uuid, + "roadm_uuid" : roadm_get_uuid(device_id), + "channel_uuid" : channel_get_uuid(f'media_channel_{channel_index}',device_uuid), + "band_name" : channel_params['band_name'], + "lower_frequency" : int(channel_params["lower_frequency"]) if "lower_frequency" in channel_params else 0, + "upper_frequency" : int(channel_params["upper_frequency"]) if "upper_frequency" in channel_params else 0, + "dest_port" : channel_params["dest_port"] if "dest_port" in channel_params else '', + "src_port" : channel_params["src_port"] if "src_port" in channel_params else '', + "status" : channel_params["status"] if "status" in channel_params else "", + "type" : 'media_channel', + "optical_band_parent" : str(channel_params['optical_band_parent']) if 'optical_band_parent' in channel_params else None, + "channel_index" : channel_index if channel_index is not None else None, + }) + if 'optical_bands' in config and len(config['optical_bands']) > 0: + #channels = [channel['name']['index'] for channel in config['channels']] + channel_num = 0 + for channel_params in config['optical_bands']: + channel_num += 1 + channels.append({ + # "opticalconfig_uuid":opticalconfig_uuid, + "roadm_uuid" : roadm_get_uuid(device_id), + "channel_uuid" : channel_get_uuid(f'optical_bands_{channel_num}',device_uuid), + "band_name" : channel_params['band_name'], + "lower_frequency" : int(channel_params["lower_frequency"]) if "lower_frequency" in channel_params else 0, + "upper_frequency" : int(channel_params["upper_frequency"]) if "upper_frequency" in channel_params else 0, + "dest_port" : channel_params["dest_port"] if "dest_port" in channel_params else '', + "src_port" : channel_params["src_port"] if "src_port" in channel_params else '', + "status" : channel_params["status"] if "status" in channel_params else "", + "type" : 'optical_band', + "channel_index" : channel_params['channel_index'] if 'channel_index' in channel_params else None, + "optical_band_parent": None + }) + + roadms.append({ + "roadm_uuid" : roadm_get_uuid(device_id), + "opticalconfig_uuid" : opticalconfig_uuid, + }) + + OpticalConfig_data.append({ + "opticalconfig_uuid" : opticalconfig_uuid, + # "transcievers" : transceivers, + # "interfaces" :"", + "channel_namespace" : channel_namespace , + "endpoints" : [json.dumps(endpoint) for endpoint in config.get("endpoints",[])], + "device_uuid": device_uuid, + "type":config_type + }) + + def callback(session:Session)->bool: + stmt = insert(OpticalConfigModel).values(OpticalConfig_data) + stmt = stmt.on_conflict_do_update( + index_elements=[OpticalConfigModel.opticalconfig_uuid], + set_=dict( + channel_namespace=stmt.excluded.channel_namespace + ) + ) + stmt = stmt.returning(OpticalConfigModel.opticalconfig_uuid) + opticalconfig_id = session.execute(stmt).fetchone() + if config_type == DeviceTypeEnum.OPTICAL_TRANSPONDER._value_: + if (len(transponder)>0): + stmt = insert(TransponderTypeModel).values(transponder) + stmt = stmt.on_conflict_do_update( + index_elements=[TransponderTypeModel.transponder_uuid], + set_=dict( + transcievers= stmt.excluded.transcievers , + ) + + ) + stmt = stmt.returning(TransponderTypeModel.transponder_uuid) + transponder_id = session.execute(stmt).fetchone() + + if (len(channels) > 0): + stmt = insert(OpticalChannelModel).values(channels) + stmt = stmt.on_conflict_do_update( + index_elements=[OpticalChannelModel.channel_uuid], + set_=dict( + channel_name= stmt.excluded.channel_name , + frequency = stmt.excluded.frequency, + operational_mode=stmt.excluded.operational_mode, + target_output_power=stmt.excluded.target_output_power, + ) + ) + stmt = stmt.returning(OpticalChannelModel.channel_uuid) + opticalChannel_id = session.execute(stmt).fetchone() + + if config_type == DeviceTypeEnum.OPTICAL_ROADM._value_: + if len(roadms) > 0: + stmt = insert(RoadmTypeModel).values(roadms) + stmt = stmt.on_conflict_do_update( + index_elements=[RoadmTypeModel.roadm_uuid], + set_=dict( + circuits=stmt.excluded.circuits + ) + ) + stmt = stmt.returning(RoadmTypeModel.roadm_uuid) + roadm_id = session.execute(stmt).fetchone() + + if (channels is not None and len(channels) > 0): + stmt = insert(ChannelModel).values(channels) + stmt = stmt.on_conflict_do_update( + index_elements=[ChannelModel.channel_uuid ], + set_=dict( + band_name = stmt.excluded.band_name , + lower_frequency = stmt.excluded.lower_frequency, + upper_frequency = stmt.excluded.upper_frequency, + type = stmt.excluded.type, + status = stmt.excluded.status, + dest_port = stmt.excluded.dest_port, + src_port = stmt.excluded.src_port, + channel_index = stmt.excluded.channel_index, + optical_band_parent = stmt.excluded.optical_band_parent + ) + ) + stmt = stmt.returning(ChannelModel.channel_uuid) + opticalChannel_id = session.execute(stmt).fetchone() + + opticalconfig_id = run_transaction(sessionmaker(bind=db_engine), callback) + return {'opticalconfig_uuid': opticalconfig_id} + +def update_opticalconfig(db_engine : Engine, request : OpticalConfig): + opticalconfig_id = OpticalConfigId() + device_id = request.device_id + device_uuid = request.device_id.device_uuid.uuid + channels = [] + transponder=[] + roadms=[] + channel_namespace= None + OpticalConfig_data = [] + config_type=None + #is_transpondre=False + opticalconfig_uuid =opticalconfig_get_uuid(device_id) + + if request.config : + config = json.loads(request.config) + + if 'new_config' in config: + if 'type' in config: + config_type = config['type'] + if "type" in config['new_config'] and config_type is None: + config_type = config['new_config']["type"] + if 'channel_namespace' in config['new_config']: + channel_namespace = config['new_config'] ['channel_namespace'] + + if config_type == DeviceTypeEnum.OPTICAL_TRANSPONDER._value_: + is_transpondre = True + transceivers = [] + if channel_namespace is None and 'channel_namespace' in config: + channel_namespace=config['channel_namespace'] + + if 'transceivers' in config['new_config'] and len(config['new_config']['transceivers']['transceiver']) > 0: + transceivers = [transceiver for transceiver in config['new_config'] ['transceivers']['transceiver']] + + if 'channels' in config['new_config'] and len(config['new_config']['channels']) > 0: + #channels = [channel['name']['index'] for channel in config['channels']] + + for channel_params in config['new_config']['channels']: + channels.append({ + # "opticalconfig_uuid":opticalconfig_uuid, + "transponder_uuid" : transponder_get_uuid(device_id), + "channel_uuid" : channel_get_uuid(channel_params['name']['index'],device_uuid), + "channel_name" : channel_params['name']['index'], + "frequency" : int(channel_params["frequency"]) if "frequency" in channel_params else 0, + "operational_mode" : int(channel_params["operational-mode"]) if "operational-mode" in channel_params else 0, + "target_output_power" : channel_params["target-output-power"] if "target-output-power" in channel_params else '', + "status" : channel_params["status"] if "status" in channel_params else "" + }) + elif 'flow_handled' in config and 'new_config' in config : + target_config = config['new_config'] + dest_pair = None + src = None + dst = None + src_pair = config['flow_handled'][0] + + src, dst = src_pair + if src_pair is None and len(config['flow_handled'])>1 : + dest_pair = config['flow_handled'][1] + src, dst = dest_pair + channel_index = src if src is not None and src !='0' else dst + channel_name = f"channel-{channel_index}" + channels.append({ + # "opticalconfig_uuid":opticalconfig_uuid, + "transponder_uuid" : transponder_get_uuid(device_id), + "channel_uuid" : channel_get_uuid(channel_name,device_uuid), + "channel_name" : channel_name, + "frequency" : int(target_config["frequency"]) if "frequency" in target_config else 0, + "operational_mode" : int(target_config["operational-mode"]) if "operational-mode" in target_config else 0, + "target_output_power" : target_config["target-output-power"] if "target-output-power" in target_config else '', + "status" : target_config["status"] if "status" in target_config else "" + }) + + transponder.append({ + "transponder_uuid" : transponder_get_uuid(device_id), + "transcievers" : transceivers, + "interfaces" : None, + "opticalconfig_uuid": opticalconfig_uuid, + }) + + if config_type == DeviceTypeEnum.OPTICAL_ROADM._value_: + if channel_namespace is None and 'channel_namespace' in config['new_config']: + channel_namespace=config['new_config']['channel_namespace'] + if 'is_opticalband' in config and not config['is_opticalband']: + #channels = [channel['name']['index'] for channel in config['channels']] + if 'flow_handled' in config and len(config['flow_handled'])>0 : + num = 0 + flow_id = config["new_config"]["flow_id"] if 'flow_id' in config['new_config'] else None + for flow in config['flow_handled']: + src_port, dest_port = flow + channel_index = flow_id + num + num += 1 + channels.append({ + # "opticalconfig_uuid":opticalconfig_uuid, + "roadm_uuid" : roadm_get_uuid(device_id), + "channel_uuid" : channel_get_uuid(f'media_channel_{channel_index}',device_uuid), + "band_name" : config['new_config']['band_type'], + "lower_frequency" : int(int(config['new_config']['frequency']) - (int(config['new_config']['band'])/2)) if "frequency" in config['new_config'] else 0, + "upper_frequency" : int(int(config['new_config']['frequency']) + (int(config['new_config']['band'])/2)) if "frequency" in config['new_config'] else 0, + "dest_port" : dest_port, + "src_port" : src_port, + "status" : config['new_config']["status"] if "status" in config['new_config'] else "", + "type" : 'media_channel', + "optical_band_parent" : str( config['new_config']['ob_id']) if 'ob_id' in config['new_config'] else None, + "channel_index" : str(channel_index) if channel_index is not None else None + }) + if 'is_opticalband' in config and config['is_opticalband']: + #channels = [channel['name']['index'] for channel in config['channels']] + if 'flow_handled' in config and len(config['flow_handled']) > 0: + ob_id = config['new_config']['ob_id'] if 'ob_id' in config['new_config'] else None + num = 0 + for flow in config['flow_handled']: + src_port, dest_port = flow + channel_index = ob_id + num + num += 1 + channels.append({ + # "opticalconfig_uuid":opticalconfig_uuid, + "roadm_uuid" : roadm_get_uuid(device_id), + "channel_uuid" : channel_get_uuid(f'optical_bands_{channel_index}',device_uuid), + "band_name" : config['new_config']['band_type'], + "lower_frequency" : int(config['new_config']["low-freq"]) if "low-freq" in config['new_config'] else 0, + "upper_frequency" : int(config['new_config']["up-freq"]) if "up-freq" in config['new_config'] else 0, + "dest_port" : dest_port, + "src_port" : src_port, + "status" : config['new_config']["status"] if "status" in config['new_config'] else "", + "type" : 'optical_band', + "channel_index" : str( channel_index) if channel_index is not None else None + }) + + roadms.append({ + "roadm_uuid" : roadm_get_uuid(device_id), + "opticalconfig_uuid": opticalconfig_uuid, + }) + + OpticalConfig_data.append({ + "opticalconfig_uuid": opticalconfig_uuid, + # "transcievers" : transceivers, + # "interfaces" :"", + "channel_namespace" : channel_namespace , + "endpoints" : [json.dumps(endpoint) for endpoint in config['new_config'].get("endpoints",[])], + "device_uuid" : device_uuid, + "type" : config_type + }) def callback(session:Session)->bool: - stmt = insert(OpticalConfigModel).values(my_config_data) + stmt = insert(OpticalConfigModel).values(OpticalConfig_data) stmt = stmt.on_conflict_do_update( index_elements=[OpticalConfigModel.opticalconfig_uuid], set_=dict( @@ -71,11 +371,66 @@ def set_opticalconfig(db_engine : Engine, request : OpticalConfig): ) ) stmt = stmt.returning(OpticalConfigModel.opticalconfig_uuid) - id = session.execute(stmt).fetchone() + opticalconfig_id = session.execute(stmt).fetchone() + if config_type == DeviceTypeEnum.OPTICAL_TRANSPONDER._value_: + if (len(transponder)>0): + stmt = insert(TransponderTypeModel).values(transponder) + stmt = stmt.on_conflict_do_update( + index_elements=[TransponderTypeModel.transponder_uuid], + set_=dict( + transcievers= stmt.excluded.transcievers, + ) + ) + stmt = stmt.returning(TransponderTypeModel.transponder_uuid) + transponder_id = session.execute(stmt).fetchone() + + if len(channels) > 0: + stmt = insert(OpticalChannelModel).values(channels) + stmt = stmt.on_conflict_do_update( + index_elements=[OpticalChannelModel.channel_uuid ], + set_=dict( + channel_name= stmt.excluded.channel_name , + frequency = stmt.excluded.frequency, + operational_mode=stmt.excluded.operational_mode, + target_output_power=stmt.excluded.target_output_power, + status = stmt.excluded.status, + ) + ) + stmt = stmt.returning(OpticalChannelModel.channel_uuid) + opticalChannel_id = session.execute(stmt).fetchone() + if config_type == DeviceTypeEnum.OPTICAL_ROADM._value_: + if len(roadms) > 0: + stmt = insert(RoadmTypeModel).values(roadms) + stmt = stmt.on_conflict_do_update( + index_elements=[RoadmTypeModel.roadm_uuid], + set_=dict( + circuits=stmt.excluded.circuits + ) + ) + stmt = stmt.returning(RoadmTypeModel.roadm_uuid) + roadm_id = session.execute(stmt).fetchone() + + if (channels is not None and len(channels) > 0): + stmt = insert(ChannelModel).values(channels) + stmt = stmt.on_conflict_do_update( + index_elements=[ChannelModel.channel_uuid ], + set_=dict( + band_name= stmt.excluded.band_name , + lower_frequency = stmt.excluded.lower_frequency, + upper_frequency = stmt.excluded.upper_frequency, + type=stmt.excluded.type, + status=stmt.excluded.status, + dest_port=stmt.excluded.dest_port, + src_port=stmt.excluded.src_port, + ) + ) + stmt = stmt.returning(ChannelModel.channel_uuid) + opticalChannel_id = session.execute(stmt).fetchone() + opticalconfig_id = run_transaction(sessionmaker(bind=db_engine), callback) return {'opticalconfig_uuid': opticalconfig_id} -def select_opticalconfig(db_engine:Engine,request:OpticalConfigId): +def select_opticalconfig(db_engine : Engine, request : OpticalConfigId): def callback(session : Session) -> OpticalConfig: result = OpticalConfig() stmt = session.query(OpticalConfigModel) @@ -83,6 +438,102 @@ def select_opticalconfig(db_engine:Engine,request:OpticalConfigId): obj = stmt.first() if obj is not None: result.config = json.dumps(obj.dump()) - result.opticalconfig_id.opticalconfig_uuid = obj.opticalconfig_uuid + ids_obj = obj.dump_id() + result.opticalconfig_id.opticalconfig_uuid = ids_obj["opticalconfig_uuid"] + result.device_id.device_uuid.uuid=ids_obj["device_uuid"] return result return run_transaction(sessionmaker(bind=db_engine, expire_on_commit=False), callback) + +def delete_opticalconfig(db_engine : Engine, messagebroker : MessageBroker, request : OpticalConfigId): + opticalconfig_uuid = request.opticalconfig_uuid + def callback(session : Session): + #query = session.query(OpticalConfigModel) + num_deleted = session.query(OpticalConfigModel).filter_by(opticalconfig_uuid=opticalconfig_uuid).delete() + return num_deleted > 0 + deleted = run_transaction(sessionmaker(bind=db_engine), callback) + if deleted: + notify_event_opticalconfig(messagebroker, EventTypeEnum.EVENTTYPE_REMOVE, opticalconfig_uuid) + return Empty() + +def delete_opticalchannel(db_engine : Engine, messagebroker : MessageBroker, request : OpticalConfig): + config = json.loads(request.config) + device_id = request.device_id + device_uuid = request.device_id.device_uuid.uuid + opticalconfig_uuid = request.opticalconfig_id.opticalconfig_uuid + channels = [] + config_type = None + + if "type" in config : + config_type= config["type"] + if 'new_config' in config: + if config_type == DeviceTypeEnum.OPTICAL_TRANSPONDER._value_: + for flow in config['flow']: + src,dest = flow + channel_index= src if src is not None and src!='0' else dest + channel_name= f"channel-{channel_index}" + channels.append({ + # "opticalconfig_uuid":opticalconfig_uuid, + "transponder_uuid" : transponder_get_uuid(device_id), + "channel_uuid" : channel_get_uuid(channel_name ,device_uuid), + "channel_name" : channel_name , + "frequency" : 0, + "operational_mode" : None, + "target_output_power": None, + "status" : "DISABLED" + }) + elif config_type == DeviceTypeEnum.OPTICAL_ROADM._value_: + channel_num = 0 + if 'flow' in config : + if 'is_opticalband' in config: + if config['is_opticalband']: + ob_id = config['new_config']['ob_id'] if 'ob_id' in config['new_config'] else None + if len(config['flow']) == 0: + channel_index = ob_id + channel_num + channel_name = f'optical_bands_{channel_index}' + channels.append(channel_get_uuid(channel_name, device_uuid)) + else: + for flow in config['flow']: + channel_index = ob_id+channel_num + channel_num +=1 + channel_name = f'optical_bands_{channel_index}' + channels.append(channel_get_uuid(channel_name, device_uuid)) + else: + if config['flow'] == 0: + channel_num = 1 + channel_name = f'media_channel_{channel_num}' + channels.append(channel_get_uuid(channel_name, device_uuid)) + else: + for flow in config['flow']: + channel_num += 1 + channel_name = f'media_channel_{channel_num}' + channels.append(channel_get_uuid(channel_name, device_uuid)) + + def callback(session : Session): + all_suceed = [] + if config_type == DeviceTypeEnum.OPTICAL_ROADM._value_: + for channel_uuid in channels: + num_deleted = session.query(ChannelModel).filter_by(channel_uuid=channel_uuid).delete() + all_suceed.append(num_deleted > 0) + elif config_type == DeviceTypeEnum.OPTICAL_TRANSPONDER._value_: + if len(channels) > 0: + stmt = insert(OpticalChannelModel).values(channels) + stmt = stmt.on_conflict_do_update( + index_elements=[OpticalChannelModel.channel_uuid ], + set_=dict( + channel_name= stmt.excluded.channel_name , + frequency = stmt.excluded.frequency, + operational_mode=stmt.excluded.operational_mode, + target_output_power=stmt.excluded.target_output_power, + status=stmt.excluded.status + ) + ) + stmt = stmt.returning(OpticalChannelModel.channel_uuid) + opticalChannel_id = session.execute(stmt).fetchone() + all_suceed.append(True) + return all_suceed + + all_deleted = run_transaction(sessionmaker(bind=db_engine), callback) + for stat in all_deleted: + if not stat: return + notify_event_opticalconfig(messagebroker, EventTypeEnum.EVENTTYPE_REMOVE, opticalconfig_uuid) + return Empty() diff --git a/src/context/service/database/OpticalLink.py b/src/context/service/database/OpticalLink.py new file mode 100644 index 0000000000000000000000000000000000000000..93f43ef34497cb353f50a9b001da8ec4a6f5f92f --- /dev/null +++ b/src/context/service/database/OpticalLink.py @@ -0,0 +1,183 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime, logging +from sqlalchemy.dialects.postgresql import insert +from sqlalchemy.engine import Engine +from sqlalchemy.orm import Session, selectinload, sessionmaker +from sqlalchemy_cockroachdb import run_transaction +from typing import Dict, List, Optional, Set, Tuple +from common.proto.context_pb2 import ( + Empty, EventTypeEnum, OpticalLink, LinkId, OpticalLinkList, TopologyId +) +from common.message_broker.MessageBroker import MessageBroker +from common.method_wrappers.ServiceExceptions import NotFoundException +from common.tools.object_factory.Link import json_link_id +from .models.OpticalLinkModel import OpticalLinkModel,OpticalLinkEndPointModel +from .models.TopologyModel import TopologyOpticalLinkModel, TopologyModel +from .uuids.EndPoint import endpoint_get_uuid +from .uuids.Link import link_get_uuid +from .uuids.Topology import topology_get_uuid +from .Events import notify_event_link + +LOGGER = logging.getLogger(__name__) + + +def optical_link_list_objs(db_engine : Engine) -> OpticalLinkList: + def callback(session : Session) -> List[Dict]: + obj_list : List[OpticalLinkModel] = session.query(OpticalLinkModel)\ + .options(selectinload(OpticalLinkModel.opticallink_endpoints))\ + .all() + return [obj.dump() for obj in obj_list] + links = run_transaction(sessionmaker(bind=db_engine), callback) + return OpticalLinkList(optical_links=links) + +def optical_link_get(db_engine : Engine, request : LinkId) -> OpticalLink: + link_uuid = link_get_uuid(request, allow_random=False) + def callback(session : Session) -> Optional[Dict]: + obj : Optional[OpticalLinkModel] = session.query(OpticalLinkModel)\ + .options(selectinload(OpticalLinkModel.opticallink_endpoints))\ + .filter_by(opticallink_uuid=link_uuid).one_or_none() + return None if obj is None else obj.dump() + obj = run_transaction(sessionmaker(bind=db_engine), callback) + if obj is None: + raw_link_uuid = request.link_uuid.uuid + raise NotFoundException('Optical Link', raw_link_uuid, extra_details=[ + 'link_uuid generated was: {:s}'.format(link_uuid) + ]) + return OpticalLink(**obj) + +def optical_link_set(db_engine : Engine, messagebroker : MessageBroker, request : OpticalLink) -> LinkId: + raw_link_uuid = request.link_id.link_uuid.uuid + raw_link_name = request.name + link_name = raw_link_uuid if len(raw_link_name) == 0 else raw_link_name + link_uuid = link_get_uuid(request.link_id, link_name=link_name, allow_random=True) + + now = datetime.datetime.utcnow() + + # By default, always add link to default Context/Topology + topology_uuids : Set[str] = set() + related_topologies : List[Dict] = list() + _,topology_uuid = topology_get_uuid(TopologyId(), allow_random=False, allow_default=True) + related_topologies.append({ + 'topology_uuid': topology_uuid, + 'optical_link_uuid' : link_uuid, + }) + topology_uuids.add(topology_uuid) + + link_endpoints_data : List[Dict] = list() + + for i,endpoint_id in enumerate(request.link_endpoint_ids): + endpoint_topology_uuid, endpoint_device_uuid, endpoint_uuid = endpoint_get_uuid( + endpoint_id, endpoint_name="", allow_random=True) + + link_endpoints_data.append({ + 'link_uuid' : link_uuid, + 'endpoint_uuid': endpoint_uuid, + + }) + + if endpoint_topology_uuid not in topology_uuids: + related_topologies.append({ + 'topology_uuid': endpoint_topology_uuid, + 'optical_link_uuid' : link_uuid, + }) + topology_uuids.add(endpoint_topology_uuid) + + optical_link_data = [{ + 'opticallink_uuid' : link_uuid, + 'name' : link_name, + 'created_at' : now, + 'updated_at' : now, + 'length' : request.optical_details.length, + "src_port" : request.optical_details.src_port, + "dst_port" : request.optical_details.dst_port, + "local_peer_port" : request.optical_details.local_peer_port, + "remote_peer_port" : request.optical_details.remote_peer_port, + "used" : request.optical_details.used, + "c_slots" : request.optical_details.c_slots , + "l_slots" : request.optical_details.l_slots, + "s_slots" : request.optical_details.s_slots, + }] + + def callback(session : Session) -> Tuple[bool, List[Dict]]: + stmt = insert(OpticalLinkModel).values(optical_link_data) + stmt = stmt.on_conflict_do_update( + index_elements=[OpticalLinkModel.opticallink_uuid], + set_=dict( + updated_at = stmt.excluded.updated_at, + src_port = stmt.excluded.src_port, + dst_port = stmt.excluded.dst_port, + local_peer_port = stmt.excluded.local_peer_port, + remote_peer_port = stmt.excluded.remote_peer_port, + used = stmt.excluded.used , + c_slots = stmt.excluded.c_slots, + l_slots = stmt.excluded.l_slots, + s_slots = stmt.excluded.s_slots + ) + ) + stmt = stmt.returning(OpticalLinkModel.created_at, OpticalLinkModel.updated_at) + created_at,updated_at = session.execute(stmt).fetchone() + updated = updated_at > created_at + + updated_endpoints = False + if len(link_endpoints_data) > 0: + stmt = insert(OpticalLinkEndPointModel).values(link_endpoints_data) + stmt = stmt.on_conflict_do_nothing( + index_elements=[OpticalLinkEndPointModel.link_uuid, OpticalLinkEndPointModel.endpoint_uuid] + ) + link_endpoint_inserts = session.execute(stmt) + updated_endpoints = int(link_endpoint_inserts.rowcount) > 0 + + if not updated or len(related_topologies) > 1: + # Only update topology-link relations when link is created (not updated) or when endpoint_ids are + # modified (len(related_topologies) > 1). + stmt = insert(TopologyOpticalLinkModel).values(related_topologies) + stmt = stmt.on_conflict_do_nothing( + index_elements=[TopologyOpticalLinkModel.topology_uuid, TopologyOpticalLinkModel.optical_link_uuid] + ) + stmt = stmt.returning(TopologyOpticalLinkModel.topology_uuid) + topology_uuids = session.execute(stmt).fetchall() + + #LOGGER.warning('RAW topology_uuids={:s}'.format(str(topology_uuids))) + if len(topology_uuids) > 0: + topology_uuids = [topology_uuid[0] for topology_uuid in topology_uuids] + #LOGGER.warning('NEW topology_uuids={:s}'.format(str(topology_uuids))) + query = session.query(TopologyModel) + query = query.filter(TopologyModel.topology_uuid.in_(topology_uuids)) + link_topologies : List[TopologyModel] = query.all() + link_topology_ids = [obj.dump_id() for obj in link_topologies] + #LOGGER.warning('link_topology_ids={:s}'.format(str(link_topology_ids))) + return updated or updated_endpoints + + updated = run_transaction(sessionmaker(bind=db_engine), callback ) + link_id = json_link_id(link_uuid) + event_type = EventTypeEnum.EVENTTYPE_UPDATE if updated else EventTypeEnum.EVENTTYPE_CREATE + notify_event_link(messagebroker, event_type, link_id) + + return LinkId(**link_id) + +def optical_link_delete(db_engine : Engine, messagebroker : MessageBroker, request : LinkId) -> Empty: + link_uuid = link_get_uuid(request, allow_random=False) + + def callback(session : Session) -> bool: + num_deleted = session.query(OpticalLinkModel).filter_by(opticallink_uuid=link_uuid).delete() + return num_deleted > 0 + deleted = run_transaction(sessionmaker(bind=db_engine), callback) + + link_id = json_link_id(link_uuid) + if deleted: + notify_event_link(messagebroker, EventTypeEnum.EVENTTYPE_REMOVE, link_id) + + return Empty() diff --git a/src/context/service/database/Topology.py b/src/context/service/database/Topology.py index e1c59ea118915e2ad0d69d4b8cacf59c354b8297..7dc1b60f977fb4447fa86a47a48d0335270c8f1a 100644 --- a/src/context/service/database/Topology.py +++ b/src/context/service/database/Topology.py @@ -27,7 +27,8 @@ from common.tools.object_factory.Topology import json_topology_id from context.Config import ALLOW_EXPLICIT_ADD_DEVICE_TO_TOPOLOGY, ALLOW_EXPLICIT_ADD_LINK_TO_TOPOLOGY from .models.DeviceModel import DeviceModel from .models.LinkModel import LinkModel -from .models.TopologyModel import TopologyDeviceModel, TopologyLinkModel, TopologyModel +from .models.OpticalLinkModel import OpticalLinkModel +from .models.TopologyModel import TopologyDeviceModel, TopologyLinkModel, TopologyModel, TopologyOpticalLinkModel from .uuids.Context import context_get_uuid from .uuids.Device import device_get_uuid from .uuids.Link import link_get_uuid @@ -79,6 +80,7 @@ def topology_get_details(db_engine : Engine, request : TopologyId) -> TopologyDe obj : Optional[TopologyModel] = session.query(TopologyModel)\ .options(selectinload(TopologyModel.topology_devices, TopologyDeviceModel.device, DeviceModel.endpoints))\ .options(selectinload(TopologyModel.topology_links, TopologyLinkModel.link, LinkModel.link_endpoints))\ + .options(selectinload(TopologyModel.topology_optical_links, TopologyOpticalLinkModel.optical_link, OpticalLinkModel.opticallink_endpoints))\ .filter_by(topology_uuid=topology_uuid).one_or_none() #.options(selectinload(DeviceModel.components))\ return None if obj is None else obj.dump_details() diff --git a/src/context/service/database/models/DeviceModel.py b/src/context/service/database/models/DeviceModel.py index 2aeb90dfdeaa57ba96ff9c9ed095f363c5f8f0fb..0613caba6a433d4440908f4f796c26349f8d9af7 100644 --- a/src/context/service/database/models/DeviceModel.py +++ b/src/context/service/database/models/DeviceModel.py @@ -34,10 +34,11 @@ class DeviceModel(_Base): updated_at = Column(DateTime, nullable=False) #topology_devices = relationship('TopologyDeviceModel', back_populates='device') - config_rules = relationship('DeviceConfigRuleModel', passive_deletes=True) # lazy='joined', back_populates='device' - endpoints = relationship('EndPointModel', passive_deletes=True) # lazy='joined', back_populates='device' - components = relationship('ComponentModel', passive_deletes=True) # lazy='joined', back_populates='device' - controller = relationship('DeviceModel', remote_side=[device_uuid], passive_deletes=True) # lazy='joined', back_populates='device' + config_rules = relationship('DeviceConfigRuleModel', passive_deletes=True) # lazy='joined', back_populates='device' + endpoints = relationship('EndPointModel', passive_deletes=True) # lazy='joined', back_populates='device' + components = relationship('ComponentModel', passive_deletes=True) # lazy='joined', back_populates='device' + controller = relationship('DeviceModel', remote_side=[device_uuid], passive_deletes=True) # lazy='joined', back_populates='device' + optical_config = relationship('OpticalConfigModel', passive_deletes=True) def dump_id(self) -> Dict: return {'device_uuid': {'uuid': self.device_uuid}} diff --git a/src/context/service/database/models/EndPointModel.py b/src/context/service/database/models/EndPointModel.py index 6355f14af453b3695bb688285aa19c8641aac4c0..37a03393bd6a571d8587be7f9c345097971d5111 100644 --- a/src/context/service/database/models/EndPointModel.py +++ b/src/context/service/database/models/EndPointModel.py @@ -33,8 +33,9 @@ class EndPointModel(_Base): updated_at = Column(DateTime, nullable=False) endpoint_location = Column(String, nullable=True) - device = relationship('DeviceModel', back_populates='endpoints') # lazy='selectin' - topology = relationship('TopologyModel', lazy='selectin') + device = relationship('DeviceModel', back_populates='endpoints') # lazy='selectin' + topology = relationship('TopologyModel', lazy='selectin') + optical_link_endpoints = relationship('OpticalLinkEndPointModel', back_populates='endpoint' ) #link_endpoints = relationship('LinkEndPointModel', back_populates='endpoint' ) #service_endpoints = relationship('ServiceEndPointModel', back_populates='endpoint' ) diff --git a/src/context/service/database/models/OpticalConfig/OpticalConfigModel.py b/src/context/service/database/models/OpticalConfig/OpticalConfigModel.py new file mode 100644 index 0000000000000000000000000000000000000000..fa77898337055c88edc120aa53e517ffcd6ed2b1 --- /dev/null +++ b/src/context/service/database/models/OpticalConfig/OpticalConfigModel.py @@ -0,0 +1,64 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +from sqlalchemy import Column, String, ForeignKey +from sqlalchemy.dialects.postgresql import ARRAY +from sqlalchemy.orm import relationship +from context.service.database.models._Base import _Base + +class OpticalConfigModel(_Base): + __tablename__ = 'optical_config' + opticalconfig_uuid = Column(String, primary_key=True) + channel_namespace = Column(String, nullable=True) + endpoints = Column(ARRAY(String), nullable=True) + type = Column(String,nullable=False) + + # transcievers = Column(ARRAY(String), nullable=True) + # interfaces = Column(String, nullable=True) + # channels = relationship("OpticalChannelModel") + transponders = relationship("TransponderTypeModel") + roadms = relationship("RoadmTypeModel") + + device_uuid = Column(ForeignKey("device.device_uuid",ondelete="CASCADE"),index=True ,nullable=False) + device = relationship("DeviceModel", back_populates='optical_config') + + def dump_id (self ): + return { + "opticalconfig_uuid":self.opticalconfig_uuid, + "device_uuid" :self.device_uuid + } + + def dump(self): + obj={ + # "channels" : [channel.dump() for channel in self.channels], + # "transceivers" : {"transceiver": [transciever for transciever in self.transcievers]}, + # "interfaces" : {"interface":json.loads(self.interfaces) if self.interfaces else ''}, + "channel_namespace" : self.channel_namespace, + "endpoints" : [json.loads(endpoint) for endpoint in self.endpoints if endpoint], + "device_name" : self.device.device_name, + "type" : self.type + } + if self.type =="optical-transponder" : + channels= [transponer.dump() for transponer in self.transponders ][0] + obj['channels']=channels['channels'] if 'channels' in channels else None + obj['transceivers']=channels['transceivers'] if 'transceivers' in channels else None + obj['interfaces']=channels['interfaces'] if 'interfaces' in channels else None + obj['trasponder_uuid']=channels['trasponder_uuid'] if 'trasponder_uuid' in channels else None + + if self.type =="optical-roadm" : + channels=[roadms.dump() for roadms in self.roadms ][0] + obj['channels']=channels['channels'] if 'channels' in channels else None + obj['roadm_uuid']=channels['roadm_uuid'] if 'roadm_uuid' in channels else None + return obj diff --git a/src/context/service/database/models/OpticalConfig/RoadmModel.py b/src/context/service/database/models/OpticalConfig/RoadmModel.py new file mode 100644 index 0000000000000000000000000000000000000000..31ebf4cfabb2b91252b3c80884d47b93d07db033 --- /dev/null +++ b/src/context/service/database/models/OpticalConfig/RoadmModel.py @@ -0,0 +1,74 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from sqlalchemy import Column, String, Integer , ForeignKey +from sqlalchemy.orm import relationship +from context.service.database.models._Base import _Base + +class RoadmTypeModel (_Base): + __tablename__ = 'roadm_type' + roadm_uuid = Column(String, primary_key=True) + circuits = Column(String, nullable=True) + opticalconfig_uuid = Column(ForeignKey('optical_config.opticalconfig_uuid', ondelete='CASCADE'), index=True, nullable=False) + channels = relationship("ChannelModel") + opticalconfig = relationship('OpticalConfigModel', back_populates='roadms') + + def dump_id (self): + return { + "roadm_uuid" : self.roadm_uuid + } + + def dump (self): + return { + "channels" : [channel.dump() for channel in self.channels], + "roadm_uuid" : self.dump_id(), + } + +class ChannelModel(_Base): + __tablename__ = 'channel' + + channel_uuid = Column(String, primary_key=True) + band_name = Column(String, nullable=True) + lower_frequency = Column(Integer, nullable=True) + upper_frequency = Column(Integer, nullable=True) + channel_index = Column(String, nullable=True) + status = Column(String, nullable=True) + src_port = Column(String, nullable=True) + dest_port = Column(String, nullable=True) + type = Column(String, nullable=False) + optical_band_parent = Column(String, nullable=True) + roadm_uuid = Column(ForeignKey('roadm_type.roadm_uuid', ondelete='CASCADE'), nullable=False) + + roadm = relationship('RoadmTypeModel',back_populates='channels') + + # opticalconfig_uuid = Column(ForeignKey('optical_config.opticalconfig_uuid', ondelete='CASCADE'), primary_key=True) + # opticalconfig = relationship('OpticalConfigModel', back_populates='channels') + + def dump_id (self ): + return { + "channel_uuid": self.channel_uuid + } + + def dump(self): + return { + "band_name" : self.band_name, + "lower_frequency" : self.lower_frequency, + "upper_frequency" : self.upper_frequency, + "type" : self.type, + "src_port" : self.src_port, + "dest_port" : self.dest_port, + "status" : self.status, + "optical_band_parent" : self.optical_band_parent, + "channel_index" : self.channel_index, + } diff --git a/src/context/service/database/models/OpticalConfig/TransponderModel.py b/src/context/service/database/models/OpticalConfig/TransponderModel.py new file mode 100644 index 0000000000000000000000000000000000000000..33e509d354b257d380d61c9795e86fdce7a68348 --- /dev/null +++ b/src/context/service/database/models/OpticalConfig/TransponderModel.py @@ -0,0 +1,72 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +from sqlalchemy import Column, String, Integer , ForeignKey +from sqlalchemy.dialects.postgresql import ARRAY +from sqlalchemy.orm import relationship +from context.service.database.models._Base import _Base + +class TransponderTypeModel (_Base): + __tablename__ = 'transponder_type' + + transponder_uuid = Column(String, primary_key=True) + transcievers = Column(ARRAY(String), nullable=True) + interfaces = Column(String, nullable=True) + opticalconfig_uuid = Column(ForeignKey('optical_config.opticalconfig_uuid', ondelete='CASCADE'), index=True, nullable=False) + + channels = relationship("OpticalChannelModel") + opticalconfig = relationship('OpticalConfigModel', back_populates='transponders') + + def dump_id (self): + return { + "transponder_uuid": self.transponder_uuid + } + + def dump (self): + return { + "channels" : [channel.dump() for channel in self.channels], + "transceivers" : {"transceiver": [transciever for transciever in self.transcievers]}, + "interfaces" : {"interface":json.loads(self.interfaces) if self.interfaces else ''}, + "trasponder_uuid" : self.dump_id() + } + +class OpticalChannelModel(_Base): + __tablename__ = 'optical_channel' + + channel_uuid = Column(String, primary_key=True) + channel_name = Column(String, nullable=True) + frequency = Column(Integer, nullable=True) + operational_mode = Column(Integer, nullable=True) + status = Column(String, nullable=True) + target_output_power = Column(String, nullable=True) + transponder_uuid = Column(ForeignKey('transponder_type.transponder_uuid', ondelete='CASCADE'), nullable=False) + transponder = relationship('TransponderTypeModel', back_populates='channels') + + # opticalconfig_uuid = Column(ForeignKey('optical_config.opticalconfig_uuid', ondelete='CASCADE'), primary_key=True) + # opticalconfig = relationship('OpticalConfigModel', back_populates='channels') + + def dump_id (self ): + return { + "channel_uuid": self.channel_uuid + } + + def dump(self): + return { + "name" : {'index':self.channel_name}, + "frequency" : self.frequency, + "target-output-power" : self.target_output_power, + "operational-mode" : self.operational_mode, + "status" : self.status, + } diff --git a/src/context/service/database/models/OpticalConfig/__init__.py b/src/context/service/database/models/OpticalConfig/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..bbfc943b68af13a11e562abbc8680ade71db8f02 --- /dev/null +++ b/src/context/service/database/models/OpticalConfig/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/src/context/service/database/models/OpticalConfigModel.py b/src/context/service/database/models/OpticalConfigModel.py deleted file mode 100644 index d7274da17070e0f21774d8173abcfad12e1dcae7..0000000000000000000000000000000000000000 --- a/src/context/service/database/models/OpticalConfigModel.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import json -from sqlalchemy import Column, String, Integer -from sqlalchemy.dialects.postgresql import ARRAY -from ._Base import _Base - -class OpticalConfigModel(_Base): - __tablename__ = 'optical_config' - opticalconfig_uuid = Column(String, primary_key=True) - channels = Column(ARRAY(String), nullable=True) - transcievers = Column(ARRAY(String), nullable=True) - interfaces = Column(String, nullable=True) - channel_namespace = Column(String, nullable=True) - endpoints = Column(ARRAY(String), nullable=True) - frequency = Column(Integer, nullable=True) - operational_mode = Column(Integer, nullable=True) - output_power = Column(String, nullable=True) - - def dump(self): - return { - "channels" : [{'name': {'index': channel}} for channel in self.channels], - "transceivers" : {"transceiver": [transciever for transciever in self.transcievers]}, - "interfaces" : {"interface": json.loads(self.interfaces)}, - "channel_namespace" : self.channel_namespace, - "endpoints" : [json.loads(endpoint) for endpoint in self.endpoints], - "frequency" : self.frequency, - "output_power" : self.output_power, - "operational_mode" : self.operational_mode, - } diff --git a/src/context/service/database/models/OpticalLinkModel.py b/src/context/service/database/models/OpticalLinkModel.py new file mode 100644 index 0000000000000000000000000000000000000000..6db1b316160aeb3f01d6d70b74338ba67d9b528a --- /dev/null +++ b/src/context/service/database/models/OpticalLinkModel.py @@ -0,0 +1,74 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from sqlalchemy import Column, DateTime, ForeignKey, Integer, String ,Boolean +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import relationship +from typing import Dict +from ._Base import _Base +from .Slot import C_Slot ,S_Slot , L_Slot + +class OpticalLinkModel(_Base): + __tablename__ = 'opticallink' + + opticallink_uuid = Column(UUID(as_uuid=False), primary_key=True) + name = Column(String, nullable=False) + created_at = Column(DateTime, nullable=False) + updated_at = Column(DateTime, nullable=False) + length = Column(Integer, nullable=True) + src_port = Column(String, nullable=True) + dst_port = Column(String, nullable=True) + local_peer_port = Column(String, nullable=True) + remote_peer_port = Column(String, nullable=True) + used = Column(Boolean ,nullable=True) + c_slots = Column (C_Slot,nullable=True) + l_slots = Column (L_Slot,nullable=True) + s_slots = Column (S_Slot,nullable=True) + opticallink_endpoints = relationship('OpticalLinkEndPointModel') + topology_optical_links = relationship('TopologyOpticalLinkModel', back_populates='optical_link') + + def dump_id(self) -> Dict: + return {'link_uuid': {'uuid': self.opticallink_uuid}} + + def dump(self) -> Dict: + result = { + 'link_id' : self.dump_id(), + 'name' : self.name, + 'optical_details' : { + 'length' : self.length, + 'src_port' : self.src_port, + 'dst_port' : self.dst_port, + 'local_peer_port' : self.local_peer_port, + 'remote_peer_port' : self.remote_peer_port, + 'used' : self.used, + 'c_slots' : self.c_slots if self.c_slots is not None else {}, + 'l_slots' : self.l_slots if self.l_slots is not None else {}, + 's_slots' : self.s_slots if self.s_slots is not None else {}, + }, + 'link_endpoint_ids' : [ + optical_endpoint.endpoint.dump_id() + for optical_endpoint in self.opticallink_endpoints + ], + } + return result + +class OpticalLinkEndPointModel(_Base): + __tablename__ = 'opticallink_endpoint' + + link_uuid = Column(ForeignKey('opticallink.opticallink_uuid', ondelete='CASCADE' ), primary_key=True) + endpoint_uuid = Column(ForeignKey('endpoint.endpoint_uuid', ondelete='RESTRICT'), primary_key=True, index=True) + + optical_link = relationship('OpticalLinkModel', back_populates='opticallink_endpoints') + endpoint = relationship('EndPointModel', lazy='selectin') diff --git a/src/context/service/database/models/Slot.py b/src/context/service/database/models/Slot.py new file mode 100644 index 0000000000000000000000000000000000000000..3d8602bd40464eafa8a6234881b100c73134bed7 --- /dev/null +++ b/src/context/service/database/models/Slot.py @@ -0,0 +1,80 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from sqlalchemy.types import TypeDecorator, Integer + +class SlotType(TypeDecorator): + impl = Integer + + def process_bind_param(self, value, dialect): + if value is not None: + bin_num = "0b" + for i,(key,val) in enumerate(value.items()): + bin_num =bin_num + f"{val}" + int_num = int(bin_num,2) + return int_num + + def process_result_value(self, value, dialect): + if value is not None: + slot = dict() + bin_num = bin(value) + sliced_num = bin_num[2:] + for i in range(len(sliced_num)): + slot[str(i+1)]=int(sliced_num[i]) + return slot + +class C_Slot(SlotType): + start_point = 0 + + def process_result_value(self, value, dialect): + if value is not None: + slot = dict() + bin_num = bin(value) + sliced_num = bin_num[2:] + if (len(sliced_num) != 20) : + for i in range(0,20 - len(sliced_num)): + sliced_num = '0' + sliced_num + for i in range(len(sliced_num)): + slot[str(self.start_point+i+1)]=int(sliced_num[i]) + return slot + +class L_Slot (SlotType): + start_point = 100 + + def process_result_value(self, value, dialect): + if value is not None: + slot = dict() + bin_num = bin(value) + sliced_num = bin_num[2:] + if (len(sliced_num) != 20) : + for i in range(0,20 - len(sliced_num)): + sliced_num='0'+sliced_num + for i in range(len(sliced_num)): + slot[str(self.start_point+i+1)]=int(sliced_num[i]) + return slot + +class S_Slot (SlotType): + start_point = 500 + + def process_result_value(self, value, dialect): + if value is not None: + slot= dict() + bin_num = bin(value) + sliced_num=bin_num[2:] + if (len(sliced_num) != 20) : + for i in range(0,20 - len(sliced_num)): + sliced_num='0'+sliced_num + for i in range(len(sliced_num)): + slot[str(self.start_point+i+1)]=int(sliced_num[i]) + return slot diff --git a/src/context/service/database/models/TopologyModel.py b/src/context/service/database/models/TopologyModel.py index 995abab3a40bfacb446360c55c784b0a5f4207ec..cd731ed60efd6be266dea4bf3c9847bbceb0370e 100644 --- a/src/context/service/database/models/TopologyModel.py +++ b/src/context/service/database/models/TopologyModel.py @@ -27,9 +27,10 @@ class TopologyModel(_Base): created_at = Column(DateTime, nullable=False) updated_at = Column(DateTime, nullable=False) - context = relationship('ContextModel', back_populates='topologies', lazy='selectin') - topology_devices = relationship('TopologyDeviceModel') # back_populates='topology' - topology_links = relationship('TopologyLinkModel' ) # back_populates='topology' + context = relationship('ContextModel', back_populates='topologies', lazy='selectin') + topology_devices = relationship('TopologyDeviceModel') # back_populates='topology' + topology_links = relationship('TopologyLinkModel' ) # back_populates='topology' + topology_optical_links = relationship("TopologyOpticalLinkModel") def dump_id(self) -> Dict: return { @@ -39,10 +40,11 @@ class TopologyModel(_Base): def dump(self) -> Dict: return { - 'topology_id': self.dump_id(), - 'name' : self.topology_name, - 'device_ids' : [{'device_uuid': {'uuid': td.device_uuid}} for td in self.topology_devices], - 'link_ids' : [{'link_uuid' : {'uuid': tl.link_uuid }} for tl in self.topology_links ], + 'topology_id' : self.dump_id(), + 'name' : self.topology_name, + 'device_ids' : [{'device_uuid': {'uuid': td.device_uuid }} for td in self.topology_devices ], + 'link_ids' : [{'link_uuid' : {'uuid': tl.link_uuid }} for tl in self.topology_links ], + 'optical_link_ids': [{'link_uuid' : {'uuid': to.optical_link_uuid}} for to in self.topology_optical_links], } def dump_details(self) -> Dict: @@ -54,11 +56,16 @@ class TopologyModel(_Base): tl.link.dump() for tl in self.topology_links ] + optical_links=[ + ol.optical_link.dump() + for ol in self.topology_optical_links + ] return { - 'topology_id': self.dump_id(), - 'name' : self.topology_name, - 'devices' : devices, - 'links' : links, + 'topology_id' : self.dump_id(), + 'name' : self.topology_name, + 'devices' : devices, + 'links' : links, + 'optical_links': optical_links, } class TopologyDeviceModel(_Base): @@ -78,3 +85,14 @@ class TopologyLinkModel(_Base): topology = relationship('TopologyModel', lazy='selectin', viewonly=True) # back_populates='topology_links' link = relationship('LinkModel', lazy='selectin') # back_populates='topology_links' + +#---------------------------------------- Experimental --------------------------------------- + +class TopologyOpticalLinkModel(_Base): + __tablename__ = 'topology_optical_link' + + topology_uuid = Column(ForeignKey('topology.topology_uuid', ondelete='RESTRICT'), primary_key=True, index=True) + optical_link_uuid = Column(ForeignKey('opticallink.opticallink_uuid', ondelete='CASCADE' ), primary_key=True, index=True) + + topology = relationship('TopologyModel', lazy='selectin', viewonly=True) # back_populates='topology_optical_links' + optical_link = relationship('OpticalLinkModel', lazy='selectin') # back_populates='topology_optical_links' diff --git a/src/context/service/database/uuids/OpticalConfig.py b/src/context/service/database/uuids/OpticalConfig.py new file mode 100644 index 0000000000000000000000000000000000000000..f2cd443a983d5b37009ddc1c2dc821dce960ddb8 --- /dev/null +++ b/src/context/service/database/uuids/OpticalConfig.py @@ -0,0 +1,64 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from common.method_wrappers.ServiceExceptions import InvalidArgumentsException +from common.proto.context_pb2 import DeviceId +from ._Builder import get_uuid_from_string, get_uuid_random + +def channel_get_uuid( + channel_name : str , device_id : str, allow_random : bool = False +) -> str: + if len(channel_name) > 0: + return get_uuid_from_string(channel_name) + device_id + if allow_random: return get_uuid_random() + + raise InvalidArgumentsException([ + ('channel uuid', channel_name), + ], extra_details=['Channel name is required to produce a channel UUID']) + +def transponder_get_uuid( + opticalconfig_id : str, allow_random : bool = False +) -> str: + if opticalconfig_id is not None: + return get_uuid_from_string(f"{opticalconfig_id}-transponder") + if allow_random: return get_uuid_random() + + raise InvalidArgumentsException([ + ('transponder uuid', opticalconfig_id), + + ], extra_details=['opticalconfig id is required to produce a transponder UUID']) + +def roadm_get_uuid( + opticalconfig_id : str, allow_random : bool = False +) -> str: + if opticalconfig_id is not None: + return get_uuid_from_string(f"{opticalconfig_id}-roadm") + if allow_random: return get_uuid_random() + + raise InvalidArgumentsException([ + ('roadm uuid', opticalconfig_id), + + ], extra_details=['opticalconfig id is required to produce a roadm UUID']) + +def opticalconfig_get_uuid( + device_id : DeviceId, allow_random : bool = False +) -> str: + device_uuid = device_id.device_uuid.uuid + if (len(device_uuid)>0): + return get_uuid_from_string(f"{device_uuid}_opticalconfig") + if allow_random: return get_uuid_random() + + raise InvalidArgumentsException([ + ('DeviceId ', device_id), + ], extra_details=['device_id is required to produce a OpticalConfig UUID']) diff --git a/src/context/service/database/uuids/OpticalEndPoint.py b/src/context/service/database/uuids/OpticalEndPoint.py new file mode 100644 index 0000000000000000000000000000000000000000..fedafd6f470af174a6c4701e330f93fea00a5a3f --- /dev/null +++ b/src/context/service/database/uuids/OpticalEndPoint.py @@ -0,0 +1,38 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Tuple +from common.proto.context_pb2 import EndPointId +from common.method_wrappers.ServiceExceptions import InvalidArgumentsException +from ._Builder import get_uuid_from_string, get_uuid_random +from .Device import device_get_uuid + +def optical_endpoint_get_uuid( + endpoint_id : EndPointId, endpoint_name : str = '', allow_random : bool = False +) -> Tuple[str, str, str]: + device_uuid = device_get_uuid(endpoint_id.device_id, allow_random=False) + raw_endpoint_uuid = endpoint_id.endpoint_uuid.uuid + if len(raw_endpoint_uuid) > 0: + prefix_for_name = '{:s}'.format( device_uuid) + return device_uuid, get_uuid_from_string(raw_endpoint_uuid, prefix_for_name=prefix_for_name) + if len(endpoint_name) > 0: + prefix_for_name = '{:s}'.format( device_uuid) + return device_uuid, get_uuid_from_string(endpoint_name, prefix_for_name=prefix_for_name) + if allow_random: + return device_uuid, get_uuid_random() + + raise InvalidArgumentsException([ + ('optical_endpoint_id.endpoint_uuid.uuid', raw_endpoint_uuid), + ('name', endpoint_name), + ], extra_details=['At least one is required to produce a OpticalEndPoint UUID']) diff --git a/src/context/service/database/uuids/OpticalLink.py b/src/context/service/database/uuids/OpticalLink.py new file mode 100644 index 0000000000000000000000000000000000000000..152686423aa808194f0582075557e6133e405071 --- /dev/null +++ b/src/context/service/database/uuids/OpticalLink.py @@ -0,0 +1,32 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from common.method_wrappers.ServiceExceptions import InvalidArgumentsException +from common.proto.context_pb2 import LinkId +from ._Builder import get_uuid_from_string, get_uuid_random + +optical_detail_sp = "Optical_link_detail" + +def opticaldetail_get_uuid( + link_id : LinkId,allow_random=False +) -> str: + link_uuid = link_id.link_uuid.uuid + if len(link_uuid) > 0: + str_uuid=f"{link_uuid}{optical_detail_sp}" + return get_uuid_from_string(str_uuid) + + if allow_random: return get_uuid_random() + raise InvalidArgumentsException([ + ('link_id.link_uuid.uuid', link_uuid), + ], extra_details=['At least one is required to produce a Optical Link Detail UUID']) diff --git a/src/device/Dockerfile b/src/device/Dockerfile index 42bf4335cc6b0c6337c166dae8680e18d46d1360..31dffddc40a6c9dc3dca8f0f155ac543fd10b12d 100644 --- a/src/device/Dockerfile +++ b/src/device/Dockerfile @@ -16,9 +16,24 @@ FROM python:3.9-slim # Install dependencies RUN apt-get --yes --quiet --quiet update && \ - apt-get --yes --quiet --quiet install wget g++ git && \ + apt-get --yes --quiet --quiet install wget g++ git build-essential cmake libpcre2-dev python3-dev python3-cffi && \ rm -rf /var/lib/apt/lists/* +# Download, build and install libyang. Note that APT package is outdated +# - Ref: https://github.com/CESNET/libyang +# - Ref: https://github.com/CESNET/libyang-python/ +RUN mkdir -p /var/libyang +RUN git clone https://github.com/CESNET/libyang.git /var/libyang +WORKDIR /var/libyang +RUN git fetch +RUN git checkout v2.1.148 +RUN mkdir -p /var/libyang/build +WORKDIR /var/libyang/build +RUN cmake -D CMAKE_BUILD_TYPE:String="Release" .. +RUN make +RUN make install +RUN ldconfig + # Set Python to show logs as they occur ENV PYTHONUNBUFFERED=0 @@ -62,17 +77,30 @@ RUN python3 -m pip install -r requirements.txt # Add component files into working directory WORKDIR /var/teraflow +COPY src/device/. device/ COPY src/context/__init__.py context/__init__.py COPY src/context/client/. context/client/ COPY src/monitoring/__init__.py monitoring/__init__.py COPY src/monitoring/client/. monitoring/client/ -COPY src/device/. device/ +# Clone test mock tools RUN mkdir -p tests/tools/mock_ietf_actn_sdn_ctrl RUN touch tests/__init__.py RUN touch tests/tools/__init__.py RUN touch tests/tools/mock_ietf_actn_sdn_ctrl/__init__.py COPY src/tests/tools/mock_ietf_actn_sdn_ctrl/. tests/tools/mock_ietf_actn_sdn_ctrl/ +# Clone OpenConfig YANG models +RUN mkdir -p /var/teraflow/device/service/drivers/gnmi_openconfig/git/openconfig/public +RUN mkdir -p /tmp/openconfig +RUN git clone https://github.com/openconfig/public.git /tmp/openconfig +WORKDIR /tmp/openconfig +RUN git fetch +RUN git checkout v4.4.0 +RUN mv /tmp/openconfig/release /var/teraflow/device/service/drivers/gnmi_openconfig/git/openconfig/public +RUN mv /tmp/openconfig/third_party /var/teraflow/device/service/drivers/gnmi_openconfig/git/openconfig/public +RUN rm -rf /tmp/openconfig +WORKDIR /var/teraflow + # Start the service ENTRYPOINT ["python", "-m", "device.service"] diff --git a/src/device/client/DeviceClient.py b/src/device/client/DeviceClient.py index d479b21cd1223e06abb48213cecc23db4fd651dc..8397b89b8c43917a5a22dee2ed3ad5ec0d105aae 100644 --- a/src/device/client/DeviceClient.py +++ b/src/device/client/DeviceClient.py @@ -15,12 +15,16 @@ import grpc, logging from common.Constants import ServiceNameEnum from common.Settings import get_service_host, get_service_port_grpc -from common.proto.context_pb2 import Device, DeviceConfig, DeviceId, Empty,OpticalConfig,OpticalConfigId +from common.proto.context_pb2 import ( + Device, DeviceConfig, DeviceId, Empty, + OpticalConfig, OpticalConfigId, OpticalConfigList +) from common.proto.device_pb2 import MonitoringSettings from common.proto.device_pb2_grpc import DeviceServiceStub +from common.proto.optical_device_pb2_grpc import OpenConfigServiceStub from common.tools.client.RetryDecorator import retry, delay_exponential from common.tools.grpc.Tools import grpc_message_to_json_string -from common.proto.openconfig_device_pb2_grpc import OpenConfigServiceStub + LOGGER = logging.getLogger(__name__) MAX_RETRIES = 15 DELAY_FUNCTION = delay_exponential(initial=0.01, increment=2.0, maximum=5.0) @@ -41,12 +45,13 @@ class DeviceClient: def connect(self): self.channel = grpc.insecure_channel(self.endpoint) self.stub = DeviceServiceStub(self.channel) - self.openconfig_stub=OpenConfigServiceStub(self.channel) + self.openconfig_stub = OpenConfigServiceStub(self.channel) def close(self): if self.channel is not None: self.channel.close() self.channel = None self.stub = None + self.openconfig_stub = None @RETRY_DECORATOR def AddDevice(self, request : Device) -> DeviceId: @@ -82,8 +87,21 @@ class DeviceClient: response = self.stub.MonitorDeviceKpi(request) LOGGER.debug('MonitorDeviceKpi result: {:s}'.format(grpc_message_to_json_string(response))) return response + def ConfigureOpticalDevice(self, request : OpticalConfig) -> OpticalConfigId: LOGGER.debug('ConfigureOpticalDevice request: {:s}'.format(grpc_message_to_json_string(request))) response = self.openconfig_stub.ConfigureOpticalDevice(request) LOGGER.debug('ConfigureOpticalDevice result: {:s}'.format(grpc_message_to_json_string(response))) return response + + def GetDeviceConfiguration(self, request : OpticalConfigList) -> Empty: + LOGGER.debug('ConfigureOpticalDevice request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.openconfig_stub.GetDeviceConfiguration(request) + LOGGER.debug('ConfigureOpticalDevice result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + def DisableOpticalDevice(self, request : OpticalConfig) -> Empty: + LOGGER.debug('DisableOpticalDevice request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.openconfig_stub.DisableOpticalDevice(request) + LOGGER.debug('DisableOpticalDevice result: {:s}'.format(grpc_message_to_json_string(response))) + return response diff --git a/src/device/requirements.in b/src/device/requirements.in index bf5e6a2b3128f438a7c044c3f3cf9ee393de2265..a4d818b52b494f2ef6cc0938d69ddb133ab40859 100644 --- a/src/device/requirements.in +++ b/src/device/requirements.in @@ -12,9 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. - anytree==2.8.0 APScheduler==3.10.1 +bitarray==2.8.* cryptography==36.0.2 deepdiff==6.7.* deepmerge==1.1.* @@ -22,26 +22,27 @@ deepmerge==1.1.* Flask==2.1.3 Flask-HTTPAuth==4.5.0 Flask-RESTful==0.3.9 +ipaddress Jinja2==3.0.3 -numpy<2.0.0 +libyang==2.8.0 +macaddress ncclient==0.6.15 +numpy<2.0.0 p4runtime==1.3.0 pandas==1.5.* paramiko==2.9.2 +pyang==2.6.* +git+https://github.com/robshakir/pyangbind.git 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 -macaddress -yattag -pyang==2.6.0 -git+https://github.com/robshakir/pyangbind.git websockets==10.4 werkzeug==2.3.7 +xmltodict==0.12.0 +yattag # pip's dependency resolver does not take into account installed packages. # p4runtime does not specify the version of grpcio/protobuf it needs, so it tries to install latest one diff --git a/src/device/service/DeviceService.py b/src/device/service/DeviceService.py index 1769a16afb49aab6cb986b49e3ca74b9f3b572be..cac36f62230e42ffd8fbbcba242cadb46486b050 100644 --- a/src/device/service/DeviceService.py +++ b/src/device/service/DeviceService.py @@ -16,7 +16,7 @@ import os from common.Constants import ServiceNameEnum from common.Settings import get_service_port_grpc from common.proto.device_pb2_grpc import add_DeviceServiceServicer_to_server -from common.proto.openconfig_device_pb2_grpc import add_OpenConfigServiceServicer_to_server +from common.proto.optical_device_pb2_grpc import add_OpenConfigServiceServicer_to_server from common.tools.service.GenericGrpcService import GenericGrpcService from device.Config import LOAD_ALL_DEVICE_DRIVERS from .driver_api.DriverInstanceCache import DriverInstanceCache diff --git a/src/device/service/DeviceServiceServicerImpl.py b/src/device/service/DeviceServiceServicerImpl.py index 7546c225e67fd3122ec845b3154606eddb7cd9ff..6e6fa6a9311751888d51e053e323a63d810dad4e 100644 --- a/src/device/service/DeviceServiceServicerImpl.py +++ b/src/device/service/DeviceServiceServicerImpl.py @@ -33,9 +33,11 @@ from .driver_api.DriverInstanceCache import DriverInstanceCache, get_driver from .monitoring.MonitoringLoops import MonitoringLoops from .ErrorMessages import ERROR_MISSING_DRIVER, ERROR_MISSING_KPI from .Tools import ( - check_connect_rules, check_no_endpoints, compute_rules_to_add_delete, configure_rules, deconfigure_rules, - get_device_controller_uuid, populate_config_rules, populate_endpoint_monitoring_resources, populate_endpoints, - populate_initial_config_rules, subscribe_kpi, unsubscribe_kpi, update_endpoints) + check_connect_rules, check_no_endpoints, compute_rules_to_add_delete, configure_rules, + deconfigure_rules, get_device_controller_uuid, populate_config_rules, + populate_endpoint_monitoring_resources, populate_endpoints, populate_initial_config_rules, + subscribe_kpi, unsubscribe_kpi, update_endpoints +) LOGGER = logging.getLogger(__name__) @@ -60,8 +62,7 @@ class DeviceServiceServicerImpl(DeviceServiceServicer): device_uuid = request.device_id.device_uuid.uuid connection_config_rules = check_connect_rules(request.device_config) - if request.device_drivers[0] != DeviceDriverEnum.DEVICEDRIVER_OC: - check_no_endpoints(request.device_endpoints) + check_no_endpoints(request.device_endpoints) t1 = time.time() @@ -107,18 +108,24 @@ class DeviceServiceServicerImpl(DeviceServiceServicer): # (which controller is in charge of which sub-device). new_sub_devices : Dict[str, Device] = dict() new_sub_links : Dict[str, Link] = dict() + + #----- Experimental ------------ + new_optical_configs : Dict[str, OpticalConfig] = dict() if len(device.device_endpoints) == 0: t5 = time.time() # created from request, populate endpoints using driver errors.extend(populate_endpoints( - device, driver, self.monitoring_loops, new_sub_devices, new_sub_links)) + device, driver, self.monitoring_loops, new_sub_devices, new_sub_links, + new_optical_configs + )) t6 = time.time() t_pop_endpoints = t6 - t5 else: t_pop_endpoints = None - if len(device.device_config.config_rules) == len(connection_config_rules): + is_optical_device = request.device_drivers[0] == DeviceDriverEnum.DEVICEDRIVER_OC + if len(device.device_config.config_rules) == len(connection_config_rules) and not is_optical_device: # created from request, populate config rules using driver t7 = time.time() errors.extend(populate_config_rules(device, driver)) @@ -143,13 +150,15 @@ class DeviceServiceServicerImpl(DeviceServiceServicer): else: # ZTP is not deployed; assume the device is ready while onboarding and set them as enabled. device.device_operational_status = DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED - - # temporary line - if request.device_drivers[0] == DeviceDriverEnum.DEVICEDRIVER_OC and len(request.device_endpoints) > 0: + + # temporary line + if is_optical_device: #for endpoint in request.device_endpoints: # #endpoint.endpoint_id.device_id.CopyFrom(device.device_id) # pass - device.device_endpoints.extend(request.device_endpoints) + + if 'new_optical_config' in new_optical_configs and 'opticalconfig' in new_optical_configs["new_optical_config"]: + context_client.SetOpticalConfig(new_optical_configs["new_optical_config"]['opticalconfig']) device_id = context_client.SetDevice(device) diff --git a/src/device/service/OpenConfigServicer.py b/src/device/service/OpenConfigServicer.py index 1dddade18e171380132ed4e5850dfbc7e180b4d8..bee2d4d5ccf6a5757b854f2d8b2275c4da64758f 100644 --- a/src/device/service/OpenConfigServicer.py +++ b/src/device/service/OpenConfigServicer.py @@ -16,7 +16,8 @@ import grpc, logging, json from common.method_wrappers.Decorator import MetricsPool, safe_and_metered_rpc_method from common.method_wrappers.ServiceExceptions import NotFoundException from common.proto.context_pb2 import ( - Device, DeviceId, DeviceOperationalStatusEnum, Empty, OpticalConfig, OpticalConfig + Device, DeviceId, DeviceOperationalStatusEnum, Empty, OpticalConfig, + OpticalConfig, OpticalConfigList ) from common.proto.device_pb2_grpc import DeviceServiceServicer from common.tools.context_queries.Device import get_device @@ -75,21 +76,103 @@ class OpenConfigServicer(DeviceServiceServicer): @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def ConfigureOpticalDevice (self, request : OpticalConfig, context : grpc.ServicerContext) -> Empty: - device_uuid = request.opticalconfig_id.opticalconfig_uuid - resources=[] - config =json.loads(request.config) + device_uuid = request.device_id.device_uuid.uuid + resources : list[dict] = [] + is_all_good = True + config = json.loads(request.config) + results = None + + try: + context_client = ContextClient() + device = get_device( + context_client, device_uuid, rw_copy=True, include_endpoints=True, include_components=False, + include_config_rules=False) + if device is None: + raise NotFoundException('Device', device_uuid, extra_details='loading in ConfigureDevice') + resources, conditions = extract_resources(config=config, device=device) + + driver : _Driver = get_driver(self.driver_instance_cache, device) + results = driver.SetConfig(resources=resources,conditions=conditions) + for result in results: + if not result : + is_all_good = False + + if is_all_good: + #driver.GetConfig(resource_keys=[]) + config = json.loads(request.config) + handled_flow = next((i for i in resources if i['resource_key'] == 'handled_flow'), None) + if handled_flow is not None and len(handled_flow) > 0: + config['flow_handled'] = handled_flow['value'] + request.config=json.dumps(config) + context_client.UpdateOpticalConfig(request) + context_client.close() + except Exception as e: + LOGGER.info("error in configuring %s",e) + return Empty() + + @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) + def GetDeviceConfiguration (self, request : OpticalConfigList, context : grpc.ServicerContext) -> Empty: + context_client = ContextClient() + + for configs in request.opticalconfigs: + device_uuid = configs.device_id.device_uuid.uuid + try: + device = get_device( + context_client, device_uuid, rw_copy=True, include_endpoints=True, include_components=False, + include_config_rules=False) + if device is None: + raise NotFoundException('Device', device_uuid, extra_details='loading in ConfigureDevice') + driver : _Driver = get_driver(self.driver_instance_cache, device) + results = driver.GetConfig(resource_keys=[]) + for resource_data in results : + resource_key, resource_value = resource_data + if resource_key.startswith('/opticalconfigs/opticalconfig/'): + if 'opticalconfig' in resource_value: + context_client.SetOpticalConfig(resource_value['opticalconfig']) + + #TODO: add a control with the NETCONF get + #driver.GetConfig(resource_keys=filter_fields) + except Exception as e: + LOGGER.info("error in configuring %s",e) + context_client.close() + return Empty() + + @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) + def DisableOpticalDevice (self, request : OpticalConfig, context : grpc.ServicerContext) -> Empty: + roadm_configuration = None + device_uuid = request.device_id.device_uuid.uuid + resources : list[dict] = [] + is_all_good = True + config = json.loads(request.config) + try: context_client = ContextClient() device = get_device( context_client, device_uuid, rw_copy=True, include_endpoints=True, include_components=False, include_config_rules=False) + if device is None: raise NotFoundException('Device', device_uuid, extra_details='loading in ConfigureDevice') - resources,conditions=extract_resources(config=config,device=device) + + resources, conditions = extract_resources(config=config, device=device) + driver : _Driver = get_driver(self.driver_instance_cache, device) - result = driver.SetConfig(resources=resources,conditions=conditions) - #TODO: add a control with the NETCONF get - #driver.GetConfig(resource_keys=filter_fields) + if 'edit_type' in conditions and conditions['edit_type'] == 'optical-band': + roadm_configuration = driver.GetConfig() + for resource_data in roadm_configuration: + resource_key, resource_value = resource_data + if resource_key.startswith('/opticalconfigs/opticalconfig/'): + roadm_configuration=resource_value["opticalconfig"] + results = driver.DeleteConfig(resources=resources,conditions=conditions,optical_device_configuration=roadm_configuration) + for result in results: + if not result : + is_all_good = False + + if is_all_good: + config = json.loads(request.config) + if "new_config" in config : + context_client.DeleteOpticalChannel(request) + context_client.close() except Exception as e: - LOGGER.info("error in configuring %s",e) + LOGGER.info("error in Disable configuring %s",e) return Empty() diff --git a/src/device/service/Tools.py b/src/device/service/Tools.py index 91926b9e59cccac2e233ac14bbac497bbb0ac15c..967a6a8c19ca4da763f29bfa164f50ad88d27363 100644 --- a/src/device/service/Tools.py +++ b/src/device/service/Tools.py @@ -17,7 +17,9 @@ from typing import Any, Dict, List, Optional, Tuple, Union from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME from common.DeviceTypes import DeviceTypeEnum from common.method_wrappers.ServiceExceptions import InvalidArgumentException, NotFoundException -from common.proto.context_pb2 import ConfigActionEnum, ConfigRule_ACL, Device, DeviceConfig, EndPoint, Link, Location +from common.proto.context_pb2 import ( + ConfigActionEnum, ConfigRule_ACL, Device, DeviceConfig, EndPoint, Link, Location, OpticalConfig +) from common.proto.device_pb2 import MonitoringSettings from common.proto.kpi_sample_types_pb2 import KpiSampleType from common.tools.grpc.ConfigRules import update_config_rule_custom @@ -26,7 +28,8 @@ from common.type_checkers.Checkers import chk_length, chk_type from .driver_api._Driver import _Driver, RESOURCE_ENDPOINTS from .monitoring.MonitoringLoops import MonitoringLoops from .ErrorMessages import ( - ERROR_BAD_RESOURCE, ERROR_DELETE, ERROR_GET, ERROR_GET_INIT, ERROR_MISSING_KPI, ERROR_SAMPLETYPE, ERROR_SET, + ERROR_BAD_RESOURCE, ERROR_DELETE, ERROR_GET, ERROR_GET_INIT, + ERROR_MISSING_KPI, ERROR_SAMPLETYPE, ERROR_SET, ERROR_SUBSCRIBE, ERROR_UNSUBSCRIBE, ERROR_UNSUP_RESOURCE ) @@ -106,7 +109,8 @@ def get_device_controller_uuid(device : Device) -> Optional[str]: def populate_endpoints( device : Device, driver : _Driver, monitoring_loops : MonitoringLoops, - new_sub_devices : Dict[str, Device], new_sub_links : Dict[str, Link] + new_sub_devices : Dict[str, Device], new_sub_links : Dict[str, Link], + new_optical_configs : Dict[str, OpticalConfig] ) -> List[str]: device_uuid = device.device_id.device_uuid.uuid device_name = device.name @@ -237,6 +241,9 @@ def populate_endpoints( _sub_link_endpoint_id.device_id.device_uuid.uuid = device_uuid _sub_link_endpoint_id.endpoint_uuid.uuid = endpoint_uuid + # ----------Experimental -------------- + elif resource_key.startswith('/opticalconfigs/opticalconfig/'): + new_optical_configs["new_optical_config"]=resource_value else: errors.append(ERROR_UNSUP_RESOURCE.format(device_uuid=device_uuid, resource_data=str(resource_data))) continue @@ -454,7 +461,7 @@ def update_endpoints(src_device : Device, dst_device : Device) -> None: def get_edit_target(device : Device, is_opticalband : bool) -> str: if is_opticalband: return 'optical-band' - if device.device_type == DeviceTypeEnum.OPTICAL_ROADM: return 'media-channel' + if device.device_type == DeviceTypeEnum.OPTICAL_ROADM._value_: return 'media-channel' return 'optical-channel' def is_key_existed(key : str, keys_dic = dict, key_name_to_use = None) -> dict: @@ -468,18 +475,20 @@ def is_key_existed(key : str, keys_dic = dict, key_name_to_use = None) -> dict: dic['value'] = None return dic -def extract_resources(config : dict, device : Device) -> list: +def extract_resources(config : dict, device : Device) -> list[list[dict], dict]: conditions = {} - resources = [] + resources : list[dict] = [] resources.append(is_key_existed('channel_namespace', config)) resources.append(is_key_existed('add_transceiver', config)) is_opticalband = config.get('is_opticalband', False) conditions['is_opticalband'] = is_opticalband conditions['edit_type'] = get_edit_target(device, is_opticalband) + if 'flow' in config: #for tuple_value in config['flow'][device.name]: source_vals = [] dest_vals = [] + handled_flow = [] for tuple_value in config['flow']: source_port = None destination_port = None @@ -492,9 +501,10 @@ def extract_resources(config : dict, device : Device) -> list: dst_endpoint_obj = get_endpoint_matching(device, destination_port_uuid) destination_port = dst_endpoint_obj.name dest_vals.append(destination_port) - resources.append({'resource_key': 'source_port', 'value': source_vals}) - resources.append({'resource_key': 'destination_port', 'value': dest_vals }) - + handled_flow.append((source_port, destination_port)) + resources.append({'resource_key': 'source_port', 'value': source_vals }) + resources.append({'resource_key': 'destination_port', 'value': dest_vals }) + resources.append({'resource_key': 'handled_flow', 'value': handled_flow}) if 'new_config' in config: lower_frequency = None upper_frequency = None @@ -502,15 +512,15 @@ def extract_resources(config : dict, device : Device) -> list: resources.append(is_key_existed('frequency', keys_dic=config['new_config'])) resources.append(is_key_existed('operational-mode', keys_dic=config['new_config'])) resources.append(is_key_existed('line-port', keys_dic=config['new_config'])) + resources.append(is_key_existed('status', keys_dic=config['new_config'] , key_name_to_use="admin-state")) resources.append(is_key_existed('band_type', keys_dic=config['new_config'], key_name_to_use='name')) resources.append(is_key_existed('ob_id', keys_dic=config['new_config'], key_name_to_use='optical-band-parent')) - resources.append(is_key_existed('name', keys_dic=config['new_config'], key_name_to_use='channel_name')) + #resources.append(is_key_existed('name', keys_dic=config['new_config'], key_name_to_use='channel_name')) if not is_opticalband: if 'frequency' in config['new_config'] and 'band' in config['new_config'] and conditions['edit_type'] == 'media-channel': - lower_frequency = int(int(config['new_config']['frequency']) - (int(config['new_config']['band'])/2)) - upper_frequency = int(int(config['new_config']['frequency']) + (int(config['new_config']['band'])/2)) - #lower_frequency = (config['new_config']['frequency'] - config['new_config']['band'])/2 - #upper_frequency = (config['new_config']['frequency'] + config['new_config']['band'])/2 + if config['new_config']['frequency'] is not None and config['new_config']['band'] is not None: + lower_frequency = int(int(config['new_config']['frequency']) - (int(config['new_config']['band'])/2)+1) + upper_frequency = int(int(config['new_config']['frequency']) + (int(config['new_config']['band'])/2)) resources.append(is_key_existed('flow_id', keys_dic=config['new_config'], key_name_to_use='index')) #resources.append({'resource_key':'index','value':config['new_config']['flow_id'] if 'flow_id' in config['new_config'] else None}) else: diff --git a/src/device/service/drivers/gnmi_openconfig/DeltaSampleCache.py b/src/device/service/drivers/gnmi_openconfig/DeltaSampleCache.py index daf04be5a1ff82a79031d8c3ffe19da10739fbcb..140efe84038ed5117d6c7924b188a7dc7dbd7958 100644 --- a/src/device/service/drivers/gnmi_openconfig/DeltaSampleCache.py +++ b/src/device/service/drivers/gnmi_openconfig/DeltaSampleCache.py @@ -13,13 +13,15 @@ # limitations under the License. import copy -from typing import Any, Dict, Tuple, Union +from typing import Any, Dict, Optional, Tuple, Union class DeltaSampleCache: def __init__(self) -> None: self._previous_samples : Dict[str, Tuple[float, Union[int, float]]] = dict() - def get_delta(self, path : str, current_timestamp : float, current_value : Any) -> None: + def get_delta( + self, path : str, current_timestamp : float, current_value : Any + ) -> Optional[Tuple[float, Optional[Any]]]: previous_sample = copy.deepcopy(self._previous_samples.get(path)) self._previous_samples[path] = current_timestamp, current_value @@ -30,6 +32,10 @@ class DeltaSampleCache: delta_value = max(0, current_value - previous_value) delay = current_timestamp - previous_timestamp - delta_sample = current_timestamp, delta_value / delay - - return delta_sample + if delay < 1.e-12: + # return a special value meaning, at that timestamp, + # computed value is not a number, e.g., division by zero + # also, recover previuos samples to do not miss any packet/byte + self._previous_samples[path] = previous_sample + return current_timestamp, None + return current_timestamp, delta_value / delay diff --git a/src/device/service/drivers/gnmi_openconfig/GnmiSessionHandler.py b/src/device/service/drivers/gnmi_openconfig/GnmiSessionHandler.py index 4428fb81ca304d20ee4b3bce259924278400249f..3668c6a3eeb4ab31e2ee01517efaab157c98f8d1 100644 --- a/src/device/service/drivers/gnmi_openconfig/GnmiSessionHandler.py +++ b/src/device/service/drivers/gnmi_openconfig/GnmiSessionHandler.py @@ -19,7 +19,8 @@ from common.type_checkers.Checkers import chk_float, chk_length, chk_string, chk from .gnmi.gnmi_pb2_grpc import gNMIStub from .gnmi.gnmi_pb2 import Encoding, GetRequest, SetRequest, UpdateResult # pylint: disable=no-name-in-module from .handlers import ALL_RESOURCE_KEYS, compose, get_path, parse -from .tools.Capabilities import get_supported_encodings +from .handlers.YangHandler import YangHandler +from .tools.Capabilities import check_capabilities from .tools.Channel import get_grpc_channel from .tools.Path import path_from_string, path_to_string #, compose_path from .tools.Subscriptions import Subscriptions @@ -39,12 +40,22 @@ class GnmiSessionHandler: self._use_tls = settings.get('use_tls', False) self._channel : Optional[grpc.Channel] = None self._stub : Optional[gNMIStub] = None + self._yang_handler = None self._monit_thread = None - self._supported_encodings = None + self._yang_handler = YangHandler() self._subscriptions = Subscriptions() self._in_subscriptions = queue.Queue() self._out_samples = queue.Queue() + def __del__(self) -> None: + self._logger.info('Destroying YangValidator...') + if self._yang_handler is not None: + self._logger.debug('yang_validator.data:') + for path, dnode in self._yang_handler.get_data_paths().items(): + self._logger.debug(' {:s}: {:s}'.format(str(path), json.dumps(dnode.print_dict()))) + self._yang_handler.destroy() + self._logger.info('DONE') + @property def subscriptions(self): return self._subscriptions @@ -58,8 +69,7 @@ class GnmiSessionHandler: with self._lock: self._channel = get_grpc_channel(self._address, self._port, self._use_tls, self._logger) self._stub = gNMIStub(self._channel) - self._supported_encodings = get_supported_encodings( - self._stub, self._username, self._password, timeout=120) + check_capabilities(self._stub, self._username, self._password, timeout=120) self._monit_thread = MonitoringThread( self._stub, self._logger, self._settings, self._in_subscriptions, self._out_samples) self._monit_thread.start() @@ -96,13 +106,15 @@ class GnmiSessionHandler: self._logger.exception(MSG.format(str_resource_name, str(resource_key))) parsing_results.append((resource_key, e)) # if validation fails, store the exception + self._logger.debug('parsing_results={:s}'.format(str(parsing_results))) + if len(parsing_results) > 0: return parsing_results metadata = [('username', self._username), ('password', self._password)] timeout = None # GNMI_SUBSCRIPTION_TIMEOUT = int(sampling_duration) get_reply = self._stub.Get(get_request, metadata=metadata, timeout=timeout) - #self._logger.info('get_reply={:s}'.format(grpc_message_to_json_string(get_reply))) + self._logger.debug('get_reply={:s}'.format(grpc_message_to_json_string(get_reply))) results = [] #results[str_filter] = [i, None, False] # (index, value, processed?) @@ -119,7 +131,7 @@ class GnmiSessionHandler: # resource_key_tuple[2] = True for update in notification.update: - #self._logger.info('update={:s}'.format(grpc_message_to_json_string(update))) + self._logger.debug('update={:s}'.format(grpc_message_to_json_string(update))) str_path = path_to_string(update.path) #resource_key_tuple = results.get(str_path) #if resource_key_tuple is None: @@ -130,10 +142,10 @@ class GnmiSessionHandler: value = decode_value(update.val) #resource_key_tuple[1] = value #resource_key_tuple[2] = True - results.extend(parse(str_path, value)) + results.extend(parse(str_path, value, self._yang_handler)) except Exception as e: # pylint: disable=broad-except - MSG = 'Exception processing notification {:s}' - self._logger.exception(MSG.format(grpc_message_to_json_string(notification))) + MSG = 'Exception processing update {:s}' + self._logger.exception(MSG.format(grpc_message_to_json_string(update))) results.append((str_path, e)) # if validation fails, store the exception #_results = sorted(results.items(), key=lambda x: x[1][0]) @@ -158,31 +170,34 @@ class GnmiSessionHandler: set_request = SetRequest() #for resource_key in resource_keys: + resources_requested = list() for resource_key, resource_value in resources: - self._logger.info('---1') - self._logger.info(str(resource_key)) - self._logger.info(str(resource_value)) + #self._logger.info('---1') + #self._logger.info(str(resource_key)) + #self._logger.info(str(resource_value)) #resource_tuple = resource_tuples.get(resource_key) #if resource_tuple is None: continue #_, value, exists, operation_done = resource_tuple if isinstance(resource_value, str): resource_value = json.loads(resource_value) - str_path, str_data = compose(resource_key, resource_value, delete=False) - self._logger.info('---3') - self._logger.info(str(str_path)) - self._logger.info(str(str_data)) + str_path, str_data = compose(resource_key, resource_value, self._yang_handler, delete=False) + if str_path is None: continue # nothing to set + #self._logger.info('---3') + #self._logger.info(str(str_path)) + #self._logger.info(str(str_data)) set_request_list = set_request.update #if exists else set_request.replace set_request_entry = set_request_list.add() set_request_entry.path.CopyFrom(path_from_string(str_path)) set_request_entry.val.json_val = str_data.encode('UTF-8') + resources_requested.append((resource_key, resource_value)) - self._logger.info('set_request={:s}'.format(grpc_message_to_json_string(set_request))) + self._logger.debug('set_request={:s}'.format(grpc_message_to_json_string(set_request))) metadata = [('username', self._username), ('password', self._password)] timeout = None # GNMI_SUBSCRIPTION_TIMEOUT = int(sampling_duration) set_reply = self._stub.Set(set_request, metadata=metadata, timeout=timeout) - self._logger.info('set_reply={:s}'.format(grpc_message_to_json_string(set_reply))) + self._logger.debug('set_reply={:s}'.format(grpc_message_to_json_string(set_reply))) results = [] - for (resource_key, resource_value), update_result in zip(resources, set_reply.response): + for (resource_key, resource_value), update_result in zip(resources_requested, set_reply.response): operation = update_result.op if operation == UpdateResult.UPDATE: results.append((resource_key, True)) @@ -227,30 +242,34 @@ class GnmiSessionHandler: set_request = SetRequest() #for resource_key in resource_keys: + resources_requested = list() for resource_key, resource_value in resources: - self._logger.info('---1') - self._logger.info(str(resource_key)) - self._logger.info(str(resource_value)) + #self._logger.info('---1') + #self._logger.info(str(resource_key)) + #self._logger.info(str(resource_value)) #resource_tuple = resource_tuples.get(resource_key) #if resource_tuple is None: continue #_, value, exists, operation_done = resource_tuple #if not exists: continue if isinstance(resource_value, str): resource_value = json.loads(resource_value) - str_path, str_data = compose(resource_key, resource_value, delete=True) - self._logger.info('---3') - self._logger.info(str(str_path)) - self._logger.info(str(str_data)) + # pylint: disable=unused-variable + str_path, str_data = compose(resource_key, resource_value, self._yang_handler, delete=True) + if str_path is None: continue # nothing to do with this resource_key + #self._logger.info('---3') + #self._logger.info(str(str_path)) + #self._logger.info(str(str_data)) set_request_entry = set_request.delete.add() set_request_entry.CopyFrom(path_from_string(str_path)) + resources_requested.append((resource_key, resource_value)) - self._logger.info('set_request={:s}'.format(grpc_message_to_json_string(set_request))) + self._logger.debug('set_request={:s}'.format(grpc_message_to_json_string(set_request))) metadata = [('username', self._username), ('password', self._password)] timeout = None # GNMI_SUBSCRIPTION_TIMEOUT = int(sampling_duration) set_reply = self._stub.Set(set_request, metadata=metadata, timeout=timeout) - self._logger.info('set_reply={:s}'.format(grpc_message_to_json_string(set_reply))) + self._logger.debug('set_reply={:s}'.format(grpc_message_to_json_string(set_reply))) results = [] - for (resource_key, resource_value), update_result in zip(resources, set_reply.response): + for (resource_key, resource_value), update_result in zip(resources_requested, set_reply.response): operation = update_result.op if operation == UpdateResult.DELETE: results.append((resource_key, True)) diff --git a/src/device/service/drivers/gnmi_openconfig/MonitoringThread.py b/src/device/service/drivers/gnmi_openconfig/MonitoringThread.py index 8bf6704a854542b3a085af05d55391e23c8d224f..505c2f009cc7a2ab312e062f1ad82bd01d4c183e 100644 --- a/src/device/service/drivers/gnmi_openconfig/MonitoringThread.py +++ b/src/device/service/drivers/gnmi_openconfig/MonitoringThread.py @@ -94,9 +94,14 @@ class MonitoringThread(threading.Thread): subscriptions = [] while not self._terminate.is_set(): try: - subscription = self._in_subscriptions.get(block=True, timeout=0.1) + # Some devices do not support to process multiple + # SubscriptionList requests in a bidirectional channel. + # Increased timeout to 5 seconds assuming it should + # bring enough time to receive all the subscriptions in + # the queue and process them in bulk. + subscription = self._in_subscriptions.get(block=True, timeout=5.0) operation, resource_key, sampling_duration, sampling_interval = subscription # pylint: disable=unused-variable - if operation != 'subscribe': continue # Unsubscribe not supported by gNM, needs to cancel entire connection + if operation != 'subscribe': continue # Unsubscribe not supported by gNMI, needs to cancel entire connection # options.timeout = int(sampling_duration) #_path = parse_xpath(resource_key) path = path_from_string(resource_key) @@ -107,15 +112,15 @@ class MonitoringThread(threading.Thread): subscriptions.append(subscription) except queue.Empty: if len(subscriptions) == 0: continue - #self._logger.warning('[generate_requests] process') + self._logger.debug('[generate_requests] process') prefix = path_from_string(GNMI_PATH_PREFIX) if GNMI_PATH_PREFIX is not None else None qos = QOSMarking(marking=GNMI_QOS_MARKING) if GNMI_QOS_MARKING is not None else None subscriptions_list = SubscriptionList( prefix=prefix, mode=GNMI_SUBSCRIPTION_LIST_MODE, allow_aggregation=GNMI_ALLOW_AGGREGATION, encoding=GNMI_ENCODING, subscription=subscriptions, qos=qos) subscribe_request = SubscribeRequest(subscribe=subscriptions_list) - #str_subscribe_request = grpc_message_to_json_string(subscribe_request) - #self._logger.warning('[generate_requests] subscribe_request={:s}'.format(str_subscribe_request)) + str_subscribe_request = grpc_message_to_json_string(subscribe_request) + self._logger.debug('[generate_requests] subscribe_request={:s}'.format(str_subscribe_request)) yield subscribe_request subscriptions = [] except: # pylint: disable=bare-except @@ -134,7 +139,7 @@ class MonitoringThread(threading.Thread): self._response_iterator = self._stub.Subscribe(request_iterator, metadata=metadata, timeout=timeout) for subscribe_response in self._response_iterator: str_subscribe_response = grpc_message_to_json_string(subscribe_response) - self._logger.warning('[run] subscribe_response={:s}'.format(str_subscribe_response)) + self._logger.debug('[run] subscribe_response={:s}'.format(str_subscribe_response)) update = subscribe_response.update timestamp_device = float(update.timestamp) / 1.e9 timestamp_local = datetime.timestamp(datetime.utcnow()) @@ -145,25 +150,37 @@ class MonitoringThread(threading.Thread): else: # might be clocks are not synchronized, use local timestamp timestamp = timestamp_local + str_prefix = path_to_string(update.prefix) if len(update.prefix.elem) > 0 else '' for update_entry in update.update: str_path = path_to_string(update_entry.path) + if len(str_prefix) > 0: + str_path = '{:s}/{:s}'.format(str_prefix, str_path) + str_path = str_path.replace('//', '/') + if str_path.startswith('/interfaces/'): + # Add namespace, if missing + str_path_parts = str_path.split('/') + str_path_parts[1] = 'openconfig-interfaces:interfaces' + str_path = '/'.join(str_path_parts) #if str_path != '/system/name/host-name': continue #counter_name = update_entry.path[-1].name value_type = update_entry.val.WhichOneof('value') value = getattr(update_entry.val, value_type) - if re.match(r'^[0-9]+$', value) is not None: - value = int(value) - elif re.match(r'^[0-9]*\.[0-9]*$', value) is not None: - value = float(value) - else: - value = str(value) + if isinstance(value, str): + if re.match(r'^[0-9]+$', value) is not None: + value = int(value) + elif re.match(r'^[0-9]*\.[0-9]*$', value) is not None: + value = float(value) + else: + value = str(value) delta_sample = self._delta_sample_cache.get_delta(str_path, timestamp, value) if delta_sample is None: sample = (timestamp, str_path, value) else: sample = (delta_sample[0], str_path, delta_sample[1]) - self._logger.warning('[run] sample={:s}'.format(str(sample))) - self._out_samples.put_nowait(sample) + self._logger.debug('[run] sample={:s}'.format(str(sample))) + if sample[2] is not None: + # Skip not-a-number (e.g., division by zero) samples + self._out_samples.put_nowait(sample) except grpc.RpcError as e: if e.code() != grpc.StatusCode.CANCELLED: raise # pylint: disable=no-member if e.details() != 'Locally cancelled by application!': raise # pylint: disable=no-member diff --git a/src/device/service/drivers/gnmi_openconfig/clone-yang-models.sh b/src/device/service/drivers/gnmi_openconfig/clone-yang-models.sh new file mode 100755 index 0000000000000000000000000000000000000000..b41fc6bbba60bf18c0cccebdb3536c3f96372c39 --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/clone-yang-models.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +BASE_PATH=~/tfs-ctrl/src/device/service/drivers/gnmi_openconfig +GIT_BASE_PATH=${BASE_PATH}/git/openconfig + +rm -rf ${GIT_BASE_PATH} + +OC_PUBLIC_PATH=${GIT_BASE_PATH}/public +mkdir -p ${OC_PUBLIC_PATH} +git clone https://github.com/openconfig/public.git ${OC_PUBLIC_PATH} + +#OC_HERCULES_PATH=${GIT_BASE_PATH}/hercules +#mkdir -p ${OC_HERCULES_PATH} +#git clone https://github.com/openconfig/hercules.git ${OC_HERCULES_PATH} diff --git a/src/device/service/drivers/gnmi_openconfig/handlers/Component.py b/src/device/service/drivers/gnmi_openconfig/handlers/Component.py index 5ac8754c6081245f79f28b89e026d5a859bc363a..e669872fb5e675ea5490b5e36376ceced4e296ee 100644 --- a/src/device/service/drivers/gnmi_openconfig/handlers/Component.py +++ b/src/device/service/drivers/gnmi_openconfig/handlers/Component.py @@ -12,45 +12,52 @@ # See the License for the specific language governing permissions and # limitations under the License. -import logging +import json, logging, re # libyang from typing import Any, Dict, List, Tuple from common.proto.kpi_sample_types_pb2 import KpiSampleType from ._Handler import _Handler +from .YangHandler import YangHandler LOGGER = logging.getLogger(__name__) -PATH_IF_CTR = "/interfaces/interface[name={:s}]/state/counters/{:s}" +PATH_IF_CTR = '/openconfig-interfaces:interfaces/interface[name={:s}]/state/counters/{:s}' +#pylint: disable=abstract-method class ComponentHandler(_Handler): def get_resource_key(self) -> str: return '/endpoints/endpoint' - def get_path(self) -> str: return '/components/component' + def get_path(self) -> str: return '/openconfig-platform:components' - def parse(self, json_data : Dict) -> List[Tuple[str, Dict[str, Any]]]: - #LOGGER.info('json_data = {:s}'.format(json.dumps(json_data))) - json_component_list : List[Dict] = json_data.get('component', []) - response = [] - for json_component in json_component_list: - #LOGGER.info('json_component = {:s}'.format(json.dumps(json_component))) + def parse( + self, json_data : Dict, yang_handler : YangHandler + ) -> List[Tuple[str, Dict[str, Any]]]: + LOGGER.debug('json_data = {:s}'.format(json.dumps(json_data))) - endpoint = {} + yang_components_path = self.get_path() + json_data_valid = yang_handler.parse_to_dict(yang_components_path, json_data, fmt='json') - component_type = json_component.get('state', {}).get('type') - if component_type is None: continue - component_type = component_type.replace('oc-platform-types:', '') - component_type = component_type.replace('openconfig-platform-types:', '') - if component_type not in {'PORT'}: continue - endpoint['type'] = '-' + entries = [] + for component in json_data_valid['components']['component']: + LOGGER.debug('component={:s}'.format(str(component))) + + component_name = component['name'] + #component_config = component.get('config', {}) - #LOGGER.info('PORT json_component = {:s}'.format(json.dumps(json_component))) + #yang_components : libyang.DContainer = yang_handler.get_data_path(yang_components_path) + #yang_component_path = 'component[name="{:s}"]'.format(component_name) + #yang_component : libyang.DContainer = yang_components.create_path(yang_component_path) + #yang_component.merge_data_dict(component, strict=True, validate=False) - component_name = json_component.get('name') - if component_name is None: continue + component_state = component.get('state', {}) + component_type = component_state.get('type') + if component_type is None: continue + component_type = component_type.split(':')[-1] + if component_type not in {'PORT'}: continue # TODO: improve mapping between interface name and component name # By now, computed by time for the sake of saving time for the Hackfest. - interface_name = component_name.lower().replace('-port', '') + interface_name = re.sub(r'\-[pP][oO][rR][tT]', '', component_name) - endpoint['uuid'] = interface_name + endpoint = {'uuid': interface_name, 'type': '-'} endpoint['sample_types'] = { KpiSampleType.KPISAMPLETYPE_BYTES_RECEIVED : PATH_IF_CTR.format(interface_name, 'in-octets' ), KpiSampleType.KPISAMPLETYPE_BYTES_TRANSMITTED : PATH_IF_CTR.format(interface_name, 'out-octets'), @@ -58,6 +65,6 @@ class ComponentHandler(_Handler): KpiSampleType.KPISAMPLETYPE_PACKETS_TRANSMITTED: PATH_IF_CTR.format(interface_name, 'out-pkts' ), } - if len(endpoint) == 0: continue - response.append(('/endpoints/endpoint[{:s}]'.format(endpoint['uuid']), endpoint)) - return response + entries.append(('/endpoints/endpoint[{:s}]'.format(endpoint['uuid']), endpoint)) + + return entries diff --git a/src/device/service/drivers/gnmi_openconfig/handlers/Interface.py b/src/device/service/drivers/gnmi_openconfig/handlers/Interface.py index e97855aa8b97fc855b07848f1a7f0c7e93717a70..03cfc6ff15e0490aee99be89c3ff1e9327dddf15 100644 --- a/src/device/service/drivers/gnmi_openconfig/handlers/Interface.py +++ b/src/device/service/drivers/gnmi_openconfig/handlers/Interface.py @@ -12,20 +12,23 @@ # See the License for the specific language governing permissions and # limitations under the License. -import json, logging +import json, libyang, logging from typing import Any, Dict, List, Tuple from ._Handler import _Handler -from .Tools import dict_get_first +from .Tools import get_bool, get_int, get_str +from .YangHandler import YangHandler LOGGER = logging.getLogger(__name__) class InterfaceHandler(_Handler): - def get_resource_key(self) -> str: return '/interface' - def get_path(self) -> str: return '/interfaces/interface' + def get_resource_key(self) -> str: return '/interface/subinterface' + def get_path(self) -> str: return '/openconfig-interfaces:interfaces' - def compose(self, resource_key : str, resource_value : Dict, delete : bool = False) -> Tuple[str, str]: - if_name = str (resource_value['name' ]) # ethernet-1/1 - sif_index = int (resource_value.get('sub_if_index' , 0 )) # 0 + def compose( + self, resource_key : str, resource_value : Dict, yang_handler : YangHandler, delete : bool = False + ) -> Tuple[str, str]: + if_name = get_str(resource_value, 'name' ) # ethernet-1/1 + sif_index = get_int(resource_value, 'index', 0) # 0 if delete: PATH_TMPL = '/interfaces/interface[name={:s}]/subinterfaces/subinterface[index={:d}]' @@ -33,216 +36,159 @@ class InterfaceHandler(_Handler): str_data = json.dumps({}) return str_path, str_data - if_enabled = bool(resource_value.get('enabled' , True)) # True/False - sif_enabled = bool(resource_value.get('sub_if_enabled' , True)) # True/False - sif_ipv4_enabled = bool(resource_value.get('sub_if_ipv4_enabled', True)) # True/False - sif_ipv4_address = str (resource_value['sub_if_ipv4_address' ]) # 172.16.0.1 - sif_ipv4_prefix = int (resource_value['sub_if_ipv4_prefix' ]) # 24 + enabled = get_bool(resource_value, 'enabled', True) # True/False + #if_type = get_str (resource_value, 'type' ) # 'l3ipvlan' + vlan_id = get_int (resource_value, 'vlan_id', ) # 127 + address_ip = get_str (resource_value, 'address_ip' ) # 172.16.0.1 + address_prefix = get_int (resource_value, 'address_prefix') # 24 + mtu = get_int (resource_value, 'mtu' ) # 1500 + + yang_ifs : libyang.DContainer = yang_handler.get_data_path('/openconfig-interfaces:interfaces') + yang_if_path = 'interface[name="{:s}"]'.format(if_name) + yang_if : libyang.DContainer = yang_ifs.create_path(yang_if_path) + yang_if.create_path('config/name', if_name ) + if enabled is not None: yang_if.create_path('config/enabled', enabled) + if mtu is not None: yang_if.create_path('config/mtu', mtu) + + yang_sifs : libyang.DContainer = yang_if.create_path('subinterfaces') + yang_sif_path = 'subinterface[index="{:d}"]'.format(sif_index) + yang_sif : libyang.DContainer = yang_sifs.create_path(yang_sif_path) + yang_sif.create_path('config/index', sif_index) + if enabled is not None: yang_sif.create_path('config/enabled', enabled) + + if vlan_id is not None: + yang_subif_vlan : libyang.DContainer = yang_sif.create_path('openconfig-vlan:vlan') + yang_subif_vlan.create_path('match/single-tagged/config/vlan-id', vlan_id) + + yang_ipv4 : libyang.DContainer = yang_sif.create_path('openconfig-if-ip:ipv4') + if enabled is not None: yang_ipv4.create_path('config/enabled', enabled) + + if address_ip is not None and address_prefix is not None: + yang_ipv4_addrs : libyang.DContainer = yang_ipv4.create_path('addresses') + yang_ipv4_addr_path = 'address[ip="{:s}"]'.format(address_ip) + yang_ipv4_addr : libyang.DContainer = yang_ipv4_addrs.create_path(yang_ipv4_addr_path) + yang_ipv4_addr.create_path('config/ip', address_ip) + yang_ipv4_addr.create_path('config/prefix-length', address_prefix) + if mtu is not None: yang_ipv4_addr.create_path('config/mtu', mtu) str_path = '/interfaces/interface[name={:s}]'.format(if_name) - str_data = json.dumps({ - 'name': if_name, - 'config': {'name': if_name, 'enabled': if_enabled}, - 'subinterfaces': { - 'subinterface': { - 'index': sif_index, - 'config': {'index': sif_index, 'enabled': sif_enabled}, - 'ipv4': { - 'config': {'enabled': sif_ipv4_enabled}, - 'addresses': { - 'address': { - 'ip': sif_ipv4_address, - 'config': {'ip': sif_ipv4_address, 'prefix_length': sif_ipv4_prefix}, - } - } - } - } - } - }) + str_data = yang_if.print_mem('json') + json_data = json.loads(str_data) + json_data = json_data['openconfig-interfaces:interface'][0] + str_data = json.dumps(json_data) return str_path, str_data - def parse(self, json_data : Dict) -> List[Tuple[str, Dict[str, Any]]]: - #LOGGER.info('json_data = {:s}'.format(json.dumps(json_data))) - json_interface_list : List[Dict] = json_data.get('interface', []) - - response = [] - for json_interface in json_interface_list: - #LOGGER.info('json_interface = {:s}'.format(json.dumps(json_interface))) - - interface = {} - - interface_name = json_interface.get('name') - if interface_name is None: - LOGGER.info('DISCARDED json_interface = {:s}'.format(json.dumps(json_interface))) - continue - interface['name'] = interface_name - - CONFIG_FIELDS = ('config', 'openconfig-interface:config', 'oci:config') - json_config : Dict = dict_get_first(json_interface, CONFIG_FIELDS, default={}) - - STATE_FIELDS = ('state', 'openconfig-interface:state', 'oci:state') - json_state : Dict = dict_get_first(json_interface, STATE_FIELDS, default={}) - - interface_type = json_config.get('type') - if interface_type is None: interface_type = json_state.get('type') - if interface_type is None: - LOGGER.info('DISCARDED json_interface = {:s}'.format(json.dumps(json_interface))) - continue - interface_type = interface_type.replace('ianaift:', '') - interface_type = interface_type.replace('iana-if-type:', '') - interface['type'] = interface_type - - interface_mtu = json_config.get('mtu') - if interface_mtu is None: interface_mtu = json_state.get('mtu') - if interface_mtu is not None: interface['mtu'] = int(interface_mtu) - - interface_enabled = json_config.get('enabled') - if interface_enabled is None: interface_enabled = json_state.get('enabled') - interface['enabled'] = False if interface_enabled is None else bool(interface_enabled) - - interface_management = json_config.get('management') - if interface_management is None: interface_management = json_state.get('management') - interface['management'] = False if interface_management is None else bool(interface_management) - - interface_descr = json_interface.get('config', {}).get('description') - if interface_descr is not None: interface['description'] = interface_descr - - json_subinterfaces = json_interface.get('subinterfaces', {}) - json_subinterface_list : List[Dict] = json_subinterfaces.get('subinterface', []) - - for json_subinterface in json_subinterface_list: - #LOGGER.info('json_subinterface = {:s}'.format(json.dumps(json_subinterface))) - - subinterface = {} - - subinterface_index = json_subinterface.get('state', {}).get('index') - if subinterface_index is None: continue - subinterface['index'] = int(subinterface_index) - - subinterface_name = json_subinterface.get('state', {}).get('name') - if subinterface_name is None: continue - subinterface['name'] = subinterface_name - - subinterface_enabled = json_subinterface.get('state', {}).get('enabled', False) - subinterface['enabled'] = bool(subinterface_enabled) - - VLAN_FIELDS = ('vlan', 'openconfig-vlan:vlan', 'ocv:vlan') - json_vlan = dict_get_first(json_subinterface, VLAN_FIELDS, default={}) - - MATCH_FIELDS = ('match', 'openconfig-vlan:match', 'ocv:match') - json_vlan = dict_get_first(json_vlan, MATCH_FIELDS, default={}) - - SIN_TAG_FIELDS = ('single-tagged', 'openconfig-vlan:single-tagged', 'ocv:single-tagged') - json_vlan = dict_get_first(json_vlan, SIN_TAG_FIELDS, default={}) - - CONFIG_FIELDS = ('config', 'openconfig-vlan:config', 'ocv:config') - json_vlan = dict_get_first(json_vlan, CONFIG_FIELDS, default={}) - - VLAN_ID_FIELDS = ('vlan-id', 'openconfig-vlan:vlan-id', 'ocv:vlan-id') - subinterface_vlan_id = dict_get_first(json_vlan, VLAN_ID_FIELDS) - if subinterface_vlan_id is not None: subinterface['vlan_id'] = subinterface_vlan_id - - - # TODO: implement support for multiple IP addresses per subinterface - - IPV4_FIELDS = ('ipv4', 'openconfig-if-ip:ipv4', 'ociip:ipv4') - json_ipv4 = dict_get_first(json_subinterface, IPV4_FIELDS, default={}) - - IPV4_ADDRESSES_FIELDS = ('addresses', 'openconfig-if-ip:addresses', 'ociip:addresses') - json_ipv4_addresses = dict_get_first(json_ipv4, IPV4_ADDRESSES_FIELDS, default={}) - - IPV4_ADDRESS_FIELDS = ('address', 'openconfig-if-ip:address', 'ociip:address') - json_ipv4_address_list : List[Dict] = dict_get_first(json_ipv4_addresses, IPV4_ADDRESS_FIELDS, default=[]) - - #ipv4_addresses = [] - for json_ipv4_address in json_ipv4_address_list: - #LOGGER.info('json_ipv4_address = {:s}'.format(json.dumps(json_ipv4_address))) - - STATE_FIELDS = ('state', 'openconfig-if-ip:state', 'ociip:state') - json_ipv4_address_state = dict_get_first(json_ipv4_address, STATE_FIELDS, default={}) - - #ipv4_address = {} - - #ORIGIN_FIELDS = ('origin', 'openconfig-if-ip:origin', 'ociip:origin') - #ipv4_address_origin = dict_get_first(json_ipv4_address_state, ORIGIN_FIELDS, default={}) - #if ipv4_address_origin is not None: ipv4_address['origin'] = ipv4_address_origin - - IP_FIELDS = ('ip', 'openconfig-if-ip:ip', 'ociip:ip') - ipv4_address_ip = dict_get_first(json_ipv4_address_state, IP_FIELDS) - #if ipv4_address_ip is not None: ipv4_address['address_ip'] = ipv4_address_ip - if ipv4_address_ip is not None: subinterface['address_ip'] = ipv4_address_ip - - PREFIX_FIELDS = ('prefix-length', 'openconfig-if-ip:prefix-length', 'ociip:prefix-length') - ipv4_address_prefix = dict_get_first(json_ipv4_address_state, PREFIX_FIELDS) - #if ipv4_address_prefix is not None: ipv4_address['address_prefix'] = int(ipv4_address_prefix) - if ipv4_address_prefix is not None: subinterface['address_prefix'] = int(ipv4_address_prefix) - - #if len(ipv4_address) == 0: continue - #ipv4_addresses.append(ipv4_address) - - #subinterface['ipv4_addresses'] = ipv4_addresses - - if len(subinterface) == 0: continue - resource_key = '/interface[{:s}]/subinterface[{:s}]'.format(interface['name'], str(subinterface['index'])) - response.append((resource_key, subinterface)) - - if len(interface) == 0: continue - response.append(('/interface[{:s}]'.format(interface['name']), interface)) - - return response - - def parse_counters(self, json_data : Dict) -> List[Tuple[str, Dict[str, Any]]]: - LOGGER.info('[parse_counters] json_data = {:s}'.format(json.dumps(json_data))) - json_interface_list : List[Dict] = json_data.get('interface', []) - - response = [] - for json_interface in json_interface_list: - LOGGER.info('[parse_counters] json_interface = {:s}'.format(json.dumps(json_interface))) - - interface = {} - - NAME_FIELDS = ('name', 'openconfig-interface:name', 'oci:name') - interface_name = dict_get_first(json_interface, NAME_FIELDS) - if interface_name is None: continue - interface['name'] = interface_name - - STATE_FIELDS = ('state', 'openconfig-interface:state', 'oci:state') - json_state = dict_get_first(json_interface, STATE_FIELDS, default={}) - - COUNTERS_FIELDS = ('counters', 'openconfig-interface:counters', 'oci:counters') - json_counters = dict_get_first(json_state, COUNTERS_FIELDS, default={}) - - IN_PKTS_FIELDS = ('in-pkts', 'openconfig-interface:in-pkts', 'oci:in-pkts') - interface_in_pkts = dict_get_first(json_counters, IN_PKTS_FIELDS) - if interface_in_pkts is not None: interface['in-pkts'] = int(interface_in_pkts) - - IN_OCTETS_FIELDS = ('in-octets', 'openconfig-interface:in-octets', 'oci:in-octets') - interface_in_octets = dict_get_first(json_counters, IN_OCTETS_FIELDS) - if interface_in_octets is not None: interface['in-octets'] = int(interface_in_octets) - - IN_ERRORS_FIELDS = ('in-errors', 'openconfig-interface:in-errors', 'oci:in-errors') - interface_in_errors = dict_get_first(json_counters, IN_ERRORS_FIELDS) - if interface_in_errors is not None: interface['in-errors'] = int(interface_in_errors) - - OUT_OCTETS_FIELDS = ('out-octets', 'openconfig-interface:out-octets', 'oci:out-octets') - interface_out_octets = dict_get_first(json_counters, OUT_OCTETS_FIELDS) - if interface_out_octets is not None: interface['out-octets'] = int(interface_out_octets) - - OUT_PKTS_FIELDS = ('out-pkts', 'openconfig-interface:out-pkts', 'oci:out-pkts') - interface_out_pkts = dict_get_first(json_counters, OUT_PKTS_FIELDS) - if interface_out_pkts is not None: interface['out-pkts'] = int(interface_out_pkts) - - OUT_ERRORS_FIELDS = ('out-errors', 'openconfig-interface:out-errors', 'oci:out-errors') - interface_out_errors = dict_get_first(json_counters, OUT_ERRORS_FIELDS) - if interface_out_errors is not None: interface['out-errors'] = int(interface_out_errors) - - OUT_DISCARDS_FIELDS = ('out-discards', 'openconfig-interface:out-discards', 'oci:out-discards') - interface_out_discards = dict_get_first(json_counters, OUT_DISCARDS_FIELDS) - if interface_out_discards is not None: interface['out-discards'] = int(interface_out_discards) - - #LOGGER.info('[parse_counters] interface = {:s}'.format(str(interface))) - - if len(interface) == 0: continue - response.append(('/interface[{:s}]'.format(interface['name']), interface)) - - return response + def parse( + self, json_data : Dict, yang_handler : YangHandler + ) -> List[Tuple[str, Dict[str, Any]]]: + LOGGER.debug('json_data = {:s}'.format(json.dumps(json_data))) + + yang_interfaces_path = self.get_path() + json_data_valid = yang_handler.parse_to_dict(yang_interfaces_path, json_data, fmt='json') + + entries = [] + for interface in json_data_valid['interfaces']['interface']: + LOGGER.debug('interface={:s}'.format(str(interface))) + + interface_name = interface['name'] + interface_config = interface.get('config', {}) + + #yang_interfaces : libyang.DContainer = yang_handler.get_data_path(yang_interfaces_path) + #yang_interface_path = 'interface[name="{:s}"]'.format(interface_name) + #yang_interface : libyang.DContainer = yang_interfaces.create_path(yang_interface_path) + #yang_interface.merge_data_dict(interface, strict=True, validate=False) + + interface_state = interface.get('state', {}) + interface_type = interface_state.get('type') + if interface_type is None: continue + interface_type = interface_type.split(':')[-1] + if interface_type not in {'ethernetCsmacd'}: continue + + _interface = { + 'name' : interface_name, + 'type' : interface_type, + 'mtu' : interface_state['mtu'], + 'admin-status' : interface_state['admin-status'], + 'oper-status' : interface_state['oper-status'], + 'management' : interface_state['management'], + } + if not interface_state['management'] and 'ifindex' in interface_state: + _interface['ifindex'] = interface_state['ifindex'] + if 'description' in interface_config: + _interface['description'] = interface_config['description'] + if 'enabled' in interface_config: + _interface['enabled'] = interface_config['enabled'] + if 'hardware-port' in interface_state: + _interface['hardware-port'] = interface_state['hardware-port'] + if 'transceiver' in interface_state: + _interface['transceiver'] = interface_state['transceiver'] + + entry_interface_key = '/interface[{:s}]'.format(interface_name) + entries.append((entry_interface_key, _interface)) + + if interface_type == 'ethernetCsmacd': + ethernet_state = interface['ethernet']['state'] + + _ethernet = { + 'mac-address' : ethernet_state['mac-address'], + 'hw-mac-address' : ethernet_state['hw-mac-address'], + 'port-speed' : ethernet_state['port-speed'].split(':')[-1], + 'negotiated-port-speed' : ethernet_state['negotiated-port-speed'].split(':')[-1], + } + entry_ethernet_key = '{:s}/ethernet'.format(entry_interface_key) + entries.append((entry_ethernet_key, _ethernet)) + + subinterfaces = interface.get('subinterfaces', {}).get('subinterface', []) + for subinterface in subinterfaces: + LOGGER.debug('subinterface={:s}'.format(str(subinterface))) + + subinterface_index = subinterface['index'] + subinterface_state = subinterface.get('state', {}) + + _subinterface = {'index': subinterface_index} + if 'name' in subinterface_state: + _subinterface['name'] = subinterface_state['name'] + if 'enabled' in subinterface_state: + _subinterface['enabled'] = subinterface_state['enabled'] + + if 'vlan' in subinterface: + vlan = subinterface['vlan'] + vlan_match = vlan['match'] + + single_tagged = vlan_match.pop('single-tagged', None) + if single_tagged is not None: + single_tagged_config = single_tagged['config'] + vlan_id = single_tagged_config['vlan-id'] + _subinterface['vlan_id'] = vlan_id + + if len(vlan_match) > 0: + raise Exception('Unsupported VLAN schema: {:s}'.format(str(vlan))) + + ipv4_addresses = subinterface.get('ipv4', {}).get('addresses', {}).get('address', []) + if len(ipv4_addresses) > 1: + raise Exception('Multiple IPv4 Addresses not supported: {:s}'.format(str(ipv4_addresses))) + for ipv4_address in ipv4_addresses: + LOGGER.debug('ipv4_address={:s}'.format(str(ipv4_address))) + _subinterface['address_ip'] = ipv4_address['ip'] + ipv4_address_state = ipv4_address.get('state', {}) + #if 'origin' in ipv4_address_state: + # _subinterface['origin'] = ipv4_address_state['origin'] + if 'prefix-length' in ipv4_address_state: + _subinterface['address_prefix'] = ipv4_address_state['prefix-length'] + + ipv6_addresses = subinterface.get('ipv6', {}).get('addresses', {}).get('address', []) + if len(ipv6_addresses) > 1: + raise Exception('Multiple IPv6 Addresses not supported: {:s}'.format(str(ipv6_addresses))) + for ipv6_address in ipv6_addresses: + LOGGER.debug('ipv6_address={:s}'.format(str(ipv6_address))) + _subinterface['address_ipv6'] = ipv6_address['ip'] + ipv6_address_state = ipv6_address.get('state', {}) + #if 'origin' in ipv6_address_state: + # _subinterface['origin_ipv6'] = ipv6_address_state['origin'] + if 'prefix-length' in ipv6_address_state: + _subinterface['address_prefix_ipv6'] = ipv6_address_state['prefix-length'] + + entry_subinterface_key = '{:s}/subinterface[{:d}]'.format(entry_interface_key, subinterface_index) + entries.append((entry_subinterface_key, _subinterface)) + + return entries diff --git a/src/device/service/drivers/gnmi_openconfig/handlers/InterfaceCounter.py b/src/device/service/drivers/gnmi_openconfig/handlers/InterfaceCounter.py index 502868c2204553d30e7cdd529184cf994d03fd21..ae6d86c43addf4f3083b9900796365b3e0601620 100644 --- a/src/device/service/drivers/gnmi_openconfig/handlers/InterfaceCounter.py +++ b/src/device/service/drivers/gnmi_openconfig/handlers/InterfaceCounter.py @@ -12,69 +12,53 @@ # See the License for the specific language governing permissions and # limitations under the License. -import json, logging +import json, libyang, logging from typing import Any, Dict, List, Tuple from ._Handler import _Handler -from .Tools import dict_get_first +from .YangHandler import YangHandler LOGGER = logging.getLogger(__name__) +#pylint: disable=abstract-method class InterfaceCounterHandler(_Handler): def get_resource_key(self) -> str: return '/interface/counters' - def get_path(self) -> str: return '/interfaces/interface/state/counters' - - def parse(self, json_data : Dict) -> List[Tuple[str, Dict[str, Any]]]: - LOGGER.info('[parse] json_data = {:s}'.format(json.dumps(json_data))) - json_interface_list : List[Dict] = json_data.get('interface', []) - - response = [] - for json_interface in json_interface_list: - LOGGER.info('[parse] json_interface = {:s}'.format(json.dumps(json_interface))) - - interface = {} - - NAME_FIELDS = ('name', 'openconfig-interface:name', 'oci:name') - interface_name = dict_get_first(json_interface, NAME_FIELDS) - if interface_name is None: continue - interface['name'] = interface_name - - STATE_FIELDS = ('state', 'openconfig-interface:state', 'oci:state') - json_state = dict_get_first(json_interface, STATE_FIELDS, default={}) - - COUNTERS_FIELDS = ('counters', 'openconfig-interface:counters', 'oci:counters') - json_counters = dict_get_first(json_state, COUNTERS_FIELDS, default={}) - - IN_PKTS_FIELDS = ('in-pkts', 'openconfig-interface:in-pkts', 'oci:in-pkts') - interface_in_pkts = dict_get_first(json_counters, IN_PKTS_FIELDS) - if interface_in_pkts is not None: interface['in-pkts'] = int(interface_in_pkts) - - IN_OCTETS_FIELDS = ('in-octets', 'openconfig-interface:in-octets', 'oci:in-octets') - interface_in_octets = dict_get_first(json_counters, IN_OCTETS_FIELDS) - if interface_in_octets is not None: interface['in-octets'] = int(interface_in_octets) - - IN_ERRORS_FIELDS = ('in-errors', 'openconfig-interface:in-errors', 'oci:in-errors') - interface_in_errors = dict_get_first(json_counters, IN_ERRORS_FIELDS) - if interface_in_errors is not None: interface['in-errors'] = int(interface_in_errors) - - OUT_OCTETS_FIELDS = ('out-octets', 'openconfig-interface:out-octets', 'oci:out-octets') - interface_out_octets = dict_get_first(json_counters, OUT_OCTETS_FIELDS) - if interface_out_octets is not None: interface['out-octets'] = int(interface_out_octets) - - OUT_PKTS_FIELDS = ('out-pkts', 'openconfig-interface:out-pkts', 'oci:out-pkts') - interface_out_pkts = dict_get_first(json_counters, OUT_PKTS_FIELDS) - if interface_out_pkts is not None: interface['out-pkts'] = int(interface_out_pkts) - - OUT_ERRORS_FIELDS = ('out-errors', 'openconfig-interface:out-errors', 'oci:out-errors') - interface_out_errors = dict_get_first(json_counters, OUT_ERRORS_FIELDS) - if interface_out_errors is not None: interface['out-errors'] = int(interface_out_errors) - - OUT_DISCARDS_FIELDS = ('out-discards', 'openconfig-interface:out-discards', 'oci:out-discards') - interface_out_discards = dict_get_first(json_counters, OUT_DISCARDS_FIELDS) - if interface_out_discards is not None: interface['out-discards'] = int(interface_out_discards) - - #LOGGER.info('[parse] interface = {:s}'.format(str(interface))) - - if len(interface) == 0: continue - response.append(('/interface[{:s}]'.format(interface['name']), interface)) - - return response + def get_path(self) -> str: return '/openconfig-interfaces:interfaces/interface/state/counters' + + def parse( + self, json_data : Dict, yang_handler : YangHandler + ) -> List[Tuple[str, Dict[str, Any]]]: + LOGGER.debug('json_data = {:s}'.format(json.dumps(json_data))) + + yang_interfaces_path = self.get_path() + json_data_valid = yang_handler.parse_to_dict(yang_interfaces_path, json_data, fmt='json') + + entries = [] + for interface in json_data_valid['interfaces']['interface']: + LOGGER.debug('interface={:s}'.format(str(interface))) + + interface_name = interface['name'] + interface_counters = interface.get('state', {}).get('counters', {}) + _interface = { + 'name' : interface_name, + 'in-broadcast-pkts' : interface_counters['in_broadcast_pkts' ], + 'in-discards' : interface_counters['in_discards' ], + 'in-errors' : interface_counters['in_errors' ], + 'in-fcs-errors' : interface_counters['in_fcs_errors' ], + 'in-multicast-pkts' : interface_counters['in_multicast_pkts' ], + 'in-octets' : interface_counters['in_octets' ], + 'in-pkts' : interface_counters['in_pkts' ], + 'in-unicast-pkts' : interface_counters['in_unicast_pkts' ], + 'out-broadcast-pkts': interface_counters['out_broadcast_pkts'], + 'out-discards' : interface_counters['out_discards' ], + 'out-errors' : interface_counters['out_errors' ], + 'out-multicast-pkts': interface_counters['out_multicast_pkts'], + 'out-octets' : interface_counters['out_octets' ], + 'out-pkts' : interface_counters['out_pkts' ], + 'out-unicast-pkts' : interface_counters['out_unicast_pkts' ], + } + LOGGER.debug('interface = {:s}'.format(str(interface))) + + entry_interface_key = '/interface[{:s}]'.format(interface_name) + entries.append((entry_interface_key, _interface)) + + return entries diff --git a/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstance.py b/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstance.py index d522eec964b2c6beffe953075411dbba2594eaa4..f1d1c56b4023a1250263aaf0995975d815eb5fec 100644 --- a/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstance.py +++ b/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstance.py @@ -12,18 +12,39 @@ # See the License for the specific language governing permissions and # limitations under the License. -import json, logging +import json, libyang, logging from typing import Any, Dict, List, Tuple from ._Handler import _Handler +from .Tools import get_str +from .YangHandler import YangHandler LOGGER = logging.getLogger(__name__) +MAP_NETWORK_INSTANCE_TYPE = { + # special routing instance; acts as default/global routing instance for a network device + 'DEFAULT': 'openconfig-network-instance-types:DEFAULT_INSTANCE', + + # private L3-only routing instance; formed of one or more RIBs + 'L3VRF': 'openconfig-network-instance-types:L3VRF', + + # private L2-only switch instance; formed of one or more L2 forwarding tables + 'L2VSI': 'openconfig-network-instance-types:L2VSI', + + # private L2-only forwarding instance; point to point connection between two endpoints + 'L2P2P': 'openconfig-network-instance-types:L2P2P', + + # private Layer 2 and Layer 3 forwarding instance + 'L2L3': 'openconfig-network-instance-types:L2L3', +} + class NetworkInstanceHandler(_Handler): def get_resource_key(self) -> str: return '/network_instance' - def get_path(self) -> str: return '/network-instances/network-instance' + def get_path(self) -> str: return '/openconfig-network-instance:network-instances' - def compose(self, resource_key : str, resource_value : Dict, delete : bool = False) -> Tuple[str, str]: - ni_name = str(resource_value['name']) # test-svc + def compose( + self, resource_key : str, resource_value : Dict, yang_handler : YangHandler, delete : bool = False + ) -> Tuple[str, str]: + ni_name = get_str(resource_value, 'name') # test-svc if delete: PATH_TMPL = '/network-instances/network-instance[name={:s}]' @@ -31,32 +52,124 @@ class NetworkInstanceHandler(_Handler): str_data = json.dumps({}) return str_path, str_data - ni_type = str(resource_value['type']) # L3VRF / L2VSI / ... + ni_type = get_str(resource_value, 'type') # L3VRF / L2VSI / ... + ni_type = MAP_NETWORK_INSTANCE_TYPE.get(ni_type, ni_type) + + str_path = '/network-instances/network-instance[name={:s}]'.format(ni_name) + #str_data = json.dumps({ + # 'name': ni_name, + # 'config': {'name': ni_name, 'type': ni_type}, + #}) - # not works: [FailedPrecondition] unsupported identifier 'DIRECTLY_CONNECTED' - #protocols = [self._compose_directly_connected()] + yang_nis : libyang.DContainer = yang_handler.get_data_path('/openconfig-network-instance:network-instances') + yang_ni_path = 'network-instance[name="{:s}"]'.format(ni_name) + yang_ni : libyang.DContainer = yang_nis.create_path(yang_ni_path) + yang_ni.create_path('config/name', ni_name) + yang_ni.create_path('config/type', ni_type) - MAP_OC_NI_TYPE = { - 'L3VRF': 'openconfig-network-instance-types:L3VRF', - } - ni_type = MAP_OC_NI_TYPE.get(ni_type, ni_type) + # 'DIRECTLY_CONNECTED' is implicitly added + #'protocols': {'protocol': protocols}, - str_path = '/network-instances/network-instance[name={:s}]'.format(ni_name) - str_data = json.dumps({ - 'name': ni_name, - 'config': {'name': ni_name, 'type': ni_type}, - #'protocols': {'protocol': protocols}, - }) + str_data = yang_ni.print_mem('json') + json_data = json.loads(str_data) + json_data = json_data['openconfig-network-instance:network-instance'][0] + str_data = json.dumps(json_data) return str_path, str_data - def _compose_directly_connected(self, name=None, enabled=True) -> Dict: - identifier = 'DIRECTLY_CONNECTED' - if name is None: name = 'DIRECTLY_CONNECTED' - return { - 'identifier': identifier, 'name': name, - 'config': {'identifier': identifier, 'name': name, 'enabled': enabled}, - } - - def parse(self, json_data : Dict) -> List[Tuple[str, Dict[str, Any]]]: - response = [] - return response + def parse( + self, json_data : Dict, yang_handler : YangHandler + ) -> List[Tuple[str, Dict[str, Any]]]: + LOGGER.debug('json_data = {:s}'.format(json.dumps(json_data))) + + # Arista Parsing Fixes: + # - Default instance comes with mpls/signaling-protocols/rsvp-te/global/hellos/state/hello-interval set to 0 + # overwrite with .../hellos/config/hello-interval + network_instances = json_data.get('openconfig-network-instance:network-instance', []) + for network_instance in network_instances: + if network_instance['name'] != 'default': continue + mpls_rsvp_te = network_instance.get('mpls', {}).get('signaling-protocols', {}).get('rsvp-te', {}) + mpls_rsvp_te_hellos = mpls_rsvp_te.get('global', {}).get('hellos', {}) + hello_interval = mpls_rsvp_te_hellos.get('config', {}).get('hello-interval', 9000) + mpls_rsvp_te_hellos.get('state', {})['hello-interval'] = hello_interval + + yang_network_instances_path = self.get_path() + json_data_valid = yang_handler.parse_to_dict(yang_network_instances_path, json_data, fmt='json', strict=False) + + entries = [] + for network_instance in json_data_valid['network-instances']['network-instance']: + LOGGER.debug('network_instance={:s}'.format(str(network_instance))) + ni_name = network_instance['name'] + + ni_config = network_instance['config'] + ni_type = ni_config['type'].split(':')[-1] + + _net_inst = {'name': ni_name, 'type': ni_type} + entry_net_inst_key = '/network_instance[{:s}]'.format(ni_name) + entries.append((entry_net_inst_key, _net_inst)) + + ni_interfaces = network_instance.get('interfaces', {}).get('interface', []) + for ni_interface in ni_interfaces: + #ni_if_id = ni_interface['id'] + ni_if_config = ni_interface['config'] + ni_if_name = ni_if_config['interface'] + ni_sif_index = ni_if_config['subinterface'] + ni_if_id = '{:s}.{:d}'.format(ni_if_name, ni_sif_index) + + _interface = {'name': ni_name, 'id': ni_if_id, 'if_name': ni_if_name, 'sif_index': ni_sif_index} + entry_interface_key = '{:s}/interface[{:s}]'.format(entry_net_inst_key, ni_if_id) + entries.append((entry_interface_key, _interface)) + + ni_protocols = network_instance.get('protocols', {}).get('protocol', []) + for ni_protocol in ni_protocols: + ni_protocol_id = ni_protocol['identifier'].split(':')[-1] + ni_protocol_name = ni_protocol['name'] + + _protocol = {'name': ni_name, 'identifier': ni_protocol_id, 'protocol_name': ni_protocol_name} + entry_protocol_key = '{:s}/protocols[{:s}]'.format(entry_net_inst_key, ni_protocol_id) + entries.append((entry_protocol_key, _protocol)) + + if ni_protocol_id == 'STATIC': + static_routes = ni_protocol.get('static-routes', {}).get('static', []) + for static_route in static_routes: + static_route_prefix = static_route['prefix'] + for next_hop in static_route.get('next-hops', {}).get('next-hop', []): + static_route_metric = next_hop['config']['metric'] + _static_route = { + 'prefix' : static_route_prefix, + 'index' : next_hop['index'], + 'next_hop': next_hop['config']['next-hop'], + 'metric' : static_route_metric, + } + _static_route.update(_protocol) + entry_static_route_key = '{:s}/static_route[{:s}:{:d}]'.format( + entry_protocol_key, static_route_prefix, static_route_metric + ) + entries.append((entry_static_route_key, _static_route)) + + ni_tables = network_instance.get('tables', {}).get('table', []) + for ni_table in ni_tables: + ni_table_protocol = ni_table['protocol'].split(':')[-1] + ni_table_address_family = ni_table['address-family'].split(':')[-1] + _table = {'protocol': ni_table_protocol, 'address_family': ni_table_address_family} + entry_table_key = '{:s}/table[{:s},{:s}]'.format( + entry_net_inst_key, ni_table_protocol, ni_table_address_family + ) + entries.append((entry_table_key, _table)) + + ni_vlans = network_instance.get('vlans', {}).get('vlan', []) + for ni_vlan in ni_vlans: + ni_vlan_id = ni_vlan['vlan-id'] + + #ni_vlan_config = ni_vlan['config'] + ni_vlan_state = ni_vlan['state'] + ni_vlan_name = ni_vlan_state['name'] + + _members = [ + member['state']['interface'] + for member in ni_vlan.get('members', {}).get('member', []) + ] + _vlan = {'vlan_id': ni_vlan_id, 'name': ni_vlan_name, 'members': _members} + entry_vlan_key = '{:s}/vlan[{:d}]'.format(entry_net_inst_key, ni_vlan_id) + entries.append((entry_vlan_key, _vlan)) + + return entries diff --git a/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstanceInterface.py b/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstanceInterface.py index cc22618bc4179ba6eabdd2b4232b8cbfb92f1587..5e5ea9bf0bdc38b09d3959e8b6ecfc3534319817 100644 --- a/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstanceInterface.py +++ b/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstanceInterface.py @@ -12,35 +12,62 @@ # See the License for the specific language governing permissions and # limitations under the License. -import json, logging +import json, libyang, logging from typing import Any, Dict, List, Tuple from ._Handler import _Handler +from .Tools import get_int, get_str +from .YangHandler import YangHandler LOGGER = logging.getLogger(__name__) +IS_CEOS = True + class NetworkInstanceInterfaceHandler(_Handler): def get_resource_key(self) -> str: return '/network_instance/interface' - def get_path(self) -> str: return '/network-instances/network-instance/interfaces' + def get_path(self) -> str: return '/openconfig-network-instance:network-instances/network-instance/interfaces' + + def compose( + self, resource_key : str, resource_value : Dict, yang_handler : YangHandler, delete : bool = False + ) -> Tuple[str, str]: + ni_name = get_str(resource_value, 'name' ) # test-svc + ni_if_id = get_str(resource_value, 'id' ) # ethernet-1/1.0 + if_name = get_str(resource_value, 'interface' ) # ethernet-1/1 + sif_index = get_int(resource_value, 'subinterface', 0) # 0 - def compose(self, resource_key : str, resource_value : Dict, delete : bool = False) -> Tuple[str, str]: - ni_name = str(resource_value['name' ]) # test-svc - if_name = str(resource_value['if_name' ]) # ethernet-1/1 - sif_index = int(resource_value['sif_index']) # 0 - if_id = '{:s}.{:d}'.format(if_name, sif_index) + if IS_CEOS: ni_if_id = if_name if delete: PATH_TMPL = '/network-instances/network-instance[name={:s}]/interfaces/interface[id={:s}]' - str_path = PATH_TMPL.format(ni_name, if_id) + str_path = PATH_TMPL.format(ni_name, ni_if_id) str_data = json.dumps({}) return str_path, str_data - str_path = '/network-instances/network-instance[name={:s}]/interfaces/interface[id={:s}]'.format(ni_name, if_id) - str_data = json.dumps({ - 'id': if_id, - 'config': {'id': if_id, 'interface': if_name, 'subinterface': sif_index}, - }) + str_path = '/network-instances/network-instance[name={:s}]/interfaces/interface[id={:s}]'.format( + ni_name, ni_if_id + ) + #str_data = json.dumps({ + # 'id': if_id, + # 'config': {'id': if_id, 'interface': if_name, 'subinterface': sif_index}, + #}) + + yang_nis : libyang.DContainer = yang_handler.get_data_path('/openconfig-network-instance:network-instances') + yang_ni : libyang.DContainer = yang_nis.create_path('network-instance[name="{:s}"]'.format(ni_name)) + yang_ni_ifs : libyang.DContainer = yang_ni.create_path('interfaces') + yang_ni_if_path = 'interface[id="{:s}"]'.format(ni_if_id) + yang_ni_if : libyang.DContainer = yang_ni_ifs.create_path(yang_ni_if_path) + yang_ni_if.create_path('config/id', ni_if_id) + yang_ni_if.create_path('config/interface', if_name) + yang_ni_if.create_path('config/subinterface', sif_index) + + str_data = yang_ni_if.print_mem('json') + json_data = json.loads(str_data) + json_data = json_data['openconfig-network-instance:interface'][0] + str_data = json.dumps(json_data) return str_path, str_data - def parse(self, json_data : Dict) -> List[Tuple[str, Dict[str, Any]]]: + def parse( + self, json_data : Dict, yang_handler : YangHandler + ) -> List[Tuple[str, Dict[str, Any]]]: + LOGGER.debug('[parse] json_data = {:s}'.format(str(json_data))) response = [] return response diff --git a/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstanceProtocol.py b/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstanceProtocol.py new file mode 100644 index 0000000000000000000000000000000000000000..f155fa1ca5c0a990abf18ab1c78059673d94e04e --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstanceProtocol.py @@ -0,0 +1,79 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json, libyang, logging +from typing import Any, Dict, List, Tuple +from ._Handler import _Handler +from .Tools import get_str +from .YangHandler import YangHandler + +LOGGER = logging.getLogger(__name__) + +class NetworkInstanceProtocolHandler(_Handler): + def get_resource_key(self) -> str: return '/network_instance/protocols' + def get_path(self) -> str: + return '/openconfig-network-instance:network-instances/network-instance/protocols/protocol' + + def compose( + self, resource_key : str, resource_value : Dict, yang_handler : YangHandler, delete : bool = False + ) -> Tuple[str, str]: + ni_name = get_str(resource_value, 'name' ) # test-svc + identifier = get_str(resource_value, 'identifier') # 'STATIC' + proto_name = get_str(resource_value, 'protocol_name') # 'STATIC' + + if ':' not in identifier: + identifier = 'openconfig-policy-types:{:s}'.format(identifier) + PATH_TMPL = '/network-instances/network-instance[name={:s}]/protocols/protocol[identifier={:s}][name={:s}]' + str_path = PATH_TMPL.format(ni_name, identifier, proto_name) + + if delete: + str_data = json.dumps({}) + return str_path, str_data + + #str_data = json.dumps({ + # 'identifier': identifier, 'name': name, + # 'config': {'identifier': identifier, 'name': name, 'enabled': True}, + # 'static_routes': {'static': [{ + # 'prefix': prefix, + # 'config': {'prefix': prefix}, + # 'next_hops': { + # 'next-hop': [{ + # 'index': next_hop_index, + # 'config': {'index': next_hop_index, 'next_hop': next_hop} + # }] + # } + # }]} + #}) + + yang_nis : libyang.DContainer = yang_handler.get_data_path('/openconfig-network-instance:network-instances') + yang_ni : libyang.DContainer = yang_nis.create_path('network-instance[name="{:s}"]'.format(ni_name)) + yang_ni_prs : libyang.DContainer = yang_ni.create_path('protocols') + yang_ni_pr_path = 'protocol[identifier="{:s}"][name="{:s}"]'.format(identifier, proto_name) + yang_ni_pr : libyang.DContainer = yang_ni_prs.create_path(yang_ni_pr_path) + yang_ni_pr.create_path('config/identifier', identifier) + yang_ni_pr.create_path('config/name', proto_name) + yang_ni_pr.create_path('config/enabled', True ) + + str_data = yang_ni_pr.print_mem('json') + json_data = json.loads(str_data) + json_data = json_data['openconfig-network-instance:protocol'][0] + str_data = json.dumps(json_data) + return str_path, str_data + + def parse( + self, json_data : Dict, yang_handler : YangHandler + ) -> List[Tuple[str, Dict[str, Any]]]: + LOGGER.debug('[parse] json_data = {:s}'.format(str(json_data))) + response = [] + return response diff --git a/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstanceStaticRoute.py b/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstanceStaticRoute.py index 6294f9c9bf46b3b60da48f19c08ad57f096fa39b..9f80b647b8e1188c80609dbdb47bbe9ea0d68b5f 100644 --- a/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstanceStaticRoute.py +++ b/src/device/service/drivers/gnmi_openconfig/handlers/NetworkInstanceStaticRoute.py @@ -12,50 +12,90 @@ # See the License for the specific language governing permissions and # limitations under the License. -import json, logging +import json, libyang, logging from typing import Any, Dict, List, Tuple from ._Handler import _Handler +from .Tools import get_int, get_str +from .YangHandler import YangHandler LOGGER = logging.getLogger(__name__) class NetworkInstanceStaticRouteHandler(_Handler): - def get_resource_key(self) -> str: return '/network_instance/static_route' - def get_path(self) -> str: return '/network-instances/network-instance/static_route' + def get_resource_key(self) -> str: return '/network_instance/protocols/static_route' + def get_path(self) -> str: + return '/openconfig-network-instance:network-instances/network-instance/protocols/protocol/static-routes' - def compose(self, resource_key : str, resource_value : Dict, delete : bool = False) -> Tuple[str, str]: - ni_name = str(resource_value['name' ]) # test-svc - prefix = str(resource_value['prefix' ]) # '172.0.1.0/24' + def compose( + self, resource_key : str, resource_value : Dict, yang_handler : YangHandler, delete : bool = False + ) -> Tuple[str, str]: + ni_name = get_str(resource_value, 'name' ) # test-svc + identifier = get_str(resource_value, 'identifier') # 'STATIC' + proto_name = get_str(resource_value, 'protocol_name') # 'STATIC' + prefix = get_str(resource_value, 'prefix') # '172.0.1.0/24' + + if ':' not in identifier: + identifier = 'openconfig-policy-types:{:s}'.format(identifier) - identifier = 'STATIC' - name = 'static' if delete: PATH_TMPL = '/network-instances/network-instance[name={:s}]/protocols' PATH_TMPL += '/protocol[identifier={:s}][name={:s}]/static-routes/static[prefix={:s}]' - str_path = PATH_TMPL.format(ni_name, identifier, name, prefix) + str_path = PATH_TMPL.format(ni_name, identifier, proto_name, prefix) str_data = json.dumps({}) return str_path, str_data - next_hop = str(resource_value['next_hop' ]) # '172.0.0.1' - next_hop_index = int(resource_value.get('next_hop_index', 0)) # 0 + next_hop = get_str(resource_value, 'next_hop') # '172.0.0.1' + metric = get_int(resource_value, 'metric' ) # 20 + index = get_str(resource_value, 'index' ) # AUTO_1_172-0-0-1 + if index is None: + index = 'AUTO_{:d}_{:s}'.format(metric, next_hop) PATH_TMPL = '/network-instances/network-instance[name={:s}]/protocols/protocol[identifier={:s}][name={:s}]' - str_path = PATH_TMPL.format(ni_name, identifier, name) - str_data = json.dumps({ - 'identifier': identifier, 'name': name, - 'config': {'identifier': identifier, 'name': name, 'enabled': True}, - 'static_routes': {'static': [{ - 'prefix': prefix, - 'config': {'prefix': prefix}, - 'next_hops': { - 'next-hop': [{ - 'index': next_hop_index, - 'config': {'index': next_hop_index, 'next_hop': next_hop} - }] - } - }]} - }) + str_path = PATH_TMPL.format(ni_name, identifier, proto_name) + #str_data = json.dumps({ + # 'identifier': identifier, 'name': name, + # 'config': {'identifier': identifier, 'name': name, 'enabled': True}, + # 'static_routes': {'static': [{ + # 'prefix': prefix, + # 'config': {'prefix': prefix}, + # 'next_hops': { + # 'next-hop': [{ + # 'index': next_hop_index, + # 'config': {'index': next_hop_index, 'next_hop': next_hop} + # }] + # } + # }]} + #}) + + yang_nis : libyang.DContainer = yang_handler.get_data_path('/openconfig-network-instance:network-instances') + yang_ni : libyang.DContainer = yang_nis.create_path('network-instance[name="{:s}"]'.format(ni_name)) + yang_ni_prs : libyang.DContainer = yang_ni.create_path('protocols') + yang_ni_pr_path = 'protocol[identifier="{:s}"][name="{:s}"]'.format(identifier, proto_name) + yang_ni_pr : libyang.DContainer = yang_ni_prs.create_path(yang_ni_pr_path) + yang_ni_pr.create_path('config/identifier', identifier) + yang_ni_pr.create_path('config/name', proto_name) + yang_ni_pr.create_path('config/enabled', True ) + + yang_ni_pr_srs : libyang.DContainer = yang_ni_pr.create_path('static-routes') + yang_ni_pr_sr_path = 'static[prefix="{:s}"]'.format(prefix) + yang_ni_pr_sr : libyang.DContainer = yang_ni_pr_srs.create_path(yang_ni_pr_sr_path) + yang_ni_pr_sr.create_path('config/prefix', prefix) + + yang_ni_pr_sr_nhs : libyang.DContainer = yang_ni_pr_sr.create_path('next-hops') + yang_ni_pr_sr_nh_path = 'next-hop[index="{:s}"]'.format(index) + yang_ni_pr_sr_nh : libyang.DContainer = yang_ni_pr_sr_nhs.create_path(yang_ni_pr_sr_nh_path) + yang_ni_pr_sr_nh.create_path('config/index', index) + yang_ni_pr_sr_nh.create_path('config/next-hop', next_hop) + yang_ni_pr_sr_nh.create_path('config/metric', metric) + + str_data = yang_ni_pr.print_mem('json') + json_data = json.loads(str_data) + json_data = json_data['openconfig-network-instance:protocol'][0] + str_data = json.dumps(json_data) return str_path, str_data - def parse(self, json_data : Dict) -> List[Tuple[str, Dict[str, Any]]]: + def parse( + self, json_data : Dict, yang_handler : YangHandler + ) -> List[Tuple[str, Dict[str, Any]]]: + LOGGER.debug('[parse] json_data = {:s}'.format(str(json_data))) response = [] return response diff --git a/src/device/service/drivers/gnmi_openconfig/handlers/Tools.py b/src/device/service/drivers/gnmi_openconfig/handlers/Tools.py index 358c7de9fa3b9c46c4a7c70142deb1c2ab396ad0..f258de926d44c48a655eab877eeb0350df511b69 100644 --- a/src/device/service/drivers/gnmi_openconfig/handlers/Tools.py +++ b/src/device/service/drivers/gnmi_openconfig/handlers/Tools.py @@ -13,7 +13,7 @@ # limitations under the License. import re -from typing import Any, Dict, Iterable +from typing import Any, Callable, Dict, Iterable, Optional RE_REMOVE_FILTERS = re.compile(r'\[[^\]]+\]') RE_REMOVE_NAMESPACES = re.compile(r'\/[a-zA-Z0-9\_\-]+:') @@ -23,8 +23,39 @@ def get_schema(resource_key : str): resource_key = RE_REMOVE_NAMESPACES.sub('/', resource_key) return resource_key -def dict_get_first(d : Dict, field_names : Iterable[str], default=None) -> Any: - for field_name in field_names: - if field_name not in d: continue - return d[field_name] +def container_get_first( + container : Dict[str, Any], key_name : str, namespace : Optional[str]=None, namespaces : Iterable[str]=tuple(), + default : Optional[Any] = None +) -> Any: + value = container.get(key_name) + if value is not None: return value + + if namespace is not None: + if len(namespaces) > 0: + raise Exception('At maximum, one of namespace or namespaces can be specified') + namespaces = (namespace,) + + for namespace in namespaces: + namespace_key_name = '{:s}:{:s}'.format(namespace, key_name) + if namespace_key_name in container: return container[namespace_key_name] + return default + +def get_value( + resource_value : Dict, field_name : str, cast_func : Callable = lambda x:x, default : Optional[Any] = None +) -> Optional[Any]: + field_value = resource_value.get(field_name, default) + if field_value is not None: field_value = cast_func(field_value) + return field_value + +def get_bool(resource_value : Dict, field_name : bool, default : Optional[Any] = None) -> bool: + return get_value(resource_value, field_name, cast_func=bool, default=default) + +def get_float(resource_value : Dict, field_name : float, default : Optional[Any] = None) -> float: + return get_value(resource_value, field_name, cast_func=float, default=default) + +def get_int(resource_value : Dict, field_name : int, default : Optional[Any] = None) -> int: + return get_value(resource_value, field_name, cast_func=int, default=default) + +def get_str(resource_value : Dict, field_name : str, default : Optional[Any] = None) -> str: + return get_value(resource_value, field_name, cast_func=str, default=default) diff --git a/src/device/service/drivers/gnmi_openconfig/handlers/YangHandler.py b/src/device/service/drivers/gnmi_openconfig/handlers/YangHandler.py new file mode 100644 index 0000000000000000000000000000000000000000..944dfa60781162d841c5da12813a4581c282471b --- /dev/null +++ b/src/device/service/drivers/gnmi_openconfig/handlers/YangHandler.py @@ -0,0 +1,110 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json, libyang, logging, os +from typing import Dict, Optional + +YANG_BASE_PATH = os.path.join(os.path.dirname(__file__), '..', 'git', 'openconfig', 'public') +YANG_SEARCH_PATHS = ':'.join([ + os.path.join(YANG_BASE_PATH, 'release'), + os.path.join(YANG_BASE_PATH, 'third_party'), +]) + +YANG_MODULES = [ + 'iana-if-type', + 'openconfig-bgp-types', + 'openconfig-vlan-types', + + 'openconfig-interfaces', + 'openconfig-if-8021x', + 'openconfig-if-aggregate', + 'openconfig-if-ethernet-ext', + 'openconfig-if-ethernet', + 'openconfig-if-ip-ext', + 'openconfig-if-ip', + 'openconfig-if-poe', + 'openconfig-if-sdn-ext', + 'openconfig-if-tunnel', + + 'openconfig-vlan', + + 'openconfig-types', + 'openconfig-policy-types', + 'openconfig-mpls-types', + 'openconfig-network-instance-types', + 'openconfig-network-instance', + + 'openconfig-platform', + 'openconfig-platform-controller-card', + 'openconfig-platform-cpu', + 'openconfig-platform-ext', + 'openconfig-platform-fabric', + 'openconfig-platform-fan', + 'openconfig-platform-integrated-circuit', + 'openconfig-platform-linecard', + 'openconfig-platform-pipeline-counters', + 'openconfig-platform-port', + 'openconfig-platform-psu', + 'openconfig-platform-software', + 'openconfig-platform-transceiver', + 'openconfig-platform-types', +] + +LOGGER = logging.getLogger(__name__) + +class YangHandler: + def __init__(self) -> None: + self._yang_context = libyang.Context(YANG_SEARCH_PATHS) + self._loaded_modules = set() + for yang_module_name in YANG_MODULES: + LOGGER.info('Loading module: {:s}'.format(str(yang_module_name))) + self._yang_context.load_module(yang_module_name).feature_enable_all() + self._loaded_modules.add(yang_module_name) + self._data_path_instances = dict() + + def get_data_paths(self) -> Dict[str, libyang.DNode]: + return self._data_path_instances + + def get_data_path(self, path : str) -> libyang.DNode: + data_path_instance = self._data_path_instances.get(path) + if data_path_instance is None: + data_path_instance = self._yang_context.create_data_path(path) + self._data_path_instances[path] = data_path_instance + return data_path_instance + + def parse_to_dict( + self, request_path : str, json_data : Dict, fmt : str = 'json', strict : bool = True + ) -> Dict: + if fmt != 'json': raise Exception('Unsupported format: {:s}'.format(str(fmt))) + LOGGER.debug('request_path = {:s}'.format(str(request_path))) + LOGGER.debug('json_data = {:s}'.format(str(json_data))) + LOGGER.debug('format = {:s}'.format(str(fmt))) + + parent_path_parts = list(filter(lambda s: len(s) > 0, request_path.split('/'))) + for parent_path_part in reversed(parent_path_parts): + json_data = {parent_path_part: json_data} + str_data = json.dumps(json_data) + + dnode : Optional[libyang.DNode] = self._yang_context.parse_data_mem( + str_data, fmt, strict=strict, parse_only=True, #validate_present=True, #validate=True, + ) + if dnode is None: raise Exception('Unable to parse Data({:s})'.format(str(json_data))) + + parsed = dnode.print_dict() + LOGGER.debug('parsed = {:s}'.format(json.dumps(parsed))) + dnode.free() + return parsed + + def destroy(self) -> None: + self._yang_context.destroy() diff --git a/src/device/service/drivers/gnmi_openconfig/handlers/_Handler.py b/src/device/service/drivers/gnmi_openconfig/handlers/_Handler.py index f051c43534ab4e1d6c15a34ae2070067034ba9e1..215a4d499e534ca9797c487adb0c798487a4a7cf 100644 --- a/src/device/service/drivers/gnmi_openconfig/handlers/_Handler.py +++ b/src/device/service/drivers/gnmi_openconfig/handlers/_Handler.py @@ -13,6 +13,7 @@ # limitations under the License. from typing import Any, Dict, List, Tuple +from .YangHandler import YangHandler class _Handler: def get_resource_key(self) -> str: @@ -23,10 +24,14 @@ class _Handler: # Retrieve the OpenConfig path schema used to interrogate the device raise NotImplementedError() - def compose(self, resource_key : str, resource_value : Dict, delete : bool = False) -> Tuple[str, str]: + def compose( + self, resource_key : str, resource_value : Dict, yang_handler : YangHandler, delete : bool = False + ) -> Tuple[str, str]: # Compose a Set/Delete message based on the resource_key/resource_value fields, and the delete flag raise NotImplementedError() - def parse(self, json_data : Dict) -> List[Tuple[str, Dict[str, Any]]]: + def parse( + self, json_data : Dict, yang_handler : YangHandler + ) -> List[Tuple[str, Dict[str, Any]]]: # Parse a Reply from the device and return a list of resource_key/resource_value pairs raise NotImplementedError() diff --git a/src/device/service/drivers/gnmi_openconfig/handlers/__init__.py b/src/device/service/drivers/gnmi_openconfig/handlers/__init__.py index 4ff8e8b38b4a76ba9bb0d2b91fb70b54b06a170e..b36313bb2030ed9d07f1f3c198ea9447a0cafbaa 100644 --- a/src/device/service/drivers/gnmi_openconfig/handlers/__init__.py +++ b/src/device/service/drivers/gnmi_openconfig/handlers/__init__.py @@ -13,7 +13,7 @@ # limitations under the License. import logging -from typing import Dict, List, Optional, Tuple, Union +from typing import Any, Dict, List, Optional, Tuple, Union from device.service.driver_api._Driver import RESOURCE_ENDPOINTS, RESOURCE_INTERFACES, RESOURCE_NETWORK_INSTANCES from ._Handler import _Handler from .Component import ComponentHandler @@ -21,8 +21,10 @@ from .Interface import InterfaceHandler from .InterfaceCounter import InterfaceCounterHandler from .NetworkInstance import NetworkInstanceHandler from .NetworkInstanceInterface import NetworkInstanceInterfaceHandler +from .NetworkInstanceProtocol import NetworkInstanceProtocolHandler from .NetworkInstanceStaticRoute import NetworkInstanceStaticRouteHandler from .Tools import get_schema +from .YangHandler import YangHandler LOGGER = logging.getLogger(__name__) @@ -31,6 +33,7 @@ ifaceh = InterfaceHandler() ifctrh = InterfaceCounterHandler() nih = NetworkInstanceHandler() niifh = NetworkInstanceInterfaceHandler() +niph = NetworkInstanceProtocolHandler() nisrh = NetworkInstanceStaticRouteHandler() ALL_RESOURCE_KEYS = [ @@ -46,9 +49,10 @@ RESOURCE_KEY_MAPPER = { } PATH_MAPPER = { - '/components' : comph.get_path(), - '/interfaces' : ifaceh.get_path(), - '/network-instances' : nih.get_path(), + '/components' : comph.get_path(), + '/components/component' : comph.get_path(), + '/interfaces' : ifaceh.get_path(), + '/network-instances' : nih.get_path(), } RESOURCE_KEY_TO_HANDLER = { @@ -57,6 +61,7 @@ RESOURCE_KEY_TO_HANDLER = { ifctrh.get_resource_key() : ifctrh, nih.get_resource_key() : nih, niifh.get_resource_key() : niifh, + niph.get_resource_key() : niph, nisrh.get_resource_key() : nisrh, } @@ -66,11 +71,13 @@ PATH_TO_HANDLER = { ifctrh.get_path() : ifctrh, nih.get_path() : nih, niifh.get_path() : niifh, + niph.get_path() : niph, nisrh.get_path() : nisrh, } def get_handler( - resource_key : Optional[str] = None, path : Optional[str] = None, raise_if_not_found=True + resource_key : Optional[str] = None, path : Optional[str] = None, + raise_if_not_found=True ) -> Optional[_Handler]: if (resource_key is None) == (path is None): MSG = 'Exactly one of resource_key({:s}) or path({:s}) must be specified' @@ -88,16 +95,24 @@ def get_handler( path_schema = PATH_MAPPER.get(path_schema, path_schema) handler = PATH_TO_HANDLER.get(path_schema) if handler is None and raise_if_not_found: - MSG = 'Handler not found: resource_key={:s} resource_key_schema={:s}' + MSG = 'Handler not found: path={:s} path_schema={:s}' # pylint: disable=broad-exception-raised - raise Exception(MSG.format(str(resource_key), str(resource_key_schema))) + raise Exception(MSG.format(str(path), str(path_schema))) return handler def get_path(resource_key : str) -> str: - return get_handler(resource_key=resource_key).get_path() + handler = get_handler(resource_key=resource_key) + return handler.get_path() -def parse(str_path : str, value : Union[Dict, List]): - return get_handler(path=str_path).parse(value) +def parse( + str_path : str, value : Union[Dict, List], yang_handler : YangHandler +) -> List[Tuple[str, Dict[str, Any]]]: + handler = get_handler(path=str_path) + return handler.parse(value, yang_handler) -def compose(resource_key : str, resource_value : Union[Dict, List], delete : bool = False) -> Tuple[str, str]: - return get_handler(resource_key=resource_key).compose(resource_key, resource_value, delete=delete) +def compose( + resource_key : str, resource_value : Union[Dict, List], + yang_handler : YangHandler, delete : bool = False +) -> Tuple[str, str]: + handler = get_handler(resource_key=resource_key) + return handler.compose(resource_key, resource_value, yang_handler, delete=delete) diff --git a/src/device/service/drivers/gnmi_openconfig/tools/Capabilities.py b/src/device/service/drivers/gnmi_openconfig/tools/Capabilities.py index 093a96233f26399be4e224a48a78ea6329da4304..66b30da9832e661b35e1f13b3e3a47c7a50098cb 100644 --- a/src/device/service/drivers/gnmi_openconfig/tools/Capabilities.py +++ b/src/device/service/drivers/gnmi_openconfig/tools/Capabilities.py @@ -17,7 +17,7 @@ from common.tools.grpc.Tools import grpc_message_to_json from ..gnmi.gnmi_pb2 import CapabilityRequest # pylint: disable=no-name-in-module from ..gnmi.gnmi_pb2_grpc import gNMIStub -def get_supported_encodings( +def check_capabilities( stub : gNMIStub, username : str, password : str, timeout : Optional[int] = None ) -> Set[Union[str, int]]: metadata = [('username', username), ('password', password)] @@ -25,6 +25,17 @@ def get_supported_encodings( reply = stub.Capabilities(req, metadata=metadata, timeout=timeout) data = grpc_message_to_json(reply) + + gnmi_version = data.get('gNMI_version') + if gnmi_version is None or gnmi_version != '0.7.0': + raise Exception('Unsupported gNMI version: {:s}'.format(str(gnmi_version))) + + #supported_models = { + # supported_model['name']: supported_model['version'] + # for supported_model in data.get('supported_models', []) + #} + # TODO: check supported models and versions + supported_encodings = { supported_encoding for supported_encoding in data.get('supported_encodings', []) @@ -33,4 +44,6 @@ def get_supported_encodings( if len(supported_encodings) == 0: # pylint: disable=broad-exception-raised raise Exception('No supported encodings found') - return supported_encodings + if 'JSON_IETF' not in supported_encodings: + # pylint: disable=broad-exception-raised + raise Exception('JSON_IETF encoding not supported') diff --git a/src/device/service/drivers/gnmi_openconfig/tools/Path.py b/src/device/service/drivers/gnmi_openconfig/tools/Path.py index 7ce0631dada58ce581900c974a0b24d170df2f39..1955fc4d94eff0106d324a067809f93151da5699 100644 --- a/src/device/service/drivers/gnmi_openconfig/tools/Path.py +++ b/src/device/service/drivers/gnmi_openconfig/tools/Path.py @@ -19,8 +19,8 @@ from ..gnmi.gnmi_pb2 import Path, PathElem RE_PATH_SPLIT = re.compile(r'/(?=(?:[^\[\]]|\[[^\[\]]+\])*$)') RE_PATH_KEYS = re.compile(r'\[(.*?)\]') -def path_from_string(path='/'): - if not path: return Path(elem=[]) +def path_from_string(path='/'): #, origin='openconfig' + if not path: return Path(elem=[]) #, origin=origin if path[0] == '/': if path[-1] == '/': @@ -40,7 +40,7 @@ def path_from_string(path='/'): dict_keys = dict(x.split('=', 1) for x in elem_keys) path.append(PathElem(name=elem_name, key=dict_keys)) - return Path(elem=path) + return Path(elem=path) #, origin=origin def path_to_string(path : Path) -> str: path_parts = list() diff --git a/src/device/service/drivers/gnmi_openconfig/tools/Value.py b/src/device/service/drivers/gnmi_openconfig/tools/Value.py index 077bdd40ec8859202b8e8e6053b731339edfe7fe..325dacd51b8b1157ea63ff71eed1f9a758d56c3b 100644 --- a/src/device/service/drivers/gnmi_openconfig/tools/Value.py +++ b/src/device/service/drivers/gnmi_openconfig/tools/Value.py @@ -13,9 +13,36 @@ # limitations under the License. import base64, json -from typing import Any +from typing import Any, Dict, List, Union from ..gnmi.gnmi_pb2 import TypedValue +REMOVE_NAMESPACES = ( + 'arista-intf-augments', + 'arista-netinst-augments', + 'openconfig-hercules-platform', +) + +def remove_fields(key : str) -> bool: + parts = key.split(':') + if len(parts) == 1: return False + namespace = parts[0].lower() + return namespace in REMOVE_NAMESPACES + +def recursive_remove_keys(container : Union[Dict, List, Any]) -> None: + if isinstance(container, dict): + remove_keys = [ + key + for key in container.keys() + if remove_fields(key) + ] + for key in remove_keys: + container.pop(key, None) + for value in container.values(): + recursive_remove_keys(value) + elif isinstance(container, list): + for value in container: + recursive_remove_keys(value) + def decode_value(value : TypedValue) -> Any: encoding = value.WhichOneof('value') if encoding == 'json_val': @@ -31,9 +58,13 @@ def decode_value(value : TypedValue) -> Any: raise NotImplementedError() #return value elif encoding == 'json_ietf_val': - value : str = value.json_ietf_val + str_value : str = value.json_ietf_val.decode('UTF-8') try: - return json.loads(value) + # Cleanup and normalize the records according to OpenConfig + #str_value = str_value.replace('openconfig-platform-types:', 'oc-platform-types:') + json_value = json.loads(str_value) + recursive_remove_keys(json_value) + return json_value except json.decoder.JSONDecodeError: # Assume is Base64-encoded b_b64_value = value.encode('UTF-8') diff --git a/src/device/service/drivers/oc_driver/OCDriver.py b/src/device/service/drivers/oc_driver/OCDriver.py index 89dc791f6982239c3eb878662b02f7f845c6746b..087e58b7673e752d208eaa82b30a697e4eb8d08c 100644 --- a/src/device/service/drivers/oc_driver/OCDriver.py +++ b/src/device/service/drivers/oc_driver/OCDriver.py @@ -13,33 +13,31 @@ # limitations under the License. import json -import anytree, copy, logging, pytz, queue, re, threading +import logging, pytz, queue, re, threading #import lxml.etree as ET -from datetime import datetime, timedelta -from typing import Any, Dict, Iterator, List, Optional, Tuple, Union +from typing import Any, List, Tuple, Union from apscheduler.executors.pool import ThreadPoolExecutor -import xml.etree.ElementTree as ET -from apscheduler.job import Job from apscheduler.jobstores.memory import MemoryJobStore from apscheduler.schedulers.background import BackgroundScheduler from ncclient.manager import Manager, connect_ssh from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method from common.tools.client.RetryDecorator import delay_exponential -from common.type_checkers.Checkers import chk_length, chk_string, chk_type, chk_float +from common.type_checkers.Checkers import chk_type from device.service.driver_api.Exceptions import UnsupportedResourceKeyException from device.service.driver_api._Driver import _Driver -from device.service.driver_api.AnyTreeTools import TreeNode, get_subnode, set_subnode_value #dump_subtree +from device.service.driver_api.AnyTreeTools import TreeNode +from .templates.VPN.common import seperate_port_config #from .Tools import xml_pretty_print, xml_to_dict, xml_to_file -from .templates.Interfaces.interfaces import interface_template -from .templates.VPN.physical import create_optical_channel,add_transceiver,create_media_channel,create_optical_band +from .templates.VPN.roadms import ( + create_optical_band, disable_media_channel, delete_optical_band, create_media_channel_v2 +) +from .templates.VPN.transponder import edit_optical_channel, change_optical_channel_status from .RetryDecorator import retry from context.client.ContextClient import ContextClient -from common.proto.context_pb2 import ( - OpticalConfig, - ConfigActionEnum, Device, DeviceDriverEnum, DeviceId, DeviceList, DeviceOperationalStatusEnum, Empty - ,OpticalConfigId,Uuid) -from .templates.Tools import extractor -from .Tools import generate_uuid_from_numbers +from common.proto.context_pb2 import OpticalConfig +from .templates.descovery_tool.transponders import transponder_values_extractor +from .templates.descovery_tool.roadms import roadm_values_extractor, openroadm_values_extractor + DEBUG_MODE = False logging.getLogger('ncclient.manager').setLevel(logging.DEBUG if DEBUG_MODE else logging.WARNING) logging.getLogger('ncclient.transport.ssh').setLevel(logging.DEBUG if DEBUG_MODE else logging.WARNING) @@ -56,13 +54,14 @@ RE_GET_ENDPOINT_FROM_INTERFACE_XPATH = re.compile(r".*interface\[oci\:name\='([^ SAMPLE_EVICTION_SECONDS = 30.0 # seconds SAMPLE_RESOURCE_KEY = 'interfaces/interface/state/counters' -filter_fields= ["frequency","target-output-power","interface","operational-mode","line-port"] +transponder_filter_fields = ["frequency", "target-output-power", "operational-mode", "line-port", "admin-state"] MAX_RETRIES = 15 DELAY_FUNCTION = delay_exponential(initial=0.01, increment=2.0, maximum=5.0) RETRY_DECORATOR = retry(max_retries=MAX_RETRIES, delay_function=DELAY_FUNCTION, prepare_method_name='connect') context_client= ContextClient() port_xml_filter=f"/components/component[state[type='oc-platform-types:PORT']]/*" transceiver_xml_filter="/components/component[state[type='oc-platform-types:TRANSCEIVER']]/*" + class NetconfSessionHandler: def __init__(self, address : str, port : int, **settings) -> None: self.__lock = threading.RLock() @@ -83,6 +82,7 @@ class NetconfSessionHandler: self.__manager_params = settings.get('manager_params', {}) self.__nc_params = settings.get('nc_params', {}) self.__message_renderer = settings.get('message_renderer','jinja') + self.__manager : Manager = None self.__candidate_supported = False @@ -109,10 +109,10 @@ class NetconfSessionHandler: @property def vendor(self): return self.__vendor - + @property def version(self): return self.__version - + @property def message_renderer(self): return self.__message_renderer @@ -120,7 +120,6 @@ class NetconfSessionHandler: def get(self, filter=None, with_defaults=None): # pylint: disable=redefined-builtin with self.__lock: config=self.__manager.get(filter=filter, with_defaults=with_defaults) - return config @RETRY_DECORATOR @@ -128,14 +127,16 @@ class NetconfSessionHandler: self, config, target='running', default_operation=None, test_option=None, error_option=None, format='xml' # pylint: disable=redefined-builtin ): - - - + response = None with self.__lock: response= self.__manager.edit_config( config, target=target, default_operation=default_operation, test_option=test_option, error_option=error_option, format=format) - + + str_respones = str(response) + if re.search(r'<ok/>', str_respones): + return True + return False @RETRY_DECORATOR def locked(self, target): @@ -144,7 +145,7 @@ class NetconfSessionHandler: @RETRY_DECORATOR def commit(self, confirmed=False, timeout=None, persist=None, persist_id=None): return self.__manager.commit(confirmed=confirmed, timeout=timeout, persist=persist, persist_id=persist_id) - + DRIVER_NAME = 'oc' METRICS_POOL = MetricsPool('Device', 'Driver', labels={'driver': DRIVER_NAME}) def edit_config( # edit the configuration of openconfig devices @@ -153,80 +154,80 @@ def edit_config( commit_per_rule=False, target='running', default_operation='merge', test_option=None, error_option=None, format='xml' ): - #str_method = 'DeleteConfig' if delete else 'SetConfig' + str_method = 'DeleteConfig' if delete else 'SetConfig' results = [] str_config_messages=[] - - - if (conditions['edit_type']=='optical-channel'): - #transponder - str_config_messages = create_optical_channel(resources) - elif (conditions['edit_type']=='optical-band'): - #roadm optical-band - str_config_messages = create_optical_band(resources) - else : - #roadm media-channel - str_config_messages=create_media_channel(resources) - - - + if str_method == 'SetConfig': + if (conditions['edit_type']=='optical-channel'): + #transponder + str_config_messages = edit_optical_channel(resources) + elif (conditions['edit_type']=='optical-band'): + #roadm optical-band + str_config_messages = create_optical_band(resources) + else : + #roadm media-channel + str_config_messages=create_media_channel_v2(resources) + #Disabling of the Configuration + else: + # Device type is Transponder + if (conditions['edit_type'] == "optical-channel"): + _,ports,_=seperate_port_config(resources) + str_config_messages=change_optical_channel_status(state="DISABLED",ports=ports) + + # Device type is Roadm + elif (conditions['edit_type']=='optical-band'): + str_config_messages=delete_optical_band(resources) + else : + str_config_messages=disable_media_channel(resources) + for str_config_message in str_config_messages: - # configuration of the received templates - if str_config_message is None: raise UnsupportedResourceKeyException("CONFIG") - - netconf_handler.edit_config( # configure the device - config=str_config_message, target=target, default_operation=default_operation, - test_option=test_option, error_option=error_option, format=format) - if commit_per_rule: - netconf_handler.commit() # configuration commit - + # configuration of the received templates + if str_config_message is None: raise UnsupportedResourceKeyException("CONFIG") + result= netconf_handler.edit_config( # configure the device + config=str_config_message, target=target, default_operation=default_operation, + test_option=test_option, error_option=error_option, format=format) + if commit_per_rule: + netconf_handler.commit() # configuration commit + #results[i] = True - results.append(True) - + results.append(result) + + return results class OCDriver(_Driver): def __init__(self, address : str, port : int,device_uuid=None, **settings) -> None: super().__init__(DRIVER_NAME, address, port, **settings) self.__logger = logging.getLogger('{:s}:[{:s}:{:s}]'.format(str(__name__), str(self.address), str(self.port))) self.__lock = threading.Lock() - #self.__initial = TreeNode('.') - #self.__running = TreeNode('.') - self.__subscriptions = TreeNode('.') + self.__started = threading.Event() self.__terminate = threading.Event() - self.__scheduler = BackgroundScheduler(daemon=True) # scheduler used to emulate sampling events + self.__scheduler = BackgroundScheduler(daemon=True) self.__scheduler.configure( jobstores = {'default': MemoryJobStore()}, - executors = {'default': ThreadPoolExecutor(max_workers=1)}, # important! 1 = avoid concurrent requests + executors = {'default': ThreadPoolExecutor(max_workers=1)}, job_defaults = {'coalesce': False, 'max_instances': 3}, timezone=pytz.utc) self._temp_address=f"{address}{port}" - self.__out_samples = queue.Queue() + self.__netconf_handler = NetconfSessionHandler(self.address, self.port, **(self.settings)) - - self.__device_uuid=device_uuid - + self.__type = self.settings.get("type","optical-transponder") + self.__device_uuid = device_uuid self.Connect() - #self.GetConfig() - #self.__samples_cache = SamplesCache(self.__netconf_handler, self.__logger) - + def Connect(self) -> bool: with self.__lock: if self.__started.is_set(): return True self.__netconf_handler.connect() - # Connect triggers activation of sampling events that will be scheduled based on subscriptions self.__scheduler.start() self.__started.set() return True def Disconnect(self) -> bool: with self.__lock: - # Trigger termination of loops and processes self.__terminate.set() - # If not started, assume it is already disconnected if not self.__started.is_set(): return True - # Disconnect triggers deactivation of sampling events self.__scheduler.shutdown() self.__netconf_handler.disconnect() return True @@ -238,78 +239,90 @@ class OCDriver(_Driver): @metered_subclass_method(METRICS_POOL) def GetConfig(self, resource_keys : List[str] = []) -> List[Tuple[str, Union[Any, None, Exception]]]: - - chk_type('resources', resource_keys, list) results = [] opticalConfig= OpticalConfig() - j=0 - + with self.__lock: - - - context_client.connect() - config={} - channels_lst=[] - transceivers={} - + config = {} + transceivers = {} + oc_values = {} + ports_result = [] + oc_values["type"] = self.__type try: xml_data = self.__netconf_handler.get().data_xml - transceivers,interfaces,channels_lst,channel_namespace,endpoints=extractor(data_xml=xml_data,resource_keys=filter_fields,dic=config) - - + if self.__type == "optical-transponder": + extracted_values = transponder_values_extractor( + data_xml=xml_data, resource_keys=transponder_filter_fields, dic=config + ) + transceivers, optical_channels_params, channel_namespace, endpoints, ports_result = extracted_values + oc_values["channels"] = optical_channels_params + oc_values["transceivers"] = transceivers + oc_values["channel_namespace"] = channel_namespace + oc_values["endpoints"] = endpoints + oc_values["ports"] = ports_result + elif (self.__type =='openroadm'): + extracted_values=openroadm_values_extractor(data_xml=xml_data, resource_keys=[], dic=oc_values) + ports_result = extracted_values[1] + else: + extracted_values = roadm_values_extractor(data_xml=xml_data, resource_keys=[], dic=config) + ports_result = extracted_values[0] + oc_values['optical_bands']=extracted_values[1] + oc_values['media_channels']=extracted_values[2] + + #results.append((resource_key, e)) # if validation fails, store the exception + + #///////////////////////// store optical configurtaion //////////////////////////////////////////////////////// + + opticalConfig.config=json.dumps(oc_values) + if self.__device_uuid is not None: + opticalConfig.device_id.device_uuid.uuid=self.__device_uuid + results.append((f"/opticalconfigs/opticalconfig/{self.__device_uuid}",{"opticalconfig":opticalConfig})) + # context_client.connect() + # config_id=context_client.SetOpticalConfig(opticalConfig) + # context_client.close() except Exception as e: # pylint: disable=broad-except - MSG = 'Exception retrieving {:s}' - self.__logger.info("error from getConfig %s",e) - self.__logger.exception(MSG.format(e)) - #results.append((resource_key, e)) # if validation fails, store the exception - - #///////////////////////// divider //////////////////////////////////////////////////////// - - - value_dic={} - value_dic["channels"]=channels_lst - value_dic["transceivers"]=transceivers - value_dic["interfaces"]=interfaces - value_dic["channel_namespace"]=channel_namespace - value_dic["endpoints"]=endpoints - - opticalConfig.config=json.dumps(value_dic) - opticalConfig.opticalconfig_id.opticalconfig_uuid=self.__device_uuid if self.__device_uuid is not None else "" - config_id=context_client.SetOpticalConfig(opticalConfig) - - context_client.close() - + MSG = 'Exception retrieving {:s}' + self.__logger.info("error from getConfig %s",e) + self.__logger.exception(MSG.format(e)) + + if len(ports_result) > 0: results.extend(ports_result) return results @metered_subclass_method(METRICS_POOL) def SetConfig(self, resources : List[Tuple[str, Any]],conditions:dict) -> List[Union[bool, Exception]]: if len(resources) == 0: return [] - results=[] + results = [] with self.__lock: - if self.__netconf_handler.use_candidate: - with self.__netconf_handler.locked(target='candidate'): - results = edit_config( - self.__netconf_handler, self.__logger, resources,conditions, target='candidate', - commit_per_rule=self.__netconf_handler.commit_per_rule - ,) - else: - results = edit_config(self.__netconf_handler, self.__logger, resources,conditions=conditions - ) + if self.__netconf_handler.use_candidate: + with self.__netconf_handler.locked(target='candidate'): + results = edit_config( + self.__netconf_handler, self.__logger, resources, conditions, target='candidate', + commit_per_rule=self.__netconf_handler.commit_per_rule + ) + else: + results = edit_config( + self.__netconf_handler, self.__logger, resources, conditions=conditions + ) return results @metered_subclass_method(METRICS_POOL) - def DeleteConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: + def DeleteConfig( + self, resources : List[Tuple[str, Any]], conditions : dict, + optical_device_configuration = None + ) -> List[Union[bool, Exception]]: chk_type('resources', resources, list) if len(resources) == 0: return [] + with self.__lock: if self.__netconf_handler.use_candidate: with self.__netconf_handler.locked(target='candidate'): results = edit_config( self.__netconf_handler, self.__logger, resources, target='candidate', delete=True, - commit_per_rule=self.__netconf_handler.commit_per_rule) + commit_per_rule=self.__netconf_handler.commit_per_rule, conditions=conditions + ) else: - results = edit_config(self.__netconf_handler, self.__logger, resources, delete=True) + results = edit_config( + self.__netconf_handler, self.__logger, resources, delete=True, conditions=conditions + ) return results - - diff --git a/src/device/service/drivers/oc_driver/templates/Interfaces/interfaces.py b/src/device/service/drivers/oc_driver/templates/Interfaces/interfaces.py index 87ba8835ac6f7ad66a709be90195e6597bc2bf6c..b8c3812f292270b63dbe1391d922cbd989415458 100644 --- a/src/device/service/drivers/oc_driver/templates/Interfaces/interfaces.py +++ b/src/device/service/drivers/oc_driver/templates/Interfaces/interfaces.py @@ -14,6 +14,7 @@ from yattag import Doc, indent import logging + def interface_template (interface_data:dict) : data={"name":"eth0","ip":"192.168.1.1","prefix-length":'24'} doc, tag, text = Doc().tagtext() @@ -31,13 +32,10 @@ def interface_template (interface_data:dict) : with tag('config'): with tag('ip'):text(interface_data["ip"]) with tag('prefix-length'):text(interface_data["prefix-length"]) - + result = indent( doc.getvalue(), indentation = ' '*2, newline = '\r\n' ) - logging.info("interfaces %s",result) - return result - - \ No newline at end of file + return result diff --git a/src/device/service/drivers/oc_driver/templates/Tools.py b/src/device/service/drivers/oc_driver/templates/Tools.py index 98086adf7bcc1877ca719fec6ef3f7b9dc890941..92607aca88c1581218c97484c7dfcc87831b5912 100644 --- a/src/device/service/drivers/oc_driver/templates/Tools.py +++ b/src/device/service/drivers/oc_driver/templates/Tools.py @@ -19,6 +19,7 @@ from typing import Collection, Dict, Any from yattag import Doc, indent from .VPN.physical import create_optical_channel + def add_value_from_tag(target : Dict, field_name: str, field_value : ET.Element, cast=None) -> None: if isinstance(field_value,str) or field_value is None or field_value.text is None: return field_value = field_value.text @@ -55,9 +56,32 @@ def generate_templates(resource_key: str, resource_value: str, channel:str) -> s data['name']=channel data['resource_key']=resource_key data['value']=resource_value - result_templates.append(create_physical_config(data)) + #result_templates.append(create_physical_config(data)) return result_templates + + +def extract_status (dic:dict,resource_key:str,xml_data:str,channel_name:str): + + xml_bytes = xml_data.encode("utf-8") + root = ET.fromstring(xml_bytes) + channel_name=channel_name if 'index' not in channel_name else channel_name['index'] + index=None + if channel_name.find('-') != -1 : + index= channel_name.split("-")[1] + + + namespaces = { "td": "http://openconfig.net/yang/terminal-device"} + channels = root.findall(f".//td:terminal-device/td:logical-channels/td:channel",namespaces) + for channel in channels : + + index_ele= channel.find(f".//td:config[td:index='{index}']/td:{resource_key}",namespaces) + if index_ele is not None : + dic["status"]=index_ele.text + print(index_ele.text) + return dic + + def extract_channel_xmlns (data_xml:str,is_opticalband:bool): xml_bytes = data_xml.encode("utf-8") root = ET.fromstring(xml_bytes) @@ -88,6 +112,7 @@ def extract_channel_xmlns (data_xml:str,is_opticalband:bool): return namespace + def extract_channels_based_on_channelnamespace (xml_data:str,channel_namespace:str,is_opticalband:bool): xml_bytes = xml_data.encode("utf-8") root = ET.fromstring(xml_bytes) @@ -122,6 +147,7 @@ def extract_channels_based_on_channelnamespace (xml_data:str,channel_namespace:s # Retrieve port-name for dest return channels + def extract_channels_based_on_type (xml_data:str): xml_bytes = xml_data.encode("utf-8") root = ET.fromstring(xml_bytes) @@ -140,9 +166,10 @@ def extract_channels_based_on_type (xml_data:str): return channel_names def extract_value(resource_key:str,xml_data:str,dic:dict,channel_name:str,channel_namespace:str): + xml_bytes = xml_data.encode("utf-8") root = ET.fromstring(xml_bytes) - + channel_name=channel_name if 'index' not in channel_name else channel_name['index'] namespace = {'oc': 'http://openconfig.net/yang/platform', 'td': channel_namespace} @@ -153,10 +180,13 @@ def extract_value(resource_key:str,xml_data:str,dic:dict,channel_name:str,channe if (parameter is not None): value = parameter.text dic[resource_key]=value + else : + logging.info("parameter is None") else: + logging.info("element is None") print(" element not found.") - + return dic @@ -192,6 +222,7 @@ def extract_tranceiver (data_xml:str,dic:dict): component_names = [component.text for component in transceiver_components] dic['transceiver']=component_names return dic + def extract_interface (xml_data:str,dic:dict): xml_bytes = xml_data.encode("utf-8") root = ET.fromstring(xml_bytes) @@ -214,6 +245,7 @@ def extract_interface (xml_data:str,dic:dict): else : dic['interface']={} return dic + def has_opticalbands(xml_data:str): xml_bytes = xml_data.encode("utf-8") root = ET.fromstring(xml_bytes) @@ -226,32 +258,237 @@ def has_opticalbands(xml_data:str): else : has_opticalbands=False return has_opticalbands + +def extract_ports_based_on_type (xml_data:str): + pattern = r':\s*PORT\b' + xml_bytes = xml_data.encode("utf-8") + root = ET.fromstring(xml_bytes) + namespace = {'oc': 'http://openconfig.net/yang/platform', 'typex': 'http://openconfig.net/yang/platform-types'} + ports = [] + components = root.findall(".//oc:state[oc:type]",namespace) + for component in components: + type_ele = component.find(".//oc:type",namespace) + match = re.search(pattern, type_ele.text) + if match is not None : + name_element= component.find(".//oc:name",namespace) + port_name =name_element.text + port_index=name_element.text.split("-")[1] + port = (port_name,port_index) + ports.append(port) + return ports -def extractor(data_xml:str,resource_keys:list,dic:dict): +def transponder_values_extractor(data_xml:str,resource_keys:list,dic:dict): endpoints=[] is_opticalband=has_opticalbands(xml_data=data_xml) - channel_namespace=extract_channel_xmlns(data_xml=data_xml,is_opticalband=is_opticalband) # channel_names=extract_channels_based_on_type(xml_data=data_xml) # if len(channel_names)==0 : channel_names= extract_channels_based_on_channelnamespace(xml_data=data_xml,channel_namespace=channel_namespace,is_opticalband=is_opticalband) - - lst_dic=[] + ports = extract_ports_based_on_type(data_xml) + optical_channels_params=[] + ports_result=[] if (is_opticalband): endpoints=channel_names else: for channel_name in channel_names: dic={} - for resource_key in resource_keys : + for resource_key in resource_keys : - if (resource_key != 'interface'): - dic=extract_value(dic=dic,resource_key=resource_key,xml_data=data_xml,channel_name=channel_name,channel_namespace=channel_namespace) + if (resource_key != 'admin-state'): + + dic=extract_value(dic=dic,resource_key=resource_key,xml_data=data_xml + ,channel_name=channel_name,channel_namespace=channel_namespace) + else : + dic = extract_status(dic=dic,resource_key=resource_key,xml_data=data_xml, channel_name=channel_name) dic["name"]=channel_name endpoints.append({"endpoint_uuid":{"uuid":channel_name}}) - lst_dic.append(dic) - transceivers_dic=extract_tranceiver(data_xml=data_xml,dic={}) - interfaces_dic=extract_interface(xml_data=data_xml,dic={}) + optical_channels_params.append(dic) + #transceivers_dic=extract_tranceiver(data_xml=data_xml,dic={}) + transceivers_dic={"transceiver":[]} + #interfaces_dic=extract_interface(xml_data=data_xml,dic={}) + if len(ports)>0 : + for port in ports : + endpoint_name,endpoint_id=port + resource_key = '/endpoints/endpoint[{:s}]'.format(endpoint_id) + resource_value = {'uuid': endpoint_id, 'type':endpoint_name} + ports_result.append((resource_key, resource_value)) + + + return [transceivers_dic,optical_channels_params,channel_namespace,endpoints,ports_result] + +######################################################################### + +#################################### ROADMAs ############################ + +########################################################################## + +def extract_roadm_ports (xml_data:str): + + ports =[] + pattern1 = r'\bMG_ON_OPTICAL_PORT_WAVEBAND\b' + pattern2 = r'\bMG_ON_OPTICAL_PORT_MEDIACHANNEL\b' + pattern3 = r'\bINPUT\b' + pattern4 = r'\bOUTPUT\b' + + xml_bytes = xml_data.encode("utf-8") + root = ET.fromstring(xml_bytes) + with open('xml.log', 'w') as f: + print(xml_bytes, file=f) + + + namespace = {'oc': 'http://openconfig.net/yang/platform'} + ports_wb_in = [] + ports_mc_in = [] + ports_wb_out = [] + ports_mc_out = [] + components = root.findall('.//oc:component',namespace) + print(f"component {components}") + + + for component in components: + + properties = component.find(".//oc:properties",namespace) + name_element= component.find(".//oc:name",namespace) + print(name_element.text) + if (properties is not None): + wb_x = 0 + mc_x = 0 + in_x = 0 + out_x = 0 + for property in properties : + value = property.find(".//oc:value",namespace) + if (re.search(pattern1,value.text)): + wb_x = 1 + elif (re.search(pattern2,value.text)): + mc_x = 1 + elif (re.search(pattern3,value.text)): + in_x = 1 + elif (re.search(pattern4,value.text)): + out_x = 1 + if wb_x == 1: + if in_x ==1: + ports_wb_in.append(name_element.text) + elif out_x == 1: + ports_wb_out.append(name_element.text) + if mc_x == 1: + if in_x ==1: + ports_mc_in.append(name_element.text) + elif out_x == 1: + ports_mc_out.append(name_element.text) + + return ports_wb_in, ports_wb_out, ports_mc_in, ports_mc_out + + + +def roadm_values_extractor (data_xml:str,resource_keys:list,dic:dict): + ports_result=[] + ports_wb_in, ports_wb_out, ports_mc_in, ports_mc_out = extract_roadm_ports(data_xml) + + #if len(ports)>0 : + # for port in ports : + # + # resource_key = '/endpoints/endpoint[{:s}]'.format(port) + # resource_value = {'uuid': port, 'type':'MG_ON_OPTICAL_PORT_WAVEBAND'} + # ports_result.append((resource_key, resource_value)) + if len(ports_wb_in)>0 : + for port in ports_wb_in: + + resource_key = '/endpoints/endpoint[{:s}]'.format(port) + resource_value = {'uuid': port, 'type':'MG_ON_OPTICAL_PORT_WAVEBAND'} + ports_result.append((resource_key, resource_value)) + + return [ports_result] + + + + #/////////////// OpenRoadm ////////////// + + +def extract_roadm_circuits_pack (xml_data:str): + - return [transceivers_dic,interfaces_dic,lst_dic,channel_namespace,endpoints] \ No newline at end of file + xml_bytes = xml_data.encode("utf-8") + root = ET.fromstring(xml_bytes) + # with open('xml.log', 'w') as f: + # print(xml_bytes, file=f) + + + namespace = {'oc': "http://org/openroadm/device"} + + circuits = root.findall('.//oc:circuit-packs',namespace) + + circuits_list =[] + # print(f"component {components}") + + if (circuits is not None): + circuit_info ={} + for circuit in circuits: + + circuit_name = circuit.find(".//oc:circuit-pack-name",namespace) + circuit_type=circuit.find(".//oc:circuit-pack-type",namespace) + circuit_adminstrative_status=circuit.find(".//oc:administrative-state",namespace) + circuit_equipment_state=circuit.find("./oc:equipment-state",namespace) + circuit_mode=circuit.find("./oc:circuit-pack-mode",namespace) + slot= circuit.find("./oc:slot",namespace) + shelf= circuit.find("./oc:shelf",namespace) + ports = circuit.findall("./oc:ports",namespace) + + circuit_ports=[] + if (ports is not None): + + for port in ports : + port_info={} + port_name=port.find('./oc:port-name',namespace) + port_qual= port.find("./oc:port-qual",namespace) + + if port_name is not None : + port_info["port_name"]=port_name.text + if port_qual is not None : + port_info["port_qual"]=port_qual.text + circuit_ports.append(port_info) + if (circuit_name is not None): + circuit_info["circuit_name"]=circuit_name.text + if (circuit_type is not None): + circuit_info["circuit_type"]=circuit_type.text + if (circuit_adminstrative_status is not None): + circuit_info["circuit_adminstrative_status"]=circuit_adminstrative_status.text + if (circuit_equipment_state is not None): + circuit_info["circuit_equipment_state"]=circuit_equipment_state.text + if (circuit_mode is not None): + circuit_info["circuit_mode"]=circuit_mode.text + if (slot is not None): + circuit_info["slot"]=slot.text + if (shelf is not None): + circuit_info["shelf"]=shelf.text + circuit_info["ports"]=circuit_ports + + circuits_list.append(circuit_info) + + + return circuits_list + + + +def extract_openroadm_info(xml_data:str): + roadm_info={"node-id":None,"node-number":None,"node-type":None,'clli':None} + xml_bytes = xml_data.encode("utf-8") + root = ET.fromstring(xml_bytes) + namespace = {'oc': "http://org/openroadm/device"} + info = root.findall('.//oc:info',namespace) + if info is not None : + for i in info : + node_id= i.find('.//oc:node-id',namespace) + node_number= i.find('.//oc:node-number',namespace) + node_type=i.find('.//oc:node-type',namespace) + clli=i.find('.//oc:clli',namespace) + if (node_id is not None): + roadm_info['node-id']=node_id.text + if (node_number is not None): + roadm_info['node-number']=node_number.text + if (node_type is not None): + roadm_info['node-type']=node_type.text + if (clli is not None): + roadm_info['clli']=clli.text + return roadm_info diff --git a/src/device/service/drivers/oc_driver/templates/VPN/common.py b/src/device/service/drivers/oc_driver/templates/VPN/common.py new file mode 100644 index 0000000000000000000000000000000000000000..d84afc5563ad9cf1fbc7fda44800b90876ee302e --- /dev/null +++ b/src/device/service/drivers/oc_driver/templates/VPN/common.py @@ -0,0 +1,55 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +def seperate_port_config(resources:list,unwanted_keys=[])->list[list,dict,str]: + config=[] + ports={} + index=None + for item in resources : + if len(unwanted_keys)>0: + if (item['value'] is not None and (item['resource_key'] not in unwanted_keys)): + config.append({'resource_key':item['resource_key'], 'value':item['value']} ) + #if (item['resource_key'] == 'destination_port' or item['resource_key'] == 'source_port') and item['value'] is not None: + # ports[item['resource_key']]=item['value'] + if (item['resource_key'] == 'destination_port' or item['resource_key'] == 'source_port'): + ports[item['resource_key']]=item['value'] + if (item['resource_key']=='index' and item['value'] is not None) : + index=item['value'] + + return [config,ports,index] + +def extract_ports (resources:list): + if len(resources) ==0 :return + ports=[] + flow=next((i for i in resources if i['resource_key']=='handled_flow'),None) + if flow is not None: + ports = flow['value'] + return ports + +def filter_config(resources:list,unwanted_keys=[])->list[list,dict,str]: + config=[] + ports=() + index=None + for item in resources : + if len(unwanted_keys)>0: + if (item['value'] is not None and (item['resource_key'] not in unwanted_keys)): + config.append({'resource_key':item['resource_key'], 'value':item['value']} ) + if (item['resource_key']=='index' and item['value'] is not None) : + index=item['value'] + #if (item['resource_key'] == 'destination_port' or item['resource_key'] == 'source_port') and item['value'] is not None: + # ports[item['resource_key']]=item['value'] + ports = extract_ports(resources=resources) + + return [config,ports,index] diff --git a/src/device/service/drivers/oc_driver/templates/VPN/physical.py b/src/device/service/drivers/oc_driver/templates/VPN/physical.py index 0db6ffa5065ad3013250c3bde4dd160d8c12a498..11e6df5d99f8d365eeaeec3960da52f28c717551 100644 --- a/src/device/service/drivers/oc_driver/templates/VPN/physical.py +++ b/src/device/service/drivers/oc_driver/templates/VPN/physical.py @@ -34,14 +34,14 @@ def seperate_port_config(resources:list,unwanted_keys:list[str])->list[list,dict return [config,ports,index] -def create_optical_channel(resources): +def create_optical_channel(resources:list[dict],ports:list[dict],config:list[dict] ): - unwanted_keys=['destination_port','source_port','channel_namespace','optical-band-parent','index', 'name'] + #unwanted_keys=['destination_port','source_port','channel_namespace','optical-band-parent','index', 'name','admin-state'] results =[] - data={"name":i["value"] for i in resources if i["resource_key"]=="channel_name"} + data ={} data["channel_namespace"]=next((i["value"] for i in resources if i["resource_key"] == "channel_namespace"), None) - config,ports,index=seperate_port_config(resources,unwanted_keys=unwanted_keys) - + #config,ports,index=seperate_port_config(resources,unwanted_keys=unwanted_keys) + port_val = "" if 'destination_port' in ports and ports['destination_port'][0] is not None: port_val = ports['destination_port'][0] @@ -57,10 +57,18 @@ def create_optical_channel(resources): with tag('name'):text("channel-{}".format(port_val)) with tag('config'): with tag('name'):text("channel-{}".format(port_val)) - with tag('optical-channel',xmlns=data["channel_namespace"]): + #with tag('optical-channel', xmlns="http://example.com/flexscale-terminal-device"): + with tag('optical-channel', xmlns=data["channel_namespace"]): with tag('config'): for resource in config: with tag(resource['resource_key']):text(resource['value']) + # with tag('terminal-device', xmlns="http://openconfig.net/yang/terminal-device"): + # with tag('logical-channels'): + # with tag('channel'): + # with tag('index'):text("{}".format(port_val)) + # with tag('config'): + # with tag('index'):text("{}".format(port_val)) + # with tag('admin-state'):text("ENABLED") result = indent( doc.getvalue(), indentation = ' '*2, @@ -119,6 +127,7 @@ def create_optical_band (resources) : results =[] unwanted_keys=['destination_port','source_port','channel_namespace','frequency','optical-band-parent'] config,ports,index= seperate_port_config(resources,unwanted_keys=unwanted_keys) + doc, tag, text = Doc().tagtext() #with tag('config'): with tag('config',xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"): @@ -166,6 +175,7 @@ def create_media_channel (resources): results=[] unwanted_keys=['destination_port','source_port','channel_namespace','frequency','operational-mode', 'optical-band-parent'] config,ports,index= seperate_port_config(resources,unwanted_keys=unwanted_keys) + doc, tag, text = Doc().tagtext() #with tag('config'): with tag('config',xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"): @@ -206,5 +216,49 @@ def create_media_channel (resources): results.append(result) return results - - \ No newline at end of file + +def change_optical_channel_status (channel_name:str,state:str,ports:list[dict]) : + port_val="" + if 'destination_port' in ports and ports['destination_port'][0] is not None: + port_val = ports['destination_port'][0] + else: + port_val = ports['source_port'][0] + + results=[] + doc, tag, text = Doc().tagtext() + #with tag('config'): + with tag('config',xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"): + + with tag('terminal-device',xmlns="http://openconfig.net/yang/terminal-device"): + with tag("logical-channels"): + with tag('channel'): + with tag('index'):text("{}".format(port_val)) + with tag('config'): + with tag('admin-state'):text("{}".format(state)) + + result = indent( + doc.getvalue(), + indentation = ' '*2, + newline = '' + ) + results.append(result) + + + return results + + +def edit_optical_channel (resources:list[dict]): + unwanted_keys=['destination_port','source_port','channel_namespace','optical-band-parent','index', 'name','admin-state'] + config,ports,index=seperate_port_config(resources,unwanted_keys=unwanted_keys) + results = [] + channel_name=next((i["value"] for i in resources if i["resource_key"]=="channel_name" and i["value"] != None),None) + admin_state= next((i["value"] for i in resources if i["resource_key"]== "admin-state" and i["value"] != None) , None) + + + if channel_name is not None : + if (admin_state is not None): + results.extend(change_optical_channel_status(channel_name=channel_name,state=admin_state,ports=ports)) + if admin_state is None : + results.extend(create_optical_channel(resources=resources,ports=ports,config=config) ) + + return results diff --git a/src/device/service/drivers/oc_driver/templates/VPN/roadms.py b/src/device/service/drivers/oc_driver/templates/VPN/roadms.py new file mode 100644 index 0000000000000000000000000000000000000000..7e076588cc160cf22d773476119f8e91001a5053 --- /dev/null +++ b/src/device/service/drivers/oc_driver/templates/VPN/roadms.py @@ -0,0 +1,357 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +from yattag import Doc, indent +import logging +from .common import seperate_port_config ,filter_config + + + + +def create_media_channel_old (resources): + optical_band_namespaces="http://flex-scale-project.eu/yang/flex-scale-mg-on" + results=[] + unwanted_keys=['destination_port','source_port','channel_namespace' + ,'frequency','operational-mode','target-output-power', + "admin-state","flow_handled","channel_num"] + config,ports,index= seperate_port_config(resources,unwanted_keys=unwanted_keys) + + + doc, tag, text = Doc().tagtext() + #with tag('config'): + with tag('config',xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"): + with tag('wavelength-router', xmlns="http://openconfig.net/yang/wavelength-router"): + with tag('media-channels'): + n = 0 + if 'destination_port' in ports: + n = len(ports['destination_port']) + else: + n = len(ports['source_port']) + for i in range(0, n): + #with tag('channel', operation="create"): + with tag('channel'): + with tag('index'):text(str(int(index)+i)) + with tag('config'): + #with tag('index'):text(index) + for resource in config: + + if resource['resource_key'] == "index": + with tag('index'):text(str(int(index)+i)) + elif resource['resource_key']== 'optical-band-parent' : + with tag('optical-band-parent',xmlns=optical_band_namespaces):text(resource['value']) + else: + with tag(resource['resource_key']):text(resource['value']) + if ('destination_port' in ports) and (ports['destination_port'][i] is not None): + with tag('dest'): + with tag('config'): + with tag('port-name'):text(ports['destination_port'][i]) + if ('source_port' in ports) and (ports['source_port'][i] is not None): + with tag('source'): + with tag('config'): + with tag('port-name'):text(ports['source_port'][i]) + + + result = indent( + doc.getvalue(), + indentation = ' '*2, + newline = '' + ) + results.append(result) + return results + + + + +def create_media_channel (resources): + optical_band_namespaces="http://flex-scale-project.eu/yang/flex-scale-mg-on" + results=[] + unwanted_keys=['destination_port','source_port','channel_namespace' + ,'frequency','operational-mode','target-output-power', + "admin-state","handled_flow","channel_num"] + + config,ports,index=filter_config(resources,unwanted_keys) + + doc, tag, text = Doc().tagtext() + #with tag('config'): + with tag('config',xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"): + with tag('wavelength-router', xmlns="http://openconfig.net/yang/wavelength-router"): + with tag('media-channels'): + n = 0 + + for flow in ports: + src,dest=flow + #with tag('channel', operation="create"): + with tag('channel'): + with tag('index'):text(str(int(index)+n)) + with tag('config'): + #with tag('index'):text(index) + for resource in config: + + if resource['resource_key'] == "index": + with tag('index'):text(str(int(index)+n)) + elif resource['resource_key']== 'optical-band-parent' : + with tag('optical-band-parent',xmlns=optical_band_namespaces):text(resource['value']) + else: + with tag(resource['resource_key']):text(resource['value']) + if dest is not None and dest != '0': + with tag('dest'): + with tag('config'): + with tag('port-name'):text(dest) + if src is not None and src != '0': + with tag('source'): + with tag('config'): + with tag('port-name'):text(src) + n+=1 + + result = indent( + doc.getvalue(), + indentation = ' '*2, + newline = '' + ) + results.append(result) + return results + + + + + +def create_media_channel_v2 (resources): + optical_band_namespaces="http://flex-scale-project.eu/yang/flex-scale-mg-on" + results=[] + unwanted_keys=['destination_port','source_port','channel_namespace' + ,'frequency','operational-mode','target-output-power', + "handled_flow","channel_num"] + + config,ports,index=filter_config(resources,unwanted_keys) + + n = 0 + for flow in ports: + doc, tag, text = Doc().tagtext() + with tag('config',xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"): + with tag('wavelength-router', xmlns="http://openconfig.net/yang/wavelength-router"): + with tag('media-channels'): + + + src,dest=flow + with tag('channel', operation="create"): + #with tag('channel'): + with tag('index'):text(str(int(index)+n)) + with tag('config'): + #with tag('index'):text(index) + for resource in config: + + if resource['resource_key'] == "index": + with tag('index'):text(str(int(index)+n)) + elif resource['resource_key']== 'optical-band-parent' : + with tag('optical-band-parent',xmlns=optical_band_namespaces):text(int(resource['value'])+int(n)) + elif resource['resource_key']== 'admin-state' : + with tag('admin-status'):text(resource['value']) + else: + with tag(resource['resource_key']):text(resource['value']) + + + if src is not None and src != '0': + with tag('source'): + with tag('config'): + with tag('port-name'):text(src) + if dest is not None and dest != '0': + with tag('dest'): + with tag('config'): + with tag('port-name'):text(dest) + n+=1 + + result = indent( + doc.getvalue(), + indentation = ' '*2, + newline = '' + ) + results.append(result) + return results + + + + +def create_optical_band_old (resources) : + results =[] + unwanted_keys=['destination_port','source_port','channel_namespace','frequency','optical-band-parent','flow_handled'] + config,ports,index= seperate_port_config(resources,unwanted_keys=unwanted_keys) + + doc, tag, text = Doc().tagtext() + #with tag('config'): + with tag('config',xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"): + with tag('wavelength-router', xmlns="http://openconfig.net/yang/wavelength-router"): + with tag('optical-bands',xmlns="http://flex-scale-project.eu/yang/flex-scale-mg-on"): + n = 0 + if 'destination_port' in ports: + n = len(ports['destination_port']) + else: + n = len(ports['source_port']) + for i in range(0, n): + #with tag('optical-band', operation="create"): + with tag('optical-band'): + if index is not None: + with tag('index'):text(str(int(index)+i)) + with tag('config'): + #if index is not None: + # with tag('index'):text(str(int(index)+i)) + for resource in config: + if resource['resource_key'] == "index": + with tag('index'):text(str(int(index)+i)) + else: + with tag(resource['resource_key']):text(resource['value']) + with tag('admin-status'):text('ENABLED') + #with tag('fiber-parent'):text(ports['destination_port'] if 'destination_port' in ports else ports['source_port']) + if ('destination_port' in ports) and (ports['destination_port'][i] is not None): + with tag('dest'): + with tag('config'): + with tag('port-name'):text(ports['destination_port'][i]) + if ('source_port' in ports) and (ports['source_port'][i] is not None): + with tag('source'): + with tag('config'): + with tag('port-name'):text(ports['source_port'][i]) + + + result = indent( + doc.getvalue(), + indentation = ' '*2, + newline = '' + ) + results.append(result) + return results + + + + +def create_optical_band (resources) : + results =[] + unwanted_keys=['destination_port','source_port','channel_namespace','frequency','optical-band-parent','handled_flow'] + config,ports,index= filter_config(resources,unwanted_keys=unwanted_keys) + + #with tag('config'): + n = 0 + for flow in ports: + doc, tag, text = Doc().tagtext() + with tag('config',xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"): + with tag('wavelength-router', xmlns="http://openconfig.net/yang/wavelength-router"): + with tag('optical-bands',xmlns="http://flex-scale-project.eu/yang/flex-scale-mg-on"): + + + #with tag('optical-band', operation="create"): + src,dest=flow + + with tag('optical-band'): + if index is not None: + with tag('index'):text(str(int(index)+n)) + with tag('config'): + #if index is not None: + # with tag('index'):text(str(int(index)+i)) + for resource in config: + if resource['resource_key'] == "index": + with tag('index'):text(str(int(index)+n)) + else: + with tag(resource['resource_key']):text(resource['value']) + with tag('admin-status'):text('ENABLED') + #with tag('fiber-parent'):text(ports['destination_port'] if 'destination_port' in ports else ports['source_port']) + if dest is not None and dest != '0': + with tag('dest'): + with tag('config'): + with tag('port-name'):text(dest) + if src is not None and src !='0': + with tag('source'): + with tag('config'): + with tag('port-name'):text(src) + n +=1 + + + result = indent( + doc.getvalue(), + indentation = ' '*2, + newline = '' + ) + results.append(result) + return results + + +def disable_media_channel (resources): + + results=[] + unwanted_keys=['destination_port','source_port','channel_namespace','frequency','operational-mode', 'optical-band-parent'] + config,ports,index= seperate_port_config(resources,unwanted_keys=unwanted_keys) + + doc, tag, text = Doc().tagtext() + #with tag('config'): + with tag('config',xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"): + with tag('wavelength-router', xmlns="http://openconfig.net/yang/wavelength-router"): + with tag('media-channels'): + with tag("channel",operation="delete"): + with tag('index'):text(str(int(index))) + with tag('config'): + with tag('index'):text(str(int(index))) + + result = indent( + doc.getvalue(), + indentation = ' '*2, + newline = '' + ) + results.append(result) + return results + +def disable_optical_band (resources:list,state:str): + results=[] + unwanted_keys=['destination_port','source_port','channel_namespace','frequency','operational-mode', 'optical-band-parent'] + config,ports,index= seperate_port_config(resources,unwanted_keys=unwanted_keys) + doc, tag, text = Doc().tagtext() + #with tag('config'): + with tag('config',xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"): + with tag('wavelength-router', xmlns="http://openconfig.net/yang/wavelength-router"): + with tag('optical-bands',xmlns="http://flex-scale-project.eu/yang/flex-scale-mg-on"): + with tag('optical-band'): + if index is not None: + with tag('index'):text(index) + with tag('config'): + with tag('index'):text(index) + with tag('admin-status'):text(state) + result = indent( + doc.getvalue(), + indentation = ' '*2, + newline = '' + ) + results.append(result) + return results + + +def delete_optical_band (resources:list): + results=[] + unwanted_keys=['destination_port','source_port','channel_namespace','frequency','operational-mode', 'optical-band-parent'] + config,ports,index= seperate_port_config(resources,unwanted_keys=unwanted_keys) + doc, tag, text = Doc().tagtext() + #with tag('config'): + with tag('config',xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"): + with tag('wavelength-router', xmlns="http://openconfig.net/yang/wavelength-router"): + with tag('optical-bands',xmlns="http://flex-scale-project.eu/yang/flex-scale-mg-on"): + with tag('optical-band',operation="delete"): + if index is not None: + with tag('index'):text(index) + with tag('config'): + with tag('index'):text(index) + + result = indent( + doc.getvalue(), + indentation = ' '*2, + newline = '' + ) + results.append(result) + return results diff --git a/src/device/service/drivers/oc_driver/templates/VPN/transponder.py b/src/device/service/drivers/oc_driver/templates/VPN/transponder.py new file mode 100644 index 0000000000000000000000000000000000000000..a41c7e9a0853a371bf1f6d8d343913c52f9365ac --- /dev/null +++ b/src/device/service/drivers/oc_driver/templates/VPN/transponder.py @@ -0,0 +1,157 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from yattag import Doc, indent +import logging + +from .common import seperate_port_config + +def add_transceiver (transceiver_name:str): + + doc, tag, text = Doc().tagtext() + with tag('config',xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"): + with tag('components', xmlns="http://openconfig.net/yang/platform"): + with tag('component'): + with tag('name'):text(transceiver_name) + with tag("config"): + with tag('name'):text(transceiver_name) + with tag("state"): + with tag('name'):text(transceiver_name) + with tag("type",('xmlns:oc-platform-types',"http://openconfig.net/yang/platform-types")):text("oc-platform-types:TRANSCEIVER") + with tag("transceiver",xmlns="http://openconfig.net/yang/platform/transceiver"): + with tag("config"): + with tag("enabled"):text("true") + with tag("form-factor-preconf",("xmlns:oc-opt-types","http://openconfig.net/yang/transport-types")):text("oc-opt-types:QSFP56_DD_TYPE1") + with tag("ethernet-pmd-preconf",("xmlns:oc-opt-types","http://openconfig.net/yang/transport-types")):text("oc-opt-types:ETH_400GBASE_ZR") + with tag("fec-mode",("xmlns:oc-platform-types","http://openconfig.net/yang/platform-types")):text("oc-platform-types:FEC_AUTO") + with tag("module-functional-type",("xmlns:oc-opt-types","http://openconfig.net/yang/transport-types")):text("oc-opt-types:TYPE_DIGITAL_COHERENT_OPTIC") + with tag("state"): + with tag("enabled"):text("true") + with tag("form-factor-preconf",("xmlns:oc-opt-types","http://openconfig.net/yang/transport-types")):text("oc-opt-types:QSFP56_DD_TYPE1") + with tag("ethernet-pmd-preconf",("xmlns:oc-opt-types","http://openconfig.net/yang/transport-types")):text("oc-opt-types:ETH_400GBASE_ZR") + with tag("fec-mode",("xmlns:oc-platform-types","http://openconfig.net/yang/platform-types")):text("oc-platform-types:FEC_AUTO") + with tag("module-functional-type",("xmlns:oc-opt-types","http://openconfig.net/yang/transport-types")):text("oc-opt-types:TYPE_DIGITAL_COHERENT_OPTIC") + with tag("vendor"):text("Cisco") + with tag("vendor-part"):text("400zr-QSFP-DD") + with tag("vendor-rev"):text("01") + with tag("serial-no"):text("1567321") + with tag("physical-channels"): + with tag("channel"): + with tag("index"):text("1") + with tag("config"): + with tag("index"):text("1") + with tag("associated-optical-channel"):text("channel-4") + result = indent( + doc.getvalue(), + indentation = ' '*2, + newline = '' + ) + + + return result + + +def create_optical_channel(resources:list[dict],ports:list[dict],config:list[dict] ): + + #unwanted_keys=['destination_port','source_port','channel_namespace','optical-band-parent','index', 'name','admin-state'] + results =[] + data ={} + data["channel_namespace"]=next((i["value"] for i in resources if i["resource_key"] == "channel_namespace"), None) + #config,ports,index=seperate_port_config(resources,unwanted_keys=unwanted_keys) + + port_val = "" + if 'destination_port' in ports and ports['destination_port'][0] is not None: + port_val = ports['destination_port'][0] + else: + port_val = ports['source_port'][0] + + + doc, tag, text = Doc().tagtext() + #with tag('config'): + with tag('config',xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"): + with tag('components', xmlns="http://openconfig.net/yang/platform"): + with tag('component'): + with tag('name'):text("channel-{}".format(port_val)) + with tag('config'): + with tag('name'):text("channel-{}".format(port_val)) + with tag('optical-channel',xmlns=data["channel_namespace"]): + with tag('config'): + for resource in config: + with tag(resource['resource_key']):text(resource['value']) + with tag('terminal-device', xmlns="http://openconfig.net/yang/terminal-device"): + with tag('logical-channels'): + with tag('channel'): + with tag('index'):text("{}".format(port_val)) + with tag('config'): + with tag('index'):text("{}".format(port_val)) + with tag('admin-state'):text("ENABLED") + result = indent( + doc.getvalue(), + indentation = ' '*2, + newline = '' + ) + results.append(result) + + + return results + + +def change_optical_channel_status (state:str,ports:list[dict]) : + port_val="" + if 'destination_port' in ports and ports['destination_port'][0] is not None: + port_val = ports['destination_port'][0] + else: + port_val = ports['source_port'][0] + + results=[] + doc, tag, text = Doc().tagtext() + #with tag('config'): + with tag('config',xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"): + + with tag('terminal-device',xmlns="http://openconfig.net/yang/terminal-device"): + with tag("logical-channels"): + with tag('channel'): + with tag('index'):text("{}".format(port_val)) + with tag('config'): + with tag('admin-state'):text("{}".format(state)) + + result = indent( + doc.getvalue(), + indentation = ' '*2, + newline = '' + ) + results.append(result) + + + return results + + +def edit_optical_channel (resources:list[dict]): + + unwanted_keys=['destination_port','source_port','channel_namespace' + ,'optical-band-parent','index', 'name','admin-state','handled_flow'] + config,ports,index=seperate_port_config(resources,unwanted_keys=unwanted_keys) + results = [] + # channel_name=next((i["value"] for i in resources if i["resource_key"]=="channel_name" and i["value"] != None),None) + # admin_state= next((i["value"] for i in resources if i["resource_key"]== "admin-state" and i["value"] != None) , None) + + + + # results.extend(change_optical_channel_status(state=admin_state,ports=ports)) + # else : + + results.extend(create_optical_channel(resources=resources,ports=ports,config=config) ) + + return results diff --git a/src/device/service/drivers/oc_driver/templates/descovery_tool/roadms.py b/src/device/service/drivers/oc_driver/templates/descovery_tool/roadms.py new file mode 100644 index 0000000000000000000000000000000000000000..e27fd923a4d3ec4a69b810667545d5bcae235269 --- /dev/null +++ b/src/device/service/drivers/oc_driver/templates/descovery_tool/roadms.py @@ -0,0 +1,260 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re +import lxml.etree as ET + +def extract_channel_xmlns (data_xml:str,is_opticalband:bool): + xml_bytes = data_xml.encode("utf-8") + root = ET.fromstring(xml_bytes) + + namespace=None + channels=None + + if (not is_opticalband) : + + optical_channel_namespaces = { + 'ns': 'urn:ietf:params:xml:ns:netconf:base:1.0', + 'oc': 'http://openconfig.net/yang/platform', + } + + channels= root.find('.//{*}optical-channel',optical_channel_namespaces) + if channels is not None : + optical_channel_namespace = channels.tag.replace("optical-channel", "") + namespace=optical_channel_namespace.replace("{", "").replace("}", "") + else : + optical_band_namespaces= { + 'oc':'http://openconfig.net/yang/wavelength-router' + } + + channels= root.find('.//{*}optical-bands',optical_band_namespaces) + if channels is not None: + optical_channel_namespace = channels.tag.replace("optical-bands", "") + namespace=optical_channel_namespace.replace("{", "").replace("}", "") + return namespace + +def extract_optical_bands (data_xml:str,namespace:str): + namespaces={"oc":namespace} + xml_bytes = data_xml.encode("utf-8") + root = ET.fromstring(xml_bytes) + op_bands=[] + optical_bands= root.find('.//oc:optical-bands',namespaces) + + if optical_bands is not None : + optical_bands_ele= optical_bands.findall('.//oc:optical-band',namespaces) + + for optical_band in optical_bands_ele: + band_ele=optical_band.find('.//oc:name',namespaces) + lower_freq_ele=optical_band.find('.//oc:lower-frequency',namespaces) + upper_freq_ele=optical_band.find('.//oc:upper-frequency',namespaces) + admin_status_ele=optical_band.find('.//oc:admin-status',namespaces) + source_ele=optical_band.find('.//oc:source/oc:config/oc:port-name',namespaces) + dest_ele=optical_band.find('.//oc:dest/oc:config/oc:port-name',namespaces) + channel_index= optical_band.find('.//oc:index',namespaces) + op_band_obj={ + 'band_name':band_ele.text if band_ele is not None else None, + 'lower_frequency':lower_freq_ele.text if lower_freq_ele is not None else None, + 'upper_frequency':upper_freq_ele.text if upper_freq_ele is not None else None, + 'status':admin_status_ele.text if admin_status_ele is not None else None, + 'src_port':source_ele.text if source_ele is not None else None, + 'dest_port':dest_ele.text if dest_ele is not None else None, + "channel_index":channel_index.text if channel_index is not None else None + } + op_bands.append(op_band_obj) + + return op_bands + +def extract_media_channels (data_xml:str): + optical_band_namespaces="http://flex-scale-project.eu/yang/flex-scale-mg-on" + namespaces={"oc":"http://openconfig.net/yang/wavelength-router",'ob_parent':optical_band_namespaces} + xml_bytes = data_xml.encode("utf-8") + root = ET.fromstring(xml_bytes) + media_channels= root.find(f'.//oc:media-channels',namespaces) + op_bands=[] + if media_channels is not None : + media_channels_ele= media_channels.findall('.//oc:channel',namespaces) + for optical_band in media_channels_ele: + band_ele=optical_band.find('.//oc:name',namespaces) + lower_freq_ele=optical_band.find('.//oc:lower-frequency',namespaces) + upper_freq_ele=optical_band.find('.//oc:upper-frequency',namespaces) + admin_status_ele=optical_band.find('.//oc:admin-status',namespaces) + source_ele=optical_band.find('.//oc:source/oc:config/oc:port-name',namespaces) + dest_ele=optical_band.find('.//oc:dest/oc:config/oc:port-name',namespaces) + ob_parent=optical_band.find('.//ob_parent:optical-band-parent',namespaces) + channel_index= optical_band.find('.//oc:index',namespaces) + op_band_obj={ + 'band_name':band_ele.text if band_ele is not None else None, + 'lower_frequency':lower_freq_ele.text if lower_freq_ele is not None else None, + 'upper_frequency':upper_freq_ele.text if upper_freq_ele is not None else None, + 'status':admin_status_ele.text if admin_status_ele is not None else None, + 'src_port':source_ele.text if source_ele is not None else None, + 'dest_port':dest_ele.text if dest_ele is not None else None, + 'optical_band_parent':ob_parent.text if ob_parent is not None else None, + 'channel_index':channel_index.text if channel_index is not None else None + } + op_bands.append(op_band_obj) + + return op_bands + +def extract_roadm_ports_old (xml_data:str): + ports =[] + pattern = r'\bMG_ON_OPTICAL_PORT_WAVEBAND\b' + xml_bytes = xml_data.encode("utf-8") + root = ET.fromstring(xml_bytes) + with open('xml.log', 'w') as f: + print(xml_bytes, file=f) + + namespace = {'oc': 'http://openconfig.net/yang/platform'} + ports = [] + components = root.findall('.//oc:component',namespace) + print(f"component {components}") + + for component in components: + properties = component.find(".//oc:properties",namespace) + if (properties is not None): + for property in properties : + value = property.find(".//oc:value",namespace) + if (re.search(pattern,value.text)): + name_element= component.find(".//oc:name",namespace) + ports.append(name_element.text) + return ports + +def extract_roadm_ports (xml_data:str): + ports =[] + pattern2=r'\bMG_ON_PORT_TYPE' + pattern = r'\bMG_ON_OPTICAL_PORT_WAVEBAND\b' + xml_bytes = xml_data.encode("utf-8") + root = ET.fromstring(xml_bytes) + + namespace = {'oc': 'http://openconfig.net/yang/platform'} + ports = [] + components = root.findall('.//oc:component',namespace) + #print(f"component {components}") + + for component in components: + properties = component.find(".//oc:properties",namespace) + if (properties is not None): + for property in properties : + value = property.find(".//oc:value",namespace) + name= property.find('.//oc:name',namespace) + if (re.search(pattern2,name.text)): + value = property.find(".//oc:value",namespace) + name_element= component.find(".//oc:name",namespace) + print('value',value.text) + ports.append((name_element.text,value.text)) + return ports + +def roadm_values_extractor (data_xml:str,resource_keys:list,dic:dict): + ports_result=[] + ports = extract_roadm_ports(data_xml) + namespcae= extract_channel_xmlns(data_xml,True) + optical_bands=extract_optical_bands(data_xml=data_xml,namespace=namespcae) + media_cahannels=extract_media_channels(data_xml) + if len(ports)>0 : + for port in ports : + name,type=port + resource_key = '/endpoints/endpoint[{:s}]'.format(name) + resource_value = {'uuid': name, 'type':type} + ports_result.append((resource_key, resource_value)) + return [ports_result,optical_bands,media_cahannels] + +#/////////////// OpenRoadm ////////////// + +def extract_roadm_circuits_pack (xml_data:str): + xml_bytes = xml_data.encode("utf-8") + root = ET.fromstring(xml_bytes) + # with open('xml.log', 'w') as f: + # print(xml_bytes, file=f) + + namespace = {'oc': "http://org/openroadm/device"} + circuits = root.findall('.//oc:circuit-packs',namespace) + circuits_list =[] + # print(f"component {components}") + if (circuits is not None): + for circuit in circuits: + circuit_info ={} + circuit_ports=[] + circuit_name = circuit.find(".//oc:circuit-pack-name",namespace) + circuit_type=circuit.find(".//oc:circuit-pack-type",namespace) + circuit_adminstrative_status=circuit.find(".//oc:administrative-state",namespace) + circuit_equipment_state=circuit.find("./oc:equipment-state",namespace) + circuit_mode=circuit.find("./oc:circuit-pack-mode",namespace) + slot= circuit.find("./oc:slot",namespace) + shelf= circuit.find("./oc:shelf",namespace) + ports = circuit.findall("./oc:ports",namespace) + + if (ports is not None): + for port in ports : + port_info={} + port_name=port.find('./oc:port-name',namespace) + port_qual= port.find("./oc:port-qual",namespace) + if port_name is not None : + port_info["port_name"]=port_name.text + if port_qual is not None : + port_info["port_qual"]=port_qual.text + circuit_ports.append(port_info) + if (circuit_name is not None): + circuit_info["circuit_name"]=circuit_name.text + if (circuit_type is not None): + circuit_info["circuit_type"]=circuit_type.text + if (circuit_adminstrative_status is not None): + circuit_info["circuit_adminstrative_status"]=circuit_adminstrative_status.text + if (circuit_equipment_state is not None): + circuit_info["circuit_equipment_state"]=circuit_equipment_state.text + if (circuit_mode is not None): + circuit_info["circuit_mode"]=circuit_mode.text + if (slot is not None): + circuit_info["slot"]=slot.text + if (shelf is not None): + circuit_info["shelf"]=shelf.text + circuit_info["ports"]=circuit_ports + circuits_list.append(circuit_info) + return circuits_list + +def extract_openroadm_info(xml_data:str): + roadm_info={"node-id":None,"node-number":None,"node-type":None,'clli':None} + xml_bytes = xml_data.encode("utf-8") + root = ET.fromstring(xml_bytes) + namespace = {'oc': "http://org/openroadm/device"} + info = root.findall('.//oc:info',namespace) + if info is not None : + for i in info : + node_id= i.find('.//oc:node-id',namespace) + node_number= i.find('.//oc:node-number',namespace) + node_type=i.find('.//oc:node-type',namespace) + clli=i.find('.//oc:clli',namespace) + if (node_id is not None): + roadm_info['node-id']=node_id.text + if (node_number is not None): + roadm_info['node-number']=node_number.text + if (node_type is not None): + roadm_info['node-type']=node_type.text + if (clli is not None): + roadm_info['clli']=clli.text + return roadm_info + +def openroadm_values_extractor (data_xml:str,resource_keys:list,dic:dict): + ports_result=[] + openroadm_info= extract_openroadm_info(data_xml) + circuits_list = extract_roadm_circuits_pack(data_xml) + dic["openroadm_info"]=openroadm_info + dic["circuits"]=circuits_list + + for circuit in circuits_list : + for port in circuit['ports']: + if port is not None and 'port_name' in port : + resource_key = '/endpoints/endpoint[{:s}]'.format(port["port_name"]) + resource_value = {'uuid': port["port_name"], 'type':port["port_qual"] if "port_qual" in port else None} + ports_result.append((resource_key, resource_value)) + return [dic,ports_result] diff --git a/src/device/service/drivers/oc_driver/templates/descovery_tool/transponders.py b/src/device/service/drivers/oc_driver/templates/descovery_tool/transponders.py new file mode 100644 index 0000000000000000000000000000000000000000..e0ffc12ffd63f2e5b32ea4c7a813acee7d146c58 --- /dev/null +++ b/src/device/service/drivers/oc_driver/templates/descovery_tool/transponders.py @@ -0,0 +1,301 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re,logging +import json +import lxml.etree as ET +from typing import Collection, Dict, Any + + + +def add_value_from_tag(target : Dict, field_name: str, field_value : ET.Element, cast=None) -> None: + if isinstance(field_value,str) or field_value is None or field_value.text is None: return + field_value = field_value.text + if cast is not None: field_value = cast(field_value) + target[field_name] = field_value + +def add_value_from_collection(target : Dict, field_name: str, field_value : Collection) -> None: + if field_value is None or len(field_value) == 0: return + target[field_name] = field_value + + +def generate_templates(resource_key: str, resource_value: str, channel:str) -> str: # template management to be configured + + result_templates = [] + data={} + data['name']=channel + data['resource_key']=resource_key + data['value']=resource_value + #result_templates.append(create_physical_config(data)) + + return result_templates + + +def extract_status (dic:dict,resource_key:str,xml_data:str,channel_name:str): + + xml_bytes = xml_data.encode("utf-8") + root = ET.fromstring(xml_bytes) + channel_name=channel_name if 'index' not in channel_name else channel_name['index'] + index=None + if channel_name.find('-') != -1 : + index= channel_name.split("-")[1] + + + namespaces = { "td": "http://openconfig.net/yang/terminal-device"} + channels = root.findall(f".//td:terminal-device/td:logical-channels/td:channel",namespaces) + for channel in channels : + + index_ele= channel.find(f".//td:config[td:index='{index}']/td:{resource_key}",namespaces) + if index_ele is not None : + dic["status"]=index_ele.text + print(index_ele.text) + return dic + + +def extract_channel_xmlns (data_xml:str,is_opticalband:bool): + xml_bytes = data_xml.encode("utf-8") + root = ET.fromstring(xml_bytes) + + namespace=None + channels=None + + if (not is_opticalband) : + + optical_channel_namespaces = { + 'ns': 'urn:ietf:params:xml:ns:netconf:base:1.0', + 'oc': 'http://openconfig.net/yang/platform', + } + + channels= root.find('.//{*}optical-channel',optical_channel_namespaces) + if channels is not None : + optical_channel_namespace = channels.tag.replace("optical-channel", "") + namespace=optical_channel_namespace.replace("{", "").replace("}", "") + else : + optical_band_namespaces= { + 'oc':'http://openconfig.net/yang/wavelength-router' + } + + channels= root.find('.//{*}optical-bands',optical_band_namespaces) + if channels is not None: + optical_channel_namespace = channels.tag.replace("optical-bands", "") + namespace=optical_channel_namespace.replace("{", "").replace("}", "") + + + return namespace + +def extract_channels_based_on_channelnamespace (xml_data:str,channel_namespace:str,is_opticalband:bool): + xml_bytes = xml_data.encode("utf-8") + root = ET.fromstring(xml_bytes) + channels=[] + + # Find the component names whose children include the "optical-channel" element + if (not is_opticalband): + namespace = {'namespace': 'http://openconfig.net/yang/platform','cn':channel_namespace} + + component_names = root.findall('.//namespace:component[cn:optical-channel]',namespace) + + # Extract and print the component names + for component in component_names: + component_name = component.find('namespace:name', namespace).text + channels.append({"index":component_name}) + else : + namespaces = { + 'wr': 'http://openconfig.net/yang/wavelength-router', + 'fs': channel_namespace + } + + wl = root.findall('.//fs:optical-band',namespaces=namespaces) + + for component in wl : + index=component.find('.//fs:index',namespaces).text + dest_port_name = component.find('.//fs:dest/fs:config/fs:port-name', namespaces).text + + # Retrieve port-name for source (assuming it exists in the XML structure) + source_port_name = component.find('.//fs:source/fs:config/fs:port-name', namespaces).text + channels.append({"index":index,"endpoints":(source_port_name,dest_port_name)}) + + # Retrieve port-name for dest + + return channels + +def extract_channels_based_on_type (xml_data:str): + xml_bytes = xml_data.encode("utf-8") + root = ET.fromstring(xml_bytes) + + namespace = {'oc': 'http://openconfig.net/yang/platform', 'typex': 'http://openconfig.net/yang/platform-types'} + channel_names = [] + components = root.findall('.//oc:component', namespace) + for component in components: + + type_element = component.find('.//oc:state/oc:type[.="oc-opt-types:OPTICAL_CHANNEL"]',namespaces=namespace) + + if type_element is not None and type_element.text == 'oc-opt-types:OPTICAL_CHANNEL': + name_element = component.find('oc:name', namespace) + if name_element is not None: + channel_names.append(name_element.text) + return channel_names + +def extract_value(resource_key:str,xml_data:str,dic:dict,channel_name:str,channel_namespace:str): + + xml_bytes = xml_data.encode("utf-8") + root = ET.fromstring(xml_bytes) + channel_name=channel_name if 'index' not in channel_name else channel_name['index'] + namespace = {'oc': 'http://openconfig.net/yang/platform', + 'td': channel_namespace} + + element = root.find(f'.//oc:component[oc:name="{channel_name}"]', namespace) + + if element is not None: + parameter= element.find(f'.//td:{resource_key}',namespace) + if (parameter is not None): + value = parameter.text + dic[resource_key]=value + else : + logging.info("parameter is None") + + else: + logging.info("element is None") + print(" element not found.") + + return dic + + +def extract_port_value (xml_string:list,port_name:str): + + xml_bytes = xml_string.encode("utf-8") + root = ET.fromstring(xml_bytes) + + namespace = {"oc": "http://openconfig.net/yang/platform"} + component=root.find(f".//oc:component[oc:name='{port_name}']", namespace) + onos_index = component.find( + f".//oc:property//oc:state/oc:name[.='onos-index']/../oc:value", namespace + ).text + + return (port_name,onos_index) + + + + +def extract_tranceiver (data_xml:str,dic:dict): + xml_bytes = data_xml.encode("utf-8") + root = ET.fromstring(xml_bytes) + namespaces = { + 'ns': 'urn:ietf:params:xml:ns:netconf:base:1.0', + 'oc': 'http://openconfig.net/yang/platform', + 'oc-terminal': 'http://openconfig.net/yang/terminal-device', + 'oc-platform-types': 'http://openconfig.net/yang/platform-types' + } + + + transceiver_components = root.findall('.//oc:component/oc:state/[oc:type="oc-platform-types:TRANSCEIVER"]../oc:state/oc:name', namespaces) + + component_names = [component.text for component in transceiver_components] + dic['transceiver']=component_names + return dic + +def extract_interface (xml_data:str,dic:dict): + xml_bytes = xml_data.encode("utf-8") + root = ET.fromstring(xml_bytes) + namespaces = { + 'ns': 'urn:ietf:params:xml:ns:netconf:base:1.0', + 'oc': 'http://openconfig.net/yang/interfaces', + } + ip_namespaces = { + 'oc': 'http://openconfig.net/yang/interfaces', + 'ip': 'http://openconfig.net/yang/interfaces/ip', + } + + interfaces = root.findall('.//oc:interfaces/oc:interface', namespaces) + interface_names = [interface.find('oc:name', namespaces).text for interface in interfaces] + interface_enabled=[interface.find('oc:config/oc:enabled', namespaces).text for interface in interfaces] + ip_address_element = root.find('.//ip:ip', ip_namespaces) + interface_prefix_length=root.find('.//ip:prefix-length',ip_namespaces) + if (len(interface_names) > 0): + dic['interface']={"name":interface_names[0],'ip':ip_address_element.text,'enabled':interface_enabled[0],"prefix-length":interface_prefix_length.text} + else : + dic['interface']={} + return dic + +def has_opticalbands(xml_data:str): + xml_bytes = xml_data.encode("utf-8") + root = ET.fromstring(xml_bytes) + + has_opticalbands=False + elements= root.find('.//{*}optical-bands') + + if (elements is not None and len(elements) >0): + has_opticalbands=True + else : + has_opticalbands=False + return has_opticalbands + +def extract_ports_based_on_type (xml_data:str): + pattern = r':\s*PORT\b' + xml_bytes = xml_data.encode("utf-8") + root = ET.fromstring(xml_bytes) + namespace = {'oc': 'http://openconfig.net/yang/platform', 'typex': 'http://openconfig.net/yang/platform-types'} + ports = [] + components = root.findall(".//oc:state[oc:type]",namespace) + for component in components: + type_ele = component.find(".//oc:type",namespace) + match = re.search(pattern, type_ele.text) + if match is not None : + name_element= component.find(".//oc:name",namespace) + port_name =name_element.text + port_index=name_element.text.split("-")[1] + port = (port_name,port_index) + ports.append(port) + return ports + +def transponder_values_extractor(data_xml:str,resource_keys:list,dic:dict): + + endpoints=[] + is_opticalband=has_opticalbands(xml_data=data_xml) + channel_namespace=extract_channel_xmlns(data_xml=data_xml,is_opticalband=is_opticalband) + # channel_names=extract_channels_based_on_type(xml_data=data_xml) + # if len(channel_names)==0 : + channel_names= extract_channels_based_on_channelnamespace(xml_data=data_xml,channel_namespace=channel_namespace,is_opticalband=is_opticalband) + + ports = extract_ports_based_on_type(data_xml) + optical_channels_params=[] + ports_result=[] + if (is_opticalband): + endpoints=channel_names + else: + + for channel_name in channel_names: + dic={} + for resource_key in resource_keys : + + if (resource_key != 'admin-state'): + + dic=extract_value(dic=dic,resource_key=resource_key,xml_data=data_xml + ,channel_name=channel_name,channel_namespace=channel_namespace) + else : + dic = extract_status(dic=dic,resource_key=resource_key,xml_data=data_xml, channel_name=channel_name) + dic["name"]=channel_name + endpoints.append({"endpoint_uuid":{"uuid":channel_name}}) + optical_channels_params.append(dic) + #transceivers_dic=extract_tranceiver(data_xml=data_xml,dic={}) + transceivers_dic={"transceiver":[]} + #interfaces_dic=extract_interface(xml_data=data_xml,dic={}) + if len(ports)>0 : + for port in ports : + endpoint_name,endpoint_id=port + resource_key = '/endpoints/endpoint[{:s}]'.format(endpoint_id) + resource_value = {'uuid': endpoint_id, 'type':endpoint_name} + ports_result.append((resource_key, resource_value)) + + + return [transceivers_dic,optical_channels_params,channel_namespace,endpoints,ports_result] diff --git a/src/tests/p4/__init__.py b/src/device/tests/gnmi_openconfig/__init__.py similarity index 100% rename from src/tests/p4/__init__.py rename to src/device/tests/gnmi_openconfig/__init__.py diff --git a/src/device/tests/gnmi_openconfig/storage/Storage.py b/src/device/tests/gnmi_openconfig/storage/Storage.py new file mode 100644 index 0000000000000000000000000000000000000000..ba3596be791e0bc8a473596e6c238eb87fe21f89 --- /dev/null +++ b/src/device/tests/gnmi_openconfig/storage/Storage.py @@ -0,0 +1,23 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .StorageEndpoints import StorageEndpoints +from .StorageInterface import StorageInterface +from .StorageNetworkInstance import StorageNetworkInstance + +class Storage: + def __init__(self) -> None: + self.endpoints = StorageEndpoints() + self.interfaces = StorageInterface() + self.network_instances = StorageNetworkInstance() diff --git a/src/device/tests/gnmi_openconfig/storage/StorageEndpoints.py b/src/device/tests/gnmi_openconfig/storage/StorageEndpoints.py new file mode 100644 index 0000000000000000000000000000000000000000..9876c8019317e33e58a64a86a477ea06c1ce76a9 --- /dev/null +++ b/src/device/tests/gnmi_openconfig/storage/StorageEndpoints.py @@ -0,0 +1,75 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re +from typing import Dict, List, Tuple +from .Tools import compose_resources + +RE_RESKEY_ENDPOINT = re.compile(r'^\/endpoints\/endpoint\[([^\]]+)\]$') + +ENDPOINT_PACKET_SAMPLE_TYPES : Dict[int, str] = { + 101: '/openconfig-interfaces:interfaces/interface[name={:s}]/state/counters/out-pkts', + 102: '/openconfig-interfaces:interfaces/interface[name={:s}]/state/counters/in-pkts', + 201: '/openconfig-interfaces:interfaces/interface[name={:s}]/state/counters/out-octets', + 202: '/openconfig-interfaces:interfaces/interface[name={:s}]/state/counters/in-octets', +} + +class Endpoints: + STRUCT : List[Tuple[str, List[str]]] = [ + ('/endpoints/endpoint[{:s}]', ['uuid', 'type', 'sample_types']), + ] + + def __init__(self) -> None: + self._items : Dict[str, Dict] = dict() + + def add(self, ep_uuid : str, resource_value : Dict) -> None: + item = self._items.setdefault(ep_uuid, dict()) + item['uuid'] = ep_uuid + + for _, field_names in Endpoints.STRUCT: + field_names = set(field_names) + item.update({k:v for k,v in resource_value.items() if k in field_names}) + + item['sample_types'] = { + sample_type_id : sample_type_path.format(ep_uuid) + for sample_type_id, sample_type_path in ENDPOINT_PACKET_SAMPLE_TYPES.items() + } + + def get(self, ep_uuid : str) -> Dict: + return self._items.get(ep_uuid) + + def remove(self, ep_uuid : str) -> None: + self._items.pop(ep_uuid, None) + + def compose_resources(self) -> List[Dict]: + return compose_resources(self._items, Endpoints.STRUCT) + +class StorageEndpoints: + def __init__(self) -> None: + self.endpoints = Endpoints() + + def populate(self, resources : List[Tuple[str, Dict]]) -> None: + for resource_key, resource_value in resources: + match = RE_RESKEY_ENDPOINT.match(resource_key) + if match is not None: + self.endpoints.add(match.group(1), resource_value) + continue + + MSG = 'Unhandled Resource Key: {:s} => {:s}' + raise Exception(MSG.format(str(resource_key), str(resource_value))) + + def get_expected_config(self) -> List[Tuple[str, Dict]]: + expected_config = list() + expected_config.extend(self.endpoints.compose_resources()) + return expected_config diff --git a/src/device/tests/gnmi_openconfig/storage/StorageInterface copy.py b/src/device/tests/gnmi_openconfig/storage/StorageInterface copy.py new file mode 100644 index 0000000000000000000000000000000000000000..1929ced36b9c597656d4f6022616c78c78539a6a --- /dev/null +++ b/src/device/tests/gnmi_openconfig/storage/StorageInterface copy.py @@ -0,0 +1,134 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re +from typing import Dict, List, Tuple +from .Tools import compose_resources + +PREFIX = r'^\/interface\[([^\]]+)\]' +RE_RESKEY_INTERFACE = re.compile(PREFIX + r'$') +RE_RESKEY_ETHERNET = re.compile(PREFIX + r'\/ethernet$') +RE_RESKEY_SUBINTERFACE = re.compile(PREFIX + r'\/subinterface\[([^\]]+)\]$') +#RE_RESKEY_IPV4_ADDRESS = re.compile(PREFIX + r'\/subinterface\[([^\]]+)\]\/ipv4\[([^\]]+)\]$') + +class Interfaces: + STRUCT : List[Tuple[str, List[str]]] = [ + ('/interface[{:s}]', ['name', 'type', 'admin-status', 'oper-status', 'management', 'mtu', 'ifindex', + 'hardware-port', 'transceiver']), + ('/interface[{:s}]/ethernet', ['port-speed', 'negotiated-port-speed', 'mac-address', 'hw-mac-address']), + ] + + def __init__(self) -> None: + self._items : Dict[str, Dict] = dict() + + def add(self, if_name : str, resource_value : Dict) -> None: + item = self._items.setdefault(if_name, dict()) + item['name'] = if_name + for _, field_names in Interfaces.STRUCT: + field_names = set(field_names) + item.update({k:v for k,v in resource_value.items() if k in field_names}) + + def get(self, if_name : str) -> Dict: + return self._items.get(if_name) + + def remove(self, if_name : str) -> None: + self._items.pop(if_name, None) + + def compose_resources(self) -> List[Dict]: + return compose_resources(self._items, Interfaces.STRUCT) + +class SubInterfaces: + STRUCT : List[Tuple[str, List[str]]] = [ + ('/interface[{:s}]/subinterface[{:d}]', ['index']), + ] + + def __init__(self) -> None: + self._items : Dict[Tuple[str, int], Dict] = dict() + + def add(self, if_name : str, subif_index : int) -> None: + item = self._items.setdefault((if_name, subif_index), dict()) + item['index'] = subif_index + + def get(self, if_name : str, subif_index : int) -> Dict: + return self._items.get((if_name, subif_index)) + + def remove(self, if_name : str, subif_index : int) -> None: + self._items.pop((if_name, subif_index), None) + + def compose_resources(self) -> List[Dict]: + return compose_resources(self._items, SubInterfaces.STRUCT) + +class IPv4Addresses: + STRUCT : List[Tuple[str, List[str]]] = [ + ('/interface[{:s}]/subinterface[{:d}]', ['index', 'address_ip', 'address_prefix', 'origin']), + ] + + def __init__(self) -> None: + self._items : Dict[Tuple[str, int], Dict] = dict() + + def add(self, if_name : str, subif_index : int, ipv4_address : str, resource_value : Dict) -> None: + item = self._items.setdefault((if_name, subif_index), dict()) + item['index' ] = subif_index + item['address_ip' ] = ipv4_address + item['origin' ] = resource_value.get('origin') + item['address_prefix'] = resource_value.get('prefix') + + def get(self, if_name : str, subif_index : int, ipv4_address : str) -> Dict: + return self._items.get((if_name, subif_index)) + + def remove(self, if_name : str, subif_index : int, ipv4_address : str) -> None: + self._items.pop((if_name, subif_index), None) + + def compose_resources(self) -> List[Dict]: + return compose_resources(self._items, IPv4Addresses.STRUCT) + +class StorageInterface: + def __init__(self) -> None: + self.interfaces = Interfaces() + self.subinterfaces = SubInterfaces() + self.ipv4_addresses = IPv4Addresses() + + def populate(self, resources : List[Tuple[str, Dict]]) -> None: + for resource_key, resource_value in resources: + match = RE_RESKEY_INTERFACE.match(resource_key) + if match is not None: + self.interfaces.add(match.group(1), resource_value) + continue + + match = RE_RESKEY_ETHERNET.match(resource_key) + if match is not None: + self.interfaces.add(match.group(1), resource_value) + continue + + match = RE_RESKEY_SUBINTERFACE.match(resource_key) + if match is not None: + self.subinterfaces.add(match.group(1), int(match.group(2))) + address_ip = resource_value.get('address_ip') + self.ipv4_addresses.add(match.group(1), int(match.group(2)), address_ip, resource_value) + continue + + #match = RE_RESKEY_IPV4_ADDRESS.match(resource_key) + #if match is not None: + # self.ipv4_addresses.add(match.group(1), int(match.group(2)), match.group(3), resource_value) + # continue + + MSG = 'Unhandled Resource Key: {:s} => {:s}' + raise Exception(MSG.format(str(resource_key), str(resource_value))) + + def get_expected_config(self) -> List[Tuple[str, Dict]]: + expected_config = list() + expected_config.extend(self.interfaces.compose_resources()) + #expected_config.extend(self.subinterfaces.compose_resources()) + expected_config.extend(self.ipv4_addresses.compose_resources()) + return expected_config diff --git a/src/device/tests/gnmi_openconfig/storage/StorageInterface.py b/src/device/tests/gnmi_openconfig/storage/StorageInterface.py new file mode 100644 index 0000000000000000000000000000000000000000..f07677c123048774326d61c88df61e66c254b612 --- /dev/null +++ b/src/device/tests/gnmi_openconfig/storage/StorageInterface.py @@ -0,0 +1,131 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re +from typing import Dict, List, Tuple +from .Tools import compose_resources + +PREFIX = r'^\/interface\[([^\]]+)\]' +RE_RESKEY_INTERFACE = re.compile(PREFIX + r'$') +RE_RESKEY_ETHERNET = re.compile(PREFIX + r'\/ethernet$') +RE_RESKEY_SUBINTERFACE = re.compile(PREFIX + r'\/subinterface\[([^\]]+)\]$') +RE_RESKEY_IPV4_ADDRESS = re.compile(PREFIX + r'\/subinterface\[([^\]]+)\]\/ipv4\[([^\]]+)\]$') + +class Interfaces: + STRUCT : List[Tuple[str, List[str]]] = [ + ('/interface[{:s}]', ['name', 'type', 'admin-status', 'oper-status', 'management', 'mtu', 'ifindex', + 'hardware-port', 'transceiver']), + ('/interface[{:s}]/ethernet', ['port-speed', 'negotiated-port-speed', 'mac-address', 'hw-mac-address']), + ] + + def __init__(self) -> None: + self._items : Dict[str, Dict] = dict() + + def add(self, if_name : str, resource_value : Dict) -> None: + item = self._items.setdefault(if_name, dict()) + item['name'] = if_name + for _, field_names in Interfaces.STRUCT: + field_names = set(field_names) + item.update({k:v for k,v in resource_value.items() if k in field_names}) + + def get(self, if_name : str) -> Dict: + return self._items.get(if_name) + + def remove(self, if_name : str) -> None: + self._items.pop(if_name, None) + + def compose_resources(self) -> List[Dict]: + return compose_resources(self._items, Interfaces.STRUCT) + +class SubInterfaces: + STRUCT : List[Tuple[str, List[str]]] = [ + ('/interface[{:s}]/subinterface[{:d}]', ['index']), + ] + + def __init__(self) -> None: + self._items : Dict[Tuple[str, int], Dict] = dict() + + def add(self, if_name : str, subif_index : int) -> None: + item = self._items.setdefault((if_name, subif_index), dict()) + item['index'] = subif_index + + def get(self, if_name : str, subif_index : int) -> Dict: + return self._items.get((if_name, subif_index)) + + def remove(self, if_name : str, subif_index : int) -> None: + self._items.pop((if_name, subif_index), None) + + def compose_resources(self) -> List[Dict]: + return compose_resources(self._items, SubInterfaces.STRUCT) + +class IPv4Addresses: + STRUCT : List[Tuple[str, List[str]]] = [ + ('/interface[{:s}]/subinterface[{:d}]/ipv4[{:s}]', ['ip', 'origin', 'prefix']), + ] + + def __init__(self) -> None: + self._items : Dict[Tuple[str, int, str], Dict] = dict() + + def add(self, if_name : str, subif_index : int, ipv4_address : str, resource_value : Dict) -> None: + item = self._items.setdefault((if_name, subif_index, ipv4_address), dict()) + item['ip' ] = ipv4_address + item['origin'] = resource_value.get('origin') + item['prefix'] = resource_value.get('prefix') + + def get(self, if_name : str, subif_index : int, ipv4_address : str) -> Dict: + return self._items.get((if_name, subif_index, ipv4_address)) + + def remove(self, if_name : str, subif_index : int, ipv4_address : str) -> None: + self._items.pop((if_name, subif_index, ipv4_address), None) + + def compose_resources(self) -> List[Dict]: + return compose_resources(self._items, IPv4Addresses.STRUCT) + +class StorageInterface: + def __init__(self) -> None: + self.interfaces = Interfaces() + self.subinterfaces = SubInterfaces() + self.ipv4_addresses = IPv4Addresses() + + def populate(self, resources : List[Tuple[str, Dict]]) -> None: + for resource_key, resource_value in resources: + match = RE_RESKEY_INTERFACE.match(resource_key) + if match is not None: + self.interfaces.add(match.group(1), resource_value) + continue + + match = RE_RESKEY_ETHERNET.match(resource_key) + if match is not None: + self.interfaces.add(match.group(1), resource_value) + continue + + match = RE_RESKEY_SUBINTERFACE.match(resource_key) + if match is not None: + self.subinterfaces.add(match.group(1), int(match.group(2))) + continue + + match = RE_RESKEY_IPV4_ADDRESS.match(resource_key) + if match is not None: + self.ipv4_addresses.add(match.group(1), int(match.group(2)), match.group(3), resource_value) + continue + + MSG = 'Unhandled Resource Key: {:s} => {:s}' + raise Exception(MSG.format(str(resource_key), str(resource_value))) + + def get_expected_config(self) -> List[Tuple[str, Dict]]: + expected_config = list() + expected_config.extend(self.interfaces.compose_resources()) + expected_config.extend(self.subinterfaces.compose_resources()) + expected_config.extend(self.ipv4_addresses.compose_resources()) + return expected_config diff --git a/src/device/tests/gnmi_openconfig/storage/StorageNetworkInstance.py b/src/device/tests/gnmi_openconfig/storage/StorageNetworkInstance.py new file mode 100644 index 0000000000000000000000000000000000000000..642099aba80542dd93a504c11778a91be615799f --- /dev/null +++ b/src/device/tests/gnmi_openconfig/storage/StorageNetworkInstance.py @@ -0,0 +1,218 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re +from typing import Dict, List, Tuple +from .Tools import compose_resources + +PREFIX = r'^\/network\_instance\[([^\]]+)\]' +RE_RESKEY_NET_INST = re.compile(PREFIX + r'$') +RE_RESKEY_INTERFACE = re.compile(PREFIX + r'\/interface\[([^\]]+)\]$') +RE_RESKEY_PROTOCOL = re.compile(PREFIX + r'\/protocol\[([^\]]+)\]$') +RE_RESKEY_PROTO_STATIC = re.compile(PREFIX + r'\/protocol\[([^\]]+)\]\/static\_routes\[([^\]]+)\]$') +RE_RESKEY_TABLE = re.compile(PREFIX + r'\/table\[([^\,]+)\,([^\]]+)\]$') +RE_RESKEY_VLAN = re.compile(PREFIX + r'\/vlan\[([^\]]+)\]$') + +class NetworkInstances: + STRUCT : List[Tuple[str, List[str]]] = [ + ('/network_instance[{:s}]', ['name', 'type']), + ] + + def __init__(self) -> None: + self._items : Dict[str, Dict] = dict() + + def add(self, ni_name : str, resource_value : Dict) -> None: + item = self._items.setdefault(ni_name, dict()) + item['name'] = ni_name + item['type'] = resource_value.get('type') + + def get(self, ni_name : str) -> Dict: + return self._items.get(ni_name) + + def remove(self, ni_name : str) -> None: + self._items.pop(ni_name, None) + + def compose_resources(self) -> List[Dict]: + return compose_resources(self._items, NetworkInstances.STRUCT) + +class Interfaces: + STRUCT : List[Tuple[str, List[str]]] = [ + ('/network_instance[{:s}]/interface[{:s}.{:d}]', ['name', 'id', 'if_name', 'sif_index']), + ] + + def __init__(self) -> None: + self._items : Dict[Tuple[str, str], Dict] = dict() + + def add(self, ni_name : str, if_name : str, sif_index : int) -> None: + item = self._items.setdefault((ni_name, if_name, sif_index), dict()) + item['name' ] = ni_name + item['id' ] = '{:s}.{:d}'.format(if_name, sif_index) + item['if_name' ] = if_name + item['sif_index'] = sif_index + + def get(self, ni_name : str, if_name : str, sif_index : int) -> Dict: + return self._items.get((ni_name, if_name, sif_index)) + + def remove(self, ni_name : str, if_name : str, sif_index : int) -> None: + self._items.pop((ni_name, if_name, sif_index), None) + + def compose_resources(self) -> List[Dict]: + return compose_resources(self._items, Interfaces.STRUCT) + +class Protocols: + STRUCT : List[Tuple[str, List[str]]] = [ + ('/network_instance[{:s}]/protocol[{:s}]', ['id', 'name']), + ] + + def __init__(self) -> None: + self._items : Dict[Tuple[str, str], Dict] = dict() + + def add(self, ni_name : str, protocol : str) -> None: + item = self._items.setdefault((ni_name, protocol), dict()) + item['id' ] = protocol + item['name'] = protocol + + def get(self, ni_name : str, protocol : str) -> Dict: + return self._items.get((ni_name, protocol)) + + def remove(self, ni_name : str, protocol : str) -> None: + self._items.pop((ni_name, protocol), None) + + def compose_resources(self) -> List[Dict]: + return compose_resources(self._items, Protocols.STRUCT) + +class StaticRoutes: + STRUCT : List[Tuple[str, List[str]]] = [ + ('/network_instance[{:s}]/protocol[{:s}]/static_routes[{:s}]', ['prefix', 'next_hops']), + ] + + def __init__(self) -> None: + self._items : Dict[Tuple[str, str, str], Dict] = dict() + + def add(self, ni_name : str, protocol : str, prefix : str, resource_value : Dict) -> None: + item = self._items.setdefault((ni_name, protocol, prefix), dict()) + item['prefix' ] = prefix + item['next_hops'] = resource_value.get('next_hops') + + def get(self, ni_name : str, protocol : str, prefix : str) -> Dict: + return self._items.get((ni_name, protocol, prefix)) + + def remove(self, ni_name : str, protocol : str, prefix : str) -> None: + self._items.pop((ni_name, protocol, prefix), None) + + def compose_resources(self) -> List[Dict]: + return compose_resources(self._items, StaticRoutes.STRUCT) + +class Tables: + STRUCT : List[Tuple[str, List[str]]] = [ + ('/network_instance[{:s}]/table[{:s},{:s}]', ['protocol', 'address_family']), + ] + + def __init__(self) -> None: + self._items : Dict[Tuple[str, str, str], Dict] = dict() + + def add(self, ni_name : str, protocol : str, address_family : str) -> None: + item = self._items.setdefault((ni_name, protocol, address_family), dict()) + item['protocol' ] = protocol + item['address_family'] = address_family + + def get(self, ni_name : str, protocol : str, address_family : str) -> Dict: + return self._items.get((ni_name, protocol, address_family)) + + def remove(self, ni_name : str, protocol : str, address_family : str) -> None: + self._items.pop((ni_name, protocol, address_family), None) + + def compose_resources(self) -> List[Dict]: + return compose_resources(self._items, Tables.STRUCT) + +class Vlans: + STRUCT : List[Tuple[str, List[str]]] = [ + ('/network_instance[{:s}]/vlan[{:d}]', ['vlan_id', 'name', 'members']), + ] + + def __init__(self) -> None: + self._items : Dict[Tuple[str, int], Dict] = dict() + + def add(self, ni_name : str, vlan_id : int, resource_value : Dict) -> None: + item = self._items.setdefault((ni_name, vlan_id), dict()) + item['vlan_id'] = vlan_id + item['name' ] = resource_value.get('name') + item['members'] = sorted(resource_value.get('members')) + + def get(self, ni_name : str, vlan_id : int) -> Dict: + return self._items.get((ni_name, vlan_id)) + + def remove(self, ni_name : str, vlan_id : int) -> None: + self._items.pop((ni_name, vlan_id), None) + + def compose_resources(self) -> List[Dict]: + return compose_resources(self._items, Vlans.STRUCT) + +class StorageNetworkInstance: + def __init__(self) -> None: + self.network_instances = NetworkInstances() + self.interfaces = Interfaces() + self.protocols = Protocols() + self.protocol_static = StaticRoutes() + self.tables = Tables() + self.vlans = Vlans() + + def populate(self, resources : List[Tuple[str, Dict]]) -> None: + for resource_key, resource_value in resources: + match = RE_RESKEY_NET_INST.match(resource_key) + if match is not None: + self.network_instances.add(match.group(1), resource_value) + continue + + match = RE_RESKEY_INTERFACE.match(resource_key) + if match is not None: + if_id = match.group(2) + if_id_parts = if_id.split('.') + if_name = if_id_parts[0] + sif_index = 0 if len(if_id_parts) == 1 else int(if_id_parts[1]) + self.interfaces.add(match.group(1), if_name, sif_index) + continue + + match = RE_RESKEY_PROTOCOL.match(resource_key) + if match is not None: + self.protocols.add(match.group(1), match.group(2)) + continue + + match = RE_RESKEY_PROTO_STATIC.match(resource_key) + if match is not None: + self.protocol_static.add(match.group(1), match.group(2), match.group(3), resource_value) + continue + + match = RE_RESKEY_TABLE.match(resource_key) + if match is not None: + self.tables.add(match.group(1), match.group(2), match.group(3)) + continue + + match = RE_RESKEY_VLAN.match(resource_key) + if match is not None: + self.vlans.add(match.group(1), int(match.group(2)), resource_value) + continue + + MSG = 'Unhandled Resource Key: {:s} => {:s}' + raise Exception(MSG.format(str(resource_key), str(resource_value))) + + def get_expected_config(self) -> List[Tuple[str, Dict]]: + expected_config = list() + expected_config.extend(self.network_instances.compose_resources()) + expected_config.extend(self.interfaces.compose_resources()) + expected_config.extend(self.protocols.compose_resources()) + expected_config.extend(self.protocol_static.compose_resources()) + expected_config.extend(self.tables.compose_resources()) + expected_config.extend(self.vlans.compose_resources()) + return expected_config diff --git a/src/device/tests/gnmi_openconfig/storage/Tools.py b/src/device/tests/gnmi_openconfig/storage/Tools.py new file mode 100644 index 0000000000000000000000000000000000000000..0e26417d98ccd40ec9685c56de253d0a414475e3 --- /dev/null +++ b/src/device/tests/gnmi_openconfig/storage/Tools.py @@ -0,0 +1,33 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Dict, List, Tuple + +def compose_resources( + storage : Dict[Tuple, Dict], config_struct : List[Tuple[str, List[str]]] +) -> List[Dict]: + expected_config = list() + + for resource_key_fields, resource_value_data in storage.items(): + for resource_key_template, resource_key_field_names in config_struct: + if isinstance(resource_key_fields, (str, int, float, bool)): resource_key_fields = (resource_key_fields,) + resource_key = resource_key_template.format(*resource_key_fields) + resource_value = { + field_name : resource_value_data[field_name] + for field_name in resource_key_field_names + if field_name in resource_value_data and resource_value_data[field_name] is not None + } + expected_config.append((resource_key, resource_value)) + + return expected_config diff --git a/src/tests/p4/tests/__init__.py b/src/device/tests/gnmi_openconfig/storage/__init__.py similarity index 100% rename from src/tests/p4/tests/__init__.py rename to src/device/tests/gnmi_openconfig/storage/__init__.py diff --git a/src/device/tests/gnmi_openconfig/test_unitary_gnmi_oc_arista_l2vpn.py b/src/device/tests/gnmi_openconfig/test_unitary_gnmi_oc_arista_l2vpn.py new file mode 100644 index 0000000000000000000000000000000000000000..b09d7186b684d15172c25714d2df6458442f9e80 --- /dev/null +++ b/src/device/tests/gnmi_openconfig/test_unitary_gnmi_oc_arista_l2vpn.py @@ -0,0 +1,576 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json, logging, os, pytest, time +from typing import Dict, Tuple +os.environ['DEVICE_EMULATED_ONLY'] = 'YES' + +# pylint: disable=wrong-import-position +from device.service.drivers.gnmi_openconfig.GnmiOpenConfigDriver import GnmiOpenConfigDriver +#from device.service.driver_api._Driver import ( +# RESOURCE_ENDPOINTS, RESOURCE_INTERFACES, RESOURCE_NETWORK_INSTANCES, RESOURCE_ROUTING_POLICIES, RESOURCE_SERVICES +#) + +logging.basicConfig(level=logging.DEBUG) +#logging.getLogger('ncclient.operations.rpc').setLevel(logging.INFO) +#logging.getLogger('ncclient.transport.parser').setLevel(logging.INFO) + +LOGGER = logging.getLogger(__name__) + + +##### DRIVERS FIXTURE ################################################################################################## + +DEVICES = { + 'SW1': {'address': '172.20.20.101', 'port': 6030, 'settings': { + 'username': 'admin', 'password': 'admin', + 'vendor': None, 'force_running': False, 'hostkey_verify': False, 'look_for_keys': False, 'allow_agent': False, + 'commit_per_rule': True, 'device_params': {'name': 'default'}, 'manager_params': {'timeout' : 120} + }}, + 'SW2': {'address': '10.1.1.87', 'port': 830, 'settings': { + 'username': 'ocnos', 'password': 'ocnos', + 'vendor': None, 'force_running': False, 'hostkey_verify': False, 'look_for_keys': False, 'allow_agent': False, + 'commit_per_rule': True, 'device_params': {'name': 'default'}, 'manager_params': {'timeout' : 120} + }}, +} + +@pytest.fixture(scope='session') +def drivers() -> Dict[str, OpenConfigDriver]: + _drivers : Dict[str, OpenConfigDriver] = dict() + for device_name, driver_params in DEVICES.items(): + driver = OpenConfigDriver(driver_params['address'], driver_params['port'], **(driver_params['settings'])) + driver.Connect() + _drivers[device_name] = driver + yield _drivers + time.sleep(1) + for _,driver in _drivers.items(): + driver.Disconnect() + + +def network_instance(ni_name, ni_type, ni_router_id=None, ni_route_distinguisher=None) -> Tuple[str, Dict]: + path = '/network_instance[{:s}]'.format(ni_name) + data = {'name': ni_name, 'type': ni_type} + if ni_router_id is not None: data['router_id'] = ni_router_id + if ni_route_distinguisher is not None: data['route_distinguisher'] = ni_route_distinguisher + return path, json.dumps(data) + +def network_instance_add_protocol_bgp(ni_name, ni_type, ni_router_id, ni_bgp_as, neighbors=[]) -> Tuple[str, Dict]: + path = '/network_instance[{:s}]/protocols[BGP]'.format(ni_name) + data = { + 'name': ni_name, 'type': ni_type, 'router_id': ni_router_id, 'identifier': 'BGP', + 'protocol_name': ni_bgp_as, 'as': ni_bgp_as + } + if len(neighbors) > 0: + data['neighbors'] = [ + {'ip_address': neighbor_ip_address, 'remote_as': neighbor_remote_as} + for neighbor_ip_address, neighbor_remote_as in neighbors + ] + return path, json.dumps(data) + +def network_instance_add_protocol_direct(ni_name, ni_type) -> Tuple[str, Dict]: + path = '/network_instance[{:s}]/protocols[DIRECTLY_CONNECTED]'.format(ni_name) + data = { + 'name': ni_name, 'type': ni_type, 'identifier': 'DIRECTLY_CONNECTED', + 'protocol_name': 'DIRECTLY_CONNECTED' + } + return path, json.dumps(data) + +def network_instance_add_protocol_static(ni_name, ni_type) -> Tuple[str, Dict]: + path = '/network_instance[{:s}]/protocols[STATIC]'.format(ni_name) + data = { + 'name': ni_name, 'type': ni_type, 'identifier': 'STATIC', + 'protocol_name': 'STATIC' + } + return path, json.dumps(data) + +#def network_instance_static_route(ni_name, prefix, next_hop, next_hop_index=0) -> Tuple[str, Dict]: +# path = '/network_instance[{:s}]/static_route[{:s}]'.format(ni_name, prefix) +# data = {'name': ni_name, 'prefix': prefix, 'next_hop': next_hop, 'next_hop_index': next_hop_index} +# return path, json.dumps(data) + +def network_instance_add_table_connection( + ni_name, src_protocol, dst_protocol, address_family, default_import_policy, bgp_as=None +) -> Tuple[str, Dict]: + path = '/network_instance[{:s}]/table_connections[{:s}][{:s}][{:s}]'.format( + ni_name, src_protocol, dst_protocol, address_family + ) + data = { + 'name': ni_name, 'src_protocol': src_protocol, 'dst_protocol': dst_protocol, + 'address_family': address_family, 'default_import_policy': default_import_policy, + } + if bgp_as is not None: data['as'] = bgp_as + return path, json.dumps(data) + +def interface( + name, index, description=None, if_type=None, vlan_id=None, mtu=None, ipv4_address_prefix=None, enabled=None +) -> Tuple[str, Dict]: + path = '/interface[{:s}]/subinterface[{:d}]'.format(name, index) + data = {'name': name, 'index': index} + if description is not None: data['description'] = description + if if_type is not None: data['type' ] = if_type + if vlan_id is not None: data['vlan_id' ] = vlan_id + if mtu is not None: data['mtu' ] = mtu + if enabled is not None: data['enabled' ] = enabled + if ipv4_address_prefix is not None: + ipv4_address, ipv4_prefix = ipv4_address_prefix + data['address_ip' ] = ipv4_address + data['address_prefix'] = ipv4_prefix + return path, json.dumps(data) + +def network_instance_interface(ni_name, ni_type, if_name, if_index) -> Tuple[str, Dict]: + path = '/network_instance[{:s}]/interface[{:s}.{:d}]'.format(ni_name, if_name, if_index) + data = {'name': ni_name, 'type': ni_type, 'id': if_name, 'interface': if_name, 'subinterface': if_index} + return path, json.dumps(data) + +def test_configure(drivers : Dict[str, OpenConfigDriver]): + #resources_to_get = [] + #resources_to_get = [RESOURCE_ENDPOINTS] + #resources_to_get = [RESOURCE_INTERFACES] + #resources_to_get = [RESOURCE_NETWORK_INSTANCES] + #resources_to_get = [RESOURCE_ROUTING_POLICIES] + #resources_to_get = [RESOURCE_SERVICES] + #LOGGER.info('resources_to_get = {:s}'.format(str(resources_to_get))) + #results_getconfig = driver.GetConfig(resources_to_get) + #LOGGER.info('results_getconfig = {:s}'.format(str(results_getconfig))) + + csgw1_resources_to_set = [ + network_instance('ecoc24', 'L3VRF', '192.168.150.1', '65001:1'), + network_instance_add_protocol_direct('ecoc24', 'L3VRF'), + network_instance_add_protocol_static('ecoc24', 'L3VRF'), + network_instance_add_protocol_bgp('ecoc24', 'L3VRF', '192.168.150.1', '65001', neighbors=[ + ('192.168.150.2', '65001') + ]), + network_instance_add_table_connection('ecoc24', 'DIRECTLY_CONNECTED', 'BGP', 'IPV4', 'ACCEPT_ROUTE', bgp_as='65001'), + network_instance_add_table_connection('ecoc24', 'STATIC', 'BGP', 'IPV4', 'ACCEPT_ROUTE', bgp_as='65001'), + + interface('ce1', 0, if_type='ethernetCsmacd', mtu=1500), + network_instance_interface('ecoc24', 'L3VRF', 'ce1', 0), + interface('ce1', 0, if_type='ethernetCsmacd', mtu=1500, ipv4_address_prefix=('192.168.10.1', 24), enabled=True), + + interface('xe5', 0, if_type='ethernetCsmacd', mtu=1500), + network_instance_interface('ecoc24', 'L3VRF', 'xe5', 0), + interface('xe5', 0, if_type='ethernetCsmacd', mtu=1500, ipv4_address_prefix=('192.168.150.1', 24), enabled=True), + ] + LOGGER.info('CSGW1 resources_to_set = {:s}'.format(str(csgw1_resources_to_set))) + results_setconfig = drivers['CSGW1'].SetConfig(csgw1_resources_to_set) + LOGGER.info('CSGW1 results_setconfig = {:s}'.format(str(results_setconfig))) + + csgw2_resources_to_set = [ + network_instance('ecoc24', 'L3VRF', '192.168.150.2', '65001:1'), + network_instance_add_protocol_direct('ecoc24', 'L3VRF'), + network_instance_add_protocol_static('ecoc24', 'L3VRF'), + network_instance_add_protocol_bgp('ecoc24', 'L3VRF', '192.168.150.2', '65001', neighbors=[ + ('192.168.150.1', '65001') + ]), + network_instance_add_table_connection('ecoc24', 'DIRECTLY_CONNECTED', 'BGP', 'IPV4', 'ACCEPT_ROUTE', bgp_as='65001'), + network_instance_add_table_connection('ecoc24', 'STATIC', 'BGP', 'IPV4', 'ACCEPT_ROUTE', bgp_as='65001'), + + interface('ce1', 0, if_type='ethernetCsmacd', mtu=1500), + network_instance_interface('ecoc24', 'L3VRF', 'ce1', 0), + interface('ce1', 0, if_type='ethernetCsmacd', mtu=1500, ipv4_address_prefix=('192.168.20.1', 24), enabled=True), + + interface('xe5', 0, if_type='ethernetCsmacd', mtu=1500), + network_instance_interface('ecoc24', 'L3VRF', 'xe5', 0), + interface('xe5', 0, if_type='ethernetCsmacd', mtu=1500, ipv4_address_prefix=('192.168.150.2', 24), enabled=True), + ] + LOGGER.info('CSGW2 resources_to_set = {:s}'.format(str(csgw2_resources_to_set))) + results_setconfig = drivers['CSGW2'].SetConfig(csgw2_resources_to_set) + LOGGER.info('CSGW2 results_setconfig = {:s}'.format(str(results_setconfig))) + + csgw1_resources_to_delete = [ + network_instance_interface('ecoc24', 'L3VRF', 'ce1', 0), + network_instance_interface('ecoc24', 'L3VRF', 'xe5', 0), + #interface('ce1', 0), + #interface('xe5', 0), + network_instance('ecoc24', 'L3VRF'), + ] + LOGGER.info('CSGW1 resources_to_delete = {:s}'.format(str(csgw1_resources_to_delete))) + results_deleteconfig = drivers['CSGW1'].DeleteConfig(csgw1_resources_to_delete) + LOGGER.info('CSGW1 results_deleteconfig = {:s}'.format(str(results_deleteconfig))) + + csgw2_resources_to_delete = [ + network_instance_interface('ecoc24', 'L3VRF', 'ce1', 0), + network_instance_interface('ecoc24', 'L3VRF', 'xe5', 0), + #interface('ce1', 0), + #interface('xe5', 0), + network_instance('ecoc24', 'L3VRF'), + ] + LOGGER.info('CSGW2 resources_to_delete = {:s}'.format(str(csgw2_resources_to_delete))) + results_deleteconfig = drivers['CSGW2'].DeleteConfig(csgw2_resources_to_delete) + LOGGER.info('CSGW2 results_deleteconfig = {:s}'.format(str(results_deleteconfig))) + + + + + + +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +os.environ['DEVICE_EMULATED_ONLY'] = 'YES' + +# pylint: disable=wrong-import-position +import logging, pytest, time +from typing import Dict, List +from device.service.driver_api._Driver import ( + RESOURCE_ENDPOINTS, RESOURCE_INTERFACES, RESOURCE_NETWORK_INSTANCES, + RESOURCE_ROUTING_POLICIES, RESOURCE_SERVICES +) +from device.service.drivers.gnmi_openconfig.GnmiOpenConfigDriver import GnmiOpenConfigDriver +from .storage.Storage import Storage +from .tools.manage_config import ( + check_config_endpoints, check_config_interfaces, check_config_network_instances, del_config, get_config, set_config +) +from .tools.check_updates import check_updates +from .tools.request_composers import ( + interface, network_instance, network_instance_interface, network_instance_static_route +) + +logging.basicConfig(level=logging.DEBUG) +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + + +##### DRIVER FIXTURE ################################################################################################### + +DRIVER_SETTING_ADDRESS = '172.20.20.101' +DRIVER_SETTING_PORT = 6030 +DRIVER_SETTING_USERNAME = 'admin' +DRIVER_SETTING_PASSWORD = 'admin' +DRIVER_SETTING_USE_TLS = False + +@pytest.fixture(scope='session') +def driver() -> GnmiOpenConfigDriver: + _driver = GnmiOpenConfigDriver( + DRIVER_SETTING_ADDRESS, DRIVER_SETTING_PORT, + username=DRIVER_SETTING_USERNAME, + password=DRIVER_SETTING_PASSWORD, + use_tls=DRIVER_SETTING_USE_TLS, + ) + _driver.Connect() + yield _driver + time.sleep(1) + _driver.Disconnect() + + +##### STORAGE FIXTURE ################################################################################################## + +@pytest.fixture(scope='session') +def storage() -> Dict: + yield Storage() + + +##### NETWORK INSTANCE DETAILS ######################################################################################### + +NETWORK_INSTANCES = [ + { + 'name': 'test-l3-svc', + 'type': 'L3VRF', + 'interfaces': [ + {'name': 'Ethernet1', 'index': 0, 'ipv4_addr': '192.168.1.1', 'ipv4_prefix': 24, 'enabled': True}, + {'name': 'Ethernet10', 'index': 0, 'ipv4_addr': '192.168.10.1', 'ipv4_prefix': 24, 'enabled': True}, + ], + 'static_routes': [ + {'prefix': '172.0.0.0/24', 'next_hop': '172.16.0.2', 'metric': 1}, + {'prefix': '172.2.0.0/24', 'next_hop': '172.16.0.3', 'metric': 1}, + ] + }, + #{ + # 'name': 'test-l2-svc', + # 'type': 'L2VSI', + # 'interfaces': [ + # {'name': 'Ethernet2', 'index': 0, 'ipv4_addr': '192.168.1.1', 'ipv4_prefix': 24, 'enabled': True}, + # {'name': 'Ethernet4', 'index': 0, 'ipv4_addr': '192.168.10.1', 'ipv4_prefix': 24, 'enabled': True}, + # ], + # 'static_routes': [ + # {'prefix': '172.0.0.0/24', 'next_hop': '172.16.0.2', 'metric': 1}, + # {'prefix': '172.2.0.0/24', 'next_hop': '172.16.0.3', 'metric': 1}, + # ] + #} +] + + +##### TEST METHODS ##################################################################################################### + +def test_get_endpoints( + driver : GnmiOpenConfigDriver, # pylint: disable=redefined-outer-name + storage : Storage, # pylint: disable=redefined-outer-name +) -> None: + results_getconfig = get_config(driver, [RESOURCE_ENDPOINTS]) + storage.endpoints.populate(results_getconfig) + check_config_endpoints(driver, storage) + + +def test_get_interfaces( + driver : GnmiOpenConfigDriver, # pylint: disable=redefined-outer-name + storage : Storage, # pylint: disable=redefined-outer-name +) -> None: + results_getconfig = get_config(driver, [RESOURCE_INTERFACES]) + storage.interfaces.populate(results_getconfig) + check_config_interfaces(driver, storage) + + +def test_get_network_instances( + driver : GnmiOpenConfigDriver, # pylint: disable=redefined-outer-name + storage : Storage, # pylint: disable=redefined-outer-name +) -> None: + results_getconfig = get_config(driver, [RESOURCE_NETWORK_INSTANCES]) + storage.network_instances.populate(results_getconfig) + check_config_network_instances(driver, storage) + + +def test_set_network_instances( + driver : GnmiOpenConfigDriver, # pylint: disable=redefined-outer-name + storage : Storage, # pylint: disable=redefined-outer-name +) -> None: + check_config_interfaces(driver, storage) + check_config_network_instances(driver, storage) + + resources_to_set = list() + ni_names = list() + for ni in NETWORK_INSTANCES: + ni_name = ni['name'] + ni_type = ni['type'] + resources_to_set.append(network_instance(ni_name, ni_type)) + ni_names.append(ni_name) + storage.network_instances.network_instances.add(ni_name, {'type': ni_type}) + storage.network_instances.protocols.add(ni_name, 'DIRECTLY_CONNECTED') + storage.network_instances.tables.add(ni_name, 'DIRECTLY_CONNECTED', 'IPV4') + storage.network_instances.tables.add(ni_name, 'DIRECTLY_CONNECTED', 'IPV6') + + results_setconfig = set_config(driver, resources_to_set) + check_updates(results_setconfig, '/network_instance[{:s}]', ni_names) + + check_config_interfaces(driver, storage, max_retries=10, retry_delay=2.0) + check_config_network_instances(driver, storage, max_retries=10, retry_delay=2.0) + + +def test_add_interfaces_to_network_instance( + driver : GnmiOpenConfigDriver, # pylint: disable=redefined-outer-name + storage : Storage, # pylint: disable=redefined-outer-name +) -> None: + check_config_interfaces(driver, storage) + check_config_network_instances(driver, storage) + + resources_to_set = list() + ni_if_names = list() + for ni in NETWORK_INSTANCES: + ni_name = ni['name'] + for ni_if in ni.get('interfaces', list()): + if_name = ni_if['name' ] + subif_index = ni_if['index'] + resources_to_set.append(network_instance_interface(ni_name, if_name, subif_index)) + ni_if_names.append((ni_name, '{:s}.{:d}'.format(if_name, subif_index))) + storage.network_instances.interfaces.add(ni_name, if_name, subif_index) + + results_setconfig = set_config(driver, resources_to_set) + check_updates(results_setconfig, '/network_instance[{:s}]/interface[{:s}]', ni_if_names) + + check_config_interfaces(driver, storage, max_retries=10, retry_delay=2.0) + check_config_network_instances(driver, storage, max_retries=10, retry_delay=2.0) + + +def test_set_interfaces( + driver : GnmiOpenConfigDriver, # pylint: disable=redefined-outer-name + storage : Storage, # pylint: disable=redefined-outer-name +) -> None: + check_config_interfaces(driver, storage) + check_config_network_instances(driver, storage) + + resources_to_set = list() + if_names = list() + for ni in NETWORK_INSTANCES: + for ni_if in ni.get('interfaces', list()): + if_name = ni_if['name' ] + subif_index = ni_if['index' ] + ipv4_address = ni_if['ipv4_addr' ] + ipv4_prefix = ni_if['ipv4_prefix'] + enabled = ni_if['enabled' ] + resources_to_set.append(interface( + if_name, subif_index, ipv4_address, ipv4_prefix, enabled + )) + if_names.append(if_name) + storage.interfaces.ipv4_addresses.add(if_name, subif_index, ipv4_address, { + 'origin' : 'STATIC', 'prefix': ipv4_prefix + }) + default_vlan = storage.network_instances.vlans.get('default', 1) + default_vlan_members : List[str] = default_vlan.setdefault('members', list()) + if if_name in default_vlan_members: default_vlan_members.remove(if_name) + + results_setconfig = set_config(driver, resources_to_set) + check_updates(results_setconfig, '/interface[{:s}]', if_names) + + check_config_interfaces(driver, storage, max_retries=10, retry_delay=2.0) + check_config_network_instances(driver, storage, max_retries=10, retry_delay=2.0) + + +def test_set_network_instance_static_routes( + driver : GnmiOpenConfigDriver, # pylint: disable=redefined-outer-name + storage : Storage, # pylint: disable=redefined-outer-name +) -> None: + check_config_interfaces(driver, storage) + check_config_network_instances(driver, storage) + + resources_to_set = list() + ni_sr_prefixes = list() + for ni in NETWORK_INSTANCES: + ni_name = ni['name'] + for ni_sr in ni.get('static_routes', list()): + ni_sr_prefix = ni_sr['prefix' ] + ni_sr_next_hop = ni_sr['next_hop'] + ni_sr_metric = ni_sr['metric' ] + ni_sr_next_hop_index = 'AUTO_{:d}_{:s}'.format(ni_sr_metric, '-'.join(ni_sr_next_hop.split('.'))) + resources_to_set.append(network_instance_static_route( + ni_name, ni_sr_prefix, ni_sr_next_hop_index, ni_sr_next_hop, metric=ni_sr_metric + )) + ni_sr_prefixes.append((ni_name, ni_sr_prefix)) + storage.network_instances.protocols.add(ni_name, 'STATIC') + storage.network_instances.protocol_static.add(ni_name, 'STATIC', ni_sr_prefix, { + 'prefix': ni_sr_prefix, 'next_hops': { + ni_sr_next_hop_index: {'next_hop': ni_sr_next_hop, 'metric': ni_sr_metric} + } + }) + storage.network_instances.tables.add(ni_name, 'STATIC', 'IPV4') + storage.network_instances.tables.add(ni_name, 'STATIC', 'IPV6') + + results_setconfig = set_config(driver, resources_to_set) + check_updates(results_setconfig, '/network_instance[{:s}]/static_route[{:s}]', ni_sr_prefixes) + + check_config_interfaces(driver, storage, max_retries=10, retry_delay=2.0) + check_config_network_instances(driver, storage, max_retries=10, retry_delay=2.0) + + +def test_del_network_instance_static_routes( + driver : GnmiOpenConfigDriver, # pylint: disable=redefined-outer-name + storage : Storage, # pylint: disable=redefined-outer-name +) -> None: + check_config_interfaces(driver, storage) + check_config_network_instances(driver, storage) + + resources_to_delete = list() + ni_sr_prefixes = list() + for ni in NETWORK_INSTANCES: + ni_name = ni['name'] + for ni_sr in ni.get('static_routes', list()): + ni_sr_prefix = ni_sr['prefix' ] + ni_sr_next_hop = ni_sr['next_hop'] + ni_sr_metric = ni_sr['metric' ] + ni_sr_next_hop_index = 'AUTO_{:d}_{:s}'.format(ni_sr_metric, '-'.join(ni_sr_next_hop.split('.'))) + resources_to_delete.append(network_instance_static_route( + ni_name, ni_sr_prefix, ni_sr_next_hop_index, ni_sr_next_hop, metric=ni_sr_metric + )) + ni_sr_prefixes.append((ni_name, ni_sr_prefix)) + + storage.network_instances.protocols.remove(ni_name, 'STATIC') + storage.network_instances.protocol_static.remove(ni_name, 'STATIC', ni_sr_prefix) + storage.network_instances.tables.remove(ni_name, 'STATIC', 'IPV4') + storage.network_instances.tables.remove(ni_name, 'STATIC', 'IPV6') + + results_deleteconfig = del_config(driver, resources_to_delete) + check_updates(results_deleteconfig, '/network_instance[{:s}]/static_route[{:s}]', ni_sr_prefixes) + + check_config_interfaces(driver, storage, max_retries=10, retry_delay=2.0) + #check_config_network_instances(driver, storage, max_retries=10, retry_delay=2.0) + + +def test_del_interfaces( + driver : GnmiOpenConfigDriver, # pylint: disable=redefined-outer-name + storage : Storage, # pylint: disable=redefined-outer-name +) -> None: + check_config_interfaces(driver, storage) + #check_config_network_instances(driver, storage) + + resources_to_delete = list() + if_names = list() + for ni in NETWORK_INSTANCES: + for ni_if in ni.get('interfaces', list()): + if_name = ni_if['name' ] + subif_index = ni_if['index' ] + ipv4_address = ni_if['ipv4_addr' ] + ipv4_prefix = ni_if['ipv4_prefix'] + enabled = ni_if['enabled' ] + resources_to_delete.append(interface(if_name, subif_index, ipv4_address, ipv4_prefix, enabled)) + if_names.append(if_name) + storage.interfaces.ipv4_addresses.remove(if_name, subif_index, ipv4_address) + default_vlan = storage.network_instances.vlans.get('default', 1) + default_vlan_members : List[str] = default_vlan.setdefault('members', list()) + if if_name not in default_vlan_members: default_vlan_members.append(if_name) + + results_deleteconfig = del_config(driver, resources_to_delete) + check_updates(results_deleteconfig, '/interface[{:s}]', if_names) + + check_config_interfaces(driver, storage, max_retries=10, retry_delay=2.0) + #check_config_network_instances(driver, storage, max_retries=10, retry_delay=2.0) + + +def test_del_interfaces_from_network_instance( + driver : GnmiOpenConfigDriver, # pylint: disable=redefined-outer-name + storage : Storage, # pylint: disable=redefined-outer-name +) -> None: + check_config_interfaces(driver, storage) + #check_config_network_instances(driver, storage) + + resources_to_delete = list() + ni_if_names = list() + for ni in NETWORK_INSTANCES: + ni_name = ni['name'] + for ni_if in ni.get('interfaces', list()): + if_name = ni_if['name' ] + subif_index = ni_if['index'] + resources_to_delete.append(network_instance_interface(ni_name, if_name, subif_index)) + ni_if_names.append((ni_name, '{:s}.{:d}'.format(if_name, subif_index))) + storage.network_instances.interfaces.remove(ni_name, if_name, subif_index) + + results_deleteconfig = del_config(driver, resources_to_delete) + check_updates(results_deleteconfig, '/network_instance[{:s}]/interface[{:s}]', ni_if_names) + + check_config_interfaces(driver, storage, max_retries=10, retry_delay=2.0) + #check_config_network_instances(driver, storage, max_retries=10, retry_delay=2.0) + + +def test_del_network_instances( + driver : GnmiOpenConfigDriver, # pylint: disable=redefined-outer-name + storage : Storage, # pylint: disable=redefined-outer-name +) -> None: + check_config_interfaces(driver, storage) + #check_config_network_instances(driver, storage) + + resources_to_delete = list() + ni_names = list() + for ni in NETWORK_INSTANCES: + ni_name = ni['name'] + ni_type = ni['type'] + resources_to_delete.append(network_instance(ni_name, ni_type)) + ni_names.append(ni_name) + storage.network_instances.network_instances.remove(ni_name) + storage.network_instances.protocols.remove(ni_name, 'DIRECTLY_CONNECTED') + storage.network_instances.tables.remove(ni_name, 'DIRECTLY_CONNECTED', 'IPV4') + storage.network_instances.tables.remove(ni_name, 'DIRECTLY_CONNECTED', 'IPV6') + + results_deleteconfig = del_config(driver, resources_to_delete) + check_updates(results_deleteconfig, '/network_instance[{:s}]', ni_names) + + check_config_interfaces(driver, storage, max_retries=10, retry_delay=2.0) + check_config_network_instances(driver, storage, max_retries=10, retry_delay=2.0) diff --git a/src/device/tests/gnmi_openconfig/test_unitary_gnmi_openconfig.py b/src/device/tests/gnmi_openconfig/test_unitary_gnmi_openconfig.py new file mode 100644 index 0000000000000000000000000000000000000000..1ec2e2f5f808ad5caa12c7dd1effb927bc4dd068 --- /dev/null +++ b/src/device/tests/gnmi_openconfig/test_unitary_gnmi_openconfig.py @@ -0,0 +1,360 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +os.environ['DEVICE_EMULATED_ONLY'] = 'YES' + +# pylint: disable=wrong-import-position +import logging, pytest, time +from typing import Dict, List +from device.service.driver_api._Driver import ( + RESOURCE_ENDPOINTS, RESOURCE_INTERFACES, RESOURCE_NETWORK_INSTANCES, + RESOURCE_ROUTING_POLICIES, RESOURCE_SERVICES +) +from device.service.drivers.gnmi_openconfig.GnmiOpenConfigDriver import GnmiOpenConfigDriver +from .storage.Storage import Storage +from .tools.manage_config import ( + check_config_endpoints, check_config_interfaces, check_config_network_instances, del_config, get_config, set_config +) +from .tools.check_updates import check_updates +from .tools.request_composers import ( + interface, network_instance, network_instance_interface, network_instance_static_route +) + +logging.basicConfig(level=logging.DEBUG) +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + + +##### DRIVER FIXTURE ################################################################################################### + +DRIVER_SETTING_ADDRESS = '172.20.20.101' +DRIVER_SETTING_PORT = 6030 +DRIVER_SETTING_USERNAME = 'admin' +DRIVER_SETTING_PASSWORD = 'admin' +DRIVER_SETTING_USE_TLS = False + +@pytest.fixture(scope='session') +def driver() -> GnmiOpenConfigDriver: + _driver = GnmiOpenConfigDriver( + DRIVER_SETTING_ADDRESS, DRIVER_SETTING_PORT, + username=DRIVER_SETTING_USERNAME, + password=DRIVER_SETTING_PASSWORD, + use_tls=DRIVER_SETTING_USE_TLS, + ) + _driver.Connect() + yield _driver + time.sleep(1) + _driver.Disconnect() + + +##### STORAGE FIXTURE ################################################################################################## + +@pytest.fixture(scope='session') +def storage() -> Dict: + yield Storage() + + +##### NETWORK INSTANCE DETAILS ######################################################################################### + +NETWORK_INSTANCES = [ + { + 'name': 'test-l3-svc', + 'type': 'L3VRF', + 'interfaces': [ + {'name': 'Ethernet1', 'index': 0, 'ipv4_addr': '192.168.1.1', 'ipv4_prefix': 24, 'enabled': True}, + {'name': 'Ethernet10', 'index': 0, 'ipv4_addr': '192.168.10.1', 'ipv4_prefix': 24, 'enabled': True}, + ], + 'static_routes': [ + {'prefix': '172.0.0.0/24', 'next_hop': '172.16.0.2', 'metric': 1}, + {'prefix': '172.2.0.0/24', 'next_hop': '172.16.0.3', 'metric': 1}, + ] + }, + #{ + # 'name': 'test-l2-svc', + # 'type': 'L2VSI', + # 'interfaces': [ + # {'name': 'Ethernet2', 'index': 0, 'ipv4_addr': '192.168.1.1', 'ipv4_prefix': 24, 'enabled': True}, + # {'name': 'Ethernet4', 'index': 0, 'ipv4_addr': '192.168.10.1', 'ipv4_prefix': 24, 'enabled': True}, + # ], + # 'static_routes': [ + # {'prefix': '172.0.0.0/24', 'next_hop': '172.16.0.2', 'metric': 1}, + # {'prefix': '172.2.0.0/24', 'next_hop': '172.16.0.3', 'metric': 1}, + # ] + #} +] + + +##### TEST METHODS ##################################################################################################### + +def test_get_endpoints( + driver : GnmiOpenConfigDriver, # pylint: disable=redefined-outer-name + storage : Storage, # pylint: disable=redefined-outer-name +) -> None: + results_getconfig = get_config(driver, [RESOURCE_ENDPOINTS]) + storage.endpoints.populate(results_getconfig) + check_config_endpoints(driver, storage) + + +def test_get_interfaces( + driver : GnmiOpenConfigDriver, # pylint: disable=redefined-outer-name + storage : Storage, # pylint: disable=redefined-outer-name +) -> None: + results_getconfig = get_config(driver, [RESOURCE_INTERFACES]) + storage.interfaces.populate(results_getconfig) + check_config_interfaces(driver, storage) + + +def test_get_network_instances( + driver : GnmiOpenConfigDriver, # pylint: disable=redefined-outer-name + storage : Storage, # pylint: disable=redefined-outer-name +) -> None: + results_getconfig = get_config(driver, [RESOURCE_NETWORK_INSTANCES]) + storage.network_instances.populate(results_getconfig) + check_config_network_instances(driver, storage) + + +def test_set_network_instances( + driver : GnmiOpenConfigDriver, # pylint: disable=redefined-outer-name + storage : Storage, # pylint: disable=redefined-outer-name +) -> None: + check_config_interfaces(driver, storage) + check_config_network_instances(driver, storage) + + resources_to_set = list() + ni_names = list() + for ni in NETWORK_INSTANCES: + ni_name = ni['name'] + ni_type = ni['type'] + resources_to_set.append(network_instance(ni_name, ni_type)) + ni_names.append(ni_name) + storage.network_instances.network_instances.add(ni_name, {'type': ni_type}) + storage.network_instances.protocols.add(ni_name, 'DIRECTLY_CONNECTED') + storage.network_instances.tables.add(ni_name, 'DIRECTLY_CONNECTED', 'IPV4') + storage.network_instances.tables.add(ni_name, 'DIRECTLY_CONNECTED', 'IPV6') + + results_setconfig = set_config(driver, resources_to_set) + check_updates(results_setconfig, '/network_instance[{:s}]', ni_names) + + check_config_interfaces(driver, storage, max_retries=10, retry_delay=2.0) + check_config_network_instances(driver, storage, max_retries=10, retry_delay=2.0) + + +def test_add_interfaces_to_network_instance( + driver : GnmiOpenConfigDriver, # pylint: disable=redefined-outer-name + storage : Storage, # pylint: disable=redefined-outer-name +) -> None: + check_config_interfaces(driver, storage) + check_config_network_instances(driver, storage) + + resources_to_set = list() + ni_if_names = list() + for ni in NETWORK_INSTANCES: + ni_name = ni['name'] + for ni_if in ni.get('interfaces', list()): + if_name = ni_if['name' ] + subif_index = ni_if['index'] + resources_to_set.append(network_instance_interface(ni_name, if_name, subif_index)) + ni_if_names.append((ni_name, '{:s}.{:d}'.format(if_name, subif_index))) + storage.network_instances.interfaces.add(ni_name, if_name, subif_index) + + results_setconfig = set_config(driver, resources_to_set) + check_updates(results_setconfig, '/network_instance[{:s}]/interface[{:s}]', ni_if_names) + + check_config_interfaces(driver, storage, max_retries=10, retry_delay=2.0) + check_config_network_instances(driver, storage, max_retries=10, retry_delay=2.0) + + +def test_set_interfaces( + driver : GnmiOpenConfigDriver, # pylint: disable=redefined-outer-name + storage : Storage, # pylint: disable=redefined-outer-name +) -> None: + check_config_interfaces(driver, storage) + check_config_network_instances(driver, storage) + + resources_to_set = list() + if_names = list() + for ni in NETWORK_INSTANCES: + for ni_if in ni.get('interfaces', list()): + if_name = ni_if['name' ] + subif_index = ni_if['index' ] + ipv4_address = ni_if['ipv4_addr' ] + ipv4_prefix = ni_if['ipv4_prefix'] + enabled = ni_if['enabled' ] + resources_to_set.append(interface( + if_name, subif_index, ipv4_address, ipv4_prefix, enabled + )) + if_names.append(if_name) + storage.interfaces.ipv4_addresses.add(if_name, subif_index, ipv4_address, { + 'origin' : 'STATIC', 'prefix': ipv4_prefix + }) + default_vlan = storage.network_instances.vlans.get('default', 1) + default_vlan_members : List[str] = default_vlan.setdefault('members', list()) + if if_name in default_vlan_members: default_vlan_members.remove(if_name) + + results_setconfig = set_config(driver, resources_to_set) + check_updates(results_setconfig, '/interface[{:s}]', if_names) + + check_config_interfaces(driver, storage, max_retries=10, retry_delay=2.0) + check_config_network_instances(driver, storage, max_retries=10, retry_delay=2.0) + + +def test_set_network_instance_static_routes( + driver : GnmiOpenConfigDriver, # pylint: disable=redefined-outer-name + storage : Storage, # pylint: disable=redefined-outer-name +) -> None: + check_config_interfaces(driver, storage) + check_config_network_instances(driver, storage) + + resources_to_set = list() + ni_sr_prefixes = list() + for ni in NETWORK_INSTANCES: + ni_name = ni['name'] + for ni_sr in ni.get('static_routes', list()): + ni_sr_prefix = ni_sr['prefix' ] + ni_sr_next_hop = ni_sr['next_hop'] + ni_sr_metric = ni_sr['metric' ] + ni_sr_next_hop_index = 'AUTO_{:d}_{:s}'.format(ni_sr_metric, '-'.join(ni_sr_next_hop.split('.'))) + resources_to_set.append(network_instance_static_route( + ni_name, ni_sr_prefix, ni_sr_next_hop_index, ni_sr_next_hop, metric=ni_sr_metric + )) + ni_sr_prefixes.append((ni_name, ni_sr_prefix)) + storage.network_instances.protocols.add(ni_name, 'STATIC') + storage.network_instances.protocol_static.add(ni_name, 'STATIC', ni_sr_prefix, { + 'prefix': ni_sr_prefix, 'next_hops': { + ni_sr_next_hop_index: {'next_hop': ni_sr_next_hop, 'metric': ni_sr_metric} + } + }) + storage.network_instances.tables.add(ni_name, 'STATIC', 'IPV4') + storage.network_instances.tables.add(ni_name, 'STATIC', 'IPV6') + + results_setconfig = set_config(driver, resources_to_set) + check_updates(results_setconfig, '/network_instance[{:s}]/static_route[{:s}]', ni_sr_prefixes) + + check_config_interfaces(driver, storage, max_retries=10, retry_delay=2.0) + check_config_network_instances(driver, storage, max_retries=10, retry_delay=2.0) + + +def test_del_network_instance_static_routes( + driver : GnmiOpenConfigDriver, # pylint: disable=redefined-outer-name + storage : Storage, # pylint: disable=redefined-outer-name +) -> None: + check_config_interfaces(driver, storage) + check_config_network_instances(driver, storage) + + resources_to_delete = list() + ni_sr_prefixes = list() + for ni in NETWORK_INSTANCES: + ni_name = ni['name'] + for ni_sr in ni.get('static_routes', list()): + ni_sr_prefix = ni_sr['prefix' ] + ni_sr_next_hop = ni_sr['next_hop'] + ni_sr_metric = ni_sr['metric' ] + ni_sr_next_hop_index = 'AUTO_{:d}_{:s}'.format(ni_sr_metric, '-'.join(ni_sr_next_hop.split('.'))) + resources_to_delete.append(network_instance_static_route( + ni_name, ni_sr_prefix, ni_sr_next_hop_index, ni_sr_next_hop, metric=ni_sr_metric + )) + ni_sr_prefixes.append((ni_name, ni_sr_prefix)) + + storage.network_instances.protocols.remove(ni_name, 'STATIC') + storage.network_instances.protocol_static.remove(ni_name, 'STATIC', ni_sr_prefix) + storage.network_instances.tables.remove(ni_name, 'STATIC', 'IPV4') + storage.network_instances.tables.remove(ni_name, 'STATIC', 'IPV6') + + results_deleteconfig = del_config(driver, resources_to_delete) + check_updates(results_deleteconfig, '/network_instance[{:s}]/static_route[{:s}]', ni_sr_prefixes) + + check_config_interfaces(driver, storage, max_retries=10, retry_delay=2.0) + #check_config_network_instances(driver, storage, max_retries=10, retry_delay=2.0) + + +def test_del_interfaces( + driver : GnmiOpenConfigDriver, # pylint: disable=redefined-outer-name + storage : Storage, # pylint: disable=redefined-outer-name +) -> None: + check_config_interfaces(driver, storage) + #check_config_network_instances(driver, storage) + + resources_to_delete = list() + if_names = list() + for ni in NETWORK_INSTANCES: + for ni_if in ni.get('interfaces', list()): + if_name = ni_if['name' ] + subif_index = ni_if['index' ] + ipv4_address = ni_if['ipv4_addr' ] + ipv4_prefix = ni_if['ipv4_prefix'] + enabled = ni_if['enabled' ] + resources_to_delete.append(interface(if_name, subif_index, ipv4_address, ipv4_prefix, enabled)) + if_names.append(if_name) + storage.interfaces.ipv4_addresses.remove(if_name, subif_index, ipv4_address) + default_vlan = storage.network_instances.vlans.get('default', 1) + default_vlan_members : List[str] = default_vlan.setdefault('members', list()) + if if_name not in default_vlan_members: default_vlan_members.append(if_name) + + results_deleteconfig = del_config(driver, resources_to_delete) + check_updates(results_deleteconfig, '/interface[{:s}]', if_names) + + check_config_interfaces(driver, storage, max_retries=10, retry_delay=2.0) + #check_config_network_instances(driver, storage, max_retries=10, retry_delay=2.0) + + +def test_del_interfaces_from_network_instance( + driver : GnmiOpenConfigDriver, # pylint: disable=redefined-outer-name + storage : Storage, # pylint: disable=redefined-outer-name +) -> None: + check_config_interfaces(driver, storage) + #check_config_network_instances(driver, storage) + + resources_to_delete = list() + ni_if_names = list() + for ni in NETWORK_INSTANCES: + ni_name = ni['name'] + for ni_if in ni.get('interfaces', list()): + if_name = ni_if['name' ] + subif_index = ni_if['index'] + resources_to_delete.append(network_instance_interface(ni_name, if_name, subif_index)) + ni_if_names.append((ni_name, '{:s}.{:d}'.format(if_name, subif_index))) + storage.network_instances.interfaces.remove(ni_name, if_name, subif_index) + + results_deleteconfig = del_config(driver, resources_to_delete) + check_updates(results_deleteconfig, '/network_instance[{:s}]/interface[{:s}]', ni_if_names) + + check_config_interfaces(driver, storage, max_retries=10, retry_delay=2.0) + #check_config_network_instances(driver, storage, max_retries=10, retry_delay=2.0) + + +def test_del_network_instances( + driver : GnmiOpenConfigDriver, # pylint: disable=redefined-outer-name + storage : Storage, # pylint: disable=redefined-outer-name +) -> None: + check_config_interfaces(driver, storage) + #check_config_network_instances(driver, storage) + + resources_to_delete = list() + ni_names = list() + for ni in NETWORK_INSTANCES: + ni_name = ni['name'] + ni_type = ni['type'] + resources_to_delete.append(network_instance(ni_name, ni_type)) + ni_names.append(ni_name) + storage.network_instances.network_instances.remove(ni_name) + storage.network_instances.protocols.remove(ni_name, 'DIRECTLY_CONNECTED') + storage.network_instances.tables.remove(ni_name, 'DIRECTLY_CONNECTED', 'IPV4') + storage.network_instances.tables.remove(ni_name, 'DIRECTLY_CONNECTED', 'IPV6') + + results_deleteconfig = del_config(driver, resources_to_delete) + check_updates(results_deleteconfig, '/network_instance[{:s}]', ni_names) + + check_config_interfaces(driver, storage, max_retries=10, retry_delay=2.0) + check_config_network_instances(driver, storage, max_retries=10, retry_delay=2.0) diff --git a/src/device/tests/gnmi_openconfig/tools/__init__.py b/src/device/tests/gnmi_openconfig/tools/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..3ee6f7071f145e06c3aeaefc09a43ccd88e619e3 --- /dev/null +++ b/src/device/tests/gnmi_openconfig/tools/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/src/device/tests/gnmi_openconfig/tools/check_updates.py b/src/device/tests/gnmi_openconfig/tools/check_updates.py new file mode 100644 index 0000000000000000000000000000000000000000..7e120ab89ac508623ef27b6e6a6f792b6c565840 --- /dev/null +++ b/src/device/tests/gnmi_openconfig/tools/check_updates.py @@ -0,0 +1,22 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Iterable, List, Tuple + +def check_updates(results : Iterable[Tuple[str, bool]], format_str : str, item_ids : List[Tuple]) -> None: + results = set(results) + assert len(results) == len(item_ids) + for item_id_fields in item_ids: + if isinstance(item_id_fields, (str, int, float, bool)): item_id_fields = (item_id_fields,) + assert (format_str.format(*item_id_fields), True) in results diff --git a/src/device/tests/gnmi_openconfig/tools/manage_config.py b/src/device/tests/gnmi_openconfig/tools/manage_config.py new file mode 100644 index 0000000000000000000000000000000000000000..6dc485bf256236bf9438536e9182181f784faacd --- /dev/null +++ b/src/device/tests/gnmi_openconfig/tools/manage_config.py @@ -0,0 +1,103 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import copy, deepdiff, logging, time +from typing import Callable, Dict, List, Tuple, Union +from device.service.driver_api._Driver import ( + RESOURCE_ENDPOINTS, RESOURCE_INTERFACES, RESOURCE_NETWORK_INSTANCES, + RESOURCE_ROUTING_POLICIES, RESOURCE_SERVICES +) +from device.service.drivers.gnmi_openconfig.GnmiOpenConfigDriver import GnmiOpenConfigDriver +from device.tests.gnmi_openconfig.storage.Storage import Storage +from .result_config_adapters import adapt_endpoint, adapt_interface, adapt_network_instance + +LOGGER = logging.getLogger(__name__) + +def get_config(driver : GnmiOpenConfigDriver, resources_to_get : List[str]) -> List[Tuple[str, Dict]]: + LOGGER.info('[get_config] resources_to_get = {:s}'.format(str(resources_to_get))) + results_getconfig = driver.GetConfig(resources_to_get) + LOGGER.info('[get_config] results_getconfig = {:s}'.format(str(results_getconfig))) + return results_getconfig + +def set_config( + driver : GnmiOpenConfigDriver, resources_to_set : List[Tuple[str, Dict]] +) -> List[Tuple[str, Union[bool, Exception]]]: + LOGGER.info('[set_config] resources_to_set = {:s}'.format(str(resources_to_set))) + results_setconfig = driver.SetConfig(resources_to_set) + LOGGER.info('[set_config] results_setconfig = {:s}'.format(str(results_setconfig))) + return results_setconfig + +def del_config( + driver : GnmiOpenConfigDriver, resources_to_delete : List[Tuple[str, Dict]] +) -> List[Tuple[str, Union[bool, Exception]]]: + LOGGER.info('resources_to_delete = {:s}'.format(str(resources_to_delete))) + results_deleteconfig = driver.DeleteConfig(resources_to_delete) + LOGGER.info('results_deleteconfig = {:s}'.format(str(results_deleteconfig))) + return results_deleteconfig + +def check_expected_config( + driver : GnmiOpenConfigDriver, resources_to_get : List[str], expected_config : List[Dict], + func_adapt_returned_config : Callable[[Tuple[str, Dict]], Tuple[str, Dict]] = lambda x: x, + max_retries : int = 1, retry_delay : float = 0.5 +) -> List[Dict]: + LOGGER.info('expected_config = {:s}'.format(str(expected_config))) + + num_retry = 0 + return_data = None + while num_retry < max_retries: + results_getconfig = get_config(driver, resources_to_get) + return_data = copy.deepcopy(results_getconfig) + + results_getconfig = [ + func_adapt_returned_config(resource_key, resource_value) + for resource_key, resource_value in results_getconfig + ] + + diff_data = deepdiff.DeepDiff(sorted(expected_config), sorted(results_getconfig)) + num_diffs = len(diff_data) + if num_diffs == 0: break + # let the device take some time to reconfigure + time.sleep(retry_delay) + num_retry += 1 + + if num_diffs > 0: LOGGER.error('Differences[{:d}]:\n{:s}'.format(num_diffs, str(diff_data.pretty()))) + assert num_diffs == 0 + return return_data + +def check_config_endpoints( + driver : GnmiOpenConfigDriver, storage : Storage, + max_retries : int = 1, retry_delay : float = 0.5 +) -> List[Dict]: + return check_expected_config( + driver, [RESOURCE_ENDPOINTS], storage.endpoints.get_expected_config(), + adapt_endpoint, max_retries=max_retries, retry_delay=retry_delay + ) + +def check_config_interfaces( + driver : GnmiOpenConfigDriver, storage : Storage, + max_retries : int = 1, retry_delay : float = 0.5 +) -> List[Dict]: + return check_expected_config( + driver, [RESOURCE_INTERFACES], storage.interfaces.get_expected_config(), + adapt_interface, max_retries=max_retries, retry_delay=retry_delay + ) + +def check_config_network_instances( + driver : GnmiOpenConfigDriver, storage : Storage, + max_retries : int = 1, retry_delay : float = 0.5 +) -> List[Dict]: + return check_expected_config( + driver, [RESOURCE_NETWORK_INSTANCES], storage.network_instances.get_expected_config(), + adapt_network_instance, max_retries=max_retries, retry_delay=retry_delay + ) diff --git a/src/device/tests/gnmi_openconfig/tools/request_composers copy.py b/src/device/tests/gnmi_openconfig/tools/request_composers copy.py new file mode 100644 index 0000000000000000000000000000000000000000..9545e156df2c88ea1c86f094b770e09328b368e6 --- /dev/null +++ b/src/device/tests/gnmi_openconfig/tools/request_composers copy.py @@ -0,0 +1,46 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Dict, Tuple + +def interface(if_name, sif_index, ipv4_address, ipv4_prefix, enabled) -> Tuple[str, Dict]: + str_path = '/interface[{:s}]'.format(if_name) + str_data = { + 'name': if_name, 'enabled': enabled, 'sub_if_index': sif_index, 'sub_if_enabled': enabled, + 'sub_if_ipv4_enabled': enabled, 'sub_if_ipv4_address': ipv4_address, 'sub_if_ipv4_prefix': ipv4_prefix + } + return str_path, str_data + +def network_instance(ni_name, ni_type) -> Tuple[str, Dict]: + str_path = '/network_instance[{:s}]'.format(ni_name) + str_data = { + 'name': ni_name, 'type': ni_type + } + return str_path, str_data + +def network_instance_static_route(ni_name, prefix, next_hop_index, next_hop, metric=1) -> Tuple[str, Dict]: + str_path = '/network_instance[{:s}]/static_route[{:s}]'.format(ni_name, prefix) + str_data = { + 'name': ni_name, 'identifier': 'STATIC', 'protocol_name': 'STATIC', + 'prefix': prefix, 'index': next_hop_index, 'next_hop': next_hop, 'metric': metric + } + return str_path, str_data + +def network_instance_interface(ni_name, if_name, sif_index) -> Tuple[str, Dict]: + ni_if_id = '{:s}.{:d}'.format(if_name, sif_index) + str_path = '/network_instance[{:s}]/interface[{:s}]'.format(ni_name, ni_if_id) + str_data = { + 'name': ni_name, 'id': ni_if_id, 'interface': if_name, 'subinterface': sif_index + } + return str_path, str_data diff --git a/src/device/tests/gnmi_openconfig/tools/request_composers.py b/src/device/tests/gnmi_openconfig/tools/request_composers.py new file mode 100644 index 0000000000000000000000000000000000000000..d80709114bdf36c1e634047256c7e42d638de3c5 --- /dev/null +++ b/src/device/tests/gnmi_openconfig/tools/request_composers.py @@ -0,0 +1,44 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Dict, Tuple + +def interface(if_name, sif_index, ipv4_address, ipv4_prefix, enabled) -> Tuple[str, Dict]: + str_path = '/interface[{:s}]'.format(if_name) + str_data = { + 'name': if_name, 'enabled': enabled, 'sub_if_index': sif_index, 'sub_if_enabled': enabled, + 'sub_if_ipv4_enabled': enabled, 'sub_if_ipv4_address': ipv4_address, 'sub_if_ipv4_prefix': ipv4_prefix + } + return str_path, str_data + +def network_instance(ni_name, ni_type) -> Tuple[str, Dict]: + str_path = '/network_instance[{:s}]'.format(ni_name) + str_data = { + 'name': ni_name, 'type': ni_type + } + return str_path, str_data + +def network_instance_static_route(ni_name, prefix, next_hop_index, next_hop, metric=1) -> Tuple[str, Dict]: + str_path = '/network_instance[{:s}]/static_route[{:s}]'.format(ni_name, prefix) + str_data = { + 'name': ni_name, 'prefix': prefix, 'next_hop_index': next_hop_index, 'next_hop': next_hop, 'metric': metric + } + return str_path, str_data + +def network_instance_interface(ni_name, if_name, sif_index) -> Tuple[str, Dict]: + str_path = '/network_instance[{:s}]/interface[{:s}.{:d}]'.format(ni_name, if_name, sif_index) + str_data = { + 'name': ni_name, 'if_name': if_name, 'sif_index': sif_index + } + return str_path, str_data diff --git a/src/device/tests/gnmi_openconfig/tools/result_config_adapters.py b/src/device/tests/gnmi_openconfig/tools/result_config_adapters.py new file mode 100644 index 0000000000000000000000000000000000000000..db7d5735d05eba9e4b8a2bec3b2763bdb3c3cc42 --- /dev/null +++ b/src/device/tests/gnmi_openconfig/tools/result_config_adapters.py @@ -0,0 +1,29 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re +from typing import Dict, Tuple + +def adapt_endpoint(resource_key : str, resource_value : Dict) -> Tuple[str, Dict]: + return resource_key, resource_value + +def adapt_interface(resource_key : str, resource_value : Dict) -> Tuple[str, Dict]: + return resource_key, resource_value + +def adapt_network_instance(resource_key : str, resource_value : Dict) -> Tuple[str, Dict]: + match = re.match(r'^\/network\_instance\[([^\]]+)\]\/vlan\[([^\]]+)\]$', resource_key) + if match is not None: + members = resource_value.get('members') + if len(members) > 0: resource_value['members'] = sorted(members) + return resource_key, resource_value diff --git a/src/device/tests/test_gnmi.py b/src/device/tests/test_gnmi.py deleted file mode 100644 index ebd026a206ddf9444d30c01e8ac2d097307cc0db..0000000000000000000000000000000000000000 --- a/src/device/tests/test_gnmi.py +++ /dev/null @@ -1,115 +0,0 @@ -# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import logging, os, sys, time -from typing import Dict, Tuple -os.environ['DEVICE_EMULATED_ONLY'] = 'YES' -from device.service.drivers.gnmi_openconfig.GnmiOpenConfigDriver import GnmiOpenConfigDriver # pylint: disable=wrong-import-position -#from device.service.driver_api._Driver import ( -# RESOURCE_ENDPOINTS, RESOURCE_INTERFACES, RESOURCE_NETWORK_INSTANCES, RESOURCE_ROUTING_POLICIES, RESOURCE_SERVICES -#) - -logging.basicConfig(level=logging.DEBUG) -LOGGER = logging.getLogger(__name__) -LOGGER.setLevel(logging.DEBUG) - -# +---+---------------------------+--------------+---------------------------------+-------+---------+--------------------+--------------+ -# | # | Name | Container ID | Image | Kind | State | IPv4 Address | IPv6 Address | -# +---+---------------------------+--------------+---------------------------------+-------+---------+--------------------+--------------+ -# | 1 | clab-tfs-scenario-client1 | a8d48ec3265a | ghcr.io/hellt/network-multitool | linux | running | 172.100.100.201/24 | N/A | -# | 2 | clab-tfs-scenario-client2 | fc88436d2b32 | ghcr.io/hellt/network-multitool | linux | running | 172.100.100.202/24 | N/A | -# | 3 | clab-tfs-scenario-srl1 | b995b9bdadda | ghcr.io/nokia/srlinux | srl | running | 172.100.100.101/24 | N/A | -# | 4 | clab-tfs-scenario-srl2 | aacfc38cc376 | ghcr.io/nokia/srlinux | srl | running | 172.100.100.102/24 | N/A | -# +---+---------------------------+--------------+---------------------------------+-------+---------+--------------------+--------------+ - -def interface(if_name, sif_index, ipv4_address, ipv4_prefix, enabled) -> Tuple[str, Dict]: - str_path = '/interface[{:s}]'.format(if_name) - str_data = {'name': if_name, 'enabled': enabled, 'sub_if_index': sif_index, 'sub_if_enabled': enabled, - 'sub_if_ipv4_enabled': enabled, 'sub_if_ipv4_address': ipv4_address, 'sub_if_ipv4_prefix': ipv4_prefix} - return str_path, str_data - -def network_instance(ni_name, ni_type) -> Tuple[str, Dict]: - str_path = '/network_instance[{:s}]'.format(ni_name) - str_data = {'name': ni_name, 'type': ni_type} - return str_path, str_data - -def network_instance_static_route(ni_name, prefix, next_hop, next_hop_index=0) -> Tuple[str, Dict]: - str_path = '/network_instance[{:s}]/static_route[{:s}]'.format(ni_name, prefix) - str_data = {'name': ni_name, 'prefix': prefix, 'next_hop': next_hop, 'next_hop_index': next_hop_index} - return str_path, str_data - -def network_instance_interface(ni_name, if_name, sif_index) -> Tuple[str, Dict]: - str_path = '/network_instance[{:s}]/interface[{:s}.{:d}]'.format(ni_name, if_name, sif_index) - str_data = {'name': ni_name, 'if_name': if_name, 'sif_index': sif_index} - return str_path, str_data - -def main(): - driver_settings = { - 'protocol': 'gnmi', - 'username': 'admin', - 'password': 'NokiaSrl1!', - 'use_tls' : True, - } - driver = GnmiOpenConfigDriver('172.100.100.102', 57400, **driver_settings) - driver.Connect() - - #resources_to_get = [] - #resources_to_get = [RESOURCE_ENDPOINTS] - #resources_to_get = [RESOURCE_INTERFACES] - #resources_to_get = [RESOURCE_NETWORK_INSTANCES] - #resources_to_get = [RESOURCE_ROUTING_POLICIES] - #resources_to_get = [RESOURCE_SERVICES] - #LOGGER.info('resources_to_get = {:s}'.format(str(resources_to_get))) - #results_getconfig = driver.GetConfig(resources_to_get) - #LOGGER.info('results_getconfig = {:s}'.format(str(results_getconfig))) - - #resources_to_set = [ - # network_instance('test-svc', 'L3VRF'), - # - # interface('ethernet-1/1', 0, '172.16.0.1', 24, True), - # network_instance_interface('test-svc', 'ethernet-1/1', 0), - # - # interface('ethernet-1/2', 0, '172.0.0.1', 24, True), - # network_instance_interface('test-svc', 'ethernet-1/2', 0), - # - # network_instance_static_route('test-svc', '172.0.0.0/24', '172.16.0.2'), - # network_instance_static_route('test-svc', '172.2.0.0/24', '172.16.0.3'), - #] - #LOGGER.info('resources_to_set = {:s}'.format(str(resources_to_set))) - #results_setconfig = driver.SetConfig(resources_to_set) - #LOGGER.info('results_setconfig = {:s}'.format(str(results_setconfig))) - - resources_to_delete = [ - #network_instance_static_route('d35fc1d9', '172.0.0.0/24', '172.16.0.2'), - #network_instance_static_route('d35fc1d9', '172.2.0.0/24', '172.16.0.3'), - - #network_instance_interface('d35fc1d9', 'ethernet-1/1', 0), - #network_instance_interface('d35fc1d9', 'ethernet-1/2', 0), - - interface('ethernet-1/1', 0, '172.16.1.1', 24, True), - interface('ethernet-1/2', 0, '172.0.0.2', 24, True), - - network_instance('20f66fb5', 'L3VRF'), - ] - LOGGER.info('resources_to_delete = {:s}'.format(str(resources_to_delete))) - results_deleteconfig = driver.DeleteConfig(resources_to_delete) - LOGGER.info('results_deleteconfig = {:s}'.format(str(results_deleteconfig))) - - time.sleep(1) - - driver.Disconnect() - return 0 - -if __name__ == '__main__': - sys.exit(main()) diff --git a/src/monitoring/service/EventTools.py b/src/monitoring/service/EventTools.py index 7820f11c86e87f543087c88704572e1a169c6e7d..2ad31c9cb7df68e2b57d280aa5cdcf356376d8c6 100644 --- a/src/monitoring/service/EventTools.py +++ b/src/monitoring/service/EventTools.py @@ -108,12 +108,15 @@ class EventsDeviceCollector: if config_rule.action != ConfigActionEnum.CONFIGACTION_SET: continue if config_rule.WhichOneof('config_rule') != 'custom': continue str_resource_key = str(config_rule.custom.resource_key) - if not str_resource_key.startswith('/interface['): continue - json_resource_value = json.loads(config_rule.custom.resource_value) - if 'name' not in json_resource_value: continue - if 'enabled' not in json_resource_value: continue - if not json_resource_value['enabled']: continue - enabled_endpoint_names.add(json_resource_value['name']) + if str_resource_key.startswith('/interface[') or str_resource_key.startswith('/endpoints/endpoint['): + json_resource_value = json.loads(config_rule.custom.resource_value) + if 'name' not in json_resource_value: continue + if 'enabled' in json_resource_value: + if not json_resource_value['enabled']: continue + enabled_endpoint_names.add(json_resource_value['name']) + if 'oper-status' in json_resource_value: + if str(json_resource_value['oper-status']).upper() != 'UP': continue + enabled_endpoint_names.add(json_resource_value['name']) endpoints_monitored = self._device_endpoint_monitored.setdefault(device_uuid, dict()) for endpoint in device.device_endpoints: @@ -127,7 +130,10 @@ class EventsDeviceCollector: endpoint_was_monitored = endpoints_monitored.get(endpoint_uuid, False) endpoint_is_enabled = (endpoint_name_or_uuid in enabled_endpoint_names) - if not endpoint_was_monitored and endpoint_is_enabled: + if not endpoint_was_monitored and not endpoint_is_enabled: + # endpoint is idle, do nothing + pass + elif not endpoint_was_monitored and endpoint_is_enabled: # activate for value in endpoint.kpi_sample_types: if value == KPISAMPLETYPE_UNKNOWN: continue diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/Handlers.py b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/Handlers.py index f7329cb35666f423e85f99510e5f89a82e89b7f8..1f399070aa2dbd0f9b22d32e15574b7bc38315ff 100644 --- a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/Handlers.py +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/Handlers.py @@ -195,11 +195,17 @@ def process_site(site : Dict, errors : List[Dict]) -> None: # site_static_routing: (lan-range, lan-prefix-len, lan-tag) => next-hop site_static_routing : Dict[Tuple[str, str], str] = {} - for rt_proto in site['routing-protocols']['routing-protocol']: + site_routing_protocols : Dict = site.get('routing-protocols', dict()) + site_routing_protocol : List = site_routing_protocols.get('routing-protocol', list()) + for rt_proto in site_routing_protocol: if rt_proto['type'] != 'ietf-l3vpn-svc:static': MSG = 'Site Routing Protocol Type: {:s}' raise NotImplementedError(MSG.format(str(rt_proto['type']))) - for ipv4_rt in rt_proto['static']['cascaded-lan-prefixes']['ipv4-lan-prefixes']: + + rt_proto_static : Dict = rt_proto.get('static', dict()) + rt_proto_static_clps : Dict = rt_proto_static.get('cascaded-lan-prefixes', dict()) + rt_proto_static_clps_v4 = rt_proto_static_clps.get('ipv4-lan-prefixes', list()) + for ipv4_rt in rt_proto_static_clps_v4: lan_range, lan_prefix = ipv4_rt['lan'].split('/') lan_prefix = int(lan_prefix) lan_tag = int(ipv4_rt['lan-tag'].replace('vlan', '')) diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/L3VPN_Service.py b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/L3VPN_Service.py index a313677c12203c1621b920f3fcb7f6ff0c281bfb..47fb05d5571b440e867e776a753f556c3130b119 100644 --- a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/L3VPN_Service.py +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/L3VPN_Service.py @@ -44,7 +44,7 @@ class L3VPN_Service(Resource): service_ready_status = ServiceStatusEnum.SERVICESTATUS_ACTIVE service_status = target.service_status.service_status # pylint: disable=no-member - response = jsonify({}) + response = jsonify({'service-id': target.service_id.service_uuid.uuid}) response.status_code = HTTP_OK if service_status == service_ready_status else HTTP_GATEWAYTIMEOUT except Exception as e: # pylint: disable=broad-except LOGGER.exception('Something went wrong Retrieving VPN({:s})'.format(str(vpn_id))) diff --git a/src/opticalcontroller/OpticalController.py b/src/opticalcontroller/OpticalController.py index 0febb7b1f470bf86dc30754c1db2af262779a25d..97e097c8a94382b71903b4a57ab66382beec5eb2 100644 --- a/src/opticalcontroller/OpticalController.py +++ b/src/opticalcontroller/OpticalController.py @@ -12,19 +12,21 @@ # See the License for the specific language governing permissions and # limitations under the License. +import time from flask import Flask from flask import render_template from flask_restplus import Resource, Api - from tools import * from variables import * from RSA import RSA -import time -import logging +from common.proto.context_pb2 import TopologyId +from google.protobuf.json_format import MessageToDict +global rsa +global links_dict rsa = None -LOGGER = logging.getLogger(__name__) + app = Flask(__name__) api = Api(app, version='1.0', title='Optical controller API', @@ -47,7 +49,7 @@ class AddLightpath(Resource): @staticmethod def put(src, dst, bitrate, bidir=1): - LOGGER.info("INFO: New Lightpath request from {} to {} with rate {} ".format(src, dst, bitrate)) + print("INFO: New Lightpath request from {} to {} with rate {} ".format(src, dst, bitrate)) t0 = time.time()*1000.0 if debug: rsa.g.printGraph() @@ -56,9 +58,9 @@ class AddLightpath(Resource): flow_id = rsa.rsa_computation(src, dst, bitrate, bidir) if rsa.db_flows[flow_id]["op-mode"] == 0: return 'No path found', 404 - t1 = time.time()*1000.0 + t1 = time.time() * 1000.0 elapsed = t1 - t0 - LOGGER.info("INFO: time elapsed = {} ms".format(elapsed)) + print("INFO: time elapsed = {} ms".format(elapsed)) return rsa.db_flows[flow_id], 200 else: return "Error", 404 @@ -74,18 +76,15 @@ class AddLightpath(Resource): @optical.response(404, 'Error, not found') class AddFlexLightpath(Resource): @staticmethod - def put(src, dst, bitrate,bidir=1, band=None): - + def put(src, dst, bitrate, bidir=1, band=None): + print("INFO: New FlexLightpath request from {} to {} with rate {} ".format(src, dst, bitrate)) - LOGGER.info("INFO: New FlexLightpath request from {} to {} with rate {} ".format(src, dst, bitrate)) t0 = time.time()*1000.0 if debug: rsa.g.printGraph() if rsa is not None: flow_id, optical_band_id = rsa.rsa_fs_computation(src, dst, bitrate, bidir, band) - print (f"flow_id {flow_id} and optical_band_id {optical_band_id} ") - LOGGER.debug('flow_id={:s} rsa.db_flows={:s}'.format(str(flow_id), str(rsa.db_flows))) if flow_id is not None: if rsa.db_flows[flow_id]["op-mode"] == 0: return 'No path found', 404 @@ -100,52 +99,125 @@ class AddFlexLightpath(Resource): else: t1 = time.time() * 1000.0 elapsed = t1 - t0 - LOGGER.info("INFO: time elapsed = {} ms".format(elapsed)) + print("INFO: time elapsed = {} ms".format(elapsed)) return rsa.optical_bands[optical_band_id], 200 else: return "Error", 404 -@optical.route('/DelFlexLightpath/<int:flow_id>/<string:src>/<string:dst>/<int:bitrate>/<int:o_band_id>') + +# @optical.route('/DelFlexLightpath/<string:src>/<string:dst>/<int:bitrate>/<int:o_band_id>') +@optical.route('/DelFlexLightpath/<string:src>/<string:dst>/<int:bitrate>/<int:o_band_id>') +@optical.route('/DelFlexLightpath/<string:src>/<string:dst>/<int:bitrate>/<int:o_band_id>/<int:flow_id>') @optical.response(200, 'Success') @optical.response(404, 'Error, not found') -class DelLightpath(Resource): +class DelFLightpath(Resource): @staticmethod - def delete(flow_id, src, dst, bitrate, o_band_id): - if flow_id in rsa.db_flows.keys(): - flow = rsa.db_flows[flow_id] - bidir = flow["bidir"] - match1 = flow["src"] == src and flow["dst"] == dst and flow["bitrate"] == bitrate - if bidir: - match2 = flow["src"] == dst and flow["dst"] == src and flow["bitrate"] == bitrate - if match1 or match2: - ob_id = flow["parent_opt_band"] - rsa.del_flow(flow, ob_id) - rsa.db_flows[flow_id]["is_active"] = False - rsa.optical_bands[ob_id]["served_lightpaths"].remove(flow_id) - if rsa.optical_bands[ob_id]["reverse_optical_band_id"] != 0: - rev_ob_id = rsa.optical_bands[ob_id]["reverse_optical_band_id"] - rsa.optical_bands[rev_ob_id]["served_lightpaths"].remove(flow_id) - - if debug: - LOGGER.info(links_dict) - return "flow {} deleted".format(flow_id), 200 + def delete( src, dst, bitrate, o_band_id,flow_id=None): + flow = None + match1=False + ob_id=None + if flow_id is not None: + + if flow_id in rsa.db_flows.keys(): + flow = rsa.db_flows[flow_id] + match1 = flow["src"] == src and flow["dst"] == dst and flow["bitrate"] == bitrate + ob_id = flow["parent_opt_band"] + flow['is_active']=False + if flow is not None: + + + bidir = flow["bidir"] + + if bidir: + match2 = flow["src"] == dst and flow["dst"] == src and flow["bitrate"] == bitrate + if match1 or match2: + ob_id = flow["parent_opt_band"] + rsa.del_flow(flow, ob_id) + rsa.db_flows[flow_id]["is_active"] = False + if flow_id in rsa.optical_bands[ob_id]["served_lightpaths"].remove: + rsa.optical_bands[ob_id]["served_lightpaths"].remove(flow_id) + #if rsa.optical_bands[ob_id]["reverse_optical_band_id"] != 0: + # rev_ob_id = rsa.optical_bands[ob_id]["reverse_optical_band_id"] + # rsa.optical_bands[rev_ob_id]["served_lightpaths"].remove(flow_id) + + if debug: + print(rsa.links_dict) + return "flow {} deleted".format(flow_id), 200 + else: + return "flow {} not matching".format(flow_id), 404 else: - return "flow {} not matching".format(flow_id), 404 + if match1: + # if delete_band !=0 and ob_id is not None: + # print(f"delete_lightpath {delete_band} and ob_id {ob_id}") + # if len( rsa.optical_bands[ob_id]["served_lightpaths"]) != 0: + # return "DELETE_NOT_ALLOWED" ,400 + rsa.del_flow(flow,flow_id,ob_id) + + if debug: + print(f"vor ob_id {ob_id} rsa.optical_bands {rsa.optical_bands[ob_id]}") + print(f"rsa.links_dict {rsa.links_dict}") + return "flow {} deleted".format(flow_id), 200 + else: + return "flow {} not matching".format(flow_id), 404 else: - if match1: - ob_id = flow["parent_opt_band"] - rsa.del_flow(flow, ob_id) - rsa.db_flows[flow_id]["is_active"] = False - rsa.optical_bands[ob_id]["served_lightpaths"].remove(flow_id) - if debug: - LOGGER.info(links_dict) - return "flow {} deleted".format(flow_id), 200 - else: - return "flow {} not matching".format(flow_id), 404 - else: - return "flow id {} does not exist".format(flow_id), 404 + return "flow id {} does not exist".format(flow_id), 404 + +@optical.route('/DelOpticalBand/<string:src>/<string:dst>/<int:o_band_id>',methods=['DELETE']) +@optical.response(200, 'Success') +@optical.response(404, 'Error, not found') +class DelOpticalBand(Resource): + @staticmethod + def delete( src, dst, o_band_id): + flow = None + + ob_id=None + if o_band_id is not None : + + if o_band_id in rsa.optical_bands.keys(): + flow=rsa.optical_bands[o_band_id] + #match1 = flow["src"] == src and flow["dst"] == dst + ob_id=o_band_id + + if flow is not None: + + + bidir = flow["bidir"] + + if bidir: + match2 = flow["src"] == dst and flow["dst"] == src and flow["bitrate"] + if match1 or match2: + ob_id = flow["parent_opt_band"] + #rsa.del_flow(flow, ob_id) + rsa.optical_bands[ob_id]["is_active"] = False + # if flow_id in rsa.optical_bands[ob_id]["served_lightpaths"].remove: + # rsa.optical_bands[ob_id]["served_lightpaths"].remove(flow_id) + #if rsa.optical_bands[ob_id]["reverse_optical_band_id"] != 0: + # rev_ob_id = rsa.optical_bands[ob_id]["reverse_optical_band_id"] + # rsa.optical_bands[rev_ob_id]["served_lightpaths"].remove(flow_id) + + if debug: + print(rsa.links_dict) + return "ob_id {} deleted".format(ob_id), 200 + else: + return "ob_id {} not matching".format(ob_id), 404 + else: + if ob_id is not None: + + if len( rsa.optical_bands[ob_id]["served_lightpaths"]) != 0: + return "DELETE_NOT_ALLOWED" ,400 + + rsa.del_band(flow,ob_id) + if debug: + print(f"vor ob_id {ob_id} rsa.optical_bands {rsa.optical_bands[ob_id]}") + print(f"rsa.links_dict {rsa.links_dict}") + return "ob_id {} deleted".format(ob_id), 200 + + else : + return "flow for ob_id {} not found".format(ob_id),400 + else: + return "ob_id {} does not exist".format(ob_id), 404 @optical.route('/DelLightpath/<int:flow_id>/<string:src>/<string:dst>/<int:bitrate>') @@ -162,7 +234,7 @@ class DelLightpath(Resource): rsa.del_flow(flow) rsa.db_flows[flow_id]["is_active"] = False if debug: - LOGGER.info(links_dict) + print(rsa.links_dict) return "flow {} deleted".format(flow_id), 200 else: return "flow {} not matching".format(flow_id), 404 @@ -178,22 +250,21 @@ class GetFlows(Resource): def get(): try: if debug: - LOGGER.info(rsa.db_flows) + print(rsa.db_flows) return rsa.db_flows, 200 except: return "Error", 404 + @optical.route('/GetOpticalBands') @optical.response(200, 'Success') @optical.response(404, 'Error, not found') class GetBands(Resource): @staticmethod def get(): - print("Getting ") - LOGGER.info("Getting") try: if debug: - LOGGER.info(rsa.optical_bands) + print(rsa.optical_bands) return rsa.optical_bands, 200 except: return "Error", 404 @@ -208,7 +279,7 @@ class GetBand(Resource): for ob_idx in rsa.optical_bands.keys(): if str(ob_idx) == str(ob_id): if debug: - LOGGER.info(rsa.optical_bands[ob_id]) + print(rsa.optical_bands[ob_id]) return rsa.optical_bands[ob_idx], 200 return {}, 404 @@ -219,28 +290,73 @@ class GetBand(Resource): class GetFlows(Resource): @staticmethod def get(): - global links_dict + global rsa + #global links_dict + links = None + if rsa is not None : + links = rsa.links_dict try: if debug: - LOGGER.info(links_dict) - return links_dict, 200 + print(links) + return links, 200 except: return "Error", 404 -if __name__ == '__main__': +@optical.route('/GetTopology/<path:context_id>/<path:topology_id>',methods=['GET']) +@optical.response(200, 'Success') +@optical.response(404, 'Error, not found') +class GetTopology(Resource): + @staticmethod + def get(context_id:str,topology_id:str): + + global rsa + if (rsa is not None): + return "Opticalcontroller is synchronised" ,200 + topog_id = TopologyId() + topog_id.topology_uuid.uuid=topology_id + topog_id.context_id.context_uuid.uuid=context_id + + try: + links_dict = {"optical_links": []} + node_dict = {} + topo, nodes = readTopologyDataFromContext(topog_id) + + for link in topo: + link_dict_type = MessageToDict(link, preserving_proto_field_name=True) + + if "c_slots" in link_dict_type["optical_details"]: + link_dict_type["optical_details"]["c_slots"] = link_dict_type["optical_details"]["c_slots"] + + if "l_slots" in link_dict_type["optical_details"]: + link_dict_type["optical_details"]["l_slots"] = link_dict_type["optical_details"]["l_slots"] + + if "s_slots" in link_dict_type["optical_details"]: + link_dict_type["optical_details"]["s_slots"] = link_dict_type["optical_details"]["s_slots"] + + links_dict["optical_links"].append(link_dict_type) + + for device in nodes : + dev_dic = { + "id":device.device_id.device_uuid.uuid, + #"ip":f"10.30.2.{207+i}", + #"port":"50001", + "type":"OC-ROADM" if device.device_type =="optical-roadm" else "OC-TP", + "driver": "OpticalOC" + } + node_dict[device.name] = dev_dic + #i+=1 + #print(f"refresh_optical controller optical_links_dict= {links_dict}") + #print(f"refresh_optical controller node_dict {node_dict}") + + rsa = RSA(node_dict, links_dict) + if debug: + print(rsa.init_link_slots2()) + return "ok", 200 + except Exception as e: + print(f"err {e}") + return "Error", 400 - # Start metrics server - - LOGGER.info('Starting...') - - - - nodes_dict, links_dict = readTopologyData(nodes_json, topology_json) - - #topologies, links = getTopology() - #print("topologies{} and devices {}".format(topologies,links)) - rsa = RSA(nodes_dict, links_dict) - - app.run(host='0.0.0.0', port=10060, debug=True) +if __name__ == '__main__': + app.run(host='0.0.0.0', port=10060) diff --git a/src/opticalcontroller/README.md b/src/opticalcontroller/README.md index 5fd94c59e51cbd78dd76a7db0f24aaaec4ebd9db..f121055cec91470646b9885bfe90db137027f472 100644 --- a/src/opticalcontroller/README.md +++ b/src/opticalcontroller/README.md @@ -1,17 +1,20 @@ -# optical-controller -This a framework to implement the optical controller for the RMSA algorithm. +# Optical Controller + +This is a framework to test the optical controller for the RMSA algorithm in an isolated manner. + + + +```bash #create a venv python -m venv venv -in linux +# in linux source venv/Scripts/activate -in windows +# in windows venv\Scripts\activate -pip install -r requirements_opt.txt +pip install -r requirements_optical_ctrl_test.txt python OpticalController.py - - - +``` diff --git a/src/opticalcontroller/RSA.py b/src/opticalcontroller/RSA.py index b15357c98242ad6b18d4068b6f0aa1cb08852d7a..61a74edbecc2ac635398ee8482800514295e9473 100644 --- a/src/opticalcontroller/RSA.py +++ b/src/opticalcontroller/RSA.py @@ -26,15 +26,15 @@ class RSA(): self.flow_id = 0 self.opt_band_id = 0 self.db_flows = {} - self.initGraph() + self.initGraph2() self.c_slot_number = 0 self.l_slot_number = 0 self.s_slot_number = 0 self.optical_bands = {} - def init_link_slots(self, testing): - if not testing: - for l in self.links_dict["links"]: + def init_link_slots(self): + if full_links: + for l in self.links_dict["optical_links"]: for fib in l["optical_link"]["details"]["fibers"]: #fib = self.links_dict[l]["fibers"][f] if len(fib["c_slots"]) > 0: @@ -45,7 +45,7 @@ class RSA(): fib["s_slots"] = list(range(0, Ns)) if debug: print(fib) - for l1 in self.links_dict["links"]: + for l1 in self.links_dict["optical_links"]: for fib1 in l1["optical_link"]["details"]["fibers"]: #fib1 = self.links_dict[l1]["details"]["fibers"][f1] @@ -58,11 +58,35 @@ class RSA(): break return "{},{},{}".format(self.c_slot_number, self.l_slot_number, self.s_slot_number) + def init_link_slots2(self): + if full_links: + for l in self.links_dict["optical_links"]: + fib = l["optical_details"] + #fib = self.links_dict[l]["fibers"][f] + if len(fib["c_slots"]) > 0: + for c in range(0, Nc): + fib["c_slots"][c] = 1 + if len(fib["l_slots"]) > 0: + for c in range(0, Nl): + fib["l_slots"][c] = 1 + if len(fib["s_slots"]) > 0: + for c in range(0, Ns): + fib["s_slots"][c] = 1 + if debug: + print(fib) + for l1 in self.links_dict["optical_links"]: + fib1 = l1["optical_details"] + self.c_slot_number = len(fib1["c_slots"].keys()) + self.l_slot_number = len(fib1["l_slots"].keys()) + self.s_slot_number = len(fib1["s_slots"].keys()) + break + return "{},{},{}".format(self.c_slot_number, self.l_slot_number, self.s_slot_number) + def initGraph(self): self.g = dijsktra.Graph() for n in self.nodes_dict: self.g.add_vertex(n) - for l in self.links_dict["links"]: + for l in self.links_dict["optical_links"]: if debug: print(l) [s, d] = l["optical_link"]["name"].split('-') @@ -74,6 +98,24 @@ class RSA(): if debug: self.g.printGraph() + def initGraph2(self): + + self.g = dijsktra.Graph() + + for n in self.nodes_dict: + self.g.add_vertex(n) + for l in self.links_dict["optical_links"]: + if debug: + print(l) + [s, d] = l["name"].split('-') + ps = l["optical_details"]["src_port"] + pd = l["optical_details"]["dst_port"] + self.g.add_edge(s, d, ps, pd, 1) + + print("INFO: Graph initiated.2") + if debug: + self.g.printGraph() + def compute_path(self, src, dst): path = dijsktra.shortest_path(self.g, self.g.get_vertex(src), self.g.get_vertex(dst)) print("INFO: Path from {} to {} with distance: {}".format(src, dst, self.g.get_vertex(dst).get_distance())) @@ -95,7 +137,7 @@ class RSA(): self.g.reset_graph() return links, path - def get_slots(self, links, slots, optical_band_id = None): + def get_slots(self, links, slots, optical_band_id=None): if isinstance(slots, int): val_c = slots @@ -120,38 +162,40 @@ class RSA(): add = links[0] if self.nodes_dict[dst_2]["type"] == "OC-TP": drop = links[-1] - + found = 0 for l in links: c_slots[l] = [] l_slots[l] = [] s_slots[l] = [] - found = 0 - for link in self.links_dict["links"]: - if link["optical_link"]["name"] == l: - #for f in self.links_dict[l]['fibers'].keys(): - for fib in link["optical_link"]["details"]["fibers"]: - if l == add: - if 'used' in fib: - if fib["used"]: - #if debug: - print("WARNING!!!: link {}, fiber {} is already in use".format(l, fib["ID"])) - continue - if l == drop: - if 'used' in fib: - if fib["used"]: - #if debug: - print("WARNING!!!: link {}, fiber {} is already in use".format(l, fib["ID"])) - continue - if len(fib["c_slots"]) > 0: - c_slots[l] = combine(c_slots[l], consecutives(fib["c_slots"], val_c)) - if len(fib["l_slots"]) > 0: - l_slots[l] = combine(l_slots[l], consecutives(fib["l_slots"], val_l)) - if len(fib["s_slots"]) > 0: - s_slots[l] = combine(s_slots[l], consecutives(fib["s_slots"], val_s)) - if debug: - print(l, c_slots[l]) - found = 1 - if found == 0: + + link = self.get_link_by_name(l) + fib = link["optical_details"] + if l == add: + if 'used' in fib: + if fib["used"]: + #if debug: + print("WARNING!!!: link {}, is already in use".format(l)) + return [], [], [] + if l == drop: + if 'used' in fib: + if fib["used"]: + #if debug: + print("WARNING!!!: link {} is already in use".format(l)) + return [], [], [] + c_found = l_found = s_found = 0 + if len(fib["c_slots"].keys()) > 0: + #c_slots[l] = combine(c_slots[l], consecutives(fib["c_slots"], val_c)) + c_slots[l] = combine(c_slots[l], consecutives(fib["c_slots"], val_c)) + c_found = 1 + if len(fib["l_slots"].keys()) > 0: + l_slots[l] = combine(l_slots[l], consecutives(fib["l_slots"], val_l)) + l_found = 1 + if len(fib["s_slots"].keys()) > 0: + s_slots[l] = combine(s_slots[l], consecutives(fib["s_slots"], val_s)) + s_found = 1 + if debug: + print(l, c_slots[l]) + if c_found == 0 and l_found == 0 and s_found == 0: return [], [], [] keys = list(c_slots.keys()) @@ -185,60 +229,100 @@ class RSA(): c_sts = common_slots(a_c, b_c) l_sts = common_slots(a_l, b_l) s_sts = common_slots(a_s, b_s) + ''' + if len(fib["l_slots"]) > 0: + l_slots[l] = combine(l_slots[l], consecutives(fib["l_slots"], val_l)) + l_found = 1''' if optical_band_id is not None: if "c_slots" in self.optical_bands[optical_band_id].keys(): if len(self.optical_bands[optical_band_id]["c_slots"]) > 0: a_c = c_sts - b_c = self.optical_bands[optical_band_id]["c_slots"] + #MOD + b_c = consecutives(self.optical_bands[optical_band_id]["c_slots"], val_c) + #b_c = self.optical_bands[optical_band_id]["c_slots"] c_sts = common_slots(a_c, b_c) - else: - c_sts = [] else: c_sts = [] if "l_slots" in self.optical_bands[optical_band_id].keys(): if len(self.optical_bands[optical_band_id]["l_slots"]) > 0: a_l = l_sts - b_l = self.optical_bands[optical_band_id]["l_slots"] + b_l = consecutives(self.optical_bands[optical_band_id]["l_slots"], val_c) l_sts = common_slots(a_l, b_l) - else: - l_sts = [] else: l_sts = [] if "s_slots" in self.optical_bands[optical_band_id].keys(): if len(self.optical_bands[optical_band_id]["s_slots"]) > 0: a_s = s_sts - b_s = self.optical_bands[optical_band_id]["s_slots"] + b_s = consecutives(str_list_to_int(self.optical_bands[optical_band_id]["s_slots"].keys()), val_c) s_sts = common_slots(a_s, b_s) - else: - s_sts = [] else: s_sts = [] return c_sts, l_sts, s_sts def update_link(self, fib, slots, band): + #print(fib) for i in slots: - fib[band].remove(i) + fib[band][str(i)] = 0 if 'used' in fib: fib['used'] = True + print(f"fib updated {fib}") + #print(fib) + + def update_link_2(self, fib, slots, band,link): + #print(fib) + for i in slots: + fib[band][str(i)] = 0 + if 'used' in fib: + fib['used'] = True + + set_link_update(fib,link) + #print(fib) def update_optical_band(self, optical_band_id, slots, band): for i in slots: - self.optical_bands[optical_band_id][band].remove(i) + self.optical_bands[optical_band_id][band][str(i)] = 0 + + def augment_optical_band(self, optical_band_id, slots, band): + for i in slots: + self.optical_bands[optical_band_id][band][str(i)] = 1 def restore_link(self, fib, slots, band): for i in slots: - fib[band].append(int(i)) + fib[band][str(i)] = 1 if 'used' in fib: fib['used'] = False - fib[band].sort() + #fib[band].sort() + + def restore_link_2(self, fib, slots, band,link): + print("start restoring link") + for i in slots: + fib[band][str(i)] = 1 + if 'used' in fib: + fib['used'] = False + #fib[band].keys().sort() + set_link_update(fib,link,test="restoration") def restore_optical_band(self, optical_band_id, slots, band): for i in slots: - self.optical_bands[optical_band_id][band].append(int(i)) - self.optical_bands[optical_band_id][band].sort() - - def del_flow(self, flow, o_b_id = None): + self.optical_bands[optical_band_id][band][str(i)] = 1 + + #self.optical_bands[optical_band_id][band].append(int(i)) + #self.optical_bands[optical_band_id][band].sort() + + def restore_optical_band_2(self, optical_band_id, slots, band ,links): + print(f"example of band { band}") + print(f"example of slots {slots}") + print(f"example of self.optical_bands_before { self.optical_bands}") + for i in slots: + self.optical_bands[optical_band_id][band][str(i)] = 1 + print(f"example of self.optical_bands_after { self.optical_bands}") + + #link_name= self.optical_bands[optical_band_id]['links'][0] + #link = self.get_link_by_name(link_name) + #update_optical_band(optical_bands=self.optical_bands,optical_band_id=optical_band_id,band=band,link=link) + + def del_flow(self, flow,flow_id, o_b_id = None): flows = flow["flows"] band = flow["band_type"] slots = flow["slots"] @@ -249,22 +333,123 @@ class RSA(): path = flow["path"] links = flow["links"] bidir = flow["bidir"] + flow_id = flow["flow_id"] - for l in fiber_f.keys(): + for l in links: if debug: print(l) - print(fiber_f[l]) #link = self.links_dict[l] #f = fiber_f[l] #fib = link['fibers'][f] - fib = self.get_fiber_details(l, fiber_f[l]) - if not list_in_list(slots, fib[band]): - self.restore_link(fib, slots, band) + fib = self.get_link_by_name(l)["optical_details"] + + self.restore_link(fib, slots, band) + if debug: + print(fib[band]) + + if o_b_id is not None: + if debug: + print("restoring OB") + print(f"invoking restore_optical_band o_b_id: {o_b_id} , slots {slots} , band {band} ") + self.restore_optical_band(o_b_id, slots, band) + if flow_id in self.optical_bands[o_b_id]["served_lightpaths"]: + if flow_id in self.optical_bands[o_b_id]["served_lightpaths"]: + self.optical_bands[o_b_id]["served_lightpaths"].remove(flow_id) + + #self.restore_optical_band_2(o_b_id, slots, band,links) + + if bidir: + for l in links: + r_l = reverse_link(l) + if debug: + print(r_l) + # link = self.links_dict[l] + # f = fiber_f[l] + # fib = link['fibers'][f] + fib = self.get_link_by_name(r_l)["optical_details"] + if list_in_list(slots, str_list_to_int(fib[band].keys())): + self.restore_link(fib, slots, band, link=l) + if debug: + print(fib[band]) + ''' + for rl in fiber_b.keys(): if debug: - print(fib[band]) + print(rl) + print(fiber_b[rl]) + #rlink = self.links_dict[rl] + #rf = fiber_b[rl] + #rfib = rlink['fibers'][rf] + rfib = self.get_fiber_details(rl, fiber_b[rl]) + if not list_in_list(slots, rfib[band]): + self.restore_link(rfib, slots, band) + if debug: + print(rfib[band]) + ''' + #changed according to TFS development + #if o_b_id is not None: + # rev_o_band_id = self.optical_bands[o_b_id]["reverse_optical_band_id"] + # self.restore_optical_band(rev_o_band_id, slots, band) + return True + + + def del_band(self, flow, o_b_id = None): + print(f"delete band {flow} ") + + flows = flow["flows"] + band = None + #slots = flow["slots"] + fiber_f = flow["fiber_forward"] + fiber_b = flow["fiber_backward"] + op = flow["op-mode"] + n_slots = 0 + path = flow["path"] + bidir = flow["bidir"] + links = [] if o_b_id is not None: + links= self.optical_bands[o_b_id]["links"] + band = self.optical_bands[o_b_id]["band_type"] + n_slots =self.optical_bands[o_b_id]["n_slots"] + if n_slots > 0: + slots=[i+1 for i in range(n_slots)] + + for l in links: + if debug: + print(l) + #link = self.links_dict[l] + #f = fiber_f[l] + #fib = link['fibers'][f] + fib = self.get_link_by_name(l)["optical_details"] + print(f"del_flow_fib {fib } and band {band}") + print(f"del_flow { str_list_to_int(fib[band].keys())}") + + print(f"invoking restore_link_2 fib: {fib} , slots {slots} , band {band} ") + self.restore_link(fib, slots, band) + self.optical_bands[o_b_id]["is_active"]=False + + if debug: + print(fib[band]) + + if o_b_id is not None: + + if debug: + print("restoring OB") + print(f"invoking restore_optical_band o_b_id: {o_b_id} , slots {slots} , band {band} ") self.restore_optical_band(o_b_id, slots, band) + #self.restore_optical_band_2(o_b_id, slots, band,links) if bidir: + for l in links: + r_l = reverse_link(l) + if debug: + print(r_l) + # link = self.links_dict[l] + # f = fiber_f[l] + # fib = link['fibers'][f] + fib = self.get_link_by_name(r_l)["optical_details"] + if list_in_list(slots, str_list_to_int(fib[band].keys())): + self.restore_link(fib, slots, band, link=l) + if debug: + print(fib[band]) + ''' for rl in fiber_b.keys(): if debug: print(rl) @@ -277,17 +462,26 @@ class RSA(): self.restore_link(rfib, slots, band) if debug: print(rfib[band]) + ''' #changed according to TFS development #if o_b_id is not None: # rev_o_band_id = self.optical_bands[o_b_id]["reverse_optical_band_id"] # self.restore_optical_band(rev_o_band_id, slots, band) return True + def del_handler(self, flow,flow_id, o_b_id = None,delete_band=0): + print(f" del_handler flow {flow} flow_id {flow_id} o_b_id {o_b_id} delete_band {delete_band}") + if delete_band != 0: + print(f"delete band del_band") + self.del_band(flow,flow_id,o_b_id=o_b_id) + else : + self.del_flow(flow,flow_id=flow_id,o_b_id=o_b_id) + def get_fibers_forward(self, links, slots, band): fiber_list = {} add = links[0] drop = links[-1] - print(links) + #print(links) ''' for link in self.links_dict["links"]: if link["optical_link"]["name"] == l: @@ -296,85 +490,99 @@ class RSA(): ''' for l in links: - for link in self.links_dict["links"]: - if link["optical_link"]["name"] == l: - for fib in link["optical_link"]["details"]["fibers"]: - #for f in self.links_dict[l]['fibers'].keys(): - #for fib in l["optical_link"]["details"]["fibers"]: - #fib = self.links_dict[l]['fibers'][f] - if l == add: - if 'used' in fib: - if fib["used"]: - if debug: - print("link {}, fiber {} is already in use".format(l, fib["ID"])) - continue - if l == drop: - if 'used' in fib: - if fib["used"]: - if debug: - print("link {}, fiber {} is already in use".format(l, fib["ID"])) - continue - if list_in_list(slots, fib[band]): - fiber_list[l] = fib["ID"] - self.update_link(fib, slots, band) - break + for link in self.links_dict["optical_links"]: + print(f"tracking link info {link}") + if link["name"] == l: + fib = link["optical_details"] + #for f in self.links_dict[l]['fibers'].keys(): + #for fib in l["optical_link"]["details"]["fibers"]: + #fib = self.links_dict[l]['fibers'][f] + if l == add: + if 'used' in fib: + if fib["used"]: + if debug: + print("link {} is already in use".format(l)) + continue + if l == drop: + if 'used' in fib: + if fib["used"]: + if debug: + print("link {} is already in use".format(l)) + continue + if list_in_list(slots, str_list_to_int(fib[band].keys())): + #fiber_list[l] = fib["ID"] + #self.update_link(fib, slots, band) + self.update_link_2(fib,slots,band,link) + break print("INFO: Path forward computation completed") return fiber_list def get_link_by_name (self, key): - result = None - for link in self.links_dict["links"]: - if link["optical_link"]["name"] == key: + for link in self.links_dict["optical_links"]: + if link["name"] == key: if debug: print(link) - result = link break - return result + return link def get_fiber_details(self, link_key, fiber_id): - for link in self.links_dict["links"]: - if link["optical_link"]["name"] == link_key: + for link in self.links_dict["optical_links"]: + if link["name"] == link_key: if debug: print(link) - for fib in link["optical_link"]["details"]["fibers"]: + for fib in link["optical_details"]: if fib["ID"] == fiber_id: return fib return None - - def get_fibers_backward(self, links, fibers, slots, band): + def get_fibers_backward(self, links, slots, band): fiber_list = {} #r_drop = reverse_link(links[0]) #r_add = reverse_link(links[-1]) - for l in fibers.keys(): - fib = self.get_fiber_details(l, fibers[l]) + for l in links: + fib = self.get_link_by_name(l)["optical_details"] ''' link = self.get_link_by_name(l) #port = self.links_dict[l]["fibers"][fibers[l]]["src_port"] for fib in link["optical_link"]["details"]["fibers"]: if fib["ID"] == fibers[l]: ''' - port = fib["src_port"] + s_port = fib["src_port"] + d_port = fib["dst_port"] + + if debug: + print(l, s_port, d_port) + r_l = reverse_link(l) r_link = self.get_link_by_name(r_l) + if debug: + print(r_l) + #for f in r_link["fibers"].keys(): - for r_fib in r_link["optical_link"]["details"]["fibers"]: - if r_fib["remote_peer_port"] == port: - if list_in_list(slots, r_fib[band]): - fiber_list[r_l] = r_fib["ID"] - self.update_link(r_fib, slots, band) + r_fib = r_link["optical_details"] + if r_fib["remote_peer_port"] == s_port and r_fib["local_peer_port"] == d_port: + if list_in_list(slots, str_list_to_int(r_fib[band].keys())): + #fiber_list[r_l] = r_fib["ID"] + self.update_link(r_fib, slots, band) print("INFO: Path backward computation completed") return fiber_list + #function invoked for lightpaths and OB def select_slots_and_ports(self, links, n_slots, c, l, s, bidir): if debug: - print(self.links_dict) + print (links, n_slots, c, l, s, bidir, self.c_slot_number, self.l_slot_number, self.s_slot_number) band, slots = slot_selection(c, l, s, n_slots, self.c_slot_number, self.l_slot_number, self.s_slot_number) + if debug: + print (band, slots) if band is None: print("No slots available in the three bands") - return None, None, None - if debug: - print(band, slots) + #return None, None, None, {}, {} + return None, None, None, {}, {} + + self.get_fibers_forward(links, slots, band) + if bidir: + self.get_fibers_backward(links, slots, band) + ''' fibers_f = self.get_fibers_forward(links, slots, band) fibers_b = [] @@ -385,6 +593,7 @@ class RSA(): print(fibers_f) print("backward") print(fibers_b) + ''' add = links[0] drop = links[-1] inport = "0" @@ -394,18 +603,22 @@ class RSA(): t_flows = {} #if len(links) == 1: - for lx in fibers_f: - if lx == add: + for llx in links: + if llx == add: inport = "0" r_outport = "0" - if lx == drop: + if llx == drop: outport = "0" r_inport = "0" + ''' f = fibers_f[lx] - src, dst = lx.split("-") + fibx = self.get_fiber_details(lx, f) + ''' + src, dst = llx.split("-") #outport = self.links_dict[lx]['fibers'][f]["src_port"] - outport = fibx["src_port"] + lx = self.get_link_by_name(llx)["optical_details"] + outport = lx["src_port"] t_flows[src] = {} t_flows[src]["f"] = {} @@ -414,14 +627,14 @@ class RSA(): if bidir: #r_inport = self.links_dict[lx]['fibers'][f]["local_peer_port"] - r_inport = fibx["local_peer_port"] + r_inport = lx["local_peer_port"] t_flows[src]["b"] = {"in": r_inport, "out": r_outport} #inport = self.links_dict[lx]['fibers'][f]["dst_port"] - inport = fibx["dst_port"] + inport = lx["dst_port"] if bidir: #r_outport = self.links_dict[lx]['fibers'][f]["remote_peer_port"] - r_outport = fibx["remote_peer_port"] + r_outport = lx["remote_peer_port"] t_flows[dst] = {} t_flows[dst]["f"] = {} t_flows[dst]["b"] = {} @@ -436,8 +649,9 @@ class RSA(): print(t_flows) print("INFO: Flow matrix computed") - return t_flows, band, slots, fibers_f, fibers_b + return t_flows, band, slots, {}, {} + #function ivoked for fs lightpaths only def select_slots_and_ports_fs(self, links, n_slots, c, l, s, bidir, o_band_id): if debug: print(self.links_dict) @@ -447,22 +661,20 @@ class RSA(): return None, None, None, None, None if debug: print(band, slots) - fibers_f = self.get_fibers_forward(links, slots, band) - self.update_optical_band(o_band_id, slots, band) - fibers_b = [] + self.get_fibers_forward(links, slots, band) if bidir: - fibers_b = self.get_fibers_backward(links, fibers_f, slots, band) + self.get_fibers_backward(links, slots, band) + + #fibers_f = self.get_fibers_forward(links, slots, band) + self.update_optical_band(o_band_id, slots, band) + #fibers_b = [] + #if bidir: + # fibers_b = self.get_fibers_backward(links, fibers_f, slots, band) ''' rev_o_band_id = self.optical_bands[o_band_id]["reverse_optical_band_id"] self.update_optical_band(rev_o_band_id, slots, band) ''' - if debug: - print("forward") - print(fibers_f) - if bidir: - print("backward") - print(fibers_b) add = links[0] drop = links[-1] port_0 = "0" @@ -470,11 +682,10 @@ class RSA(): t_flows = {} #flows_add_side - f = fibers_f[add] src, dst = add.split("-") - fibx = self.get_fiber_details(add, f) + lx = self.get_link_by_name(add)["optical_details"] #outport = self.links_dict[add]['fibers'][f]["src_port"] - outport = fibx["src_port"] + outport = lx["src_port"] #T1 rules t_flows[src] = {} t_flows[src]["f"] = {} @@ -482,7 +693,7 @@ class RSA(): t_flows[src]["f"] = {"in": port_0, "out": outport} if bidir: #r_inport = self.links_dict[add]['fibers'][f]["local_peer_port"] - r_inport = fibx["local_peer_port"] + r_inport = lx["local_peer_port"] t_flows[src]["b"] = {"in": r_inport, "out": port_0} #R1 rules @@ -490,27 +701,26 @@ class RSA(): t_flows[dst]["f"] = {} t_flows[dst]["b"] = {} #inport = self.links_dict[add]['fibers'][f]["dst_port"] - inport = fibx["dst_port"] + inport = lx["dst_port"] opt_band_src_port = self.optical_bands[o_band_id]["src_port"] t_flows[dst]["f"] = {"in": inport, "out": opt_band_src_port} #to modify to peer ports if bidir: #r_inport = self.links_dict[add]['fibers'][f]["local_peer_port"] - r_inport = fibx["local_peer_port"] + r_inport = lx["local_peer_port"] t_flows[src]["b"] = {"in": r_inport, "out": port_0} if bidir: rev_opt_band_dst_port = self.optical_bands[o_band_id]["rev_dst_port"] #r_outport = self.links_dict[add]['fibers'][f]["remote_peer_port"] - r_outport = fibx["remote_peer_port"] + r_outport = lx["remote_peer_port"] t_flows[dst]["b"] = {"in": rev_opt_band_dst_port, "out": r_outport} #flows_drop_side # R2 rules - f = fibers_f[drop] + ly = self.get_link_by_name(drop)["optical_details"] src, dst = drop.split("-") - fiby = self.get_fiber_details(drop, f) #outport = self.links_dict[drop]['fibers'][f]["src_port"] - outport = fiby["src_port"] + outport = ly["src_port"] t_flows[src] = {} t_flows[src]["f"] = {} @@ -520,17 +730,17 @@ class RSA(): if bidir: rev_opt_band_src_port = self.optical_bands[o_band_id]["rev_src_port"] #r_inport = self.links_dict[drop]['fibers'][f]["local_peer_port"] - r_inport = fiby["local_peer_port"] + r_inport = ly["local_peer_port"] t_flows[src]["b"] = {"in": r_inport, "out": rev_opt_band_src_port} t_flows[dst] = {} t_flows[dst]["f"] = {} t_flows[dst]["b"] = {} #inport = self.links_dict[drop]['fibers'][f]["dst_port"] - inport = fiby["dst_port"] + inport = ly["dst_port"] t_flows[dst]["f"] = {"in": inport, "out": port_0} if bidir: #r_inport = self.links_dict[drop]['fibers'][f]["remote_peer_port"] - r_inport = fiby["remote_peer_port"] + r_inport = ly["remote_peer_port"] t_flows[dst]["b"] = {"in": port_0, "out": r_inport} if debug: @@ -540,7 +750,7 @@ class RSA(): print(t_flows) print("INFO: Flow matrix computed for Flex Lightpath") - return t_flows, band, slots, fibers_f, fibers_b + return t_flows, band, slots, {}, {} def rsa_computation(self, src, dst, rate, bidir): self.flow_id += 1 @@ -565,7 +775,7 @@ class RSA(): if len(c_slots) > 0 or len(l_slots) > 0 or len(s_slots) > 0: flow_list, band_range, slots, fiber_f, fiber_b = self.select_slots_and_ports(links, num_slots, c_slots, l_slots, s_slots, bidir) - f0, band = freqency_converter(band_range, slots) + f0, band = frequency_converter(band_range, slots) if debug: print(f0, band) print("INFO: RSA completed for normal wavelenght connection") @@ -675,50 +885,43 @@ class RSA(): print(s_slots) if len(c_slots) > 0 or len(l_slots) > 0 or len(s_slots) > 0: flow_list, band_range, slots, fiber_f, fiber_b = self.select_slots_and_ports(links, num_slots, c_slots, l_slots, s_slots, bidir) - f0, band = freqency_converter(band_range, slots) - print(flow_list, band_range, slots, fiber_f, fiber_b) - ''' - - flow_list_b = {} - rev_path = path.copy() - rev_path.reverse() - rev_links = reverse_links(links) - if bidir: - for dev_x in flow_list.keys(): - flow_list_b[dev_x] = {} - flow_list_b[dev_x]["f"] = flow_list[dev_x]["b"] - del flow_list[dev_x]["b"] - rev_path = path.copy() - ''' + if debug: + print(flow_list, band_range, slots, fiber_f, fiber_b) + f0, band = frequency_converter(band_range, slots) if debug: print(f0, band) print("INFO: RSA completed for optical band") if flow_list is None: self.null_values(self.flow_id) return self.flow_id, [] - slots_i = [] + #slots_i = [] + #for i in slots: + # slots_i.append(int(i)) + slots_i = {} for i in slots: - slots_i.append(int(i)) + slots_i[str(i)] = 1 # return links, path, flow_list, band_range, slots, fiber_f, fiber_b, op, num_slots, f0, band # links, path, flows, bx, slots, fiber_f, fiber_b, op, n_slots, f0, band + if debug: + print(links) if len(flow_list) > 0: src_port = flow_list[path[0]]['f']['out'] dst_port = flow_list[path[-1]]['f']['in'] - print(flow_list) - if len(fiber_f.keys()) == 1: - link_x = list(fiber_f.keys())[0] + if debug: + print(flow_list) + if len(links) == 1: #fib_x = fiber_f[link_x] #rev_dst_port = self.links_dict[link_x]['fibers'][fib_x]["local_peer_port"] #rev_src_port = self.links_dict[link_x]['fibers'][fib_x]["remote_peer_port"] - fibx = self.get_fiber_details(link_x, fiber_f[link_x]) + fibx = self.get_link_by_name(links[0])["optical_details"] rev_dst_port = fibx["local_peer_port"] rev_src_port = fibx["remote_peer_port"] else: - link_in = list(fiber_f.keys())[0] - link_out = list(fiber_f.keys())[-1] - fib_inx = self.get_fiber_details(link_in, fiber_f[link_in]) - fib_outx = self.get_fiber_details(link_out, fiber_f[link_out]) + link_in = links[0] + link_out = links[-1] + fib_inx = self.get_link_by_name(link_in)["optical_details"] + fib_outx = self.get_link_by_name(link_out)["optical_details"] rev_dst_port = fib_inx["local_peer_port"] rev_src_port = fib_outx["remote_peer_port"] @@ -790,6 +993,7 @@ class RSA(): return self.flow_id, [] optical_band_id, temp_links = self.create_optical_band(links, path, bidir, num_slots_ob) return None, optical_band_id + print("INFO: TP to TP connection") self.flow_id += 1 self.db_flows[self.flow_id] = {} self.db_flows[self.flow_id]["flow_id"] = self.flow_id @@ -797,7 +1001,7 @@ class RSA(): self.db_flows[self.flow_id]["dst"] = dst self.db_flows[self.flow_id]["bitrate"] = rate self.db_flows[self.flow_id]["bidir"] = bidir - print("INFO: TP to TP connection") + if band is None: temp_links2 = [] temp_path = [] @@ -816,13 +1020,16 @@ class RSA(): temp_path.append(roadm_dst) temp_path.append(t_dst) existing_ob = self.get_optical_bands(roadm_src, roadm_dst) - if len(existing_ob) > 0: print("INFO: Evaluating existing OB {}".format(existing_ob)) #first checking in existing OB ob_found = 0 for ob_id in existing_ob: + if "is_active" in self.optical_bands[ob_id].keys(): + is_active = self.optical_bands[ob_id]["is_active"] + if not is_active: + continue op, num_slots = map_rate_to_slot(rate) if debug: print(temp_links2) @@ -836,7 +1043,7 @@ class RSA(): c_slots, l_slots, s_slots, bidir, ob_id) - f0, band = freqency_converter(band_range, slots) + f0, band = frequency_converter(band_range, slots) if debug: print(f0, band) print("INFO: RSA completed for Flex Lightpath with OB already in place") @@ -871,6 +1078,75 @@ class RSA(): return self.flow_id, ob_id else: print("not enough slots") + print("trying to extend OB {}".format(ob_id)) + new_slots = self.extend_optical_band(ob_id, band=None) + + if len(new_slots) > 0: + band_type = self.optical_bands[ob_id]["band_type"] + c_slots = [] + l_slots = [] + s_slots = [] + if band_type == "c_slots": + c_slots = new_slots + elif band_type == "l_slots": + l_slots = new_slots + else: + s_slots = new_slots + op, num_slots = map_rate_to_slot(rate) + if debug: + print(temp_links2) + c_slots, l_slots, s_slots = self.get_slots(temp_links2, num_slots, ob_id) + if debug: + print(c_slots) + print(l_slots) + print(s_slots) + #print(c_slots) + #print(l_slots) + #print(s_slots) + if len(c_slots) >= num_slots or len(l_slots) >= num_slots or len(s_slots) >= num_slots: + flow_list, band_range, slots, fiber_f, fiber_b = self.select_slots_and_ports_fs( + temp_links2, num_slots, + c_slots, + l_slots, s_slots, bidir, + ob_id) + f0, band = frequency_converter(band_range, slots) + if debug: + print(f0, band) + print("INFO: RSA completed for Flex Lightpath with OB already in place") + if flow_list is None: + self.null_values(self.flow_id) + continue + slots_i = [] + for i in slots: + slots_i.append(int(i)) + # return links, path, flow_list, band_range, slots, fiber_f, fiber_b, op, num_slots, f0, band + # links, path, flows, bx, slots, fiber_f, fiber_b, op, n_slots, f0, band + self.db_flows[self.flow_id]["flows"] = flow_list + self.db_flows[self.flow_id]["band_type"] = band_range + self.db_flows[self.flow_id]["slots"] = slots_i + self.db_flows[self.flow_id]["fiber_forward"] = fiber_f + self.db_flows[self.flow_id]["fiber_backward"] = fiber_b + self.db_flows[self.flow_id]["op-mode"] = op + self.db_flows[self.flow_id]["n_slots"] = num_slots + self.db_flows[self.flow_id]["links"] = temp_links2 + self.db_flows[self.flow_id]["path"] = temp_path + self.db_flows[self.flow_id]["band"] = band + self.db_flows[self.flow_id]["freq"] = f0 + self.db_flows[self.flow_id]["is_active"] = True + self.db_flows[self.flow_id]["parent_opt_band"] = ob_id + self.db_flows[self.flow_id]["new_optical_band"] = 1 + #self.db_flows[self.flow_id]["new_optical_band"] = 2 + self.optical_bands[ob_id]["served_lightpaths"].append(self.flow_id) + ''' + if bidir: + rev_ob_id = self.optical_bands[ob_id]["reverse_optical_band_id"] + self.optical_bands[rev_ob_id]["served_lightpaths"].append(self.flow_id) + ''' + return self.flow_id, ob_id + else: + print("it is not possible to allocate connection in extended OB {}".format(ob_id)) + + if band is None: print("INFO: Not existing optical-band meeting the requirements") else: @@ -879,14 +1155,6 @@ class RSA(): links, path = self.compute_path(src, dst) optical_band_id, temp_links = self.create_optical_band(links, path, bidir, num_slots_ob) op, num_slots = map_rate_to_slot(rate) - # self.flow_id += 1 - # self.db_flows[self.flow_id] = {} - # self.db_flows[self.flow_id]["flow_id"] = self.flow_id - # self.db_flows[self.flow_id]["src"] = src - # self.db_flows[self.flow_id]["dst"] = dst - # self.db_flows[self.flow_id]["bitrate"] = rate - # self.db_flows[self.flow_id]["bidir"] = bidir - if debug: print(temp_links) c_slots, l_slots, s_slots = self.get_slots(temp_links, num_slots, optical_band_id) @@ -897,7 +1165,7 @@ class RSA(): if len(c_slots) > 0 or len(l_slots) > 0 or len(s_slots) > 0: flow_list, band_range, slots, fiber_f, fiber_b = self.select_slots_and_ports_fs(temp_links, num_slots, c_slots, l_slots, s_slots, bidir, optical_band_id) - f0, band = freqency_converter(band_range, slots) + f0, band = frequency_converter(band_range, slots) if debug: print(f0, band) print("INFO: RSA completed for FLex Lightpath with new OB") @@ -929,3 +1197,42 @@ class RSA(): self.optical_bands[rev_ob_id]["served_lightpaths"].append(self.flow_id) ''' return self.flow_id, optical_band_id + + def extend_optical_band(self, ob_id, band=None): + ob = self.optical_bands[ob_id] + links = ob["links"] + old_band = ob["band"] + band_type = ob["band_type"] + f0 = ob["freq"] + slots = ob[band_type] + if band is None: + num_slots_ob = map_band_to_slot(old_band/1000.0) + else: + num_slots_ob = map_band_to_slot(band) + new_slots = [] + for l in links: + link = self.get_link_by_name(l) + fib = link["optical_details"][band_type] + #s_slots = get_side_slots_on_link(link, band_type, num_slots_ob, slots) + s_slots, s_num = get_side_slots_on_link(fib, num_slots_ob, slots) + print("NEW SLOTS {}".format(s_slots)) + if len(new_slots) == 0: + new_slots = s_slots + else: + if len(new_slots) < s_num: + new_slots = list_in_list(new_slots, s_slots) + print("NEW SLOTS {}".format(new_slots)) + self.augment_optical_band(ob_id, new_slots, band_type) + new_band = int(len(new_slots)*12.5*1000) + print("{}, {},{},{} ".format(old_band, f0, len(new_slots), new_band)) + final_band = old_band + new_band + final_f0 = int(f0 + new_band/2) + print("{}, {}".format(final_band, final_f0)) + ob["band"] = final_band + ob["freq"] = final_f0 + for link_x in links: + link = self.get_link_by_name(link_x) + fib = link["optical_details"] + self.update_link(fib, new_slots, band_type) + + return new_slots diff --git a/src/opticalcontroller/__init__.py b/src/opticalcontroller/__init__.py index bbfc943b68af13a11e562abbc8680ade71db8f02..3ee6f7071f145e06c3aeaefc09a43ccd88e619e3 100644 --- a/src/opticalcontroller/__init__.py +++ b/src/opticalcontroller/__init__.py @@ -11,3 +11,4 @@ # 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/opticalcontroller/json_files/nodes.json b/src/opticalcontroller/json_files/nodes.json deleted file mode 100644 index 60f017c19d7c7a578c0ddfc2225cab742deb0026..0000000000000000000000000000000000000000 --- a/src/opticalcontroller/json_files/nodes.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "R1":{ - "id":0, - "ip":"10.30.2.207", - "port":"50001", - "type":"OC-ROADM", - "driver": "OpticalOC" - }, - - "R2":{ - "id":1, - "ip":"10.30.2.208", - "port":"50001", - "type":"OC-ROADM", - "driver": "OpticalOC" - }, - - "R3":{ - "id":2, - "ip":"10.30.2.209", - "port":"50001", - "type":"OC-ROADM", - "driver": "OpticalOC" - }, - "T1":{ - "id":3, - "ip":"10.30.2.210", - "port":"50001", - "type":"OC-TP", - "driver": "OpticalOC" - }, - "T2":{ - "id":4, - "ip":"10.30.2.211", - "port":"50001", - "type":"OC-TP", - "driver": "OpticalOC" - } -} diff --git a/src/opticalcontroller/json_files/optical_TFSworking.json b/src/opticalcontroller/json_files/optical_TFSworking.json deleted file mode 100644 index ff1841eeea9df1e73bcfb25d07f19d64554f11e1..0000000000000000000000000000000000000000 --- a/src/opticalcontroller/json_files/optical_TFSworking.json +++ /dev/null @@ -1,486 +0,0 @@ -{ - "R1-R2": { - "length": 80, - "source": "d1", - "target": "d1", - "fibers": { - "d1-1": { - "length": 80, - "src_port": "3", - "dst_port": "14", - "local_peer_port": "13", - "remote_peer_port": "4", - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - } - } - }, - "R2-R1": { - "length": 80, - "source": "d1", - "target": "d1", - "fibers": { - "d1-1": { - "length": 80, - "src_port": "4", - "dst_port": "13", - "local_peer_port": "14", - "remote_peer_port": "3", - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - } - } - }, - "T1-R1": { - "length": 0, - "source": "muxT", - "target": "srgR", - "fibers": { - "M1": { - "length": 0, - "src_port": "1", - "dst_port": "12", - "local_peer_port": "1", - "remote_peer_port": "2", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - } - } - }, - "R1-T1": { - "length": 0, - "source": "srgT", - "target": "muxR", - "fibers": { - "S1": { - "length": 0, - "src_port": "2", - "dst_port": "1", - "local_peer_port": "12", - "remote_peer_port": "1", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - } - } - }, - "T2-R2": { - "length": 0, - "source": "muxT", - "target": "srgR", - "fibers": { - "M1": { - "length": 0, - "src_port": "6", - "dst_port": "15", - "local_peer_port": "6", - "remote_peer_port": "5", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - } - } - }, - "R2-T2": { - "length": 0, - "source": "srgT", - "target": "muxR", - "fibers": { - "S1": { - "length": 0, - "src_port": "5", - "dst_port": "6", - "local_peer_port": "15", - "remote_peer_port": "6", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - } - } - } -} diff --git a/src/opticalcontroller/json_files/optical_topoTFS.json b/src/opticalcontroller/json_files/optical_topoTFS.json deleted file mode 100644 index 7dea474cd676b7c699cffc1c180e14598b987473..0000000000000000000000000000000000000000 --- a/src/opticalcontroller/json_files/optical_topoTFS.json +++ /dev/null @@ -1,1836 +0,0 @@ -{ - "R1-R2": { - "length": 80, - "source": "d1", - "target": "d1", - "fibers": { - "d1-1": { - "length": 80, - "src_port": "101", - "dst_port": "201", - "local_peer_port": "201", - "remote_peer_port": "101", - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - }, - "d1-2": { - "length": 80, - "src_port": "102", - "dst_port": "202", - "local_peer_port": "202", - "remote_peer_port": "102", - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - } - } - }, - "R2-R1": { - "length": 80, - "source": "d1", - "target": "d1", - "fibers": { - "d1-1": { - "length": 80, - "src_port": "101", - "dst_port": "201", - "local_peer_port": "201", - "remote_peer_port": "101", - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - }, - "d1-2": { - "length": 80, - "src_port": "102", - "dst_port": "202", - "local_peer_port": "202", - "remote_peer_port": "102", - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - } - } - }, - "R1-R3": { - "length": 80, - "source": "d2", - "target": "d1", - "fibers": { - "d2-1": { - "length": 80, - "src_port": "103", - "dst_port": "201", - "local_peer_port": "203", - "remote_peer_port": "101", - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - }, - "d2-2": { - "length": 80, - "src_port": "104", - "dst_port": "202", - "local_peer_port": "204", - "remote_peer_port": "102", - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - } - } - }, - "R3-R1": { - "length": 80, - "source": "d1", - "target": "d2", - "fibers": { - "d1-1": { - "length": 80, - "src_port": "101", - "dst_port": "203", - "local_peer_port": "201", - "remote_peer_port": "103", - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - }, - "d1-2": { - "length": 80, - "src_port": "102", - "dst_port": "204", - "local_peer_port": "202", - "remote_peer_port": "104", - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - } - } - }, - "R2-R3": { - "length": 80, - "source": "d2", - "target": "d2", - "fibers": { - "d2-1": { - "length": 80, - "src_port": "103", - "dst_port": "203", - "local_peer_port": "203", - "remote_peer_port": "103", - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - }, - "d2-2": { - "length": 80, - "src_port": "104", - "dst_port": "204", - "local_peer_port": "204", - "remote_peer_port": "104", - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - } - } - }, - "R3-R2": { - "length": 80, - "source": "d2", - "target": "d2", - "fibers": { - "d2-1": { - "length": 80, - "src_port": "103", - "dst_port": "203", - "local_peer_port": "203", - "remote_peer_port": "103", - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - }, - "d2-2": { - "length": 80, - "src_port": "104", - "dst_port": "204", - "local_peer_port": "204", - "remote_peer_port": "104", - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - } - } - }, - "T1-R1": { - "length": 0, - "source": "muxT", - "target": "srgR", - "fibers": { - "M1": { - "length": 0, - "src_port": "1", - "dst_port": "2001", - "local_peer_port": "1", - "remote_peer_port": "1001", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - }, - "M2": { - "length": 0, - "src_port": "2", - "dst_port": "2002", - "local_peer_port": "2", - "remote_peer_port": "1002", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - }, - "M3": { - "length": 0, - "src_port": "3", - "dst_port": "2003", - "local_peer_port": "3", - "remote_peer_port": "1003", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - } - } - }, - "R1-T1": { - "length": 0, - "source": "srgT", - "target": "muxR", - "fibers": { - "S1": { - "length": 0, - "src_port": "1001", - "dst_port": "1", - "local_peer_port": "2001", - "remote_peer_port": "1", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - }, - "S2": { - "length": 0, - "src_port": "1002", - "dst_port": "2", - "local_peer_port": "2002", - "remote_peer_port": "2", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - }, - "S3": { - "length": 0, - "src_port": "1003", - "dst_port": "3", - "local_peer_port": "2003", - "remote_peer_port": "3", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - } - } - }, - "T2-R2": { - "length": 0, - "source": "muxT", - "target": "srgR", - "fibers": { - "M1": { - "length": 0, - "src_port": "1", - "dst_port": "2001", - "local_peer_port": "1", - "remote_peer_port": "1001", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - }, - "M2": { - "length": 0, - "src_port": "2", - "dst_port": "2002", - "local_peer_port": "2", - "remote_peer_port": "1002", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - }, - "M3": { - "length": 0, - "src_port": "3", - "dst_port": "2003", - "local_peer_port": "3", - "remote_peer_port": "1003", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - } - } - }, - "R2-T2": { - "length": 0, - "source": "srgT", - "target": "muxR", - "fibers": { - "S1": { - "length": 0, - "src_port": "1001", - "dst_port": "1", - "local_peer_port": "2001", - "remote_peer_port": "1", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - }, - "S2": { - "length": 0, - "src_port": "1002", - "dst_port": "2", - "local_peer_port": "2002", - "remote_peer_port": "2", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - }, - "S3": { - "length": 0, - "src_port": "1003", - "dst_port": "3", - "local_peer_port": "2003", - "remote_peer_port": "3", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - } - } - } -} diff --git a/src/opticalcontroller/json_files/tfs.json b/src/opticalcontroller/json_files/tfs.json deleted file mode 100644 index 31803b893b5639e957be33465599573baa475ca2..0000000000000000000000000000000000000000 --- a/src/opticalcontroller/json_files/tfs.json +++ /dev/null @@ -1,686 +0,0 @@ -{ - "links": [ - { - "link_id": { - "link_uuid": { - "uuid": "T1->R1" - } - }, - "link_endpoint_ids": [ - { - "device_id": { - "device_uuid": { - "uuid": "T1" - } - }, - "endpoint_uuid": { - "uuid": "1" - } - }, - { - "device_id": { - "device_uuid": { - "uuid": "R1" - } - }, - "endpoint_uuid": { - "uuid": "12" - } - } - ], - "optical_link": { - "name": "T1-R1", - "details": { - "length": 0, - "source": "muxT", - "target": "srgR", - "fibers": [ - { - "ID": "M1", - "length": 0, - "src_port": "1", - "dst_port": "12", - "local_peer_port": "1", - "remote_peer_port": "2", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - } - ] - } - } - }, - { - "link_id": { - "link_uuid": { - "uuid": "R1->T1" - } - }, - "link_endpoint_ids": [ - { - "device_id": { - "device_uuid": { - "uuid": "R1" - } - }, - "endpoint_uuid": { - "uuid": "2" - } - }, - { - "device_id": { - "device_uuid": { - "uuid": "T1" - } - }, - "endpoint_uuid": { - "uuid": "1" - } - } - ], - "optical_link": { - "name": "R1-T1", - "details": { - "length": 0, - "source": "srgT", - "target": "muxT", - "fibers": [ - { - "ID": "M1", - "length": 0, - "src_port": "2", - "dst_port": "1", - "local_peer_port": "12", - "remote_peer_port": "1", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - } - ] - } - } - }, - { - "link_id": { - "link_uuid": { - "uuid": "R1->R2" - } - }, - "link_endpoint_ids": [ - { - "device_id": { - "device_uuid": { - "uuid": "R1" - } - }, - "endpoint_uuid": { - "uuid": "3" - } - }, - { - "device_id": { - "device_uuid": { - "uuid": "R2" - } - }, - "endpoint_uuid": { - "uuid": "14" - } - } - ], - "optical_link": { - "name": "R1-R2", - "details": { - "length": 0, - "source": "D1", - "target": "D1", - "fibers": [ - { - "ID": "D11", - "length": 0, - "src_port": "3", - "dst_port": "14", - "local_peer_port": "13", - "remote_peer_port": "4", - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - } - ] - } - } - }, - { - "link_id": { - "link_uuid": { - "uuid": "R2->R1" - } - }, - "link_endpoint_ids": [ - { - "device_id": { - "device_uuid": { - "uuid": "R2" - } - }, - "endpoint_uuid": { - "uuid": "4" - } - }, - { - "device_id": { - "device_uuid": { - "uuid": "R1" - } - }, - "endpoint_uuid": { - "uuid": "13" - } - } - ], - "optical_link": { - "name": "R2-R1", - "details": { - "length": 0, - "source": "D1", - "target": "D1", - "fibers": [ - { - "ID": "D11", - "length": 0, - "src_port": "4", - "dst_port": "13", - "local_peer_port": "14", - "remote_peer_port": "3", - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - } - ] - } - } - }, - { - "link_id": { - "link_uuid": { - "uuid": "T2->R2" - } - }, - "link_endpoint_ids": [ - { - "device_id": { - "device_uuid": { - "uuid": "T2" - } - }, - "endpoint_uuid": { - "uuid": "6" - } - }, - { - "device_id": { - "device_uuid": { - "uuid": "R2" - } - }, - "endpoint_uuid": { - "uuid": "15" - } - } - ], - "optical_link": { - "name": "T2-R2", - "details": { - "length": 0, - "source": "srgT", - "target": "muxT", - "fibers": [ - { - "ID": "M1", - "length": 0, - "src_port": "6", - "dst_port": "15", - "local_peer_port": "6", - "remote_peer_port": "5", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - } - ] - } - } - }, - { - "link_id": { - "link_uuid": { - "uuid": "R2->T2" - } - }, - "link_endpoint_ids": [ - { - "device_id": { - "device_uuid": { - "uuid": "R2" - } - }, - "endpoint_uuid": { - "uuid": "5" - } - }, - { - "device_id": { - "device_uuid": { - "uuid": "T2" - } - }, - "endpoint_uuid": { - "uuid": "6" - } - } - ], - "optical_link": { - "name": "R2-T2", - "details": { - "length": 0, - "source": "srgT", - "target": "muxT", - "fibers": [ - { - "ID": "M1", - "length": 0, - "src_port": "5", - "dst_port": "6", - "local_peer_port": "15", - "remote_peer_port": "6", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - } - ] - } - } - } - ] -} \ No newline at end of file diff --git a/src/opticalcontroller/json_files/topo_2_links.json b/src/opticalcontroller/json_files/topo_2_links.json deleted file mode 100644 index 02165938ce675071a4ff4c3424e3b53244d40810..0000000000000000000000000000000000000000 --- a/src/opticalcontroller/json_files/topo_2_links.json +++ /dev/null @@ -1,1530 +0,0 @@ -{ - "R1-R3": { - "length": 80, - "source": "d2", - "target": "d1", - "fibers": { - "d2-1": { - "length": 80, - "src_port": "103", - "dst_port": "201", - "local_peer_port": "203", - "remote_peer_port": "101", - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - }, - "d2-2": { - "length": 80, - "src_port": "104", - "dst_port": "202", - "local_peer_port": "204", - "remote_peer_port": "102", - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - } - } - }, - "R3-R1": { - "length": 80, - "source": "d1", - "target": "d2", - "fibers": { - "d1-1": { - "length": 80, - "src_port": "101", - "dst_port": "203", - "local_peer_port": "201", - "remote_peer_port": "103", - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - }, - "d1-2": { - "length": 80, - "src_port": "102", - "dst_port": "204", - "local_peer_port": "202", - "remote_peer_port": "104", - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - } - } - }, - "R2-R3": { - "length": 80, - "source": "d2", - "target": "d2", - "fibers": { - "d2-1": { - "length": 80, - "src_port": "103", - "dst_port": "203", - "local_peer_port": "203", - "remote_peer_port": "103", - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - }, - "d2-2": { - "length": 80, - "src_port": "104", - "dst_port": "204", - "local_peer_port": "204", - "remote_peer_port": "104", - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - } - } - }, - "R3-R2": { - "length": 80, - "source": "d2", - "target": "d2", - "fibers": { - "d2-1": { - "length": 80, - "src_port": "103", - "dst_port": "203", - "local_peer_port": "203", - "remote_peer_port": "103", - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - }, - "d2-2": { - "length": 80, - "src_port": "104", - "dst_port": "204", - "local_peer_port": "204", - "remote_peer_port": "104", - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - } - } - }, - "T1-R1": { - "length": 0, - "source": "muxT", - "target": "srgR", - "fibers": { - "M1": { - "length": 0, - "src_port": "1", - "dst_port": "2001", - "local_peer_port": "1", - "remote_peer_port": "1001", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - }, - "M2": { - "length": 0, - "src_port": "2", - "dst_port": "2002", - "local_peer_port": "2", - "remote_peer_port": "1002", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - }, - "M3": { - "length": 0, - "src_port": "3", - "dst_port": "2003", - "local_peer_port": "3", - "remote_peer_port": "1003", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - } - } - }, - "R1-T1": { - "length": 0, - "source": "srgT", - "target": "muxR", - "fibers": { - "S1": { - "length": 0, - "src_port": "1001", - "dst_port": "1", - "local_peer_port": "2001", - "remote_peer_port": "1", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - }, - "S2": { - "length": 0, - "src_port": "1002", - "dst_port": "2", - "local_peer_port": "2002", - "remote_peer_port": "2", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - }, - "S3": { - "length": 0, - "src_port": "1003", - "dst_port": "3", - "local_peer_port": "2003", - "remote_peer_port": "3", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - } - } - }, - "T2-R2": { - "length": 0, - "source": "muxT", - "target": "srgR", - "fibers": { - "M1": { - "length": 0, - "src_port": "1", - "dst_port": "2001", - "local_peer_port": "1", - "remote_peer_port": "1001", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - }, - "M2": { - "length": 0, - "src_port": "2", - "dst_port": "2002", - "local_peer_port": "2", - "remote_peer_port": "1002", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - }, - "M3": { - "length": 0, - "src_port": "3", - "dst_port": "2003", - "local_peer_port": "3", - "remote_peer_port": "1003", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - } - } - }, - "R2-T2": { - "length": 0, - "source": "srgT", - "target": "muxR", - "fibers": { - "S1": { - "length": 0, - "src_port": "1001", - "dst_port": "1", - "local_peer_port": "2001", - "remote_peer_port": "1", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - }, - "S2": { - "length": 0, - "src_port": "1002", - "dst_port": "2", - "local_peer_port": "2002", - "remote_peer_port": "2", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - }, - "S3": { - "length": 0, - "src_port": "1003", - "dst_port": "3", - "local_peer_port": "2003", - "remote_peer_port": "3", - "used": false, - "c_slots": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20 - ], - "l_slots": [ - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120 - ], - "s_slots": [ - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520 - ] - } - } - } -} diff --git a/src/opticalcontroller/json_files/topology-optical.json b/src/opticalcontroller/json_files/topology-optical.json deleted file mode 100644 index e2453b654d53fc3200570ce9c2b17effc964bd25..0000000000000000000000000000000000000000 --- a/src/opticalcontroller/json_files/topology-optical.json +++ /dev/null @@ -1,252 +0,0 @@ -{ - "r1-r2": { - "length": 80, - "source" : "d1", - "target" : "d1", - "fibers" : { - "d1-1": { - "src_port": "1T", - "dst_port": "1R", - "c_slots": {"1": 1, "2": 1, "3": 1, "4": 1}, - "l_slots": {"101": 1, "102": 1, "103": 1, "104": 1}, - "s_slots": {"1001": 1, "1002": 1, "1003": 1, "1004": 1} - }, - "d1-2":{ - "src_port": "2T", - "dst_port": "2R", - "c_slots": {"1": 1, "2": 1, "3": 1, "4": 1}, - "l_slots": {"101": 1, "102": 1, "103": 1, "104": 1}, - "s_slots": {"1001": 1, "1002": 1, "1003": 1, "1004": 1} - } - } - }, - "r2-r1": { - "length": 80, - "source" : "d1", - "target" : "d1", - "fibers" : { - "d1-1" : { - "src_port": "1T", - "dst_port": "1R", - "c_slots": {"1": 1, "2": 1, "3": 1, "4": 1}, - "l_slots": {"101": 1, "102": 1, "103": 1, "104": 1}, - "s_slots": {"1001": 1, "1002": 1, "1003": 1, "1004": 1} - }, - "d1-2" : { - "src_port": "2T", - "dst_port": "2R", - "c_slots": {"1": 1, "2": 1, "3": 1, "4": 1}, - "l_slots": {"101": 1, "102": 1, "103": 1, "104": 1}, - "s_slots": {"1001": 1, "1002": 1, "1003": 1, "1004": 1} - } - } - }, - "r1-r3": { - "length": 80, - "source" : "d2", - "target" : "d1", - "fibers" : { - "d2-1":{ - "src_port": "3T", - "dst_port": "1R", - "c_slots": {"1": 1, "2": 1, "3": 1, "4": 1}, - "l_slots": {"101": 1, "102": 1, "103": 1, "104": 1}, - "s_slots": {"1001": 1, "1002": 1, "1003": 1, "1004": 1} - }, - "d2-2":{ - "src_port": "4T", - "dst_port": "2R", - "c_slots": {"1": 1, "2": 1, "3": 1, "4": 1}, - "l_slots": {"101": 1, "102": 1, "103": 1, "104": 1}, - "s_slots": {"1001": 1, "1002": 1, "1003": 1, "1004": 1} - } - } - }, - "r3-r1": { - "length": 80, - "source" : "d1", - "target" : "d2", - "fibers" : { - "d1-1": { - "src_port": "1T", - "dst_port": "3R", - "c_slots": {"1": 1, "2": 1, "3": 1, "4": 1}, - "l_slots": {"101": 1, "102": 1, "103": 1, "104": 1}, - "s_slots": {"1001": 1, "1002": 1, "1003": 1, "1004": 1} - }, - "d1-2": { - "src_port": "2T", - "dst_port": "4R", - "c_slots": {"1": 1, "2": 1, "3": 1, "4": 1}, - "l_slots": {"101": 1, "102": 1, "103": 1, "104": 1}, - "s_slots": {"1001": 1, "1002": 1, "1003": 1, "1004": 1} - } - } - }, - "r2-r3": { - "length": 80, - "source" : "d2", - "target" : "d2", - "fibers" : { - "d2-1": { - "src_port": "3T", - "dst_port": "3R", - "c_slots": {"1": 1, "2": 1, "3": 1, "4": 1}, - "l_slots": {"101": 1, "102": 1, "103": 1, "104": 1}, - "s_slots": {"1001": 1, "1002": 1, "1003": 1, "1004": 1} - }, - "d2-2": { - "src_port": "4T", - "dst_port": "4R", - "c_slots": {"1": 1, "2": 1, "3": 1, "4": 1}, - "l_slots": {"101": 1, "102": 1, "103": 1, "104": 1}, - "s_slots": {"1001": 1, "1002": 1, "1003": 1, "1004": 1} - } - } - }, - "r3-r2": { - "length": 80, - "source" : "d2", - "target" : "d2", - "fibers" : { - "d2-1": { - "src_port": "3T", - "dst_port": "3R", - "c_slots": {"1": 1, "2": 1, "3": 1, "4": 1}, - "l_slots": {"101": 1, "102": 1, "103": 1, "104": 1}, - "s_slots": {"1001": 1, "1002": 1, "1003": 1, "1004": 1} - }, - "d2-2":{ - "src_port": "4T", - "dst_port": "4R", - "c_slots": {"1": 1, "2": 1, "3": 1, "4": 1}, - "l_slots": {"101": 1, "102": 1, "103": 1, "104": 1}, - "s_slots": {"1001": 1, "1002": 1, "1003": 1, "1004": 1} - } - } - }, - "t1-r1": { - "length": 0, - "source" : "muxT", - "target" : "srgR", - "fibers" : { - "M1": { - "src_port": "1", - "dst_port": "101R", - "used": false, - "c_slots": {"1": 1, "2": 1, "3": 1, "4": 1}, - "l_slots": {"101": 1, "102": 1, "103": 1, "104": 1}, - "s_slots": {"1001": 1, "1002": 1, "1003": 1, "1004": 1} - }, - "M2": { - "src_port": "2", - "dst_port": "102R", - "used": false, - "c_slots": {"1": 1, "2": 1, "3": 1, "4": 1}, - "l_slots": {"101": 1, "102": 1, "103": 1, "104": 1}, - "s_slots": {"1001": 1, "1002": 1, "1003": 1, "1004": 1} - }, - "M3": { - "src_port": "3", - "dst_port": "103R", - "used": false, - "c_slots": {"1": 1, "2": 1, "3": 1, "4": 1}, - "l_slots": {"101": 1, "102": 1, "103": 1, "104": 1}, - "s_slots": {"1001": 1, "1002": 1, "1003": 1, "1004": 1} - } - } - }, - "r1-t1": { - "length": 0, - "source" : "srgT", - "target" : "muxR", - "fibers" : { - "S1": { - "src_port": "101T", - "dst_port": "1", - "used": false, - "c_slots": {"1": 1, "2": 1, "3": 1, "4": 1}, - "l_slots": {"101": 1, "102": 1, "103": 1, "104": 1}, - "s_slots": {"1001": 1, "1002": 1, "1003": 1, "1004": 1} - }, - "S2": { - "src_port": "102T", - "dst_port": "2", - "used": false, - "c_slots": {"1": 1, "2": 1, "3": 1, "4": 1}, - "l_slots": {"101": 1, "102": 1, "103": 1, "104": 1}, - "s_slots": {"1001": 1, "1002": 1, "1003": 1, "1004": 1} - }, - "S3": { - "src_port": "103T", - "dst_port": "3", - "used": false, - "c_slots": {"1": 1, "2": 1, "3": 1, "4": 1}, - "l_slots": {"101": 1, "102": 1, "103": 1, "104": 1}, - "s_slots": {"1001": 1, "1002": 1, "1003": 1, "1004": 1} - } - } - }, - "t2-r2": { - "length": 0, - "source" : "muxT", - "target" : "srgR", - "fibers" : { - "M1": { - "src_port": "1", - "dst_port": "101R", - "used": false, - "c_slots": {"1": 1, "2": 1, "3": 1, "4": 1}, - "l_slots": {"101": 1, "102": 1, "103": 1, "104": 1}, - "s_slots": {"1001": 1, "1002": 1, "1003": 1, "1004": 1} - }, - "M2": { - "src_port": "2", - "dst_port": "102R", - "used": false, - "c_slots": {"1": 1, "2": 1, "3": 1, "4": 1}, - "l_slots": {"101": 1, "102": 1, "103": 1, "104": 1}, - "s_slots": {"1001": 1, "1002": 1, "1003": 1, "1004": 1} - }, - "M3": { - "src_port": "3", - "dst_port": "103R", - "used": false, - "c_slots": {"1": 1, "2": 1, "3": 1, "4": 1}, - "l_slots": {"101": 1, "102": 1, "103": 1, "104": 1}, - "s_slots": {"1001": 1, "1002": 1, "1003": 1, "1004": 1} - } - } - }, - "r2-t2": { - "length": 0, - "source" : "srgT", - "target" : "muxR", - "fibers" : { - "S1": { - "src_port": "101T", - "dst_port": "1", - "used": false, - "c_slots": {"1": 1, "2": 1, "3": 1, "4": 1}, - "l_slots": {"101": 1, "102": 1, "103": 1, "104": 1}, - "s_slots": {"1001": 1, "1002": 1, "1003": 1, "1004": 1} - }, - "S2": { - "src_port": "102T", - "dst_port": "2", - "used": false, - "c_slots": {"1": 1, "2": 1, "3": 1, "4": 1}, - "l_slots": {"101": 1, "102": 1, "103": 1, "104": 1}, - "s_slots": {"1001": 1, "1002": 1, "1003": 1, "1004": 1} - }, - "S3": { - "src_port": "103T", - "dst_port": "3", - "used": false, - "c_slots": {"1": 1, "2": 1, "3": 1, "4": 1}, - "l_slots": {"101": 1, "102": 1, "103": 1, "104": 1}, - "s_slots": {"1001": 1, "1002": 1, "1003": 1, "1004": 1} - } - } - } -} diff --git a/src/opticalcontroller/json_files/topology-optical2.json b/src/opticalcontroller/json_files/topology-optical2.json deleted file mode 100644 index fe8e9866bcc64d52b2f49089ce03af47d72df9d0..0000000000000000000000000000000000000000 --- a/src/opticalcontroller/json_files/topology-optical2.json +++ /dev/null @@ -1,324 +0,0 @@ -{ - "r1-r2": { - "length": 80, - "source" : "d1", - "target" : "d1", - "fibers" : { - "d1-1": { - "length": 80, - "src_port": "1T", - "dst_port": "1R", - "local_peer_port": "1R", - "remote_peer_port": "1T", - "c_slots": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], - "l_slots": [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119 ,120], - "s_slots": [501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519 ,520] - }, - "d1-2":{ - "length": 80, - "src_port": "2T", - "dst_port": "2R", - "local_peer_port": "2R", - "remote_peer_port": "2T", - "c_slots": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], - "l_slots": [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119 ,120], - "s_slots": [501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519 ,520] - } - } - }, - "r2-r1": { - "length": 80, - "source" : "d1", - "target" : "d1", - "fibers" : { - "d1-1" : { - "length": 80, - "src_port": "1T", - "dst_port": "1R", - "local_peer_port": "1R", - "remote_peer_port": "1T", - "c_slots": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], - "l_slots": [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119 ,120], - "s_slots": [501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519 ,520] - }, - "d1-2" : { - "length": 80, - "src_port": "2T", - "dst_port": "2R", - "local_peer_port": "2R", - "remote_peer_port": "2T", - "c_slots": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], - "l_slots": [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119 ,120], - "s_slots": [501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519 ,520] - } - } - }, - "r1-r3": { - "length": 80, - "source" : "d2", - "target" : "d1", - "fibers" : { - "d2-1":{ - "length": 80, - "src_port": "3T", - "dst_port": "1R", - "local_peer_port": "3R", - "remote_peer_port": "1T", - "c_slots": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], - "l_slots": [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119 ,120], - "s_slots": [501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519 ,520] - }, - "d2-2":{ - "length": 80, - "src_port": "4T", - "dst_port": "2R", - "local_peer_port": "4R", - "remote_peer_port": "2T", - "c_slots": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], - "l_slots": [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119 ,120], - "s_slots": [501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519 ,520] - } - } - }, - "r3-r1": { - "length": 80, - "source" : "d1", - "target" : "d2", - "fibers" : { - "d1-1": { - "length": 80, - "src_port": "1T", - "dst_port": "3R", - "local_peer_port": "1R", - "remote_peer_port": "3T", - "c_slots": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], - "l_slots": [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119 ,120], - "s_slots": [501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519 ,520] - }, - "d1-2": { - "length": 80, - "src_port": "2T", - "dst_port": "4R", - "local_peer_port": "2R", - "remote_peer_port": "4T", - "c_slots": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], - "l_slots": [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119 ,120], - "s_slots": [501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519 ,520] - } - } - }, - "r2-r3": { - "length": 80, - "source" : "d2", - "target" : "d2", - "fibers" : { - "d2-1": { - "length": 80, - "src_port": "3T", - "dst_port": "3R", - "local_peer_port": "3R", - "remote_peer_port": "3T", - "c_slots": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], - "l_slots": [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119 ,120], - "s_slots": [501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519 ,520] - }, - "d2-2": { - "length": 80, - "src_port": "4T", - "dst_port": "4R", - "local_peer_port": "4R", - "remote_peer_port": "4T", - "c_slots": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], - "l_slots": [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119 ,120], - "s_slots": [501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519 ,520] - } - } - }, - "r3-r2": { - "length": 80, - "source" : "d2", - "target" : "d2", - "fibers" : { - "d2-1": { - "length": 80, - "src_port": "3T", - "dst_port": "3R", - "local_peer_port": "3R", - "remote_peer_port": "3T", - "c_slots": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], - "l_slots": [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119 ,120], - "s_slots": [501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519 ,520] - }, - "d2-2":{ - "length": 80, - "src_port": "4T", - "dst_port": "4R", - "local_peer_port": "4R", - "remote_peer_port": "4T", - "c_slots": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], - "l_slots": [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119 ,120], - "s_slots": [501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519 ,520] - } - } - }, - "t1-r1": { - "length": 0, - "source" : "muxT", - "target" : "srgR", - "fibers" : { - "M1": { - "length": 0, - "src_port": "1", - "dst_port": "101R", - "local_peer_port": "1", - "remote_peer_port": "101T", - "used": false, - "c_slots": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], - "l_slots": [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119 ,120], - "s_slots": [501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519 ,520] - }, - "M2": { - "length": 0, - "src_port": "2", - "dst_port": "102R", - "local_peer_port": "2", - "remote_peer_port": "102T", - "used": false, - "c_slots": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], - "l_slots": [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119 ,120], - "s_slots": [501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519 ,520] - }, - "M3": { - "length": 0, - "src_port": "3", - "dst_port": "103R", - "local_peer_port": "3", - "remote_peer_port": "103T", - "used": false, - "c_slots": [], - "l_slots": [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119 ,120], - "s_slots": [501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519 ,520] - } - } - }, - "r1-t1": { - "length": 0, - "source" : "srgT", - "target" : "muxR", - "fibers" : { - "S1": { - "length": 0, - "src_port": "101T", - "dst_port": "1", - "local_peer_port": "101R", - "remote_peer_port": "1", - "used": false, - "c_slots": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], - "l_slots": [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119 ,120], - "s_slots": [501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519 ,520] - }, - "S2": { - "length": 0, - "src_port": "102T", - "dst_port": "2", - "local_peer_port": "102T", - "remote_peer_port": "2", - "used": false, - "c_slots": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], - "l_slots": [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119 ,120], - "s_slots": [501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519 ,520] - }, - "S3": { - "length": 0, - "src_port": "103T", - "dst_port": "3", - "local_peer_port": "103R", - "remote_peer_port": "3", - "used": false, - "c_slots": [], - "l_slots": [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119 ,120], - "s_slots": [501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519 ,520] - } - } - }, - "t2-r2": { - "length": 0, - "source" : "muxT", - "target" : "srgR", - "fibers" : { - "M1": { - "length": 0, - "src_port": "1", - "dst_port": "101R", - "local_peer_port": "1", - "remote_peer_port": "101T", - "used": false, - "c_slots": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], - "l_slots": [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119 ,120], - "s_slots": [501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519 ,520] - }, - "M2": { - "length": 0, - "src_port": "2", - "dst_port": "102R", - "local_peer_port": "2", - "remote_peer_port": "102T", - "used": false, - "c_slots": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], - "l_slots": [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119 ,120], - "s_slots": [501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519 ,520] - }, - "M3": { - "length": 0, - "src_port": "3", - "dst_port": "103R", - "local_peer_port": "3", - "remote_peer_port": "103T", - "used": false, - "c_slots": [], - "l_slots": [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119 ,120], - "s_slots": [501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519 ,520] - } - } - }, - "r2-t2": { - "length": 0, - "source" : "srgT", - "target" : "muxR", - "fibers" : { - "S1": { - "length": 0, - "src_port": "101T", - "dst_port": "1", - "local_peer_port": "101R", - "remote_peer_port": "1", - "used": false, - "c_slots": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], - "l_slots": [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119 ,120], - "s_slots": [501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519 ,520] - }, - "S2": { - "length": 0, - "src_port": "102T", - "dst_port": "2", - "local_peer_port": "102R", - "remote_peer_port": "2", - "used": false, - "c_slots": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], - "l_slots": [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119 ,120], - "s_slots": [501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519 ,520] - }, - "S3": { - "length": 0, - "src_port": "103T", - "dst_port": "3", - "local_peer_port": "103R", - "remote_peer_port": "3", - "used": false, - "c_slots": [], - "l_slots": [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119 ,120], - "s_slots": [501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519 ,520] - } - } - } -} diff --git a/src/opticalcontroller/requirements.in b/src/opticalcontroller/requirements.in index 4732ee635a60b8320e25cd2c26388d1cfdfd25cc..15b3ca3a465efe5c9b7f5a217b1f5658a738405c 100644 --- a/src/opticalcontroller/requirements.in +++ b/src/opticalcontroller/requirements.in @@ -12,10 +12,25 @@ # See the License for the specific language governing permissions and # limitations under the License. +aniso8601==9.0.1 +attrs==23.1.0 +blinker==1.6.2 +click==8.1.7 +colorama==0.4.6 Flask==1.1.2 flask-restplus==0.13.0 +importlib-metadata==6.8.0 +importlib-resources==6.0.1 itsdangerous==1.1.0 Jinja2==2.11.3 +jsonschema==4.19.0 +jsonschema-specifications==2023.7.1 MarkupSafe==1.1.1 -numpy<2.0.0 +numpy==1.23.0 +pkgutil-resolve-name==1.3.10 +pytz==2023.3.post1 +referencing==0.30.2 +rpds-py==0.10.3 +six==1.16.0 Werkzeug==0.16.1 +zipp==3.16.2 diff --git a/src/opticalcontroller/requirements_optical_ctrl_test.txt b/src/opticalcontroller/requirements_optical_ctrl_test.txt new file mode 100644 index 0000000000000000000000000000000000000000..0b1947bee2c7f1e89491dff4f7589d3465d28c38 --- /dev/null +++ b/src/opticalcontroller/requirements_optical_ctrl_test.txt @@ -0,0 +1,21 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +Flask==1.1.2 +flask-restplus==0.13.0 +itsdangerous==1.1.0 +Jinja2==2.11.3 +MarkupSafe==1.1.1 +numpy==1.23.0 +Werkzeug==0.16.1 diff --git a/src/opticalcontroller/tools.py b/src/opticalcontroller/tools.py index a173856b8e7bd00a0e538cd3893e1c762edff8c8..08441f5c0010de1be503ee307410431ddfee54ab 100644 --- a/src/opticalcontroller/tools.py +++ b/src/opticalcontroller/tools.py @@ -12,9 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json import numpy as np from variables import * -import json +from common.proto.context_pb2 import TopologyId , LinkId , OpticalLink , OpticalLinkDetails +from common.tools.object_factory.OpticalLink import correct_slot +from context.client.ContextClient import ContextClient def common_slots(a, b): @@ -24,9 +27,11 @@ def common_slots(a, b): def map_modulation_to_op(mod): if mod == "DP-QPSK": return 1 - if mod == "DP-16QAM": - return 7 - if mod == "DP-64QAM": + elif mod == "DP-8QAM": + return 4 + elif mod == "DP-16QAM": + return 8 + elif mod == "DP-32QAM": return 10 @@ -40,13 +45,19 @@ def map_rate_to_slot(rate): slots = 4 op = map_modulation_to_op(mod) return op, slots - if rate == 400: + elif rate == 400: + mod = "DP-8QAM" + slots = 4 + op = map_modulation_to_op(mod) + return op, slots + elif rate == 800: mod = "DP-16QAM" - slots = 8 + #todo: check slot width + slots = 8#12 (150GHz) op = map_modulation_to_op(mod) return op, slots - if rate == 1000: - mod = "DP-64QAM" + elif rate == 1000: + mod = "DP-32QAM" slots = 18 op = map_modulation_to_op(mod) return op, slots @@ -54,21 +65,34 @@ def map_rate_to_slot(rate): return 2, 5 -def consecutives(x, val): +def consecutives(link, val): res = [] temp = [] + x1 = list(link.keys()) + x = str_list_to_int(x1) x.sort() - temp.append(x[0]) - y = 1 + y = 0 + if debug: + print("BLACK") + print(link) + print(x) + print(x[0]) + if link[str(x[0])] == 1: + temp.append(int(x[0])) + y = 1 for i in range(1, len(x)): - if x[i] == x[i - 1] + 1: + if (int(x[i]) == int(x[i - 1]) + 1) and link[str(x[i])] == 1: y += 1 - temp.append(x[i]) + temp.append(int(x[i])) else: if y >= val: res.extend(temp) - temp = [x[i]] - y = 1 + if link[str(x[i])] == 1: + temp = [int(x[i])] + y = 1 + else: + temp = [] + y = 0 if i == len(x) - 1 and y >= val: res.extend(temp) return res @@ -83,6 +107,14 @@ def combine(ls1, ls2): return temp +def str_list_to_int(str_list): + int_list = [] + for i in str_list: + int_list.append(int(i)) + int_list.sort() + return int_list + + def list_in_list(a, b): # convert list A to numpy array a_arr = np.array(a) @@ -112,7 +144,30 @@ def get_slot_frequency(b, n): return Fl + n * 12.5 -def freqency_converter(b, slots): +def get_side_slots_on_link(link, val, old_slots): + #link = l["optical_details"][band] + x = list(old_slots.keys()) + y = list(link.keys()) + keys = str_list_to_int(x) + keys.sort() + #print("AAAA") + #print(link, val, old_slots, keys) + #print(x) + starting_slot = keys[-1] + num = 0 + res = [] + #print(starting_slot) + for slot_id in range(starting_slot, len(y)): + if link[y[slot_id]] == 1: + num += 1 + res.append(int(y[slot_id])) + else: + return res, 0 + if num == val or slot_id == len(y) - 1: + return res, num + + +def frequency_converter(b, slots): l = len(slots) if debug: print(slots) @@ -135,14 +190,24 @@ def freqency_converter(b, slots): def readTopologyData(nodes, topology): - nodes_file = open(nodes, 'r') - topo_file = open(topology, 'r') - nodes = json.load(nodes_file) - topo = json.load(topo_file) - print(topo) - nodes_file.close() - topo_file.close() - return nodes, topo + nodes_file = open(nodes, 'r') + topo_file = open(topology, 'r') + nodes = json.load(nodes_file) + topo = json.load(topo_file) + #print(topo) + nodes_file.close() + topo_file.close() + return nodes, topo + + +def readTopologyDataFromContext(topology_id:TopologyId): + ctx_client = ContextClient() + ctx_client.connect() + topo_details = ctx_client.GetTopologyDetails(topology_id) + topo = topo_details.optical_links + nodes = topo_details.devices + ctx_client.close() + return topo , nodes def reverse_links(links): @@ -154,23 +219,26 @@ def reverse_links(links): result.append("{}-{}".format(b, a)) return result + def get_links_from_node(topology, node): result = {} - for link in topology["links"]: - if "{}-".format(node) in link["optical_link"]["name"]: - result[link["optical_link"]["name"]] = link + for link in topology["optical_links"]: + if "{}-".format(node) in link["name"]: + result[link["name"]] = link return result + def get_links_to_node(topology, node): result = {} - for link in topology["links"]: - if "-{}".format(node) in link["optical_link"]["name"]: - result[link["optical_link"]["name"]] = link + for link in topology["optical_links"]: + if "-{}".format(node) in link["name"]: + result[link["name"]] = link return result def slot_selection(c, l, s, n_slots, Nc, Nl, Ns): # First Fit + if isinstance(n_slots, int): slot_c = n_slots slot_l = n_slots @@ -183,7 +251,75 @@ def slot_selection(c, l, s, n_slots, Nc, Nl, Ns): return "c_slots", c[0: slot_c] elif len(l) >= slot_l: return "l_slots", l[0: slot_l] - elif len(l) >= slot_s: + elif len(s) >= slot_s: return "s_slots", s[0: slot_s] else: return None, None + +def handle_slot (slot_field, slot): + for key,value in slot.items() : + slot_field[key]=value + + + +def update_optical_band (optical_bands,optical_band_id,band,link): + key_list = optical_bands[optical_band_id][band].keys() + corrected_slots=optical_bands[optical_band_id][band] + print(f"band {band}") + print(f"corrected_slots_before {corrected_slots}") + if (len(key_list) < 20): + corrected_slots=correct_slot(optical_bands[optical_band_id][band]) + + fib={} + print(f"corrected_slots_after {corrected_slots}") + fib['c_slots']=link['optical_details']['c_slots'] + fib['l_slots']=link['optical_details']['l_slots'] + fib['s_slots']=link['optical_details']['s_slots'] + + fib[band]=corrected_slots + fib["src_port"]=optical_bands[optical_band_id]['src_port'] + fib["dst_port"]=optical_bands[optical_band_id]['dst_port'] + fib["local_peer_port"]=link["optical_details"]["local_peer_port"] + fib["remote_peer_port"]=link["optical_details"]["remote_peer_port"] + set_link_update(fib,link,test=f"restoring_optical_band {link['link_id']}") + +def set_link_update (fib:dict,link:dict,test="updating"): + + print(f"invoked from {test}") + print(f"fib updated {fib}") + optical_link = OpticalLink() + linkId = LinkId() + linkId.link_uuid.uuid=link["link_id"]["link_uuid"]["uuid"] + optical_details = OpticalLinkDetails() + optical_link.optical_details.length=0 + if "src_port" in fib : + optical_link.optical_details.src_port=fib["src_port"] + if "dst_port" in fib : + optical_link.optical_details.dst_port=fib["dst_port"] + if "local_peer_port" in fib : + optical_link.optical_details.local_peer_port=fib['local_peer_port'] + if "remote_peer_port" in fib: + optical_link.optical_details.remote_peer_port=fib['remote_peer_port'] + + optical_link.optical_details.used=fib['used'] if 'used' in fib else False + if "c_slots" in fib : + + handle_slot( optical_link.optical_details.c_slots,fib["c_slots"]) + if "s_slots" in fib : + + handle_slot( optical_link.optical_details.s_slots,fib["s_slots"]) + if "l_slots" in fib : + + handle_slot( optical_link.optical_details.l_slots,fib["l_slots"]) + + + optical_link.name=link['name'] + + optical_link.link_id.CopyFrom(linkId) + + ctx_client = ContextClient() + ctx_client.connect() + try: + ctx_client.SetOpticalLink(optical_link) + except Exception as err: + print (f"setOpticalLink {err}") diff --git a/src/opticalcontroller/variables.py b/src/opticalcontroller/variables.py index ca436736b20a525c866ea9745be14bc4db37dfcf..83d073e92009c8796a396d19af0bc737865c77e9 100644 --- a/src/opticalcontroller/variables.py +++ b/src/opticalcontroller/variables.py @@ -23,10 +23,4 @@ Nc = 320 #Nc = 10 Ns = 720 -nodes_json = 'json_files/nodes.json' -topology_json = 'json_files/tfs.json' #LAST -#topology_json = 'json_files/optical_TFSworking.json' #LAST -#topology_json = 'json_files/optical_topoTFS.json' -#topology_json = 'json_files/topo_2_links.json' - -testing = 1 +full_links = 0 diff --git a/src/pathcomp/frontend/service/algorithms/_Algorithm.py b/src/pathcomp/frontend/service/algorithms/_Algorithm.py index 3ed2b13fb33ae06faeacc4286959a8016ca995d1..3394d8df99933d8acc83a8cc8cebb1488ff6752c 100644 --- a/src/pathcomp/frontend/service/algorithms/_Algorithm.py +++ b/src/pathcomp/frontend/service/algorithms/_Algorithm.py @@ -14,6 +14,7 @@ import json, logging, requests, uuid from typing import Dict, List, Optional, Tuple, Union +from common.DeviceTypes import DeviceTypeEnum from common.proto.context_pb2 import ( ConfigRule, Connection, Device, DeviceList, EndPointId, Link, LinkList, Service, ServiceStatusEnum, ServiceTypeEnum ) @@ -251,21 +252,37 @@ class _Algorithm: ] self.logger.debug('path_hops = {:s}'.format(str(path_hops))) - try: - _device_dict = {k:v[0] for k,v in self.device_dict.items()} - self.logger.debug('self.device_dict = {:s}'.format(str(_device_dict))) - connections = convert_explicit_path_hops_to_connections( - path_hops, self.device_dict, main_service_uuid, main_service_type) - self.logger.debug('EXTRAPOLATED connections = {:s}'.format(str(connections))) - except: # pylint: disable=bare-except - MSG = ' '.join([ - 'Unable to Extrapolate sub-services and sub-connections.', - 'Assuming single-service and single-connection.', - ]) - self.logger.exception(MSG) + device_types = {v[0]['device_type'] for k,v in self.device_dict.items()} + DEVICES_BASIC_CONNECTION = { + DeviceTypeEnum.DATACENTER.value, DeviceTypeEnum.EMULATED_DATACENTER.value, + DeviceTypeEnum.CLIENT.value, DeviceTypeEnum.EMULATED_CLIENT.value, + DeviceTypeEnum.PACKET_ROUTER.value, DeviceTypeEnum.EMULATED_PACKET_ROUTER.value, + } + self.logger.debug('device_types = {:s}'.format(str(device_types))) + self.logger.debug('DEVICES_BASIC_CONNECTION = {:s}'.format(str(DEVICES_BASIC_CONNECTION))) + is_basic_connection = device_types.issubset(DEVICES_BASIC_CONNECTION) + self.logger.debug('is_basic_connection = {:s}'.format(str(is_basic_connection))) + if is_basic_connection: + self.logger.info('Assuming basic connections...') connections = convert_explicit_path_hops_to_plain_connection( path_hops, main_service_uuid, main_service_type) self.logger.debug('BASIC connections = {:s}'.format(str(connections))) + else: + try: + _device_dict = {k:v[0] for k,v in self.device_dict.items()} + self.logger.debug('self.device_dict = {:s}'.format(str(_device_dict))) + connections = convert_explicit_path_hops_to_connections( + path_hops, self.device_dict, main_service_uuid, main_service_type) + self.logger.debug('EXTRAPOLATED connections = {:s}'.format(str(connections))) + except: # pylint: disable=bare-except + MSG = ' '.join([ + 'Unable to Extrapolate sub-services and sub-connections.', + 'Assuming single-service and single-connection.', + ]) + self.logger.exception(MSG) + connections = convert_explicit_path_hops_to_plain_connection( + path_hops, main_service_uuid, main_service_type) + self.logger.debug('BASIC connections = {:s}'.format(str(connections))) for connection in connections: service_uuid,service_type,path_hops,_ = connection diff --git a/src/service/service/ServiceServiceServicerImpl.py b/src/service/service/ServiceServiceServicerImpl.py index 45a8e0b6c0fd9a26b45fc47d55074d8863c0caed..992b11cf3edaaed8211cd70a08452cd091cdf19a 100644 --- a/src/service/service/ServiceServiceServicerImpl.py +++ b/src/service/service/ServiceServiceServicerImpl.py @@ -17,7 +17,8 @@ from typing import Optional from common.method_wrappers.Decorator import MetricsPool, safe_and_metered_rpc_method from common.method_wrappers.ServiceExceptions import ( AlreadyExistsException, InvalidArgumentException, NotFoundException, NotImplementedException, - OperationFailedException) + OperationFailedException +) from common.proto.context_pb2 import ( Connection, ConstraintActionEnum, Empty, Service, ServiceId, ServiceStatusEnum, ServiceTypeEnum, TopologyId @@ -42,7 +43,8 @@ from .service_handler_api.ServiceHandlerFactory import ServiceHandlerFactory from .task_scheduler.TaskScheduler import TasksScheduler from .tools.GeodesicDistance import gps_distance from .tools.OpticalTools import ( - add_lightpath, delete_lightpath, adapt_reply, get_device_name_from_uuid, get_optical_band + add_lightpath, delete_lightpath, adapt_reply, get_device_name_from_uuid, + get_optical_band, refresh_opticalcontroller, DelFlexLightpath ) @@ -257,6 +259,8 @@ class ServiceServiceServicerImpl(ServiceServiceServicer): DEFAULT_TOPOLOGY_NAME, context_id_x) topology_details = context_client.GetTopologyDetails( TopologyId(**topology_id_x)) + + refresh_opticalcontroller(topology_id_x) # devices = get_devices_in_topology(context_client, TopologyId(**topology_id_x), ContextId(**context_id_x)) devices = topology_details.devices context_uuid_x = topology_details.topology_id.context_id.context_uuid.uuid @@ -292,7 +296,7 @@ class ServiceServiceServicerImpl(ServiceServiceServicer): parent_ob = reply_json["parent_opt_band"] LOGGER.debug('Parent optical-band={}'.format(parent_ob)) optical_band_txt = get_optical_band(parent_ob) - LOGGER.info('optical-band details={}'.format(optical_band_txt)) + else: LOGGER.debug('expected optical band not found') else: @@ -301,12 +305,11 @@ class ServiceServiceServicerImpl(ServiceServiceServicer): LOGGER.debug('Using existing optical band') else: LOGGER.debug('Using existing optical band') + if reply_txt is not None: optical_reply = adapt_reply( devices, _service, reply_json, context_uuid_x, topology_uuid_x, optical_band_txt ) - LOGGER.info('optical_reply={:s}'.format( - grpc_message_to_json_string(optical_reply))) tasks_scheduler.compose_from_pathcompreply( optical_reply, is_delete=False) @@ -342,6 +345,10 @@ class ServiceServiceServicerImpl(ServiceServiceServicer): if service is None: raise Exception('Service({:s}) not found'.format(grpc_message_to_json_string(request))) # pylint: disable=no-member service.service_status.service_status = ServiceStatusEnum.SERVICESTATUS_PENDING_REMOVAL + + if service.service_type == ServiceTypeEnum.SERVICETYPE_OPTICAL_CONNECTIVITY: + service.service_status.service_status = ServiceStatusEnum.SERVICESTATUS_ACTIVE + context_client.SetService(service) if is_deployed_te() and service.service_type == ServiceTypeEnum.SERVICETYPE_TE: @@ -351,7 +358,14 @@ class ServiceServiceServicerImpl(ServiceServiceServicer): context_client.RemoveService(request) return Empty() - if is_deployed_optical() and service.service_type == ServiceTypeEnum.SERVICETYPE_OPTICAL_CONNECTIVITY: + if service.service_type == ServiceTypeEnum.SERVICETYPE_OPTICAL_CONNECTIVITY: + params = { + "src" : None, + "dst" : None, + "bitrate" : None, + 'ob_id' : None, + 'flow_id' : None + } devs = [] context_id_x = json_context_id(DEFAULT_CONTEXT_NAME) @@ -364,15 +378,38 @@ class ServiceServiceServicerImpl(ServiceServiceServicer): devs.append(endpoint_id.device_id.device_uuid.uuid) src = get_device_name_from_uuid(devices, devs[0]) dst = get_device_name_from_uuid(devices, devs[1]) - - bitrate = int( - float(service.service_constraints[0].custom.constraint_value)) + bitrate = 100 + for constraint in service.service_constraints: + if "bandwidth" in constraint.custom.constraint_type: + bitrate = int(float(constraint.custom.constraint_value)) + break + + bitrate = int(float( + service.service_constraints[0].custom.constraint_value + )) if len(service.service_config.config_rules) > 0: c_rules_dict = json.loads( service.service_config.config_rules[0].custom.resource_value) + ob_id = None + flow_id = None + + if "ob_id" in c_rules_dict: + ob_id = c_rules_dict["ob_id"] if ("flow_id" in c_rules_dict): flow_id = c_rules_dict["flow_id"] - reply = delete_lightpath(flow_id, src, dst, bitrate) + #if ("ob_id" in c_rules_dict): + # ob_id = c_rules_dict["ob_id"] + + params['bitrate'] = bitrate + params['dst' ] = dst + params['src' ] = src + params['ob_id' ] = ob_id + params['flow_id'] = flow_id + + tasks_scheduler = TasksScheduler(self.service_handler_factory) + tasks_scheduler.compose_from_optical_service(service, params=params, is_delete=True) + tasks_scheduler.execute_all() + return Empty() # Normal service # Feed TaskScheduler with this service and the sub-services and sub-connections related to this service. diff --git a/src/service/service/__main__.py b/src/service/service/__main__.py index ae8a9e960cf6a0d720b508fe3ea5592632420c18..d4bbccea4fdb03fa8e8f93d8b9987b8436bb434a 100644 --- a/src/service/service/__main__.py +++ b/src/service/service/__main__.py @@ -16,7 +16,8 @@ import logging, signal, sys, threading from prometheus_client import start_http_server from common.Constants import ServiceNameEnum from common.Settings import ( - ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, get_env_var_name, get_log_level, get_metrics_port, + ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, + get_env_var_name, get_log_level, get_metrics_port, wait_for_environment_variables ) from .ServiceService import ServiceService diff --git a/src/service/service/service_handler_api/SettingsHandler.py b/src/service/service/service_handler_api/SettingsHandler.py index 293de54aa84be11f3c31bc1b47fce852df19a16a..24c5b638a35859b144969425d36ddad63a39d611 100644 --- a/src/service/service/service_handler_api/SettingsHandler.py +++ b/src/service/service/service_handler_api/SettingsHandler.py @@ -57,6 +57,11 @@ class SettingsHandler: def get(self, key_or_path : Union[str, List[str]], default : Optional[Any] = None) -> Optional[TreeNode]: return get_subnode(self.__resolver, self.__config, key_or_path, default=default) + def get_service_settings(self) -> Optional[TreeNode]: + service_settings_uri = '/settings' + service_settings = self.get(service_settings_uri) + return service_settings + def get_device_settings(self, device : Device) -> Optional[TreeNode]: device_keys = device.device_id.device_uuid.uuid, device.name diff --git a/src/service/service/service_handlers/l3nm_gnmi_openconfig/ConfigRuleComposer.py b/src/service/service/service_handlers/l3nm_gnmi_openconfig/ConfigRuleComposer.py index be314a8c1ee5112dd9d321dd2c1ee1dc6173aca4..777cc4588a6d3a1a3664f736faf03665efa50c79 100644 --- a/src/service/service/service_handlers/l3nm_gnmi_openconfig/ConfigRuleComposer.py +++ b/src/service/service/service_handlers/l3nm_gnmi_openconfig/ConfigRuleComposer.py @@ -12,33 +12,65 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Dict, List, Optional, Tuple -from common.proto.context_pb2 import Device, EndPoint +import json, logging, netaddr, re +from typing import Dict, List, Optional, Set, Tuple +from common.DeviceTypes import DeviceTypeEnum +from common.proto.context_pb2 import ConfigActionEnum, Device, EndPoint, Service from common.tools.object_factory.ConfigRule import json_config_rule_delete, json_config_rule_set - from service.service.service_handler_api.AnyTreeTools import TreeNode -def _interface(if_name, sif_index, ipv4_address, ipv4_prefix, enabled) -> Tuple[str, Dict]: - str_path = '/interface[{:s}]'.format(if_name) - str_data = {'name': if_name, 'enabled': enabled, 'sub_if_index': sif_index, - 'sub_if_enabled': enabled, 'sub_if_ipv4_enabled': enabled, - 'sub_if_ipv4_address': ipv4_address, 'sub_if_ipv4_prefix': ipv4_prefix} - return str_path, str_data +LOGGER = logging.getLogger(__name__) + +NETWORK_INSTANCE = 'teraflowsdn' + +RE_IF = re.compile(r'^\/interface\[([^\]]+)\]$') +RE_SUBIF = re.compile(r'^\/interface\[([^\]]+)\]\/subinterface\[([^\]]+)\]$') +RE_SR = re.compile(r'^\/network_instance\[([^\]]+)\]\/protocols\[STATIC\]/route\[([^\:]+)\:([^\]]+)\]$') + +def _interface( + interface : str, if_type : Optional[str] = 'l3ipvlan', index : int = 0, vlan_id : Optional[int] = None, + address_ip : Optional[str] = None, address_prefix : Optional[int] = None, mtu : Optional[int] = None, + enabled : bool = True +) -> Tuple[str, Dict]: + path = '/interface[{:s}]/subinterface[{:d}]'.format(interface, index) + data = {'name': interface, 'type': if_type, 'index': index, 'enabled': enabled} + if if_type is not None: data['type'] = if_type + if vlan_id is not None: data['vlan_id'] = vlan_id + if address_ip is not None: data['address_ip'] = address_ip + if address_prefix is not None: data['address_prefix'] = address_prefix + if mtu is not None: data['mtu'] = mtu + return path, data -def _network_instance(ni_name, ni_type) -> Tuple[str, Dict]: - str_path = '/network_instance[{:s}]'.format(ni_name) - str_data = {'name': ni_name, 'type': ni_type} - return str_path, str_data +def _network_instance(ni_name : str, ni_type : str) -> Tuple[str, Dict]: + path = '/network_instance[{:s}]'.format(ni_name) + data = {'name': ni_name, 'type': ni_type} + return path, data -def _network_instance_static_route(ni_name, prefix, next_hop, next_hop_index=0) -> Tuple[str, Dict]: - str_path = '/network_instance[{:s}]/static_route[{:s}]'.format(ni_name, prefix) - str_data = {'name': ni_name, 'prefix': prefix, 'next_hop': next_hop, 'next_hop_index': next_hop_index} - return str_path, str_data +def _network_instance_protocol(ni_name : str, protocol : str) -> Tuple[str, Dict]: + path = '/network_instance[{:s}]/protocols[{:s}]'.format(ni_name, protocol) + data = {'name': ni_name, 'identifier': protocol, 'protocol_name': protocol} + return path, data -def _network_instance_interface(ni_name, if_name, sif_index) -> Tuple[str, Dict]: - str_path = '/network_instance[{:s}]/interface[{:s}.{:d}]'.format(ni_name, if_name, sif_index) - str_data = {'name': ni_name, 'if_name': if_name, 'sif_index': sif_index} - return str_path, str_data +def _network_instance_protocol_static(ni_name : str) -> Tuple[str, Dict]: + return _network_instance_protocol(ni_name, 'STATIC') + +def _network_instance_protocol_static_route( + ni_name : str, prefix : str, next_hop : str, metric : int +) -> Tuple[str, Dict]: + protocol = 'STATIC' + path = '/network_instance[{:s}]/protocols[{:s}]/static_route[{:s}:{:d}]'.format(ni_name, protocol, prefix, metric) + index = 'AUTO_{:d}_{:s}'.format(metric, next_hop.replace('.', '-')) + data = { + 'name': ni_name, 'identifier': protocol, 'protocol_name': protocol, + 'prefix': prefix, 'index': index, 'next_hop': next_hop, 'metric': metric + } + return path, data + +def _network_instance_interface(ni_name : str, interface : str, sub_interface_index : int) -> Tuple[str, Dict]: + sub_interface_name = '{:s}.{:d}'.format(interface, sub_interface_index) + path = '/network_instance[{:s}]/interface[{:s}]'.format(ni_name, sub_interface_name) + data = {'name': ni_name, 'id': sub_interface_name, 'interface': interface, 'subinterface': sub_interface_index} + return path, data class EndpointComposer: def __init__(self, endpoint_uuid : str) -> None: @@ -46,74 +78,201 @@ class EndpointComposer: self.objekt : Optional[EndPoint] = None self.sub_interface_index = 0 self.ipv4_address = None - self.ipv4_prefix = None + self.ipv4_prefix_len = None - def configure(self, endpoint_obj : EndPoint, settings : Optional[TreeNode]) -> None: - self.objekt = endpoint_obj + def configure(self, endpoint_obj : Optional[EndPoint], settings : Optional[TreeNode]) -> None: + if endpoint_obj is not None: + self.objekt = endpoint_obj if settings is None: return json_settings : Dict = settings.value - self.ipv4_address = json_settings['ipv4_address'] - self.ipv4_prefix = json_settings['ipv4_prefix'] - self.sub_interface_index = json_settings['sub_interface_index'] + + if 'address_ip' in json_settings: + self.ipv4_address = json_settings['address_ip'] + elif 'ip_address' in json_settings: + self.ipv4_address = json_settings['ip_address'] + else: + MSG = 'IP Address not found. Tried: address_ip and ip_address. endpoint_obj={:s} settings={:s}' + LOGGER.warning(MSG.format(str(endpoint_obj), str(settings))) + + if 'address_prefix' in json_settings: + self.ipv4_prefix_len = json_settings['address_prefix'] + elif 'prefix_length' in json_settings: + self.ipv4_prefix_len = json_settings['prefix_length'] + else: + MSG = 'IP Address Prefix not found. Tried: address_prefix and prefix_length. endpoint_obj={:s} settings={:s}' + LOGGER.warning(MSG.format(str(endpoint_obj), str(settings))) + + self.sub_interface_index = json_settings.get('index', 0) def get_config_rules(self, network_instance_name : str, delete : bool = False) -> List[Dict]: + if self.ipv4_address is None: return [] + if self.ipv4_prefix_len is None: return [] json_config_rule = json_config_rule_delete if delete else json_config_rule_set - return [ - json_config_rule(*_interface( - self.objekt.name, self.sub_interface_index, self.ipv4_address, self.ipv4_prefix, True - )), + config_rules = [ json_config_rule(*_network_instance_interface( network_instance_name, self.objekt.name, self.sub_interface_index )), ] + if not delete: + config_rules.extend([ + json_config_rule(*_interface( + self.objekt.name, index=self.sub_interface_index, address_ip=self.ipv4_address, + address_prefix=self.ipv4_prefix_len, enabled=True + )), + ]) + return config_rules + + def dump(self) -> Dict: + return { + 'index' : self.sub_interface_index, + 'address_ip' : self.ipv4_address, + 'address_prefix': self.ipv4_prefix_len, + } class DeviceComposer: def __init__(self, device_uuid : str) -> None: self.uuid = device_uuid self.objekt : Optional[Device] = None - self.endpoints : Dict[str, EndpointComposer] = dict() - self.static_routes : Dict[str, str] = dict() - + self.aliases : Dict[str, str] = dict() # endpoint_name => endpoint_uuid + self.endpoints : Dict[str, EndpointComposer] = dict() # endpoint_uuid => EndpointComposer + self.connected : Set[str] = set() + self.static_routes : Dict[str, Dict[int, str]] = dict() # {prefix => {metric => next_hop}} + + def set_endpoint_alias(self, endpoint_name : str, endpoint_uuid : str) -> None: + self.aliases[endpoint_name] = endpoint_uuid + def get_endpoint(self, endpoint_uuid : str) -> EndpointComposer: + endpoint_uuid = self.aliases.get(endpoint_uuid, endpoint_uuid) if endpoint_uuid not in self.endpoints: self.endpoints[endpoint_uuid] = EndpointComposer(endpoint_uuid) return self.endpoints[endpoint_uuid] def configure(self, device_obj : Device, settings : Optional[TreeNode]) -> None: self.objekt = device_obj + for endpoint_obj in device_obj.device_endpoints: + endpoint_uuid = endpoint_obj.endpoint_id.endpoint_uuid.uuid + self.set_endpoint_alias(endpoint_obj.name, endpoint_uuid) + self.get_endpoint(endpoint_obj.name).configure(endpoint_obj, None) + + # Find management interfaces + mgmt_ifaces = set() + for config_rule in device_obj.device_config.config_rules: + if config_rule.action != ConfigActionEnum.CONFIGACTION_SET: continue + if config_rule.WhichOneof('config_rule') != 'custom': continue + config_rule_custom = config_rule.custom + match = RE_IF.match(config_rule_custom.resource_key) + if match is None: continue + if_name = match.groups()[0] + resource_value = json.loads(config_rule_custom.resource_value) + management = resource_value.get('management', False) + if management: mgmt_ifaces.add(if_name) + + # Find data plane interfaces + for config_rule in device_obj.device_config.config_rules: + if config_rule.action != ConfigActionEnum.CONFIGACTION_SET: continue + if config_rule.WhichOneof('config_rule') != 'custom': continue + config_rule_custom = config_rule.custom + + match = RE_SUBIF.match(config_rule_custom.resource_key) + if match is not None: + if_name, subif_index = match.groups() + if if_name in mgmt_ifaces: continue + resource_value = json.loads(config_rule_custom.resource_value) + if 'address_ip' not in resource_value: continue + if 'address_prefix' not in resource_value: continue + ipv4_network = str(resource_value['address_ip']) + ipv4_prefix_len = int(resource_value['address_prefix']) + endpoint = self.get_endpoint(if_name) + endpoint.ipv4_address = ipv4_network + endpoint.ipv4_prefix_len = ipv4_prefix_len + endpoint.sub_interface_index = int(subif_index) + endpoint_ip_network = netaddr.IPNetwork('{:s}/{:d}'.format(ipv4_network, ipv4_prefix_len)) + self.connected.add(str(endpoint_ip_network.cidr)) + + match = RE_SR.match(config_rule_custom.resource_key) + if match is not None: + ni_name, prefix, metric = match.groups() + if ni_name != NETWORK_INSTANCE: continue + resource_value : Dict = json.loads(config_rule_custom.resource_value) + next_hop = resource_value['next_hop'] + self.static_routes.setdefault(prefix, dict())[metric] = next_hop + if settings is None: return json_settings : Dict = settings.value - static_routes = json_settings.get('static_routes', []) + static_routes : List[Dict] = json_settings.get('static_routes', []) for static_route in static_routes: prefix = static_route['prefix'] next_hop = static_route['next_hop'] - self.static_routes[prefix] = next_hop + metric = static_route.get('metric', 0) + self.static_routes.setdefault(prefix, dict())[metric] = next_hop def get_config_rules(self, network_instance_name : str, delete : bool = False) -> List[Dict]: + SELECTED_DEVICES = {DeviceTypeEnum.PACKET_ROUTER.value, DeviceTypeEnum.EMULATED_PACKET_ROUTER.value} + if self.objekt.device_type not in SELECTED_DEVICES: return [] + json_config_rule = json_config_rule_delete if delete else json_config_rule_set config_rules = [ json_config_rule(*_network_instance(network_instance_name, 'L3VRF')) ] for endpoint in self.endpoints.values(): config_rules.extend(endpoint.get_config_rules(network_instance_name, delete=delete)) - for prefix, next_hop in self.static_routes.items(): + if len(self.static_routes) > 0: config_rules.append( - json_config_rule(*_network_instance_static_route(network_instance_name, prefix, next_hop)) + json_config_rule(*_network_instance_protocol_static(network_instance_name)) ) + for prefix, metric_next_hop in self.static_routes.items(): + for metric, next_hop in metric_next_hop.items(): + config_rules.append( + json_config_rule(*_network_instance_protocol_static_route( + network_instance_name, prefix, next_hop, metric + )) + ) if delete: config_rules = list(reversed(config_rules)) return config_rules + def dump(self) -> Dict: + return { + 'endpoints' : { + endpoint_uuid : endpoint.dump() + for endpoint_uuid, endpoint in self.endpoints.items() + }, + 'connected' : list(self.connected), + 'static_routes' : self.static_routes, + } + class ConfigRuleComposer: def __init__(self) -> None: - self.devices : Dict[str, DeviceComposer] = dict() + self.objekt : Optional[Service] = None + self.aliases : Dict[str, str] = dict() # device_name => device_uuid + self.devices : Dict[str, DeviceComposer] = dict() # device_uuid => DeviceComposer + + def set_device_alias(self, device_name : str, device_uuid : str) -> None: + self.aliases[device_name] = device_uuid def get_device(self, device_uuid : str) -> DeviceComposer: + device_uuid = self.aliases.get(device_uuid, device_uuid) if device_uuid not in self.devices: self.devices[device_uuid] = DeviceComposer(device_uuid) return self.devices[device_uuid] - def get_config_rules(self, network_instance_name : str, delete : bool = False) -> Dict[str, List[Dict]]: + def configure(self, service_obj : Service, settings : Optional[TreeNode]) -> None: + self.objekt = service_obj + if settings is None: return + #json_settings : Dict = settings.value + # For future use + + def get_config_rules( + self, network_instance_name : str = NETWORK_INSTANCE, delete : bool = False + ) -> Dict[str, List[Dict]]: return { device_uuid : device.get_config_rules(network_instance_name, delete=delete) for device_uuid, device in self.devices.items() } + + def dump(self) -> Dict: + return { + 'devices' : { + device_uuid : device.dump() + for device_uuid, device in self.devices.items() + } + } diff --git a/src/service/service/service_handlers/l3nm_gnmi_openconfig/L3NMGnmiOpenConfigServiceHandler.py b/src/service/service/service_handlers/l3nm_gnmi_openconfig/L3NMGnmiOpenConfigServiceHandler.py index 5856b5f61893174a92ce02a303ae9ad30be16005..88bb5655b872dfce90988686e7d8bc242d866bf0 100644 --- a/src/service/service/service_handlers/l3nm_gnmi_openconfig/L3NMGnmiOpenConfigServiceHandler.py +++ b/src/service/service/service_handlers/l3nm_gnmi_openconfig/L3NMGnmiOpenConfigServiceHandler.py @@ -15,14 +15,17 @@ import json, logging from typing import Any, Dict, List, Optional, Tuple, Union from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method -from common.proto.context_pb2 import ConfigRule, DeviceId, Service +from common.proto.context_pb2 import ConfigRule, ConnectionId, DeviceId, Service +from common.tools.object_factory.Connection import json_connection_id from common.tools.object_factory.Device import json_device_id from common.type_checkers.Checkers import chk_type -from service.service.service_handler_api.Tools import get_device_endpoint_uuids, get_endpoint_matching from service.service.service_handler_api._ServiceHandler import _ServiceHandler from service.service.service_handler_api.SettingsHandler import SettingsHandler +from service.service.service_handler_api.Tools import get_device_endpoint_uuids, get_endpoint_matching from service.service.task_scheduler.TaskExecutor import TaskExecutor +from service.service.tools.EndpointIdFormatters import endpointids_to_raw from .ConfigRuleComposer import ConfigRuleComposer +from .StaticRouteGenerator import StaticRouteGenerator LOGGER = logging.getLogger(__name__) @@ -35,24 +38,35 @@ class L3NMGnmiOpenConfigServiceHandler(_ServiceHandler): self.__service = service self.__task_executor = task_executor self.__settings_handler = SettingsHandler(service.service_config, **settings) - self.__composer = ConfigRuleComposer() - self.__endpoint_map : Dict[Tuple[str, str], str] = dict() + self.__config_rule_composer = ConfigRuleComposer() + self.__static_route_generator = StaticRouteGenerator(self.__config_rule_composer) + self.__endpoint_map : Dict[Tuple[str, str], Tuple[str, str]] = dict() def _compose_config_rules(self, endpoints : List[Tuple[str, str, Optional[str]]]) -> None: + if len(endpoints) % 2 != 0: raise Exception('Number of endpoints should be even') + + service_settings = self.__settings_handler.get_service_settings() + self.__config_rule_composer.configure(self.__service, service_settings) + for endpoint in endpoints: device_uuid, endpoint_uuid = get_device_endpoint_uuids(endpoint) device_obj = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid))) device_settings = self.__settings_handler.get_device_settings(device_obj) - _device = self.__composer.get_device(device_obj.name) + self.__config_rule_composer.set_device_alias(device_obj.name, device_uuid) + _device = self.__config_rule_composer.get_device(device_obj.name) _device.configure(device_obj, device_settings) endpoint_obj = get_endpoint_matching(device_obj, endpoint_uuid) endpoint_settings = self.__settings_handler.get_endpoint_settings(device_obj, endpoint_obj) + _device.set_endpoint_alias(endpoint_obj.name, endpoint_uuid) _endpoint = _device.get_endpoint(endpoint_obj.name) _endpoint.configure(endpoint_obj, endpoint_settings) - self.__endpoint_map[(device_uuid, endpoint_uuid)] = device_obj.name + self.__endpoint_map[(device_uuid, endpoint_uuid)] = (device_obj.name, endpoint_obj.name) + + self.__static_route_generator.compose(endpoints) + LOGGER.debug('config_rule_composer = {:s}'.format(json.dumps(self.__config_rule_composer.dump()))) def _do_configurations( self, config_rules_per_device : Dict[str, List[Dict]], endpoints : List[Tuple[str, str, Optional[str]]], @@ -62,7 +76,7 @@ class L3NMGnmiOpenConfigServiceHandler(_ServiceHandler): results_per_device = dict() for device_name,json_config_rules in config_rules_per_device.items(): try: - device_obj = self.__composer.get_device(device_name).objekt + device_obj = self.__config_rule_composer.get_device(device_name).objekt if len(json_config_rules) == 0: continue del device_obj.device_config.config_rules[:] for json_config_rule in json_config_rules: @@ -78,7 +92,8 @@ class L3NMGnmiOpenConfigServiceHandler(_ServiceHandler): results = [] for endpoint in endpoints: device_uuid, endpoint_uuid = get_device_endpoint_uuids(endpoint) - device_name = self.__endpoint_map[(device_uuid, endpoint_uuid)] + device_name, _ = self.__endpoint_map[(device_uuid, endpoint_uuid)] + if device_name not in results_per_device: continue results.append(results_per_device[device_name]) return results @@ -88,12 +103,16 @@ class L3NMGnmiOpenConfigServiceHandler(_ServiceHandler): ) -> List[Union[bool, Exception]]: chk_type('endpoints', endpoints, list) if len(endpoints) == 0: return [] - service_uuid = self.__service.service_id.service_uuid.uuid - #settings = self.__settings_handler.get('/settings') - self._compose_config_rules(endpoints) - network_instance_name = service_uuid.split('-')[0] - config_rules_per_device = self.__composer.get_config_rules(network_instance_name, delete=False) + #service_uuid = self.__service.service_id.service_uuid.uuid + connection = self.__task_executor.get_connection(ConnectionId(**json_connection_id(connection_uuid))) + connection_endpoint_ids = endpointids_to_raw(connection.path_hops_endpoint_ids) + self._compose_config_rules(connection_endpoint_ids) + #network_instance_name = service_uuid.split('-')[0] + #config_rules_per_device = self.__config_rule_composer.get_config_rules(network_instance_name, delete=False) + config_rules_per_device = self.__config_rule_composer.get_config_rules(delete=False) + LOGGER.debug('config_rules_per_device={:s}'.format(str(config_rules_per_device))) results = self._do_configurations(config_rules_per_device, endpoints) + LOGGER.debug('results={:s}'.format(str(results))) return results @metered_subclass_method(METRICS_POOL) @@ -102,12 +121,16 @@ class L3NMGnmiOpenConfigServiceHandler(_ServiceHandler): ) -> List[Union[bool, Exception]]: chk_type('endpoints', endpoints, list) if len(endpoints) == 0: return [] - service_uuid = self.__service.service_id.service_uuid.uuid - #settings = self.__settings_handler.get('/settings') - self._compose_config_rules(endpoints) - network_instance_name = service_uuid.split('-')[0] - config_rules_per_device = self.__composer.get_config_rules(network_instance_name, delete=True) + #service_uuid = self.__service.service_id.service_uuid.uuid + connection = self.__task_executor.get_connection(ConnectionId(**json_connection_id(connection_uuid))) + connection_endpoint_ids = endpointids_to_raw(connection.path_hops_endpoint_ids) + self._compose_config_rules(connection_endpoint_ids) + #network_instance_name = service_uuid.split('-')[0] + #config_rules_per_device = self.__config_rule_composer.get_config_rules(network_instance_name, delete=True) + config_rules_per_device = self.__config_rule_composer.get_config_rules(delete=True) + LOGGER.debug('config_rules_per_device={:s}'.format(str(config_rules_per_device))) results = self._do_configurations(config_rules_per_device, endpoints, delete=True) + LOGGER.debug('results={:s}'.format(str(results))) return results @metered_subclass_method(METRICS_POOL) diff --git a/src/service/service/service_handlers/l3nm_gnmi_openconfig/StaticRouteGenerator.py b/src/service/service/service_handlers/l3nm_gnmi_openconfig/StaticRouteGenerator.py new file mode 100644 index 0000000000000000000000000000000000000000..a16e4d5b1c46da600b9dc078ca9f6a74c7eaa187 --- /dev/null +++ b/src/service/service/service_handlers/l3nm_gnmi_openconfig/StaticRouteGenerator.py @@ -0,0 +1,197 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json, logging, netaddr, sys +from typing import List, Optional, Tuple +from .ConfigRuleComposer import ConfigRuleComposer + +LOGGER = logging.getLogger(__name__) + +# Used to infer routing networks for adjacent ports when there is no hint in device/endpoint settings +ROOT_NEIGHBOR_ROUTING_NETWORK = netaddr.IPNetwork('10.254.254.0/16') +NEIGHBOR_ROUTING_NETWORKS_PREFIX_LEN = 30 +NEIGHBOR_ROUTING_NETWORKS = set(ROOT_NEIGHBOR_ROUTING_NETWORK.subnet(NEIGHBOR_ROUTING_NETWORKS_PREFIX_LEN)) + +def _generate_neighbor_addresses() -> Tuple[netaddr.IPAddress, netaddr.IPAddress, int]: + ip_network = NEIGHBOR_ROUTING_NETWORKS.pop() + ip_addresses = list(ip_network.iter_hosts()) + ip_addresses.append(NEIGHBOR_ROUTING_NETWORKS_PREFIX_LEN) + return ip_addresses + +def _compute_gateway(ip_network : netaddr.IPNetwork, gateway_host=1) -> netaddr.IPAddress: + return netaddr.IPAddress(ip_network.cidr.first + gateway_host) + +def _compose_ipv4_network(ipv4_network, ipv4_prefix_len) -> netaddr.IPNetwork: + return netaddr.IPNetwork('{:s}/{:d}'.format(str(ipv4_network), int(ipv4_prefix_len))) + +class StaticRouteGenerator: + def __init__(self, config_rule_composer : ConfigRuleComposer) -> None: + self._config_rule_composer = config_rule_composer + + def compose(self, connection_hop_list : List[Tuple[str, str, Optional[str]]]) -> None: + link_endpoints = self._compute_link_endpoints(connection_hop_list) + LOGGER.debug('link_endpoints = {:s}'.format(str(link_endpoints))) + + self._compute_link_addresses(link_endpoints) + LOGGER.debug('config_rule_composer = {:s}'.format(json.dumps(self._config_rule_composer.dump()))) + + self._discover_connected_networks(connection_hop_list) + LOGGER.debug('config_rule_composer = {:s}'.format(json.dumps(self._config_rule_composer.dump()))) + + # Compute and propagate static routes forward (service_endpoint_a => service_endpoint_b) + self._compute_static_routes(link_endpoints) + + # Compute and propagate static routes backward (service_endpoint_b => service_endpoint_a) + reversed_endpoints = list(reversed(connection_hop_list)) + reversed_link_endpoints = self._compute_link_endpoints(reversed_endpoints) + LOGGER.debug('reversed_link_endpoints = {:s}'.format(str(reversed_link_endpoints))) + self._compute_static_routes(reversed_link_endpoints) + + LOGGER.debug('config_rule_composer = {:s}'.format(json.dumps(self._config_rule_composer.dump()))) + + def _compute_link_endpoints( + self, connection_hop_list : List[Tuple[str, str, Optional[str]]] + ) -> List[Tuple[Tuple[str, str, Optional[str]], Tuple[str, str, Optional[str]]]]: + num_connection_hops = len(connection_hop_list) + if num_connection_hops % 2 != 0: raise Exception('Number of connection hops must be even') + if num_connection_hops < 4: raise Exception('Number of connection hops must be >= 4') + + # Skip service endpoints (first and last) + it_connection_hops = iter(connection_hop_list[1:-1]) + return list(zip(it_connection_hops, it_connection_hops)) + + def _compute_link_addresses( + self, link_endpoints_list : List[Tuple[Tuple[str, str, Optional[str]], Tuple[str, str, Optional[str]]]] + ) -> None: + for link_endpoints in link_endpoints_list: + device_endpoint_a, device_endpoint_b = link_endpoints + + device_uuid_a, endpoint_uuid_a = device_endpoint_a[0:2] + endpoint_a = self._config_rule_composer.get_device(device_uuid_a).get_endpoint(endpoint_uuid_a) + + device_uuid_b, endpoint_uuid_b = device_endpoint_b[0:2] + endpoint_b = self._config_rule_composer.get_device(device_uuid_b).get_endpoint(endpoint_uuid_b) + + if endpoint_a.ipv4_address is None and endpoint_b.ipv4_address is None: + ip_endpoint_a, ip_endpoint_b, prefix_len = _generate_neighbor_addresses() + endpoint_a.ipv4_address = str(ip_endpoint_a) + endpoint_a.ipv4_prefix_len = prefix_len + endpoint_b.ipv4_address = str(ip_endpoint_b) + endpoint_b.ipv4_prefix_len = prefix_len + elif endpoint_a.ipv4_address is not None and endpoint_b.ipv4_address is None: + prefix_len = endpoint_a.ipv4_prefix_len + ip_network_a = _compose_ipv4_network(endpoint_a.ipv4_address, prefix_len) + if prefix_len > 30: + MSG = 'Unsupported prefix_len for {:s}: {:s}' + raise Exception(MSG.format(str(endpoint_a), str(prefix_len))) + ip_endpoint_b = _compute_gateway(ip_network_a, gateway_host=1) + if ip_endpoint_b == ip_network_a.ip: + ip_endpoint_b = _compute_gateway(ip_network_a, gateway_host=2) + endpoint_b.ipv4_address = str(ip_endpoint_b) + endpoint_b.ipv4_prefix_len = prefix_len + elif endpoint_a.ipv4_address is None and endpoint_b.ipv4_address is not None: + prefix_len = endpoint_b.ipv4_prefix_len + ip_network_b = _compose_ipv4_network(endpoint_b.ipv4_address, prefix_len) + if prefix_len > 30: + MSG = 'Unsupported prefix_len for {:s}: {:s}' + raise Exception(MSG.format(str(endpoint_b), str(prefix_len))) + ip_endpoint_a = _compute_gateway(ip_network_b, gateway_host=1) + if ip_endpoint_a == ip_network_b.ip: + ip_endpoint_a = _compute_gateway(ip_network_b, gateway_host=2) + endpoint_a.ipv4_address = str(ip_endpoint_a) + endpoint_a.ipv4_prefix_len = prefix_len + elif endpoint_a.ipv4_address is not None and endpoint_b.ipv4_address is not None: + ip_network_a = _compose_ipv4_network(endpoint_a.ipv4_address, endpoint_a.ipv4_prefix_len) + ip_network_b = _compose_ipv4_network(endpoint_b.ipv4_address, endpoint_b.ipv4_prefix_len) + if ip_network_a.cidr != ip_network_b.cidr: + MSG = 'Incompatible CIDRs: endpoint_a({:s})=>{:s} endpoint_b({:s})=>{:s}' + raise Exception(MSG.format(str(endpoint_a), str(ip_network_a), str(endpoint_b), str(ip_network_b))) + if ip_network_a.ip == ip_network_b.ip: + MSG = 'Duplicated IP: endpoint_a({:s})=>{:s} endpoint_b({:s})=>{:s}' + raise Exception(MSG.format(str(endpoint_a), str(ip_network_a), str(endpoint_b), str(ip_network_b))) + + def _discover_connected_networks(self, connection_hop_list : List[Tuple[str, str, Optional[str]]]) -> None: + for connection_hop in connection_hop_list: + device_uuid, endpoint_uuid = connection_hop[0:2] + device = self._config_rule_composer.get_device(device_uuid) + endpoint = device.get_endpoint(endpoint_uuid) + + if endpoint.ipv4_address is None: continue + ip_network = _compose_ipv4_network(endpoint.ipv4_address, endpoint.ipv4_prefix_len) + + device.connected.add(str(ip_network.cidr)) + + def _compute_static_routes( + self, link_endpoints_list : List[Tuple[Tuple[str, str, Optional[str]], Tuple[str, str, Optional[str]]]] + ) -> None: + for link_endpoints in link_endpoints_list: + device_endpoint_a, device_endpoint_b = link_endpoints + + device_uuid_a, endpoint_uuid_a = device_endpoint_a[0:2] + device_a = self._config_rule_composer.get_device(device_uuid_a) + endpoint_a = device_a.get_endpoint(endpoint_uuid_a) + + device_uuid_b, endpoint_uuid_b = device_endpoint_b[0:2] + device_b = self._config_rule_composer.get_device(device_uuid_b) + endpoint_b = device_b.get_endpoint(endpoint_uuid_b) + + # Compute static routes from networks connected in device_a + for ip_network_a in device_a.connected: + if ip_network_a in device_b.connected: continue + if ip_network_a in device_b.static_routes: continue + if ip_network_a in ROOT_NEIGHBOR_ROUTING_NETWORK: continue + endpoint_a_ip_network = _compose_ipv4_network(endpoint_a.ipv4_address, endpoint_a.ipv4_prefix_len) + next_hop = str(endpoint_a_ip_network.ip) + metric = 1 + device_b.static_routes.setdefault(ip_network_a, dict())[metric] = next_hop + + # Compute static routes from networks connected in device_b + for ip_network_b in device_b.connected: + if ip_network_b in device_a.connected: continue + if ip_network_b in device_a.static_routes: continue + if ip_network_b in ROOT_NEIGHBOR_ROUTING_NETWORK: continue + endpoint_b_ip_network = _compose_ipv4_network(endpoint_b.ipv4_address, endpoint_b.ipv4_prefix_len) + next_hop = str(endpoint_b_ip_network.ip) + metric = 1 + device_a.static_routes.setdefault(ip_network_b, dict())[metric] = next_hop + + # Propagate static routes from networks connected in device_a + for ip_network_a, metric_next_hop in device_a.static_routes.items(): + if ip_network_a in device_b.connected: continue + if ip_network_a in ROOT_NEIGHBOR_ROUTING_NETWORK: continue + endpoint_a_ip_network = _compose_ipv4_network(endpoint_a.ipv4_address, endpoint_a.ipv4_prefix_len) + if ip_network_a in device_b.static_routes: + current_metric = min(device_b.static_routes[ip_network_a].keys()) + else: + current_metric = int(sys.float_info.max) + for metric, next_hop in metric_next_hop.items(): + new_metric = metric + 1 + if new_metric >= current_metric: continue + next_hop_a = str(endpoint_a_ip_network.ip) + device_b.static_routes.setdefault(ip_network_a, dict())[metric] = next_hop_a + + # Propagate static routes from networks connected in device_b + for ip_network_b in device_b.static_routes.keys(): + if ip_network_b in device_a.connected: continue + if ip_network_b in ROOT_NEIGHBOR_ROUTING_NETWORK: continue + endpoint_b_ip_network = _compose_ipv4_network(endpoint_b.ipv4_address, endpoint_b.ipv4_prefix_len) + if ip_network_b in device_a.static_routes: + current_metric = min(device_a.static_routes[ip_network_b].keys()) + else: + current_metric = int(sys.float_info.max) + for metric, next_hop in metric_next_hop.items(): + new_metric = metric + 1 + if new_metric >= current_metric: continue + next_hop_b = str(endpoint_b_ip_network.ip) + device_a.static_routes.setdefault(ip_network_b, dict())[metric] = next_hop_b diff --git a/src/service/service/service_handlers/oc/ConfigRules.py b/src/service/service/service_handlers/oc/ConfigRules.py deleted file mode 100644 index 44245705ecdb48d33c9152328a352d736cc3dd27..0000000000000000000000000000000000000000 --- a/src/service/service/service_handlers/oc/ConfigRules.py +++ /dev/null @@ -1,255 +0,0 @@ -# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import Dict, List -from common.tools.object_factory.ConfigRule import json_config_rule_delete, json_config_rule_set -from service.service.service_handler_api.AnyTreeTools import TreeNode - -def setup_config_rules( - service_uuid : str, connection_uuid : str, device_uuid : str, endpoint_uuid : str, endpoint_name : str, - service_settings : TreeNode, endpoint_settings : TreeNode -) -> List[Dict]: - - if service_settings is None: return [] - if endpoint_settings is None: return [] - - json_settings : Dict = service_settings.value - json_endpoint_settings : Dict = endpoint_settings.value - - service_short_uuid = service_uuid.split('-')[-1] - network_instance_name = '{:s}-NetInst'.format(service_short_uuid) - network_interface_desc = '{:s}-NetIf'.format(service_uuid) - network_subinterface_desc = '{:s}-NetSubIf'.format(service_uuid) - - mtu = json_settings.get('mtu', 1450 ) # 1512 - #address_families = json_settings.get('address_families', [] ) # ['IPV4'] - bgp_as = json_settings.get('bgp_as', 0 ) # 65000 - bgp_route_target = json_settings.get('bgp_route_target', '0:0') # 65000:333 - - #router_id = json_endpoint_settings.get('router_id', '0.0.0.0') # '10.95.0.10' - route_distinguisher = json_endpoint_settings.get('route_distinguisher', '0:0' ) # '60001:801' - sub_interface_index = json_endpoint_settings.get('sub_interface_index', 0 ) # 1 - vlan_id = json_endpoint_settings.get('vlan_id', 1 ) # 400 - address_ip = json_endpoint_settings.get('address_ip', '0.0.0.0') # '2.2.2.1' - address_prefix = json_endpoint_settings.get('address_prefix', 24 ) # 30 - if_subif_name = '{:s}.{:d}'.format(endpoint_name, vlan_id) - - json_config_rules = [ - json_config_rule_set( - '/network_instance[{:s}]'.format(network_instance_name), { - 'name': network_instance_name, 'description': network_interface_desc, 'type': 'L3VRF', - 'route_distinguisher': route_distinguisher, - #'router_id': router_id, 'address_families': address_families, - }), - json_config_rule_set( - '/interface[{:s}]'.format(endpoint_name), { - 'name': endpoint_name, 'description': network_interface_desc, 'mtu': mtu, - }), - json_config_rule_set( - '/interface[{:s}]/subinterface[{:d}]'.format(endpoint_name, sub_interface_index), { - 'name': endpoint_name, 'index': sub_interface_index, - 'description': network_subinterface_desc, 'vlan_id': vlan_id, - 'address_ip': address_ip, 'address_prefix': address_prefix, - }), - json_config_rule_set( - '/network_instance[{:s}]/interface[{:s}]'.format(network_instance_name, if_subif_name), { - 'name': network_instance_name, 'id': if_subif_name, 'interface': endpoint_name, - 'subinterface': sub_interface_index, - }), - json_config_rule_set( - '/network_instance[{:s}]/protocols[BGP]'.format(network_instance_name), { - 'name': network_instance_name, 'identifier': 'BGP', 'protocol_name': 'BGP', 'as': bgp_as, - }), - json_config_rule_set( - '/network_instance[{:s}]/table_connections[STATIC][BGP][IPV4]'.format(network_instance_name), { - 'name': network_instance_name, 'src_protocol': 'STATIC', 'dst_protocol': 'BGP', - 'address_family': 'IPV4', #'default_import_policy': 'REJECT_ROUTE', - }), - json_config_rule_set( - '/network_instance[{:s}]/table_connections[DIRECTLY_CONNECTED][BGP][IPV4]'.format( - network_instance_name), { - 'name': network_instance_name, 'src_protocol': 'DIRECTLY_CONNECTED', 'dst_protocol': 'BGP', - 'address_family': 'IPV4', #'default_import_policy': 'REJECT_ROUTE', - }), - json_config_rule_set( - '/routing_policy/bgp_defined_set[{:s}_rt_import]'.format(network_instance_name), { - 'ext_community_set_name': '{:s}_rt_import'.format(network_instance_name), - }), - json_config_rule_set( - '/routing_policy/bgp_defined_set[{:s}_rt_import][route-target:{:s}]'.format( - network_instance_name, bgp_route_target), { - 'ext_community_set_name': '{:s}_rt_import'.format(network_instance_name), - 'ext_community_member' : 'route-target:{:s}'.format(bgp_route_target), - }), - json_config_rule_set( - '/routing_policy/policy_definition[{:s}_import]'.format(network_instance_name), { - 'policy_name': '{:s}_import'.format(network_instance_name), - }), - json_config_rule_set( - '/routing_policy/policy_definition[{:s}_import]/statement[{:s}]'.format( - network_instance_name, '3'), { - 'policy_name': '{:s}_import'.format(network_instance_name), 'statement_name': '3', - 'ext_community_set_name': '{:s}_rt_import'.format(network_instance_name), - 'match_set_options': 'ANY', 'policy_result': 'ACCEPT_ROUTE', - }), - json_config_rule_set( - # pylint: disable=duplicate-string-formatting-argument - '/network_instance[{:s}]/inter_instance_policies[{:s}_import]'.format( - network_instance_name, network_instance_name), { - 'name': network_instance_name, 'import_policy': '{:s}_import'.format(network_instance_name), - }), - json_config_rule_set( - '/routing_policy/bgp_defined_set[{:s}_rt_export]'.format(network_instance_name), { - 'ext_community_set_name': '{:s}_rt_export'.format(network_instance_name), - }), - json_config_rule_set( - '/routing_policy/bgp_defined_set[{:s}_rt_export][route-target:{:s}]'.format( - network_instance_name, bgp_route_target), { - 'ext_community_set_name': '{:s}_rt_export'.format(network_instance_name), - 'ext_community_member' : 'route-target:{:s}'.format(bgp_route_target), - }), - json_config_rule_set( - '/routing_policy/policy_definition[{:s}_export]'.format(network_instance_name), { - 'policy_name': '{:s}_export'.format(network_instance_name), - }), - json_config_rule_set( - '/routing_policy/policy_definition[{:s}_export]/statement[{:s}]'.format( - network_instance_name, '3'), { - 'policy_name': '{:s}_export'.format(network_instance_name), 'statement_name': '3', - 'ext_community_set_name': '{:s}_rt_export'.format(network_instance_name), - 'match_set_options': 'ANY', 'policy_result': 'ACCEPT_ROUTE', - }), - json_config_rule_set( - # pylint: disable=duplicate-string-formatting-argument - '/network_instance[{:s}]/inter_instance_policies[{:s}_export]'.format( - network_instance_name, network_instance_name), { - 'name': network_instance_name, 'export_policy': '{:s}_export'.format(network_instance_name), - }), - ] - - return json_config_rules - -def teardown_config_rules( - service_uuid : str, connection_uuid : str, device_uuid : str, endpoint_uuid : str, endpoint_name : str, - service_settings : TreeNode, endpoint_settings : TreeNode -) -> List[Dict]: - - if service_settings is None: return [] - if endpoint_settings is None: return [] - - json_settings : Dict = service_settings.value - json_endpoint_settings : Dict = endpoint_settings.value - - #mtu = json_settings.get('mtu', 1450 ) # 1512 - #address_families = json_settings.get('address_families', [] ) # ['IPV4'] - #bgp_as = json_settings.get('bgp_as', 0 ) # 65000 - bgp_route_target = json_settings.get('bgp_route_target', '0:0') # 65000:333 - - #router_id = json_endpoint_settings.get('router_id', '0.0.0.0') # '10.95.0.10' - #route_distinguisher = json_endpoint_settings.get('route_distinguisher', '0:0' ) # '60001:801' - sub_interface_index = json_endpoint_settings.get('sub_interface_index', 0 ) # 1 - vlan_id = json_endpoint_settings.get('vlan_id', 1 ) # 400 - #address_ip = json_endpoint_settings.get('address_ip', '0.0.0.0') # '2.2.2.1' - #address_prefix = json_endpoint_settings.get('address_prefix', 24 ) # 30 - - if_subif_name = '{:s}.{:d}'.format(endpoint_name, vlan_id) - service_short_uuid = service_uuid.split('-')[-1] - network_instance_name = '{:s}-NetInst'.format(service_short_uuid) - #network_interface_desc = '{:s}-NetIf'.format(service_uuid) - #network_subinterface_desc = '{:s}-NetSubIf'.format(service_uuid) - - json_config_rules = [ - json_config_rule_delete( - '/network_instance[{:s}]/interface[{:s}]'.format(network_instance_name, if_subif_name), { - 'name': network_instance_name, 'id': if_subif_name, - }), - json_config_rule_delete( - '/interface[{:s}]/subinterface[{:d}]'.format(endpoint_name, sub_interface_index), { - 'name': endpoint_name, 'index': sub_interface_index, - }), - json_config_rule_delete( - '/interface[{:s}]'.format(endpoint_name), { - 'name': endpoint_name, - }), - json_config_rule_delete( - '/network_instance[{:s}]/table_connections[DIRECTLY_CONNECTED][BGP][IPV4]'.format( - network_instance_name), { - 'name': network_instance_name, 'src_protocol': 'DIRECTLY_CONNECTED', 'dst_protocol': 'BGP', - 'address_family': 'IPV4', - }), - json_config_rule_delete( - '/network_instance[{:s}]/table_connections[STATIC][BGP][IPV4]'.format(network_instance_name), { - 'name': network_instance_name, 'src_protocol': 'STATIC', 'dst_protocol': 'BGP', - 'address_family': 'IPV4', - }), - json_config_rule_delete( - '/network_instance[{:s}]/protocols[BGP]'.format(network_instance_name), { - 'name': network_instance_name, 'identifier': 'BGP', 'protocol_name': 'BGP', - }), - json_config_rule_delete( - # pylint: disable=duplicate-string-formatting-argument - '/network_instance[{:s}]/inter_instance_policies[{:s}_import]'.format( - network_instance_name, network_instance_name), { - 'name': network_instance_name, - }), - json_config_rule_delete( - '/routing_policy/policy_definition[{:s}_import]/statement[{:s}]'.format( - network_instance_name, '3'), { - 'policy_name': '{:s}_import'.format(network_instance_name), 'statement_name': '3', - }), - json_config_rule_delete( - '/routing_policy/policy_definition[{:s}_import]'.format(network_instance_name), { - 'policy_name': '{:s}_import'.format(network_instance_name), - }), - json_config_rule_delete( - '/routing_policy/bgp_defined_set[{:s}_rt_import][route-target:{:s}]'.format( - network_instance_name, bgp_route_target), { - 'ext_community_set_name': '{:s}_rt_import'.format(network_instance_name), - 'ext_community_member' : 'route-target:{:s}'.format(bgp_route_target), - }), - json_config_rule_delete( - '/routing_policy/bgp_defined_set[{:s}_rt_import]'.format(network_instance_name), { - 'ext_community_set_name': '{:s}_rt_import'.format(network_instance_name), - }), - json_config_rule_delete( - # pylint: disable=duplicate-string-formatting-argument - '/network_instance[{:s}]/inter_instance_policies[{:s}_export]'.format( - network_instance_name, network_instance_name), { - 'name': network_instance_name, - }), - json_config_rule_delete( - '/routing_policy/policy_definition[{:s}_export]/statement[{:s}]'.format( - network_instance_name, '3'), { - 'policy_name': '{:s}_export'.format(network_instance_name), 'statement_name': '3', - }), - json_config_rule_delete( - '/routing_policy/policy_definition[{:s}_export]'.format(network_instance_name), { - 'policy_name': '{:s}_export'.format(network_instance_name), - }), - json_config_rule_delete( - '/routing_policy/bgp_defined_set[{:s}_rt_export][route-target:{:s}]'.format( - network_instance_name, bgp_route_target), { - 'ext_community_set_name': '{:s}_rt_export'.format(network_instance_name), - 'ext_community_member' : 'route-target:{:s}'.format(bgp_route_target), - }), - json_config_rule_delete( - '/routing_policy/bgp_defined_set[{:s}_rt_export]'.format(network_instance_name), { - 'ext_community_set_name': '{:s}_rt_export'.format(network_instance_name), - }), - json_config_rule_delete( - '/network_instance[{:s}]'.format(network_instance_name), { - 'name': network_instance_name - }), - ] - return json_config_rules diff --git a/src/service/service/service_handlers/oc/OCServiceHandler.py b/src/service/service/service_handlers/oc/OCServiceHandler.py index 73ff3b89dea808d7a857cd52d5b4d4e581d218cc..277080d6589d08e9765b16962b2cc5737c2c50af 100644 --- a/src/service/service/service_handlers/oc/OCServiceHandler.py +++ b/src/service/service/service_handlers/oc/OCServiceHandler.py @@ -15,15 +15,18 @@ import json, logging from typing import Any, List, Optional, Tuple, Union from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method -from common.proto.context_pb2 import ConfigRule, DeviceId, Service +from common.proto.context_pb2 import DeviceId, Service from common.tools.object_factory.Device import json_device_id from common.type_checkers.Checkers import chk_type -from service.service.service_handler_api.Tools import get_device_endpoint_uuids, get_endpoint_matching +from common.DeviceTypes import DeviceTypeEnum +from service.service.service_handler_api.Tools import get_endpoint_matching from service.service.service_handler_api._ServiceHandler import _ServiceHandler from service.service.service_handler_api.SettingsHandler import SettingsHandler from service.service.task_scheduler.TaskExecutor import TaskExecutor -from .ConfigRules import setup_config_rules, teardown_config_rules -from .OCTools import convert_endpoints_to_flows, handle_flows_names +from .OCTools import ( + convert_endpoints_to_flows, endpoints_to_flows, + #handle_flows_names, check_media_channel_existance +) LOGGER = logging.getLogger(__name__) @@ -47,70 +50,88 @@ class OCServiceHandler(_ServiceHandler): is_opticalband =False #service_uuid = self.__service.service_id.service_uuid.uuid settings=None + if self.__settings_handler.get('/settings-ob_{}'.format(connection_uuid)): is_opticalband=True settings = self.__settings_handler.get('/settings-ob_{}'.format(connection_uuid)) else: settings = self.__settings_handler.get('/settings') - + bidir = settings.value.get("bidir") + LOGGER.debug(f"Bidir bvalue is: {bidir}") # settings = self.__settings_handler.get('/settings') #flow is the new variable that stores input-output relationship - - flows = convert_endpoints_to_flows(endpoints) + #flows = convert_endpoints_to_flows(endpoints) + flows = endpoints_to_flows(endpoints, bidir, is_opticalband) #handled_flows=handle_flows_names(flows=flows,task_executor=self.__task_executor) - - #LOGGER.info("Handled Flows %s",handled_flows) - + results = [] + #new cycle for setting optical devices - for device_uuid in flows.keys(): + for device_uuid, dev_flows in flows.items(): try: - dev_flows = flows[device_uuid] device_obj = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid))) - - - if (settings): - + if settings: self.__task_executor.configure_optical_device(device_obj, settings, dev_flows, is_opticalband) results.append(True) except Exception as e: # pylint: disable=broad-except LOGGER.exception('Unable to configure Device({:s})'.format(str(device_uuid))) results.append(e) - + return results @metered_subclass_method(METRICS_POOL) def DeleteEndpoint( self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None ) -> List[Union[bool, Exception]]: + is_opticalband =False + flows = convert_endpoints_to_flows(endpoints) + chk_type('endpoints', endpoints, list) if len(endpoints) == 0: return [] - service_uuid = self.__service.service_id.service_uuid.uuid - settings = self.__settings_handler.get('/settings') + if self.__settings_handler.get('/settings-ob_{}'.format(connection_uuid)): + is_opticalband =True + settings = self.__settings_handler.get('/settings-ob_{}'.format(connection_uuid)) + else: + settings = self.__settings_handler.get('/settings') results = [] - for endpoint in endpoints: + for device_uuid, dev_flows in flows.items(): try: - device_uuid, endpoint_uuid = get_device_endpoint_uuids(endpoint) - + channel_indexes= [] device_obj = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid))) - endpoint_obj = get_endpoint_matching(device_obj, endpoint_uuid) - endpoint_settings = self.__settings_handler.get_endpoint_settings(device_obj, endpoint_obj) - endpoint_name = endpoint_obj.name - - json_config_rules = teardown_config_rules( - service_uuid, connection_uuid, device_uuid, endpoint_uuid, endpoint_name, - settings, endpoint_settings) - - if len(json_config_rules) > 0: - del device_obj.device_config.config_rules[:] - for json_config_rule in json_config_rules: - device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule)) - self.__task_executor.configure_device(device_obj) + if (device_obj.device_type == DeviceTypeEnum.OPTICAL_TRANSPONDER._value_): + for endpoint in dev_flows: + src , dst = endpoint + src_enpoint_name='0' + dist_enpoint_name='0' + if src !="0": + src_endponit_obj =get_endpoint_matching(device_obj, src) + src_enpoint_name=src_endponit_obj.name + if dst !="0": + dst_endpoint_obj = get_endpoint_matching(device_obj, dst) + dist_enpoint_name=dst_endpoint_obj.name + channel_indexes.append((src_enpoint_name,dist_enpoint_name)) + else: + if not is_opticalband: + if 'flow_id' in settings.value: + channel_indexes.append(settings.value["flow_id"]) + elif is_opticalband: + if "ob_id" in settings.value: + channel_indexes.append(settings.value["ob_id"]) + + if len(channel_indexes) > 0: + errors = self.__task_executor.deconfigure_optical_device( + device=device_obj, channel_indexes=channel_indexes, + is_opticalband=is_opticalband, dev_flow=dev_flows + ) + # if (len(errors)==0): + # service_id =self.__service.service_id + # if not is_opticalband : + # self.__task_executor.delete_setting(service_id,"/settings","value") results.append(True) except Exception as e: # pylint: disable=broad-except LOGGER.exception('Unable to DeleteEndpoint({:s})'.format(str(endpoint))) @@ -157,13 +178,21 @@ class OCServiceHandler(_ServiceHandler): def DeleteConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: chk_type('resources', resources, list) if len(resources) == 0: return [] + service_id = self.__service.service_id results = [] for resource in resources: try: self.__settings_handler.delete(resource[0]) + # self.__task_executor.delete_setting(service_id,"/settings","value") except Exception as e: # pylint: disable=broad-except LOGGER.exception('Unable to DeleteConfig({:s})'.format(str(resource))) results.append(e) return results + + def check_media_channel(self,connection_uuid): + if self.__settings_handler.get('/settings-ob_{}'.format(connection_uuid)): + return False + else: + return True diff --git a/src/service/service/service_handlers/oc/OCTools.py b/src/service/service/service_handlers/oc/OCTools.py index 7b6c344ede5512f32098fd019076ae57fe87028e..e9097c7e7136303586edc1d7c1dc56d89ad0b3dc 100644 --- a/src/service/service/service_handlers/oc/OCTools.py +++ b/src/service/service/service_handlers/oc/OCTools.py @@ -12,11 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -from service.service.service_handler_api.Tools import get_device_endpoint_uuids, get_endpoint_matching -from typing import Dict, Any, List, Optional, Tuple -import logging -from common.proto.context_pb2 import ConfigRule, DeviceId, Service +import logging, json +from typing import Dict, List, Optional, Tuple +from common.proto.context_pb2 import DeviceId, Service from common.tools.object_factory.Device import json_device_id +from service.service.service_handler_api.Tools import get_endpoint_matching log = logging.getLogger(__name__) @@ -116,27 +116,227 @@ def convert_endpoints_to_flows(endpoints : List[Tuple[str, str, Optional[str]]]) return entries -def get_device_endpint_name (endpoint_uuid:str,device_uuid:str,task_executor)->Tuple: +def ob_flows(endpoints : List[Tuple[str, str, Optional[str]]], bidir : int): + entries = {} + end = len(endpoints) + i = 0 + if bidir: + endpoint = endpoints[i] + device_uuid, endpoint_uuid = endpoint[0:2] + + if device_uuid not in entries.keys(): + entries[device_uuid] = [] + entry_tuple = "0", endpoint_uuid + entries[device_uuid].append(entry_tuple) + next_endpoint = endpoints[i+1] + next_device_uuid, next_endpoint_uuid = next_endpoint[0:2] + if next_device_uuid == device_uuid: + if next_device_uuid not in entries.keys(): + entries[next_device_uuid] = [] + entry_tuple = next_endpoint_uuid, "0" + entries[next_device_uuid].append(entry_tuple) + else: + log.info("error expected device_id {}, found {}".format(device_uuid, next_device_uuid)) + return {} + i = i + 2 + if end > 4: + + while(i < end-2): + #i + endpoint = endpoints[i] + device_uuid, endpoint_uuid = endpoint[0:2] + log.debug("current OCTools step {}, {}, {}".format(i, device_uuid, endpoint_uuid)) + if device_uuid not in entries.keys(): + entries[device_uuid] = [] + #i+1 + next_endpoint = endpoints[i+1] + next_device_uuid, next_endpoint_uuid = next_endpoint[0:2] + if next_device_uuid == device_uuid: + entry_tuple = endpoint_uuid, next_endpoint_uuid + entries[device_uuid].append(entry_tuple) + else: + log.debug("ERROR in bidirectional ob") + log.debug("{}, {}, {}".format(i, next_device_uuid, device_uuid)) + return {} + #i+2 + next_2_endpoint = endpoints[i+2] + next_2_device_uuid, next_2_endpoint_uuid = next_2_endpoint[0:2] + #i+3 + next_3_endpoint = endpoints[i+3] + next_3_device_uuid, next_3_endpoint_uuid = next_3_endpoint[0:2] + if next_2_device_uuid == next_3_device_uuid and next_3_device_uuid == device_uuid: + entry_tuple = next_2_endpoint_uuid, next_3_endpoint_uuid + entries[device_uuid].append(entry_tuple) + i = i + 4 + else: + log.debug("ERROR in bidirection ob") + return {} + endpoint = endpoints[i] + device_uuid, endpoint_uuid = endpoint[0:2] + if device_uuid not in entries.keys(): + entries[device_uuid] = [] + entry_tuple = endpoint_uuid, "0", + entries[device_uuid].append(entry_tuple) + next_endpoint = endpoints[i+1] + next_device_uuid, next_endpoint_uuid = next_endpoint[0:2] + if next_device_uuid == device_uuid: + if next_device_uuid not in entries.keys(): + entries[next_device_uuid] = [] + entry_tuple = "0", next_endpoint_uuid + entries[next_device_uuid].append(entry_tuple) + else: + log.debug("error expected device_id {}, found {}".format(device_uuid, next_device_uuid)) + else: + endpoint = endpoints[i] + device_uuid, endpoint_uuid = endpoint[0:2] + + if device_uuid not in entries.keys(): + entries[device_uuid] = [] + entry_tuple = "0", endpoint_uuid + entries[device_uuid].append(entry_tuple) + i = i + 1 + if end > 2: + + while(i < end-1): + #i + endpoint = endpoints[i] + device_uuid, endpoint_uuid = endpoint[0:2] + + if device_uuid not in entries.keys(): + entries[device_uuid] = [] + #i+1 + next_endpoint = endpoints[i+1] + next_device_uuid, next_endpoint_uuid = next_endpoint[0:2] + if next_device_uuid == device_uuid: + entry_tuple = endpoint_uuid, next_endpoint_uuid + entries[device_uuid].append(entry_tuple) + else: + log.debug("ERROR in bidirectional ob") + log.debug("{}, {}, {}".format(i, next_device_uuid, device_uuid)) + return {} + i = i + 2 + next_endpoint = endpoints[i] + next_device_uuid, next_endpoint_uuid = next_endpoint[0:2] + if next_device_uuid not in entries.keys(): + entries[next_device_uuid] = [] + entry_tuple = next_endpoint_uuid, "0" + entries[next_device_uuid].append(entry_tuple) + return entries + + +def conn_flows(endpoints : List[Tuple[str, str, Optional[str]]], bidir : int): + entries = {} + end = len(endpoints) + i = 0 + #tx tp + endpoint = endpoints[i] + device_uuid, endpoint_uuid = endpoint[0:2] + + if device_uuid not in entries.keys(): + entries[device_uuid] = [] + entry_tuple = "0", endpoint_uuid + entries[device_uuid].append(entry_tuple) + i = i + 1 + #if bidir reading 4 endpoints per node + if bidir: + i = i + 1 + while(i < end-2): + #i + endpoint = endpoints[i] + device_uuid, endpoint_uuid = endpoint[0:2] + + if device_uuid not in entries.keys(): + entries[device_uuid] = [] + #i+1 + next_endpoint = endpoints[i+1] + next_device_uuid, next_endpoint_uuid = next_endpoint[0:2] + if next_device_uuid == device_uuid: + entry_tuple = endpoint_uuid, next_endpoint_uuid + entries[device_uuid].append(entry_tuple) + else: + + return {} + #i+2 + + next_2_endpoint = endpoints[i+2] + next_2_device_uuid, next_2_endpoint_uuid = next_2_endpoint[0:2] + #i+3 + next_3_endpoint = endpoints[i+3] + next_3_device_uuid, next_3_endpoint_uuid = next_3_endpoint[0:2] + if next_2_device_uuid == next_3_device_uuid and next_3_device_uuid == device_uuid: + entry_tuple = next_2_endpoint_uuid, next_3_endpoint_uuid + entries[device_uuid].append(entry_tuple) + i = i + 4 + else: + + return {} + else: + while(i < end-1): + #i + endpoint = endpoints[i] + device_uuid, endpoint_uuid = endpoint[0:2] + + if device_uuid not in entries.keys(): + entries[device_uuid] = [] + #i+1 + next_endpoint = endpoints[i+1] + next_device_uuid, next_endpoint_uuid = next_endpoint[0:2] + if next_device_uuid == device_uuid: + entry_tuple = endpoint_uuid, next_endpoint_uuid + entries[device_uuid].append(entry_tuple) + i = i + 2 + else: + return {} + #rx tp + endpoint = endpoints[i] + device_uuid, endpoint_uuid = endpoint[0:2] + if device_uuid not in entries.keys(): + entries[device_uuid] = [] + entry_tuple = endpoint_uuid, "0", + entries[device_uuid].append(entry_tuple) + return entries + +def endpoints_to_flows(endpoints : List[Tuple[str, str, Optional[str]]], bidir : int, is_ob: bool)->Dict: + if is_ob: + entries = ob_flows(endpoints, bidir) + else: + entries = conn_flows(endpoints, bidir) + return entries + +def get_device_endpint_name(endpoint_uuid : str, device_uuid : str, task_executor) -> Tuple: device_obj = task_executor.get_device(DeviceId(**json_device_id(device_uuid))) endpoint_obj = get_endpoint_matching(device_obj, endpoint_uuid) endpoint_name = endpoint_obj.name return (device_obj.name, endpoint_name) -def handle_flows_names (task_executor,flows:dict)->Dict : - new_flows={} - for index,( device_uuid_key , device_endpoints_list) in enumerate(flows.items()): +def handle_flows_names(task_executor, flows : dict) -> Dict: + new_flows = {} + for index, (device_uuid_key, device_endpoints_list) in enumerate(flows.items()): for endpoint_tupple in device_endpoints_list: - source_port=None - destination_port=None - device_name="" - source_endpoint,destination_endpoint =endpoint_tupple - if (source_endpoint !='0'): - if get_device_endpint_name(source_endpoint,device_uuid_key,task_executor) is not None: - device_name,source_port=get_device_endpint_name(source_endpoint,device_uuid_key,task_executor) - if (destination_endpoint !='0'): - if get_device_endpint_name(destination_endpoint,device_uuid_key,task_executor) is not None: - device_name,destination_port=get_device_endpint_name(destination_endpoint,device_uuid_key,task_executor) - if (device_name not in new_flows): - new_flows[device_name]=[] - new_flows[device_name].append((source_port,destination_port)) + source_port = None + destination_port = None + device_name = "" + source_endpoint, destination_endpoint = endpoint_tupple + if source_endpoint != '0': + if get_device_endpint_name(source_endpoint, device_uuid_key, task_executor) is not None: + device_name, source_port = get_device_endpint_name( + source_endpoint, device_uuid_key, task_executor + ) + if destination_endpoint != '0': + if get_device_endpint_name(destination_endpoint, device_uuid_key, task_executor) is not None: + device_name, destination_port = get_device_endpint_name( + destination_endpoint, device_uuid_key, task_executor + ) + if device_name not in new_flows: + new_flows[device_name] = [] + new_flows[device_name].append((source_port, destination_port)) return new_flows + +def check_media_channel_existance(service : Service): + has_media_channel = False + for config_rule in service.service_config.config_rules: + if isinstance(config_rule.custom.resource_value, str): + settings = json.dumps(config_rule.custom.resource_value) + if "flow_id" in settings: + has_media_channel = True + return has_media_channel diff --git a/src/service/service/task_scheduler/TaskExecutor.py b/src/service/service/task_scheduler/TaskExecutor.py index 67f6a516c9bc3031b5cdd2aed80cd6fdd7e1c9c2..ac06e321d342fc7cddb9f54958a38ed87067c922 100644 --- a/src/service/service/task_scheduler/TaskExecutor.py +++ b/src/service/service/task_scheduler/TaskExecutor.py @@ -14,13 +14,14 @@ import json, logging from enum import Enum -from typing import TYPE_CHECKING, Any, Dict, Optional, Union +from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, Union +from common.DeviceTypes import DeviceTypeEnum from common.method_wrappers.ServiceExceptions import NotFoundException from typing import List from common.proto.qkd_app_pb2 import QKDAppStatusEnum from common.proto.context_pb2 import ( Connection, ConnectionId, Device, DeviceDriverEnum, DeviceId, Service, ServiceId, - OpticalConfig, OpticalConfigId + OpticalConfig, OpticalConfigId, ConnectionList, ServiceConfigRule ) from common.proto.qkd_app_pb2 import App, AppId from common.proto.context_pb2 import ContextId @@ -37,6 +38,7 @@ from service.service.service_handler_api.Exceptions import ( ) from service.service.service_handler_api.ServiceHandlerFactory import ServiceHandlerFactory, get_service_handler_class from service.service.tools.ObjectKeys import get_connection_key, get_device_key, get_service_key, get_qkd_app_key +from service.service.tools.object_uuid import opticalconfig_get_uuid if TYPE_CHECKING: from service.service.service_handler_api._ServiceHandler import _ServiceHandler @@ -125,17 +127,27 @@ class TaskExecutor: self._store_grpc_object(CacheableObjectType.DEVICE, device_key, device) # New function Andrea for Optical Devices - def configure_optical_device(self, device : Device, settings : str, flows : list, is_opticalband : bool): + def configure_optical_device( + self, device : Device, settings : str, flows : list, is_opticalband : bool + ): device_key = get_device_key(device.device_id) optical_config_id = OpticalConfigId() - optical_config_id.opticalconfig_uuid = device.device_id.device_uuid.uuid + optical_config_id.opticalconfig_uuid = opticalconfig_get_uuid(device.device_id) + optical_config = OpticalConfig() - setting = settings.value if settings else '' + + setting = settings.value if settings else "" + config_type = None try: result = self._context_client.SelectOpticalConfig(optical_config_id) - if result is not None: - new_config = json.loads(result.config) + + new_config = json.loads(result.config) + if 'type' in new_config: + config_type=new_config['type'] + if config_type == 'optical-transponder': + setting['status']='ENABLED' + if result is not None : new_config["new_config"] = setting new_config["is_opticalband"] = is_opticalband new_config["flow"] = flows @@ -144,7 +156,86 @@ class TaskExecutor: self._device_client.ConfigureOpticalDevice(optical_config) self._store_grpc_object(CacheableObjectType.DEVICE, device_key, device) except Exception as e: - LOGGER.info("error in config my config %s",e) + LOGGER.info("error in configure_optical_device %s",e) + + # Deconfiguring Optical Devices ( CNIT ) + def deconfigure_optical_device( + self, device : Device, channel_indexes : list, is_opticalband : bool, dev_flow : list + ): + errors = [] + flows = [] + indexes = {} + new_config = {} + optical_config_id = OpticalConfigId() + optical_config_id.opticalconfig_uuid = opticalconfig_get_uuid(device.device_id) + # if transponder the channel index is same as its endpoint + if device.device_type == DeviceTypeEnum.OPTICAL_TRANSPONDER._value_: + for index in channel_indexes : + flows.append(index) + # if Roadm the channel index is the flow_id ,or ob_id + else: + for index in channel_indexes: + if not is_opticalband: + indexes["flow_id"] = index + else: + indexes["ob_id"] = index + + try: + result = self._context_client.SelectOpticalConfig(optical_config_id) + # for extractor in device service to extract the index , dummy data for freq and band required + indexes["frequency"] = None + indexes["band"] = None + if result is not None: + new_config = json.loads(result.config) + new_config["new_config"]=indexes + new_config["flow"] = flows if len(flows)>0 else dev_flow + new_config["is_opticalband"] = is_opticalband + result.config = json.dumps(new_config) + # new_optical_config.config= json.dumps(new_config) + # new_optical_config.opticalconfig_id.CopyFrom (optical_config_id) + # new_optical_config.device_id.CopyFrom(device.device_id) + self._device_client.DisableOpticalDevice(result) + except Exception as e: + errors.append(e) + LOGGER.info("error in deconfigure_optical_device %s",e) + return errors + + def delete_setting( + self, service_id : ServiceId, config_key : str, config_value : str + ): + service_configRule = ServiceConfigRule() + service_configRule.service_id.CopyFrom( service_id) + service_configRule.configrule_custom.resource_key = config_key + service_configRule.configrule_custom.resource_value = config_value + try: + ctxt = ContextClient() + ctxt.connect() + ctxt.DeleteServiceConfigRule(service_configRule) + ctxt.close() + except Exception as e : + LOGGER.info("error in delete service config rule %s",e) + + def check_service_for_media_channel(self, connections : ConnectionList, item) -> bool: + service = item + if (isinstance(item, ServiceId)): + service = self.get_service(item) + class_service_handler = None + service_handler_settings = {} + for connection in connections.connections: + connection_uuid=connection.connection_id.connection_uuid + if class_service_handler is None: + class_service_handler = self.get_service_handler(connection, service,**service_handler_settings) + if class_service_handler.check_media_channel(connection_uuid): + return True + return False + + def check_connection_for_media_channel( + self, connection : Connection, service : Service + ) -> bool: + service_handler_settings = {} + connection_uuid = connection.connection_id.connection_uuid + service_handler_class = self.get_service_handler(connection, service, **service_handler_settings) + return service_handler_class.check_media_channel(connection_uuid) def get_device_controller(self, device : Device) -> Optional[Device]: #json_controller = None @@ -166,8 +257,8 @@ class TaskExecutor: def get_devices_from_connection( self, connection : Connection, exclude_managed_by_controller : bool = False - ) -> Dict[str, Device]: - devices = dict() + ) -> Dict[DeviceTypeEnum, Dict[str, Device]]: + devices : Dict[DeviceTypeEnum, Dict[str, Device]] = dict() for endpoint_id in connection.path_hops_endpoint_ids: device = self.get_device(endpoint_id.device_id) device_uuid = endpoint_id.device_id.device_uuid.uuid @@ -175,11 +266,14 @@ class TaskExecutor: controller = self.get_device_controller(device) if controller is None: - devices[device_uuid] = device + device_type = DeviceTypeEnum._value2member_map_[device.device_type] + devices.setdefault(device_type, dict())[device_uuid] = device else: if not exclude_managed_by_controller: - devices[device_uuid] = device - devices[controller.device_id.device_uuid.uuid] = controller + device_type = DeviceTypeEnum._value2member_map_[device.device_type] + devices.setdefault(device_type, dict())[device_uuid] = device + device_type = DeviceTypeEnum._value2member_map_[controller.device_type] + devices.setdefault(device_type, dict())[controller.device_id.device_uuid.uuid] = controller return devices # ----- Service-related methods ------------------------------------------------------------------------------------ @@ -206,28 +300,36 @@ class TaskExecutor: # ----- Service Handler Factory ------------------------------------------------------------------------------------ - def get_service_handler( + def get_service_handlers( self, connection : Connection, service : Service, **service_handler_settings - ) -> '_ServiceHandler': - connection_devices = self.get_devices_from_connection(connection, exclude_managed_by_controller=True) - try: - service_handler_class = get_service_handler_class( - self._service_handler_factory, service, connection_devices) - return service_handler_class(service, self, **service_handler_settings) - except (UnsatisfiedFilterException, UnsupportedFilterFieldException, UnsupportedFilterFieldValueException): - dict_connection_devices = { - cd_data.name : (cd_uuid, cd_data.name, { - (device_driver, DeviceDriverEnum.Name(device_driver)) - for device_driver in cd_data.device_drivers - }) - for cd_uuid,cd_data in connection_devices.items() - } - LOGGER.exception( - 'Unable to select service handler. service={:s} connection={:s} connection_devices={:s}'.format( + ) -> Dict[DeviceTypeEnum, Tuple['_ServiceHandler', Dict[str, Device]]]: + connection_device_types : Dict[DeviceTypeEnum, Dict[str, Device]] = self.get_devices_from_connection( + connection, exclude_managed_by_controller=True + ) + service_handlers : Dict[DeviceTypeEnum, Tuple['_ServiceHandler', Dict[str, Device]]] = dict() + for device_type, connection_devices in connection_device_types.items(): + try: + service_handler_class = get_service_handler_class( + self._service_handler_factory, service, connection_devices) + service_handler = service_handler_class(service, self, **service_handler_settings) + service_handlers[device_type] = (service_handler, connection_devices) + except ( + UnsatisfiedFilterException, UnsupportedFilterFieldException, + UnsupportedFilterFieldValueException + ): + dict_connection_devices = { + cd_data.name : (cd_uuid, cd_data.name, { + (device_driver, DeviceDriverEnum.Name(device_driver)) + for device_driver in cd_data.device_drivers + }) + for cd_uuid,cd_data in connection_devices.items() + } + MSG = 'Unable to select service handler. service={:s} connection={:s} connection_devices={:s}' + LOGGER.exception(MSG.format( grpc_message_to_json_string(service), grpc_message_to_json_string(connection), str(dict_connection_devices) - ) - ) + )) + return service_handlers # ----- QkdApp-related methods ------------------------------------------------------------------------------------- @@ -284,4 +386,3 @@ class TaskExecutor: except Exception as e: LOGGER.error(f"Failed to delete QKD app with AppId {app_id}: {str(e)}") raise e - diff --git a/src/service/service/task_scheduler/TaskScheduler.py b/src/service/service/task_scheduler/TaskScheduler.py index b492b4581b05cb34bed624efd9be2d4f62da5856..c2d8423156fd145a262108701860aeb17df3e6f9 100644 --- a/src/service/service/task_scheduler/TaskScheduler.py +++ b/src/service/service/task_scheduler/TaskScheduler.py @@ -14,17 +14,23 @@ import graphlib, logging, queue, time from typing import TYPE_CHECKING, Dict, Tuple -from common.proto.context_pb2 import Connection, ConnectionId, Service, ServiceId, ServiceStatusEnum +from common.proto.context_pb2 import ( + Connection, ConnectionId, Service, ServiceId, ServiceStatusEnum, ConnectionList +) from common.proto.pathcomp_pb2 import PathCompReply from common.tools.grpc.Tools import grpc_message_to_json_string from context.client.ContextClient import ContextClient from service.service.tools.ObjectKeys import get_connection_key, get_service_key from .tasks._Task import _Task from .tasks.Task_ConnectionConfigure import Task_ConnectionConfigure +from .tasks.Task_OpticalConnectionDeconfigure import Task_OpticalConnectionDeconfigure +from .tasks.Task_OpticalServiceDelete import Task_OpticalServiceDelete from .tasks.Task_ConnectionDeconfigure import Task_ConnectionDeconfigure from .tasks.Task_ServiceDelete import Task_ServiceDelete from .tasks.Task_ServiceSetStatus import Task_ServiceSetStatus from .TaskExecutor import CacheableObjectType, TaskExecutor +from .tasks.Task_OpticalServiceConfigDelete import Task_OpticalServiceConfigDelete +from service.service.tools.OpticalTools import delete_lightpath if TYPE_CHECKING: from service.service.service_handler_api.ServiceHandlerFactory import ServiceHandlerFactory @@ -70,6 +76,7 @@ class TasksScheduler: return service_planned_key, service_active_key def _service_remove(self, service_id : ServiceId) -> Tuple[str, str]: + service_removing_key = self._add_task_if_not_exists(Task_ServiceSetStatus( self._executor, service_id, ServiceStatusEnum.SERVICESTATUS_PENDING_REMOVAL)) @@ -79,6 +86,21 @@ class TasksScheduler: self._dag.add(service_delete_key, service_removing_key) return service_removing_key, service_delete_key + def _optical_service_remove( + self, service_id : ServiceId, has_media_channel : bool, has_optical_band = True + ) -> Tuple[str, str]: + service_removing_key = self._add_task_if_not_exists(Task_ServiceSetStatus( + self._executor, service_id, ServiceStatusEnum.SERVICESTATUS_ACTIVE + )) + + service_delete_key = self._add_task_if_not_exists(Task_OpticalServiceDelete( + self._executor, service_id, has_media_channel, has_optical_band + )) + + # deleting a service requires the service is in removing state + self._dag.add(service_delete_key, service_removing_key) + return service_removing_key, service_delete_key + def _connection_configure(self, connection_id : ConnectionId, service_id : ServiceId) -> str: connection_configure_key = self._add_task_if_not_exists(Task_ConnectionConfigure( self._executor, connection_id)) @@ -97,20 +119,56 @@ class TasksScheduler: def _connection_deconfigure(self, connection_id : ConnectionId, service_id : ServiceId) -> str: connection_deconfigure_key = self._add_task_if_not_exists(Task_ConnectionDeconfigure( - self._executor, connection_id)) + self._executor, connection_id + )) # the connection deconfiguration depends on its connection's service being in removing state service_pending_removal_key = self._add_task_if_not_exists(Task_ServiceSetStatus( - self._executor, service_id, ServiceStatusEnum.SERVICESTATUS_PENDING_REMOVAL)) + self._executor, service_id, ServiceStatusEnum.SERVICESTATUS_PENDING_REMOVAL + )) self._dag.add(connection_deconfigure_key, service_pending_removal_key) # the connection's service depends on the connection deconfiguration to transition to delete service_delete_key = self._add_task_if_not_exists(Task_ServiceDelete( - self._executor, service_id)) + self._executor, service_id + )) self._dag.add(service_delete_key, connection_deconfigure_key) return connection_deconfigure_key + def _optical_connection_deconfigure( + self, connection_id : ConnectionId, service_id : ServiceId, + has_media_channel : bool, has_optical_band = True + ) -> str: + connection_deconfigure_key = self._add_task_if_not_exists(Task_OpticalConnectionDeconfigure( + self._executor, connection_id, has_media_channel=has_media_channel + )) + + # the connection deconfiguration depends on its connection's service being in removing state + service_pending_removal_key = self._add_task_if_not_exists(Task_ServiceSetStatus( + self._executor, service_id, ServiceStatusEnum.SERVICESTATUS_ACTIVE + )) + self._dag.add(connection_deconfigure_key, service_pending_removal_key) + + service_delete_key = self._add_task_if_not_exists(Task_OpticalServiceDelete( + self._executor, service_id, has_media_channel, has_optical_band + )) + self._dag.add(service_delete_key, connection_deconfigure_key) + + return connection_deconfigure_key + + def _optical_service_config_remove( + self, connection_id : ConnectionId, service_id : ServiceId + ) -> str: + service_config_key = self._add_task_if_not_exists(Task_OpticalServiceConfigDelete( + self._executor, connection_id, service_id + )) + service_pending_removal_key = self._add_task_if_not_exists(Task_ServiceSetStatus( + self._executor, service_id, ServiceStatusEnum.SERVICESTATUS_ACTIVE + )) + self._dag.add(service_config_key, service_pending_removal_key) + return service_config_key + def compose_from_pathcompreply(self, pathcomp_reply : PathCompReply, is_delete : bool = False) -> None: t0 = time.time() include_service = self._service_remove if is_delete else self._service_create @@ -132,6 +190,136 @@ class TasksScheduler: t1 = time.time() LOGGER.debug('[compose_from_pathcompreply] elapsed_time: {:f} sec'.format(t1-t0)) + def check_service_for_media_channel( + self, connections : ConnectionList, item + )->Tuple[bool, bool]: + service = item + has_media_channel = False + has_optical_band = False + if isinstance(item, ServiceId): + service = self._executor.get_service(item) + class_service_handler = None + service_handler_settings = {} + for connection in connections.connections: + connection_uuid = connection.connection_id.connection_uuid.uuid + if class_service_handler is None: + class_service_handler = self._executor.get_service_handler( + connection, service, **service_handler_settings + ) + if class_service_handler.check_media_channel(connection_uuid): + has_media_channel = True + else : + has_optical_band = True + return (has_media_channel, has_optical_band) + + def compose_from_optical_service( + self, service : Service, params : dict, is_delete : bool = False + ) -> None: + t0 = time.time() + include_service = self._optical_service_remove if is_delete else self._service_create + include_connection = self._optical_connection_deconfigure if is_delete else self._connection_configure + include_service_config = self._optical_service_config_remove if is_delete else None + + explored_items = set() + pending_items_to_explore = queue.Queue() + pending_items_to_explore.put(service) + has_media_channel = None + has_optical_band = None + reply = None + code = 0 + reply_not_allowed = "DELETE_NOT_ALLOWED" + while not pending_items_to_explore.empty(): + try: + item = pending_items_to_explore.get(block=False) + except queue.Empty: + break + + if isinstance(item, Service): + str_item_key = grpc_message_to_json_string(item.service_id) + if str_item_key in explored_items: continue + connections = self._context_client.ListConnections(item.service_id) + has_media_channel, has_optical_band = self.check_service_for_media_channel( + connections=connections, item=item.service_id + ) + + if len(service.service_config.config_rules) > 0: + reply, code = delete_lightpath( + params['src'], params ['dst'], params['bitrate'], params['ob_id'], + delete_band=not has_media_channel, flow_id= params['flow_id'] + ) + + if code == 400 and reply_not_allowed in reply: + MSG = 'Deleteion for the service is not Allowed , Served Lightpaths is not empty' + raise Exception(MSG) + + include_service( + item.service_id, has_media_channel=has_media_channel, has_optical_band=has_optical_band + ) + self._add_service_to_executor_cache(item) + + for connection in connections.connections: + self._add_connection_to_executor_cache(connection) + pending_items_to_explore.put(connection) + explored_items.add(str_item_key) + + elif isinstance(item, ServiceId): + if code == 400 and reply_not_allowed in reply: break + str_item_key = grpc_message_to_json_string(item) + if str_item_key in explored_items: continue + connections = self._context_client.ListConnections(item) + has_media_channel, has_optical_band = self.check_service_for_media_channel( + connections=connections, item=item + ) + include_service( + item, has_media_channel=has_media_channel, has_optical_band=has_optical_band + ) + self._executor.get_service(item) + + for connection in connections.connections: + self._add_connection_to_executor_cache(connection) + pending_items_to_explore.put(connection) + explored_items.add(str_item_key) + + elif isinstance(item, Connection): + if code == 400 and reply_not_allowed in reply:break + str_item_key = grpc_message_to_json_string(item.connection_id) + if str_item_key in explored_items: continue + connection_key = include_connection( + item.connection_id, item.service_id, has_media_channel=has_media_channel, + has_optical_band=has_optical_band + ) + self._add_connection_to_executor_cache(connection) + + if include_service_config is not None : + connections_list = ConnectionList() + connections_list.connections.append(item) + is_media_channel, _ = self.check_service_for_media_channel( + connections=connections_list, item=service + ) + if has_optical_band and is_media_channel: + include_service_config( + item.connection_id, item.service_id + ) + self._executor.get_service(item.service_id) + pending_items_to_explore.put(item.service_id) + + for sub_service_id in item.sub_service_ids: + _,service_key_done = include_service( + sub_service_id, has_media_channel=has_media_channel, + has_optical_band=has_optical_band + ) + self._executor.get_service(sub_service_id) + self._dag.add(service_key_done, connection_key) + pending_items_to_explore.put(sub_service_id) + + explored_items.add(str_item_key) + else: + MSG = 'Unsupported item {:s}({:s})' + raise Exception(MSG.format(type(item).__name__, grpc_message_to_json_string(item))) + + t1 = time.time() + LOGGER.debug('[compose_from_service] elapsed_time: {:f} sec'.format(t1-t0)) + def compose_from_service(self, service : Service, is_delete : bool = False) -> None: t0 = time.time() include_service = self._service_remove if is_delete else self._service_create @@ -157,7 +345,6 @@ class TasksScheduler: for connection in connections.connections: self._add_connection_to_executor_cache(connection) pending_items_to_explore.put(connection) - explored_items.add(str_item_key) elif isinstance(item, ServiceId): @@ -170,7 +357,6 @@ class TasksScheduler: for connection in connections.connections: self._add_connection_to_executor_cache(connection) pending_items_to_explore.put(connection) - explored_items.add(str_item_key) elif isinstance(item, Connection): @@ -179,7 +365,6 @@ class TasksScheduler: connection_key = include_connection(item.connection_id, item.service_id) self._add_connection_to_executor_cache(connection) - self._executor.get_service(item.service_id) pending_items_to_explore.put(item.service_id) @@ -208,16 +393,20 @@ class TasksScheduler: self._add_connection_to_executor_cache(new_connection) service_updating_key = self._add_task_if_not_exists(Task_ServiceSetStatus( - self._executor, service.service_id, ServiceStatusEnum.SERVICESTATUS_UPDATING)) + self._executor, service.service_id, ServiceStatusEnum.SERVICESTATUS_UPDATING + )) old_connection_deconfigure_key = self._add_task_if_not_exists(Task_ConnectionDeconfigure( - self._executor, old_connection.connection_id)) + self._executor, old_connection.connection_id + )) new_connection_configure_key = self._add_task_if_not_exists(Task_ConnectionConfigure( - self._executor, new_connection.connection_id)) + self._executor, new_connection.connection_id + )) service_active_key = self._add_task_if_not_exists(Task_ServiceSetStatus( - self._executor, service.service_id, ServiceStatusEnum.SERVICESTATUS_ACTIVE)) + self._executor, service.service_id, ServiceStatusEnum.SERVICESTATUS_ACTIVE + )) # the old connection deconfiguration depends on service being in updating state self._dag.add(old_connection_deconfigure_key, service_updating_key) @@ -239,7 +428,7 @@ class TasksScheduler: def execute_all(self, dry_run : bool = False) -> None: ordered_task_keys = list(self._dag.static_order()) - LOGGER.debug('[execute_all] ordered_task_keys={:s}'.format(str(ordered_task_keys))) + LOGGER.info('[execute_all] ordered_task_keys={:s}'.format(str(ordered_task_keys))) results = [] for task_key in ordered_task_keys: diff --git a/src/service/service/task_scheduler/tasks/Task_ConnectionConfigure.py b/src/service/service/task_scheduler/tasks/Task_ConnectionConfigure.py index f6c543c1ccb947eb01c3d5f5fb93c0504a77ca95..3f52f337ae163a6e8c78d873a58a291ecff4bd1a 100644 --- a/src/service/service/task_scheduler/tasks/Task_ConnectionConfigure.py +++ b/src/service/service/task_scheduler/tasks/Task_ConnectionConfigure.py @@ -12,8 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import TYPE_CHECKING, Dict, Tuple +from common.DeviceTypes import DeviceTypeEnum from common.method_wrappers.ServiceExceptions import OperationFailedException -from common.proto.context_pb2 import ConnectionId +from common.proto.context_pb2 import ConnectionId, Device from common.tools.grpc.Tools import grpc_message_to_json_string from service.service.service_handler_api.Tools import check_errors_setendpoint from service.service.task_scheduler.TaskExecutor import TaskExecutor @@ -21,6 +23,9 @@ from service.service.tools.EndpointIdFormatters import endpointids_to_raw from service.service.tools.ObjectKeys import get_connection_key from ._Task import _Task +if TYPE_CHECKING: + from service.service.service_handler_api._ServiceHandler import _ServiceHandler + KEY_TEMPLATE = 'connection({connection_id:s}):configure' class Task_ConnectionConfigure(_Task): @@ -44,12 +49,24 @@ class Task_ConnectionConfigure(_Task): service = self._task_executor.get_service(connection.service_id) service_handler_settings = {} - service_handler = self._task_executor.get_service_handler(connection, service, **service_handler_settings) + service_handlers : Dict[DeviceTypeEnum, Tuple['_ServiceHandler', Dict[str, Device]]] = \ + self._task_executor.get_service_handlers(connection, service, **service_handler_settings) - endpointids_to_set = endpointids_to_raw(connection.path_hops_endpoint_ids) connection_uuid = connection.connection_id.connection_uuid.uuid - results_setendpoint = service_handler.SetEndpoint(endpointids_to_set, connection_uuid=connection_uuid) - errors = check_errors_setendpoint(endpointids_to_set, results_setendpoint) + endpointids_to_set = endpointids_to_raw(connection.path_hops_endpoint_ids) + + errors = list() + for _, (service_handler, connection_devices) in service_handlers.items(): + _endpointids_to_set = [ + (device_uuid, endpoint_uuid, topology_uuid) + for device_uuid, endpoint_uuid, topology_uuid in endpointids_to_set + if device_uuid in connection_devices + ] + results_setendpoint = service_handler.SetEndpoint( + _endpointids_to_set, connection_uuid=connection_uuid + ) + errors.extend(check_errors_setendpoint(endpointids_to_set, results_setendpoint)) + if len(errors) > 0: MSG = 'SetEndpoint for Connection({:s}) from Service({:s})' str_connection = grpc_message_to_json_string(connection) diff --git a/src/service/service/task_scheduler/tasks/Task_ConnectionDeconfigure.py b/src/service/service/task_scheduler/tasks/Task_ConnectionDeconfigure.py index 7b6b7951befbd6abd4d052ce5eec39d3398aa6e7..4ce774d208d3ea71e55482ea0653521cb7f1083a 100644 --- a/src/service/service/task_scheduler/tasks/Task_ConnectionDeconfigure.py +++ b/src/service/service/task_scheduler/tasks/Task_ConnectionDeconfigure.py @@ -12,8 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import TYPE_CHECKING, Dict, Tuple +from common.DeviceTypes import DeviceTypeEnum from common.method_wrappers.ServiceExceptions import OperationFailedException -from common.proto.context_pb2 import ConnectionId +from common.proto.context_pb2 import ConnectionId, Device from common.tools.grpc.Tools import grpc_message_to_json_string from service.service.service_handler_api.Tools import check_errors_deleteendpoint from service.service.task_scheduler.TaskExecutor import TaskExecutor @@ -21,6 +23,9 @@ from service.service.tools.EndpointIdFormatters import endpointids_to_raw from service.service.tools.ObjectKeys import get_connection_key from ._Task import _Task +if TYPE_CHECKING: + from service.service.service_handler_api._ServiceHandler import _ServiceHandler + KEY_TEMPLATE = 'connection({connection_id:s}):deconfigure' class Task_ConnectionDeconfigure(_Task): @@ -44,12 +49,24 @@ class Task_ConnectionDeconfigure(_Task): service = self._task_executor.get_service(connection.service_id) service_handler_settings = {} - service_handler = self._task_executor.get_service_handler(connection, service, **service_handler_settings) + service_handlers : Dict[DeviceTypeEnum, Tuple['_ServiceHandler', Dict[str, Device]]] = \ + self._task_executor.get_service_handlers(connection, service, **service_handler_settings) - endpointids_to_delete = endpointids_to_raw(connection.path_hops_endpoint_ids) connection_uuid = connection.connection_id.connection_uuid.uuid - results_deleteendpoint = service_handler.DeleteEndpoint(endpointids_to_delete, connection_uuid=connection_uuid) - errors = check_errors_deleteendpoint(endpointids_to_delete, results_deleteendpoint) + endpointids_to_delete = endpointids_to_raw(connection.path_hops_endpoint_ids) + + errors = list() + for _, (service_handler, connection_devices) in service_handlers.items(): + _endpointids_to_delete = [ + (device_uuid, endpoint_uuid, topology_uuid) + for device_uuid, endpoint_uuid, topology_uuid in endpointids_to_delete + if device_uuid in connection_devices + ] + results_deleteendpoint = service_handler.DeleteEndpoint( + _endpointids_to_delete, connection_uuid=connection_uuid + ) + errors.extend(check_errors_deleteendpoint(endpointids_to_delete, results_deleteendpoint)) + if len(errors) > 0: MSG = 'DeleteEndpoint for Connection({:s}) from Service({:s})' str_connection = grpc_message_to_json_string(connection) diff --git a/src/service/service/task_scheduler/tasks/Task_OpticalConnectionDeconfigure.py b/src/service/service/task_scheduler/tasks/Task_OpticalConnectionDeconfigure.py new file mode 100644 index 0000000000000000000000000000000000000000..b1afdeef19d6526d6f52b05924b5b77a8049da8f --- /dev/null +++ b/src/service/service/task_scheduler/tasks/Task_OpticalConnectionDeconfigure.py @@ -0,0 +1,75 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from common.method_wrappers.ServiceExceptions import OperationFailedException +from common.proto.context_pb2 import ConnectionId +from common.tools.grpc.Tools import grpc_message_to_json_string +from service.service.service_handler_api.Tools import check_errors_deleteendpoint +from service.service.task_scheduler.TaskExecutor import TaskExecutor +from service.service.tools.EndpointIdFormatters import endpointids_to_raw +from service.service.tools.ObjectKeys import get_connection_key +from ._Task import _Task + +KEY_TEMPLATE = 'optical_connection({connection_id:s}):deconfigure' + +class Task_OpticalConnectionDeconfigure(_Task): + def __init__( + self, task_executor : TaskExecutor, connection_id : ConnectionId, + has_media_channel : bool + ) -> None: + super().__init__(task_executor) + self._connection_id = connection_id + self._has_media_channel = has_media_channel + + @property + def connection_id(self) -> ConnectionId: return self._connection_id + + @staticmethod + def build_key(connection_id : ConnectionId) -> str: # pylint: disable=arguments-differ + str_connection_id = get_connection_key(connection_id) + return KEY_TEMPLATE.format(connection_id=str_connection_id) + + @property + def key(self) -> str: return self.build_key(self._connection_id) + + def execute(self) -> None: + connection = self._task_executor.get_connection(self._connection_id) + service = self._task_executor.get_service(connection.service_id) + errors = [] + service_handler_settings = {} + service_handler = self._task_executor.get_service_handler(connection, service, **service_handler_settings) + + endpointids_to_delete = endpointids_to_raw(connection.path_hops_endpoint_ids) + connection_uuid = connection.connection_id.connection_uuid.uuid + if self._has_media_channel: + is_media_channel = service_handler.check_media_channel(connection_uuid) + if is_media_channel: + results_deleteendpoint = service_handler.DeleteEndpoint(endpointids_to_delete, connection_uuid=connection_uuid) + errors = check_errors_deleteendpoint(endpointids_to_delete, results_deleteendpoint) + if len(errors) > 0: + MSG = 'DeleteEndpoint for OpticalConnection({:s}) from Service({:s})' + str_connection = grpc_message_to_json_string(connection) + str_service = grpc_message_to_json_string(service) + raise OperationFailedException(MSG.format(str_connection, str_service), extra_details=errors) + if is_media_channel: + self._task_executor.delete_connection(self._connection_id) + else: + results_deleteendpoint = service_handler.DeleteEndpoint(endpointids_to_delete, connection_uuid=connection_uuid) + errors = check_errors_deleteendpoint(endpointids_to_delete, results_deleteendpoint) + if len(errors) > 0: + MSG = 'DeleteEndpoint for OpticalConnection({:s}) from Service({:s})' + str_connection = grpc_message_to_json_string(connection) + str_service = grpc_message_to_json_string(service) + raise OperationFailedException(MSG.format(str_connection, str_service), extra_details=errors) + self._task_executor.delete_connection(self._connection_id) diff --git a/src/service/service/task_scheduler/tasks/Task_OpticalServiceConfigDelete.py b/src/service/service/task_scheduler/tasks/Task_OpticalServiceConfigDelete.py new file mode 100644 index 0000000000000000000000000000000000000000..4a21d246e8dfcce4f828553df68169a2bbada160 --- /dev/null +++ b/src/service/service/task_scheduler/tasks/Task_OpticalServiceConfigDelete.py @@ -0,0 +1,43 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from common.proto.context_pb2 import ServiceId , ConnectionId +from service.service.task_scheduler.TaskExecutor import TaskExecutor +from service.service.tools.ObjectKeys import get_service_key +from ._Task import _Task + +KEY_TEMPLATE = 'optical_service({service_id:s})_Config:delete' + +class Task_OpticalServiceConfigDelete(_Task): + def __init__( + self, task_executor : TaskExecutor, connection_id : ConnectionId, + service_id : ServiceId + ) -> None: + super().__init__(task_executor) + self._connection_id = connection_id + self._service_id = service_id + + @property + def service_id(self) -> ServiceId: return self._service_id + + @staticmethod + def build_key(service_id : ServiceId) -> str: # pylint: disable=arguments-differ + str_service_id = get_service_key(service_id) + return KEY_TEMPLATE.format(service_id=str_service_id) + + @property + def key(self) -> str: return self.build_key(self._service_id) + + def execute(self) -> None: + self._task_executor.delete_setting(self._service_id, '/settings', 'value') diff --git a/src/service/service/task_scheduler/tasks/Task_OpticalServiceDelete.py b/src/service/service/task_scheduler/tasks/Task_OpticalServiceDelete.py new file mode 100644 index 0000000000000000000000000000000000000000..536d954957ebf1f2d294b9bc435cae7ded7c7531 --- /dev/null +++ b/src/service/service/task_scheduler/tasks/Task_OpticalServiceDelete.py @@ -0,0 +1,45 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from common.proto.context_pb2 import ServiceId +from service.service.task_scheduler.TaskExecutor import TaskExecutor +from service.service.tools.ObjectKeys import get_service_key +from ._Task import _Task + +KEY_TEMPLATE = 'optical_service({service_id:s}):delete' + +class Task_OpticalServiceDelete(_Task): + def __init__( + self, task_executor : TaskExecutor, service_id : ServiceId, + has_media_channel : bool, has_optical_band : bool + ) -> None: + super().__init__(task_executor) + self._service_id = service_id + self._has_media_channel = has_media_channel + self._has_optical_band = has_optical_band + + @property + def service_id(self) -> ServiceId: return self._service_id + + @staticmethod + def build_key(service_id : ServiceId) -> str: # pylint: disable=arguments-differ + str_service_id = get_service_key(service_id) + return KEY_TEMPLATE.format(service_id=str_service_id) + + @property + def key(self) -> str: return self.build_key(self._service_id) + + def execute(self) -> None: + if not self._has_media_channel or not self._has_optical_band: + self._task_executor.delete_service(self._service_id) diff --git a/src/service/service/tools/OpticalTools.py b/src/service/service/tools/OpticalTools.py index ca73dce480a46369b47ce4c6baed516168b24817..f62c60ac9d256e277478f1703ed75e992961a4da 100644 --- a/src/service/service/tools/OpticalTools.py +++ b/src/service/service/tools/OpticalTools.py @@ -91,6 +91,7 @@ def get_names_from_uuids(devices: List[Device], device_uuid: str, port_uuid: str return device_name, port_name return "", "" + def get_device_name_from_uuid(devices: List[Device], device_uuid: str): device_name = "" @@ -101,6 +102,17 @@ def get_device_name_from_uuid(devices: List[Device], device_uuid: str): return "" +def refresh_opticalcontroller(topology_id : dict): + topo_id_str = topology_id["topology_uuid"]["uuid"] + cxt_id_str = topology_id["context_id"]["context_uuid"]["uuid"] + headers = {"Content-Type": "application/json"} + base_url = get_optical_controller_base_url() + urlx = "{:s}/GetTopology/{:s}/{:s}".format(base_url, cxt_id_str, topo_id_str) + res = requests.get(urlx, headers=headers) + if res is not None: + log.debug(f"DELETELIGHTPATH Response {res}") + + def add_lightpath(src, dst, bitrate, bidir, ob_band) -> str: if not TESTING: urlx = "" @@ -115,6 +127,7 @@ def add_lightpath(src, dst, bitrate, bidir, ob_band) -> str: bidir = 1 urlx = "{:s}/AddFlexLightpath/{:s}/{:s}/{:s}/{:s}/{:s}".format(base_url, src, dst, str(bitrate), str(bidir), str(ob_band)) r = requests.put(urlx, headers=headers) + print(f"addpathlight {r}") reply = r.text return reply else: @@ -139,17 +152,30 @@ def get_optical_band(idx) -> str: return optical_band_uni_txt -def delete_lightpath(flow_id, src, dst, bitrate) -> str: +def delete_lightpath( src, dst, bitrate, ob_id, delete_band, flow_id=None) -> str: reply = "200" + delete_band = 1 if delete_band else 0 + base_url = get_optical_controller_base_url() if not TESTING: - base_url = get_optical_controller_base_url() - urlx = "{:s}/DelLightpath/{:s}/{:s}/{:s}/{:s}".format(base_url, str(flow_id), src, dst, str(bitrate)) - + if flow_id is not None: + urlx = "{:s}/DelFlexLightpath/{}/{}/{}/{}/{}".format(base_url, src, dst, bitrate, ob_id, flow_id) + else : + urlx = "{:s}/DelOpticalBand/{}/{}/{}".format(base_url, src, dst, ob_id) headers = {"Content-Type": "application/json"} r = requests.delete(urlx, headers=headers) reply = r.text - return reply + code = r.status_code + return (reply, code) +def DelFlexLightpath (flow_id, src, dst, bitrate, o_band_id): + reply = "200" + base_url = get_optical_controller_base_url() + if not TESTING: + urlx = "{:s}/DelFlexLightpath/{}/{}/{}/{}/{}".format(base_url, flow_id, src, dst, bitrate, o_band_id) + headers = {"Content-Type": "application/json"} + r = requests.delete(urlx, headers=headers) + reply = r.text + return reply def get_lightpaths() -> str: base_url = get_optical_controller_base_url() @@ -160,14 +186,24 @@ def get_lightpaths() -> str: reply = r.text return reply - def adapt_reply(devices, service, reply_json, context_id, topology_id, optical_band_txt) -> PathCompReply: opt_reply = PathCompReply() - topo = TopologyId(context_id=ContextId(context_uuid=Uuid(uuid=context_id)),topology_uuid=Uuid(uuid=topology_id)) + topo = TopologyId( + context_id=ContextId(context_uuid=Uuid(uuid=context_id)), + topology_uuid=Uuid(uuid=topology_id) + ) #add optical band connection first rules_ob= [] ob_id = 0 connection_ob=None + + r = reply_json + if "parent_opt_band" in r.keys(): + ob_id = r["parent_opt_band"] + if "bidir" in r.keys(): + bidir_f = r["bidir"] + else: + bidir_f = False if optical_band_txt != "": ob_json = json.loads(optical_band_txt) ob = ob_json @@ -187,9 +223,18 @@ def adapt_reply(devices, service, reply_json, context_id, topology_id, optical_b freq = ob["freq"] bx = ob["band"] - lf = int(int(freq)-int(bx/2)) + #+1 is added to avoid overlap in the WSS of MGONs + lf = int(int(freq)-int(bx/2))+1 uf = int(int(freq)+int(bx/2)) - val_ob = {"band_type": band_type, "low-freq": lf, "up-freq": uf, "frequency": freq, "band": bx, "ob_id": ob_id} + val_ob = { + "band_type" : band_type, + "low-freq" : lf, + "up-freq" : uf, + "frequency" : freq, + "band" : bx, + "ob_id" : ob_id, + "bidir" : bidir_f + } rules_ob.append(ConfigRule_Custom(resource_key="/settings-ob_{}".format(uuuid_x), resource_value=json.dumps(val_ob))) bidir_ob = ob["bidir"] for devxb in ob["flows"].keys(): @@ -234,8 +279,7 @@ def adapt_reply(devices, service, reply_json, context_id, topology_id, optical_b else: log.info("no map device port for device {} port {}".format(devxb, out_end_point_b)) log.debug("optical-band connection {}".format(connection_ob)) - r = reply_json - bidir_f = r["bidir"] + connection_f = add_connection_to_reply(opt_reply) connection_f.connection_id.connection_uuid.uuid = str(uuid.uuid4()) connection_f.service_id.CopyFrom(service.service_id) @@ -278,12 +322,13 @@ def adapt_reply(devices, service, reply_json, context_id, topology_id, optical_b end_point = EndPointId(topology_id=topo, device_id=DeviceId(device_uuid=Uuid(uuid=d)), endpoint_uuid=Uuid(uuid=p)) connection_f.path_hops_endpoint_ids.add().CopyFrom(end_point) else: - log.info("no map device port for device {} port {}".format(devx, out_end_point_b)) + log.info("no map device port for device {} port {}".format(devx, out_end_point_b)) + #check that list of endpoints is not empty if connection_ob is not None and len(connection_ob.path_hops_endpoint_ids) == 0: log.debug("deleting empty optical-band connection") opt_reply.connections.remove(connection_ob) - + #inizialize custom optical parameters band = r["band"] if "band" in r else None op_mode = r["op-mode"] if "op-mode" in r else None @@ -298,27 +343,26 @@ def adapt_reply(devices, service, reply_json, context_id, topology_id, optical_b band_type = "C_BAND" if ob_id != 0: - val = {"frequency": frequency, "operational-mode": op_mode, "band": band, "flow_id": flow_id, "ob_id": ob_id, "band_type": band_type,} + val = {"target-output-power": "1.0", "frequency": frequency, "operational-mode": op_mode, "band": band, "flow_id": flow_id, "ob_id": ob_id, "band_type": band_type, "bidir": bidir_f} else: - val = {"frequency": frequency, "operational-mode": op_mode, "band": band, "flow_id": flow_id, "band_type": band_type,} + val = {"target-output-power": "1.0", "frequency": frequency, "operational-mode": op_mode, "band": band, "flow_id": flow_id, "band_type": band_type, "bidir": bidir_f} custom_rule = ConfigRule_Custom(resource_key="/settings", resource_value=json.dumps(val)) rule = ConfigRule(action=ConfigActionEnum.CONFIGACTION_SET, custom=custom_rule) service.service_config.config_rules.add().CopyFrom(rule) if len(rules_ob) > 0: - for rulex in rules_ob: - rule_ob = ConfigRule(action=ConfigActionEnum.CONFIGACTION_SET, custom=rulex) - service.service_config.config_rules.add().CopyFrom(rule_ob) - + for rulex in rules_ob: + rule_ob = ConfigRule(action=ConfigActionEnum.CONFIGACTION_SET, custom=rulex) + service.service_config.config_rules.add().CopyFrom(rule_ob) + opt_reply.services.add().CopyFrom(service) - - return opt_reply + return opt_reply -def add_service_to_reply(reply : PathCompReply, service : Service)-> Service: +def add_service_to_reply(reply : PathCompReply, service : Service) -> Service: service_x = reply.services.add() service_x.CopyFrom(service) return service_x - -def add_connection_to_reply(reply : PathCompReply)-> Connection: + +def add_connection_to_reply(reply : PathCompReply) -> Connection: conn = reply.connections.add() return conn diff --git a/src/service/service/tools/object_uuid.py b/src/service/service/tools/object_uuid.py new file mode 100644 index 0000000000000000000000000000000000000000..cbcbc71b9b9d4edeb31858e3c22cd0af508210b9 --- /dev/null +++ b/src/service/service/tools/object_uuid.py @@ -0,0 +1,53 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from common.method_wrappers.ServiceExceptions import InvalidArgumentsException +from typing import Optional, Union +from uuid import UUID, uuid5 +from common.proto.context_pb2 import DeviceId + +# Generate a UUIDv5-like from the SHA-1 of "TFS" and no namespace to be used as the NAMESPACE for all +# the context UUIDs generated. For efficiency purposes, the UUID is hardcoded; however, it is produced +# using the following code: +# from hashlib import sha1 +# from uuid import UUID +# hash = sha1(bytes('TFS', 'utf-8')).digest() +# NAMESPACE_TFS = UUID(bytes=hash[:16], version=5) +NAMESPACE_TFS = UUID('200e3a1f-2223-534f-a100-758e29c37f40') + +def get_uuid_from_string(str_uuid_or_name : Union[str, UUID], prefix_for_name : Optional[str] = None) -> str: + # if UUID given, assume it is already a valid UUID + if isinstance(str_uuid_or_name, UUID): return str_uuid_or_name + if not isinstance(str_uuid_or_name, str): + MSG = 'Parameter({:s}) cannot be used to produce a UUID' + raise Exception(MSG.format(str(repr(str_uuid_or_name)))) + try: + # try to parse as UUID + return str(UUID(str_uuid_or_name)) + except: # pylint: disable=bare-except + # produce a UUID within TFS namespace from parameter + if prefix_for_name is not None: + str_uuid_or_name = '{:s}/{:s}'.format(prefix_for_name, str_uuid_or_name) + return str(uuid5(NAMESPACE_TFS, str_uuid_or_name)) + +def opticalconfig_get_uuid(device_id: DeviceId) -> str: + device_uuid = device_id.device_uuid.uuid + if len(device_uuid) > 0: + return get_uuid_from_string(f"{device_uuid}_opticalconfig") + + raise InvalidArgumentsException([ + ('DeviceId ', device_id), + + ], extra_details=['device_id is required to produce a OpticalConfig UUID']) diff --git a/src/service/tests/test_l3nm_gnmi_static_rule_gen/MockServiceHandler.py b/src/service/tests/test_l3nm_gnmi_static_rule_gen/MockServiceHandler.py new file mode 100644 index 0000000000000000000000000000000000000000..9b3f76566c9d8e5b2c8bdfb05f4b2448c29b7eae --- /dev/null +++ b/src/service/tests/test_l3nm_gnmi_static_rule_gen/MockServiceHandler.py @@ -0,0 +1,160 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json, logging +from typing import Any, Dict, List, Optional, Tuple, Union +from common.proto.context_pb2 import ConfigRule, DeviceId, Service +from common.tools.object_factory.Device import json_device_id +from common.type_checkers.Checkers import chk_type +from service.service.service_handler_api._ServiceHandler import _ServiceHandler +from service.service.service_handler_api.SettingsHandler import SettingsHandler +from service.service.service_handler_api.Tools import get_device_endpoint_uuids, get_endpoint_matching +from .MockTaskExecutor import MockTaskExecutor +from service.service.service_handlers.l3nm_gnmi_openconfig.ConfigRuleComposer import ConfigRuleComposer +from service.service.service_handlers.l3nm_gnmi_openconfig.StaticRouteGenerator import StaticRouteGenerator + +LOGGER = logging.getLogger(__name__) + +class MockServiceHandler(_ServiceHandler): + def __init__( # pylint: disable=super-init-not-called + self, service : Service, task_executor : MockTaskExecutor, **settings + ) -> None: + self.__service = service + self.__task_executor = task_executor + self.__settings_handler = SettingsHandler(service.service_config, **settings) + self.__config_rule_composer = ConfigRuleComposer() + self.__static_route_generator = StaticRouteGenerator(self.__config_rule_composer) + self.__endpoint_map : Dict[Tuple[str, str], Tuple[str, str]] = dict() + + def _compose_config_rules(self, endpoints : List[Tuple[str, str, Optional[str]]]) -> None: + if len(endpoints) % 2 != 0: raise Exception('Number of endpoints should be even') + + service_settings = self.__settings_handler.get_service_settings() + self.__config_rule_composer.configure(self.__service, service_settings) + + for endpoint in endpoints: + device_uuid, endpoint_uuid = get_device_endpoint_uuids(endpoint) + + device_obj = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid))) + device_settings = self.__settings_handler.get_device_settings(device_obj) + _device = self.__config_rule_composer.get_device(device_obj.name) + _device.configure(device_obj, device_settings) + + endpoint_obj = get_endpoint_matching(device_obj, endpoint_uuid) + endpoint_settings = self.__settings_handler.get_endpoint_settings(device_obj, endpoint_obj) + _endpoint = _device.get_endpoint(endpoint_obj.name) + _endpoint.configure(endpoint_obj, endpoint_settings) + + self.__endpoint_map[(device_uuid, endpoint_uuid)] = (device_obj.name, endpoint_obj.name) + + self.__static_route_generator.compose(endpoints) + + def _do_configurations( + self, config_rules_per_device : Dict[str, List[Dict]], endpoints : List[Tuple[str, str, Optional[str]]], + delete : bool = False + ) -> List[Union[bool, Exception]]: + # Configuration is done atomically on each device, all OK / all KO per device + results_per_device = dict() + for device_name,json_config_rules in config_rules_per_device.items(): + try: + device_obj = self.__config_rule_composer.get_device(device_name).objekt + if len(json_config_rules) == 0: continue + del device_obj.device_config.config_rules[:] + for json_config_rule in json_config_rules: + device_obj.device_config.config_rules.append(ConfigRule(**json_config_rule)) + self.__task_executor.configure_device(device_obj) + results_per_device[device_name] = True + except Exception as e: # pylint: disable=broad-exception-caught + verb = 'deconfigure' if delete else 'configure' + MSG = 'Unable to {:s} Device({:s}) : ConfigRules({:s})' + LOGGER.exception(MSG.format(verb, str(device_name), str(json_config_rules))) + results_per_device[device_name] = e + + results = [] + for endpoint in endpoints: + device_uuid, endpoint_uuid = get_device_endpoint_uuids(endpoint) + device_name, _ = self.__endpoint_map[(device_uuid, endpoint_uuid)] + if device_name not in results_per_device: continue + results.append(results_per_device[device_name]) + return results + + def SetEndpoint( + self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None + ) -> List[Union[bool, Exception]]: + chk_type('endpoints', endpoints, list) + if len(endpoints) == 0: return [] + self._compose_config_rules(endpoints) + config_rules_per_device = self.__config_rule_composer.get_config_rules(delete=False) + LOGGER.debug('config_rules_per_device={:s}'.format(str(config_rules_per_device))) + results = self._do_configurations(config_rules_per_device, endpoints) + LOGGER.debug('results={:s}'.format(str(results))) + return results + + def DeleteEndpoint( + self, endpoints : List[Tuple[str, str, Optional[str]]], connection_uuid : Optional[str] = None + ) -> List[Union[bool, Exception]]: + chk_type('endpoints', endpoints, list) + if len(endpoints) == 0: return [] + self._compose_config_rules(endpoints) + config_rules_per_device = self.__config_rule_composer.get_config_rules(delete=True) + LOGGER.debug('config_rules_per_device={:s}'.format(str(config_rules_per_device))) + results = self._do_configurations(config_rules_per_device, endpoints, delete=True) + LOGGER.debug('results={:s}'.format(str(results))) + return results + + def SetConstraint(self, constraints : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: + chk_type('constraints', constraints, list) + if len(constraints) == 0: return [] + + msg = '[SetConstraint] Method not implemented. Constraints({:s}) are being ignored.' + LOGGER.warning(msg.format(str(constraints))) + return [True for _ in range(len(constraints))] + + def DeleteConstraint(self, constraints : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: + chk_type('constraints', constraints, list) + if len(constraints) == 0: return [] + + msg = '[DeleteConstraint] Method not implemented. Constraints({:s}) are being ignored.' + LOGGER.warning(msg.format(str(constraints))) + return [True for _ in range(len(constraints))] + + def SetConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: + chk_type('resources', resources, list) + if len(resources) == 0: return [] + + results = [] + for resource in resources: + try: + resource_value = json.loads(resource[1]) + self.__settings_handler.set(resource[0], resource_value) + results.append(True) + except Exception as e: # pylint: disable=broad-except + LOGGER.exception('Unable to SetConfig({:s})'.format(str(resource))) + results.append(e) + + return results + + def DeleteConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: + chk_type('resources', resources, list) + if len(resources) == 0: return [] + + results = [] + for resource in resources: + try: + self.__settings_handler.delete(resource[0]) + except Exception as e: # pylint: disable=broad-except + LOGGER.exception('Unable to DeleteConfig({:s})'.format(str(resource))) + results.append(e) + + return results diff --git a/src/service/tests/test_l3nm_gnmi_static_rule_gen/MockTaskExecutor.py b/src/service/tests/test_l3nm_gnmi_static_rule_gen/MockTaskExecutor.py new file mode 100644 index 0000000000000000000000000000000000000000..765b04477efdf06bfef934e96329887e898aa1b4 --- /dev/null +++ b/src/service/tests/test_l3nm_gnmi_static_rule_gen/MockTaskExecutor.py @@ -0,0 +1,57 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from enum import Enum +from typing import Dict, Optional, Union +from common.method_wrappers.ServiceExceptions import NotFoundException +from common.proto.context_pb2 import Connection, Device, DeviceId, Service +from service.service.tools.ObjectKeys import get_device_key + +LOGGER = logging.getLogger(__name__) + +CacheableObject = Union[Connection, Device, Service] + +class CacheableObjectType(Enum): + CONNECTION = 'connection' + DEVICE = 'device' + SERVICE = 'service' + +class MockTaskExecutor: + def __init__(self) -> None: + self._grpc_objects_cache : Dict[str, CacheableObject] = dict() + + # ----- Common methods --------------------------------------------------------------------------------------------- + + def _load_grpc_object(self, object_type : CacheableObjectType, object_key : str) -> Optional[CacheableObject]: + object_key = '{:s}:{:s}'.format(object_type.value, object_key) + return self._grpc_objects_cache.get(object_key) + + def _store_grpc_object(self, object_type : CacheableObjectType, object_key : str, grpc_object) -> None: + object_key = '{:s}:{:s}'.format(object_type.value, object_key) + self._grpc_objects_cache[object_key] = grpc_object + + def _delete_grpc_object(self, object_type : CacheableObjectType, object_key : str) -> None: + object_key = '{:s}:{:s}'.format(object_type.value, object_key) + self._grpc_objects_cache.pop(object_key, None) + + def get_device(self, device_id : DeviceId) -> Device: + device_key = get_device_key(device_id) + device = self._load_grpc_object(CacheableObjectType.DEVICE, device_key) + if device is None: raise NotFoundException('Device', device_key) + return device + + def configure_device(self, device : Device) -> None: + device_key = get_device_key(device.device_id) + self._store_grpc_object(CacheableObjectType.DEVICE, device_key, device) diff --git a/src/service/tests/test_l3nm_gnmi_static_rule_gen/__init__.py b/src/service/tests/test_l3nm_gnmi_static_rule_gen/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..3ee6f7071f145e06c3aeaefc09a43ccd88e619e3 --- /dev/null +++ b/src/service/tests/test_l3nm_gnmi_static_rule_gen/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/src/service/tests/test_l3nm_gnmi_static_rule_gen/test_unitary.py b/src/service/tests/test_l3nm_gnmi_static_rule_gen/test_unitary.py new file mode 100644 index 0000000000000000000000000000000000000000..43709b036b8158ddfc59453aa798fa2d303906e0 --- /dev/null +++ b/src/service/tests/test_l3nm_gnmi_static_rule_gen/test_unitary.py @@ -0,0 +1,147 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Run with: +# $ PYTHONPATH=./src python -m service.tests.test_l3nm_gnmi_static_rule_gen.test_unitary + +import logging +from typing import List, Optional, Tuple +from common.DeviceTypes import DeviceTypeEnum +from common.proto.context_pb2 import Device, DeviceOperationalStatusEnum, Service +from common.tools.object_factory.ConfigRule import json_config_rule_set +from common.tools.object_factory.Device import json_device, json_device_id +from common.tools.object_factory.EndPoint import json_endpoint, json_endpoint_id +from common.tools.object_factory.Service import json_service_l3nm_planned +from .MockServiceHandler import MockServiceHandler +from .MockTaskExecutor import CacheableObjectType, MockTaskExecutor + +logging.basicConfig(level=logging.DEBUG) +LOGGER = logging.getLogger(__name__) + +SERVICE_DC1_DC2 = Service(**json_service_l3nm_planned( + 'svc-dc1-dc2-uuid', + endpoint_ids=[ + json_endpoint_id(json_device_id('DC1'), 'int'), + json_endpoint_id(json_device_id('DC2'), 'int'), + ], + config_rules=[ + json_config_rule_set('/device[DC1]/endpoint[eth0]/settings', { + 'ipv4_address': '192.168.10.10', 'ipv4_prefix_len': 24, 'sub_interface_index': 0 + }), + json_config_rule_set('/device[R1]/endpoint[1/2]/settings', { + 'ipv4_address': '10.0.1.1', 'ipv4_prefix_len': 24, 'sub_interface_index': 0 + }), + #json_config_rule_set('/device[R2]/endpoint[1/2]/settings', { + # 'ipv4_address': '10.0.2.1', 'ipv4_prefix_len': 24, 'sub_interface_index': 0 + #}), + json_config_rule_set('/device[DC2]/endpoint[eth0]/settings', { + 'ipv4_address': '192.168.20.10', 'ipv4_prefix_len': 24, 'sub_interface_index': 0 + }), + ] +)) + +SERVICE_DC1_DC3 = Service(**json_service_l3nm_planned( + 'svc-dc1-dc3-uuid', + endpoint_ids=[ + json_endpoint_id(json_device_id('DC1'), 'int'), + json_endpoint_id(json_device_id('DC3'), 'int'), + ], + config_rules=[ + json_config_rule_set('/device[DC1]/endpoint[eth0]/settings', { + 'ipv4_address': '192.168.10.10', 'ipv4_prefix_len': 24, 'sub_interface_index': 0 + }), + #json_config_rule_set('/device[R1]/endpoint[1/2]/settings', { + # 'ipv4_address': '10.0.1.1', 'ipv4_prefix_len': 24, 'sub_interface_index': 0 + #}), + json_config_rule_set('/device[R4]/endpoint[1/1]/settings', { + 'ipv4_address': '10.0.4.1', 'ipv4_prefix_len': 24, 'sub_interface_index': 0 + }), + json_config_rule_set('/device[DC3]/endpoint[eth0]/settings', { + 'ipv4_address': '192.168.30.10', 'ipv4_prefix_len': 24, 'sub_interface_index': 0 + }), + ] +)) + +CONNECTION_ENDPOINTS_DC1_DC2 : List[Tuple[str, str, Optional[str]]] = [ + ('DC1', 'int', None), ('DC1', 'eth0', None), + ('R1', '1/1', None), ('R1', '1/2', None), + ('R2', '1/1', None), ('R2', '1/2', None), + ('R3', '1/1', None), ('R3', '1/2', None), + ('DC2', 'eth0', None), ('DC2', 'int', None), +] + +CONNECTION_ENDPOINTS_DC1_DC3 : List[Tuple[str, str, Optional[str]]] = [ + ('DC1', 'int', None), ('DC1', 'eth0', None), + ('R1', '1/1', None), ('R1', '1/2', None), + ('R2', '1/1', None), ('R2', '1/3', None), + ('R4', '1/1', None), ('R4', '1/2', None), + ('DC3', 'eth0', None), ('DC3', 'int', None), +] + +def test_l3nm_gnmi_static_rule_gen() -> None: + dev_op_st_enabled = DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED + + mock_task_executor = MockTaskExecutor() + mock_task_executor._store_grpc_object(CacheableObjectType.DEVICE, 'DC1', Device(**json_device( + 'uuid-DC1', DeviceTypeEnum.EMULATED_DATACENTER.value, dev_op_st_enabled, name='DC1', endpoints=[ + json_endpoint(json_device_id('uuid-DC1'), 'uuid-int', 'packet', name='int' ), + json_endpoint(json_device_id('uuid-DC1'), 'uuid-eth0', 'packet', name='eth0'), + ] + ))) + mock_task_executor._store_grpc_object(CacheableObjectType.DEVICE, 'DC2', Device(**json_device( + 'uuid-DC2', DeviceTypeEnum.EMULATED_DATACENTER.value, dev_op_st_enabled, name='DC2', endpoints=[ + json_endpoint(json_device_id('uuid-DC2'), 'uuid-int', 'packet', name='int' ), + json_endpoint(json_device_id('uuid-DC2'), 'uuid-eth0', 'packet', name='eth0'), + ] + ))) + mock_task_executor._store_grpc_object(CacheableObjectType.DEVICE, 'DC3', Device(**json_device( + 'uuid-DC3', DeviceTypeEnum.EMULATED_DATACENTER.value, dev_op_st_enabled, name='DC3', endpoints=[ + json_endpoint(json_device_id('uuid-DC3'), 'uuid-int', 'packet', name='int' ), + json_endpoint(json_device_id('uuid-DC3'), 'uuid-eth0', 'packet', name='eth0'), + ] + ))) + mock_task_executor._store_grpc_object(CacheableObjectType.DEVICE, 'R1', Device(**json_device( + 'uuid-R1', DeviceTypeEnum.EMULATED_PACKET_ROUTER.value, dev_op_st_enabled, name='R1', endpoints=[ + json_endpoint(json_device_id('uuid-R1'), 'uuid-1/1', 'packet', name='1/1'), + json_endpoint(json_device_id('uuid-R1'), 'uuid-1/2', 'packet', name='1/2'), + ] + ))) + mock_task_executor._store_grpc_object(CacheableObjectType.DEVICE, 'R2', Device(**json_device( + 'uuid-R2', DeviceTypeEnum.EMULATED_PACKET_ROUTER.value, dev_op_st_enabled, name='R2', endpoints=[ + json_endpoint(json_device_id('uuid-R2'), 'uuid-1/1', 'packet', name='1/1'), + json_endpoint(json_device_id('uuid-R2'), 'uuid-1/2', 'packet', name='1/2'), + json_endpoint(json_device_id('uuid-R2'), 'uuid-1/3', 'packet', name='1/3'), + ] + ))) + mock_task_executor._store_grpc_object(CacheableObjectType.DEVICE, 'R3', Device(**json_device( + 'uuid-R3', DeviceTypeEnum.EMULATED_PACKET_ROUTER.value, dev_op_st_enabled, name='R3', endpoints=[ + json_endpoint(json_device_id('uuid-R3'), 'uuid-1/1', 'packet', name='1/1'), + json_endpoint(json_device_id('uuid-R3'), 'uuid-1/2', 'packet', name='1/2'), + ] + ))) + mock_task_executor._store_grpc_object(CacheableObjectType.DEVICE, 'R4', Device(**json_device( + 'uuid-R4', DeviceTypeEnum.EMULATED_PACKET_ROUTER.value, dev_op_st_enabled, name='R4', endpoints=[ + json_endpoint(json_device_id('uuid-R4'), 'uuid-1/1', 'packet', name='1/1'), + json_endpoint(json_device_id('uuid-R4'), 'uuid-1/2', 'packet', name='1/2'), + ] + ))) + + mock_service_handler = MockServiceHandler(SERVICE_DC1_DC2, mock_task_executor) + mock_service_handler.SetEndpoint(CONNECTION_ENDPOINTS_DC1_DC2) + + mock_service_handler = MockServiceHandler(SERVICE_DC1_DC3, mock_task_executor) + mock_service_handler.SetEndpoint(CONNECTION_ENDPOINTS_DC1_DC3) + +if __name__ == '__main__': + test_l3nm_gnmi_static_rule_gen() diff --git a/src/tests/.gitlab-ci.yml b/src/tests/.gitlab-ci.yml index 808aebf2173361e05fee5ed2806b9c7aa6362753..b7da988bf33175e149906243f28b0df0d78d9d8b 100644 --- a/src/tests/.gitlab-ci.yml +++ b/src/tests/.gitlab-ci.yml @@ -20,4 +20,5 @@ include: #- local: '/src/tests/nfvsdn22/.gitlab-ci.yml' #- local: '/src/tests/ofc23/.gitlab-ci.yml' - local: '/src/tests/ofc24/.gitlab-ci.yml' + - local: '/src/tests/eucnc24/.gitlab-ci.yml' #- local: '/src/tests/ecoc24/.gitlab-ci.yml' diff --git a/src/tests/eucnc24/.gitignore b/src/tests/eucnc24/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..24a4b233365e23a9462f4b64e8b60fef6a62bee4 --- /dev/null +++ b/src/tests/eucnc24/.gitignore @@ -0,0 +1,5 @@ +clab-*/ +images/ +*.clab.yml.bak +*.tar +*.tar.gz diff --git a/src/tests/eucnc24/.gitlab-ci.yml b/src/tests/eucnc24/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..5dec5e36cf2a2984fcbb073190fb386e16a4e8c3 --- /dev/null +++ b/src/tests/eucnc24/.gitlab-ci.yml @@ -0,0 +1,201 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Build, tag, and push the Docker image to the GitLab Docker registry +build eucnc24: + variables: + TEST_NAME: 'eucnc24' + stage: build + before_script: + - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY + script: + - docker buildx build -t "${TEST_NAME}:latest" -f ./src/tests/${TEST_NAME}/Dockerfile . + - docker tag "${TEST_NAME}:latest" "$CI_REGISTRY_IMAGE/${TEST_NAME}:latest" + - docker push "$CI_REGISTRY_IMAGE/${TEST_NAME}:latest" + after_script: + - docker images --filter="dangling=true" --quiet | xargs -r docker rmi + rules: + - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && ($CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop" || $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH)' + - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "develop"' + - changes: + - src/common/**/*.py + - proto/*.proto + - src/tests/${TEST_NAME}/**/*.{py,in,sh,yml} + - src/tests/${TEST_NAME}/Dockerfile + - .gitlab-ci.yml + +# Deploy TeraFlowSDN and Execute end-2-end test +end2end_test eucnc24: + variables: + TEST_NAME: 'eucnc24' + stage: end2end_test + # Disable to force running it after all other tasks + #needs: + # - build eucnc24 + before_script: + - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY + - docker rm -f ${TEST_NAME} || true + - sudo containerlab destroy --all --cleanup || true + + script: + # Download Docker image to run the test + - docker pull "${CI_REGISTRY_IMAGE}/${TEST_NAME}:latest" + + # Check MicroK8s is ready + - microk8s status --wait-ready + - kubectl get pods --all-namespaces + + # Deploy ContainerLab Scenario + - RUNNER_PATH=`pwd` + #- cd $PWD/src/tests/${TEST_NAME} + - mkdir -p /tmp/clab/${TEST_NAME} + - cp -R src/tests/${TEST_NAME}/clab/* /tmp/clab/${TEST_NAME} + - tree -la /tmp/clab/${TEST_NAME} + - cd /tmp/clab/${TEST_NAME} + - sudo containerlab deploy --reconfigure --topo eucnc24.clab.yml + - cd $RUNNER_PATH + + # Wait for initialization of Device NOSes + - sleep 3 + - docker ps -a + + # Configure TeraFlowSDN deployment + # Uncomment if DEBUG log level is needed for the components + #- yq -i '((select(.kind=="Deployment").spec.template.spec.containers.[] | select(.name=="server").env.[]) | select(.name=="LOG_LEVEL").value) |= "DEBUG"' manifests/contextservice.yaml + #- yq -i '((select(.kind=="Deployment").spec.template.spec.containers.[] | select(.name=="server").env.[]) | select(.name=="LOG_LEVEL").value) |= "DEBUG"' manifests/deviceservice.yaml + #- yq -i '((select(.kind=="Deployment").spec.template.spec.containers.[] | select(.name=="frontend").env.[]) | select(.name=="LOG_LEVEL").value) |= "DEBUG"' manifests/pathcompservice.yaml + #- yq -i '((select(.kind=="Deployment").spec.template.spec.containers.[] | select(.name=="server").env.[]) | select(.name=="LOG_LEVEL").value) |= "DEBUG"' manifests/serviceservice.yaml + #- yq -i '((select(.kind=="Deployment").spec.template.spec.containers.[] | select(.name=="server").env.[]) | select(.name=="LOG_LEVEL").value) |= "DEBUG"' manifests/nbiservice.yaml + #- yq -i '((select(.kind=="Deployment").spec.template.spec.containers.[] | select(.name=="server").env.[]) | select(.name=="LOG_LEVEL").value) |= "DEBUG"' manifests/monitoringservice.yaml + + - source src/tests/${TEST_NAME}/deploy_specs.sh + #- export TFS_REGISTRY_IMAGES="${CI_REGISTRY_IMAGE}" + #- export TFS_SKIP_BUILD="YES" + #- export TFS_IMAGE_TAG="latest" + #- echo "TFS_REGISTRY_IMAGES=${CI_REGISTRY_IMAGE}" + + # Deploy TeraFlowSDN + - ./deploy/crdb.sh + - ./deploy/nats.sh + - ./deploy/qdb.sh + - ./deploy/kafka.sh + - ./deploy/tfs.sh + - ./deploy/show.sh + + ## Wait for Context to be subscribed to NATS + ## WARNING: this loop is infinite if there is no subscriber (such as monitoring). + ## Investigate if we can use a counter to limit the number of iterations. + ## For now, keep it commented out. + #- LOOP_MAX_ATTEMPTS=180 + #- LOOP_COUNTER=0 + #- > + # while ! kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/contextservice -c server 2>&1 | grep -q 'Subscriber is Ready? True'; do + # echo "Attempt: $LOOP_COUNTER" + # kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/contextservice -c server 2>&1; + # sleep 1; + # LOOP_COUNTER=$((LOOP_COUNTER + 1)) + # if [ "$LOOP_COUNTER" -ge "$LOOP_MAX_ATTEMPTS" ]; then + # echo "Max attempts reached, exiting the loop." + # break + # fi + # done + - kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/contextservice -c server + + # Run end-to-end test: onboard scenario + - > + docker run -t --rm --name ${TEST_NAME} --network=host + --volume "$PWD/tfs_runtime_env_vars.sh:/var/teraflow/tfs_runtime_env_vars.sh" + --volume "$PWD/src/tests/${TEST_NAME}:/opt/results" + $CI_REGISTRY_IMAGE/${TEST_NAME}:latest /var/teraflow/run-onboarding.sh + + # Run end-to-end test: configure service TFS + - > + docker run -t --rm --name ${TEST_NAME} --network=host + --volume "$PWD/tfs_runtime_env_vars.sh:/var/teraflow/tfs_runtime_env_vars.sh" + --volume "$PWD/src/tests/${TEST_NAME}:/opt/results" + $CI_REGISTRY_IMAGE/${TEST_NAME}:latest /var/teraflow/run-service-tfs-create.sh + + # Run end-to-end test: test connectivity with ping + - sudo containerlab exec --name eucnc24 --label clab-node-name=dc1 --cmd 'ping -n -c3 192.168.1.10' + - sudo containerlab exec --name eucnc24 --label clab-node-name=dc1 --cmd 'ping -n -c3 192.168.1.1' + - sudo containerlab exec --name eucnc24 --label clab-node-name=dc1 --cmd 'ping -n -c3 192.168.2.1' + - sudo containerlab exec --name eucnc24 --label clab-node-name=dc1 --cmd 'ping -n -c3 192.168.2.10' + - sudo containerlab exec --name eucnc24 --label clab-node-name=dc1 --cmd 'ping -n -c3 192.168.3.10' + + # Run end-to-end test: deconfigure service TFS + - > + docker run -t --rm --name ${TEST_NAME} --network=host + --volume "$PWD/tfs_runtime_env_vars.sh:/var/teraflow/tfs_runtime_env_vars.sh" + --volume "$PWD/src/tests/${TEST_NAME}:/opt/results" + $CI_REGISTRY_IMAGE/${TEST_NAME}:latest /var/teraflow/run-service-tfs-remove.sh + + # Run end-to-end test: configure service IETF + - > + docker run -t --rm --name ${TEST_NAME} --network=host + --volume "$PWD/tfs_runtime_env_vars.sh:/var/teraflow/tfs_runtime_env_vars.sh" + --volume "$PWD/src/tests/${TEST_NAME}:/opt/results" + $CI_REGISTRY_IMAGE/${TEST_NAME}:latest /var/teraflow/run-service-ietf-create.sh + + # Run end-to-end test: test connectivity with ping + - sudo containerlab exec --name eucnc24 --label clab-node-name=dc1 --cmd 'ping -n -c3 192.168.1.10' + - sudo containerlab exec --name eucnc24 --label clab-node-name=dc1 --cmd 'ping -n -c3 192.168.1.1' + - sudo containerlab exec --name eucnc24 --label clab-node-name=dc1 --cmd 'ping -n -c3 192.168.2.1' + - sudo containerlab exec --name eucnc24 --label clab-node-name=dc1 --cmd 'ping -n -c3 192.168.2.10' + - sudo containerlab exec --name eucnc24 --label clab-node-name=dc1 --cmd 'ping -n -c3 192.168.3.10' + + # Run end-to-end test: deconfigure service IETF + - > + docker run -t --rm --name ${TEST_NAME} --network=host + --volume "$PWD/tfs_runtime_env_vars.sh:/var/teraflow/tfs_runtime_env_vars.sh" + --volume "$PWD/src/tests/${TEST_NAME}:/opt/results" + $CI_REGISTRY_IMAGE/${TEST_NAME}:latest /var/teraflow/run-service-ietf-remove.sh + + # Run end-to-end test: cleanup scenario + - > + docker run -t --rm --name ${TEST_NAME} --network=host + --volume "$PWD/tfs_runtime_env_vars.sh:/var/teraflow/tfs_runtime_env_vars.sh" + --volume "$PWD/src/tests/${TEST_NAME}:/opt/results" + $CI_REGISTRY_IMAGE/${TEST_NAME}:latest /var/teraflow/run-cleanup.sh + + after_script: + # Dump TeraFlowSDN component logs + - source src/tests/${TEST_NAME}/deploy_specs.sh + - kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/contextservice -c server + - kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/deviceservice -c server + - kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/pathcompservice -c frontend + - kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/serviceservice -c server + - kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/nbiservice -c server + #- kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/monitoringservice -c server + + # Destroy Scenario + - docker rm -f ${TEST_NAME} || true + - RUNNER_PATH=`pwd` + #- cd $PWD/src/tests/${TEST_NAME} + - cd /tmp/clab/${TEST_NAME} + - sudo containerlab destroy --topo eucnc24.clab.yml --cleanup || true + - sudo rm -rf clab-eucnc24/ .eucnc24.clab.yml.bak || true + - cd $RUNNER_PATH + - kubectl delete namespaces tfs || true + + # Clean old docker images + - docker images --filter="dangling=true" --quiet | xargs -r docker rmi + + #coverage: '/TOTAL\s+\d+\s+\d+\s+(\d+%)/' + rules: + - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && ($CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop" || $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH)' + - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "develop"' + artifacts: + when: always + reports: + junit: ./src/tests/${TEST_NAME}/report_*.xml diff --git a/src/tests/eucnc24/Dockerfile b/src/tests/eucnc24/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..23a38bace79880f37fe7957566f7d84030fa7fd8 --- /dev/null +++ b/src/tests/eucnc24/Dockerfile @@ -0,0 +1,84 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM python:3.9-slim + +# Install dependencies +RUN apt-get --yes --quiet --quiet update && \ + apt-get --yes --quiet --quiet install wget g++ git && \ + rm -rf /var/lib/apt/lists/* + +# Set Python to show logs as they occur +ENV PYTHONUNBUFFERED=0 + +# Get generic Python packages +RUN python3 -m pip install --upgrade pip +RUN python3 -m pip install --upgrade setuptools wheel +RUN python3 -m pip install --upgrade pip-tools + +# Get common Python packages +# Note: this step enables sharing the previous Docker build steps among all the Python components +WORKDIR /var/teraflow +COPY common_requirements.in common_requirements.in +RUN pip-compile --quiet --output-file=common_requirements.txt common_requirements.in +RUN python3 -m pip install -r common_requirements.txt + +# Add common files into working directory +WORKDIR /var/teraflow/common +COPY src/common/. ./ +RUN rm -rf proto + +# Create proto sub-folder, copy .proto files, and generate Python code +RUN mkdir -p /var/teraflow/common/proto +WORKDIR /var/teraflow/common/proto +RUN touch __init__.py +COPY proto/*.proto ./ +RUN python3 -m grpc_tools.protoc -I=. --python_out=. --grpc_python_out=. *.proto +RUN rm *.proto +RUN find . -type f -exec sed -i -E 's/(import\ .*)_pb2/from . \1_pb2/g' {} \; + +# Create component sub-folders, get specific Python packages +RUN mkdir -p /var/teraflow/tests/eucnc24 +WORKDIR /var/teraflow/tests/eucnc24 +COPY src/tests/eucnc24/requirements.in requirements.in +RUN pip-compile --quiet --output-file=requirements.txt requirements.in +RUN python3 -m pip install -r requirements.txt + +# Add component files into working directory +WORKDIR /var/teraflow +COPY src/__init__.py ./__init__.py +COPY src/common/*.py ./common/ +COPY src/common/tests/. ./common/tests/ +COPY src/common/tools/. ./common/tools/ +COPY src/context/__init__.py context/__init__.py +COPY src/context/client/. context/client/ +COPY src/device/__init__.py device/__init__.py +COPY src/device/client/. device/client/ +COPY src/monitoring/__init__.py monitoring/__init__.py +COPY src/monitoring/client/. monitoring/client/ +COPY src/service/__init__.py service/__init__.py +COPY src/service/client/. service/client/ +COPY src/slice/__init__.py slice/__init__.py +COPY src/slice/client/. slice/client/ +COPY src/tests/*.py ./tests/ +COPY src/tests/eucnc24/__init__.py ./tests/eucnc24/__init__.py +COPY src/tests/eucnc24/data/. ./tests/eucnc24/data/ +COPY src/tests/eucnc24/tests/. ./tests/eucnc24/tests/ +COPY src/tests/eucnc24/scripts/. ./ + +RUN apt-get --yes --quiet --quiet update && \ + apt-get --yes --quiet --quiet install tree && \ + rm -rf /var/lib/apt/lists/* + +RUN tree -la /var/teraflow diff --git a/src/tests/eucnc24/README.md b/src/tests/eucnc24/README.md new file mode 100644 index 0000000000000000000000000000000000000000..f8c2f9d49793b8e0751ea4ea09bf893e7ceae0b6 --- /dev/null +++ b/src/tests/eucnc24/README.md @@ -0,0 +1,123 @@ +# DataPlane-in-a-Box - Control an Emulated DataPlane through TeraFlowSDN + +## Emulated DataPlane Deployment +- ContainerLab +- Scenario +- Descriptor + +## TeraFlowSDN Deployment +```bash +cd ~/tfs-ctrl +source dataplane-in-a-box/deploy_specs.sh +./deploy/all.sh +``` + +# ContainerLab - Arista cEOS - Commands + +## Download and install ContainerLab +```bash +sudo bash -c "$(curl -sL https://get.containerlab.dev)" -- -v 0.48.6 +``` + +## Download Arista cEOS image and create Docker image +```bash +cd ~/tfs-ctrl/dataplane-in-a-box +docker import arista/cEOS64-lab-4.31.2F.tar ceos:4.31.2F +``` + +## Deploy scenario +```bash +cd ~/tfs-ctrl/dataplane-in-a-box +sudo containerlab deploy --topo arista.clab.yml +``` + +## Inspect scenario +```bash +cd ~/tfs-ctrl/dataplane-in-a-box +sudo containerlab inspect --topo arista.clab.yml +``` + +## Destroy scenario +```bash +cd ~/tfs-ctrl/dataplane-in-a-box +sudo containerlab destroy --topo arista.clab.yml +sudo rm -rf clab-arista/ .arista.clab.yml.bak +``` + +## Access cEOS Bash +```bash +docker exec -it clab-arista-r1 bash +``` + +## Access cEOS CLI +```bash +docker exec -it clab-arista-r1 Cli +docker exec -it clab-arista-r2 Cli +``` + +## Configure ContainerLab clients +```bash +docker exec -it clab-arista-client1 bash + ip address add 192.168.1.10/24 dev eth1 + ip route add 192.168.2.0/24 via 192.168.1.1 + ip route add 192.168.3.0/24 via 192.168.1.1 + ping 192.168.2.10 + ping 192.168.3.10 + +docker exec -it clab-arista-client2 bash + ip address add 192.168.2.10/24 dev eth1 + ip route add 192.168.1.0/24 via 192.168.2.1 + ip route add 192.168.3.0/24 via 192.168.2.1 + ping 192.168.1.10 + ping 192.168.3.10 + +docker exec -it clab-arista-client3 bash + ip address add 192.168.3.10/24 dev eth1 + ip route add 192.168.2.0/24 via 192.168.3.1 + ip route add 192.168.3.0/24 via 192.168.3.1 + ping 192.168.2.10 + ping 192.168.3.10 +``` + +## Install gNMIc +```bash +sudo bash -c "$(curl -sL https://get-gnmic.kmrd.dev)" +``` + +## gNMI Capabilities request +```bash +gnmic --address clab-arista-wan1 --port 6030 --username admin --password admin --insecure capabilities +``` + +## gNMI Get request +```bash +gnmic --address clab-arista-wan1 --port 6030 --username admin --password admin --insecure --encoding json_ietf get --path / > wan1.json +gnmic --address clab-arista-wan1 --port 6030 --username admin --password admin --insecure --encoding json_ietf get --path /interfaces/interface > wan1-ifaces.json +``` + +## gNMI Set request +```bash +#gnmic --address clab-arista-wan1 --port 6030 --username admin --password admin --insecure --encoding json_ietf set --update-path /system/config/hostname --update-value srl11 +#gnmic --address clab-arista-wan1 --port 6030 --username admin --password admin --insecure --encoding json_ietf get --path /system/config/hostname +``` + +## Subscribe request +```bash +gnmic --address clab-arista-wan1 --port 6030 --username admin --password admin --insecure --encoding json_ietf subscribe --path /interfaces/interface[name=Management0]/state/ + +# In another terminal, you can generate traffic opening SSH connection +ssh admin@clab-arista-wan1 +``` + +# Check configurations done: +```bash +gnmic --address clab-arista-wan1 --port 6030 --username admin --password admin --insecure --encoding json_ietf get --path '/network-instances' > wan1-nis.json +gnmic --address clab-arista-wan1 --port 6030 --username admin --password admin --insecure --encoding json_ietf get --path '/interfaces' > wan1-ifs.json +``` + +# Delete elements: +```bash +--address clab-arista-wan1 --port 6030 --username admin --password admin --insecure --encoding json_ietf set --delete '/network-instances/network-instance[name=b19229e8]' +--address clab-arista-wan1 --port 6030 --username admin --password admin --insecure --encoding json_ietf set --delete '/interfaces/interface[name=ethernet-1/1]/subinterfaces/subinterface[index=0]' +--address clab-arista-wan1 --port 6030 --username admin --password admin --insecure --encoding json_ietf set --delete '/interfaces/interface[name=ethernet-1/2]/subinterfaces/subinterface[index=0]' +``` diff --git a/src/tests/eucnc24/__init__.py b/src/tests/eucnc24/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..3ee6f7071f145e06c3aeaefc09a43ccd88e619e3 --- /dev/null +++ b/src/tests/eucnc24/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/src/tests/eucnc24/clab/eucnc24.clab.yml b/src/tests/eucnc24/clab/eucnc24.clab.yml new file mode 100644 index 0000000000000000000000000000000000000000..c9182ba1d405cce514d54494da37d27586fd83ac --- /dev/null +++ b/src/tests/eucnc24/clab/eucnc24.clab.yml @@ -0,0 +1,70 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# TFS - Arista devices + Linux clients + +name: eucnc24 + +mgmt: + network: mgmt-net + ipv4-subnet: 172.20.20.0/24 + +topology: + kinds: + arista_ceos: + kind: arista_ceos + #image: ceos:4.30.4M + #image: ceos:4.31.2F + #image: ceos:4.31.5M + #image: ceos:4.32.0F + image: ceos:4.32.2F + #image: ceos:4.32.2.1F + linux: + kind: linux + image: ghcr.io/hellt/network-multitool:latest + + nodes: + r1: + kind: arista_ceos + mgmt-ipv4: 172.20.20.101 + + r2: + kind: arista_ceos + mgmt-ipv4: 172.20.20.102 + + r3: + kind: arista_ceos + mgmt-ipv4: 172.20.20.103 + + dc1: + kind: linux + mgmt-ipv4: 172.20.20.211 + exec: + - ip link set address 00:c1:ab:00:01:01 dev eth1 + - ip address add 192.168.1.10/24 dev eth1 + - ip route add 192.168.2.0/24 via 192.168.1.1 + + dc2: + kind: linux + mgmt-ipv4: 172.20.20.221 + exec: + - ip link set address 00:c1:ab:00:02:01 dev eth1 + - ip address add 192.168.2.10/24 dev eth1 + - ip route add 192.168.1.0/24 via 192.168.2.1 + + links: + - endpoints: ["r1:eth2", "r2:eth1"] + - endpoints: ["r2:eth3", "r3:eth2"] + - endpoints: ["r1:eth10", "dc1:eth1"] + - endpoints: ["r3:eth10", "dc2:eth1"] diff --git a/src/tests/eucnc24/data/ietf-l3vpn-service.json b/src/tests/eucnc24/data/ietf-l3vpn-service.json new file mode 100644 index 0000000000000000000000000000000000000000..a6297b28f0fea94dcc8a457ad2b45d38e77aa4ea --- /dev/null +++ b/src/tests/eucnc24/data/ietf-l3vpn-service.json @@ -0,0 +1,83 @@ +{ + "ietf-l3vpn-svc:l3vpn-svc": { + "vpn-services": {"vpn-service": [{"vpn-id": "ietf-l3vpn-svc"}]}, + "sites": { + "site": [ + { + "site-id": "site_DC1", + "management": {"type": "ietf-l3vpn-svc:provider-managed"}, + "locations": {"location": [{"location-id": "DC1"}]}, + "devices": {"device": [{"device-id": "dc1", "location": "DC1"}]}, + "site-network-accesses": { + "site-network-access": [ + { + "site-network-access-id": "int", + "site-network-access-type": "ietf-l3vpn-svc:multipoint", + "device-reference": "dc1", + "vpn-attachment": {"vpn-id": "ietf-l3vpn-svc", "site-role": "ietf-l3vpn-svc:spoke-role"}, + "ip-connection": { + "ipv4": { + "address-allocation-type": "ietf-l3vpn-svc:static-address", + "addresses": { + "provider-address": "192.168.1.1", + "customer-address": "192.168.1.10", + "prefix-length": 24 + } + } + }, + "service": { + "svc-mtu": 1500, + "svc-input-bandwidth": 1000000000, + "svc-output-bandwidth": 1000000000, + "qos": {"qos-profile": {"classes": {"class": [{ + "class-id": "qos-realtime", + "direction": "ietf-l3vpn-svc:both", + "latency": {"latency-boundary": 10}, + "bandwidth": {"guaranteed-bw-percent": 100} + }]}}} + } + } + ] + } + }, + { + "site-id": "site_DC2", + "management": {"type": "ietf-l3vpn-svc:provider-managed"}, + "locations": {"location": [{"location-id": "DC2"}]}, + "devices": {"device": [{"device-id": "dc2", "location": "DC2"}]}, + "site-network-accesses": { + "site-network-access": [ + { + "site-network-access-id": "int", + "site-network-access-type": "ietf-l3vpn-svc:multipoint", + "device-reference": "dc2", + "vpn-attachment": {"vpn-id": "ietf-l3vpn-svc", "site-role": "ietf-l3vpn-svc:hub-role"}, + "ip-connection": { + "ipv4": { + "address-allocation-type": "ietf-l3vpn-svc:static-address", + "addresses": { + "provider-address": "192.168.2.1", + "customer-address": "192.168.2.10", + "prefix-length": 24 + } + } + }, + "service": { + "svc-mtu": 1500, + "svc-input-bandwidth": 1000000000, + "svc-output-bandwidth": 1000000000, + "qos": {"qos-profile": {"classes": {"class": [{ + "class-id": "qos-realtime", + "direction": "ietf-l3vpn-svc:both", + "latency": {"latency-boundary": 10}, + "bandwidth": {"guaranteed-bw-percent": 100} + }]}}} + } + } + ] + } + } + ] + } + } +} diff --git a/src/tests/eucnc24/data/tfs-service.json b/src/tests/eucnc24/data/tfs-service.json new file mode 100644 index 0000000000000000000000000000000000000000..397fc84789111932da047acd22c7bc787888657f --- /dev/null +++ b/src/tests/eucnc24/data/tfs-service.json @@ -0,0 +1,26 @@ +{ + "services": [ + { + "service_id": { + "context_id": {"context_uuid": {"uuid": "admin"}}, "service_uuid": {"uuid": "tfs-l3vpn-svc"} + }, + "service_type": "SERVICETYPE_L3NM", + "service_status": {"service_status": "SERVICESTATUS_PLANNED"}, + "service_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "dc1"}}, "endpoint_uuid": {"uuid": "int"}}, + {"device_id": {"device_uuid": {"uuid": "dc2"}}, "endpoint_uuid": {"uuid": "int"}} + ], + "service_constraints": [], + "service_config": {"config_rules": [ + {"action": "CONFIGACTION_SET", "custom": { + "resource_key": "/device[dc1]/endpoint[eth1]/settings", + "resource_value": {"address_ip": "192.168.1.10", "address_prefix": 24, "index": 0} + }}, + {"action": "CONFIGACTION_SET", "custom": { + "resource_key": "/device[dc2]/endpoint[eth1]/settings", + "resource_value": {"address_ip": "192.168.2.10", "address_prefix": 24, "index": 0} + }} + ]} + } + ] +} diff --git a/src/tests/eucnc24/data/tfs-topology.json b/src/tests/eucnc24/data/tfs-topology.json new file mode 100644 index 0000000000000000000000000000000000000000..ac87af62d31e4728c12687c525233d8e840d7441 --- /dev/null +++ b/src/tests/eucnc24/data/tfs-topology.json @@ -0,0 +1,126 @@ +{ + "contexts": [ + {"context_id": {"context_uuid": {"uuid": "admin"}}} + ], + "topologies": [ + {"topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}} + ], + "devices": [ + { + "device_id": {"device_uuid": {"uuid": "dc1"}}, "device_type": "emu-datacenter", + "device_drivers": ["DEVICEDRIVER_UNDEFINED"], + "device_config": {"config_rules": [ + {"action": "CONFIGACTION_SET", "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": "CONFIGACTION_SET", "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": "CONFIGACTION_SET", "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"uuid": "eth1", "type": "copper"}, {"uuid": "int", "type": "copper"} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "dc2"}}, "device_type": "emu-datacenter", + "device_drivers": ["DEVICEDRIVER_UNDEFINED"], + "device_config": {"config_rules": [ + {"action": "CONFIGACTION_SET", "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": "CONFIGACTION_SET", "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": "CONFIGACTION_SET", "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"uuid": "eth1", "type": "copper"}, {"uuid": "int", "type": "copper"} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "r1"}}, "device_type": "packet-router", + "device_drivers": ["DEVICEDRIVER_GNMI_OPENCONFIG"], + "device_config": {"config_rules": [ + {"action": "CONFIGACTION_SET", "custom": {"resource_key": "_connect/address", "resource_value": "172.20.20.101"}}, + {"action": "CONFIGACTION_SET", "custom": {"resource_key": "_connect/port", "resource_value": "6030"}}, + {"action": "CONFIGACTION_SET", "custom": {"resource_key": "_connect/settings", "resource_value": { + "username": "admin", "password": "admin", "use_tls": false + }}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "r2"}}, "device_type": "packet-router", + "device_drivers": ["DEVICEDRIVER_GNMI_OPENCONFIG"], + "device_config": {"config_rules": [ + {"action": "CONFIGACTION_SET", "custom": {"resource_key": "_connect/address", "resource_value": "172.20.20.102"}}, + {"action": "CONFIGACTION_SET", "custom": {"resource_key": "_connect/port", "resource_value": "6030"}}, + {"action": "CONFIGACTION_SET", "custom": {"resource_key": "_connect/settings", "resource_value": { + "username": "admin", "password": "admin", "use_tls": false + }}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "r3"}}, "device_type": "packet-router", + "device_drivers": ["DEVICEDRIVER_GNMI_OPENCONFIG"], + "device_config": {"config_rules": [ + {"action": "CONFIGACTION_SET", "custom": {"resource_key": "_connect/address", "resource_value": "172.20.20.103"}}, + {"action": "CONFIGACTION_SET", "custom": {"resource_key": "_connect/port", "resource_value": "6030"}}, + {"action": "CONFIGACTION_SET", "custom": {"resource_key": "_connect/settings", "resource_value": { + "username": "admin", "password": "admin", "use_tls": false + }}} + ]} + } + ], + "links": [ + { + "link_id": {"link_uuid": {"uuid": "r1/Ethernet2==r2/Ethernet1"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "r1"}}, "endpoint_uuid": {"uuid": "Ethernet2"}}, + {"device_id": {"device_uuid": {"uuid": "r2"}}, "endpoint_uuid": {"uuid": "Ethernet1"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "r2/Ethernet1==r1/Ethernet2"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "r2"}}, "endpoint_uuid": {"uuid": "Ethernet1"}}, + {"device_id": {"device_uuid": {"uuid": "r1"}}, "endpoint_uuid": {"uuid": "Ethernet2"}} + ] + }, + + { + "link_id": {"link_uuid": {"uuid": "r2/Ethernet3==r3/Ethernet2"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "r2"}}, "endpoint_uuid": {"uuid": "Ethernet3"}}, + {"device_id": {"device_uuid": {"uuid": "r3"}}, "endpoint_uuid": {"uuid": "Ethernet2"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "r3/Ethernet2==r2/Ethernet3"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "r3"}}, "endpoint_uuid": {"uuid": "Ethernet2"}}, + {"device_id": {"device_uuid": {"uuid": "r2"}}, "endpoint_uuid": {"uuid": "Ethernet3"}} + ] + }, + + { + "link_id": {"link_uuid": {"uuid": "r1/Ethernet10==dc1/eth1"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "r1"}}, "endpoint_uuid": {"uuid": "Ethernet10"}}, + {"device_id": {"device_uuid": {"uuid": "dc1"}}, "endpoint_uuid": {"uuid": "eth1"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "dc1/eth1==r1/Ethernet10"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "dc1"}}, "endpoint_uuid": {"uuid": "eth1"}}, + {"device_id": {"device_uuid": {"uuid": "r1"}}, "endpoint_uuid": {"uuid": "Ethernet10"}} + ] + }, + + { + "link_id": {"link_uuid": {"uuid": "r3/Ethernet10==dc2/eth1"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "r3"}}, "endpoint_uuid": {"uuid": "Ethernet10"}}, + {"device_id": {"device_uuid": {"uuid": "dc2"}}, "endpoint_uuid": {"uuid": "eth1"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "dc2/eth1==r3/Ethernet10"}}, + "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "dc2"}}, "endpoint_uuid": {"uuid": "eth1"}}, + {"device_id": {"device_uuid": {"uuid": "r3"}}, "endpoint_uuid": {"uuid": "Ethernet10"}} + ] + } + ] +} diff --git a/src/tests/eucnc24/deploy-scripts/clab-cli-dc1.sh b/src/tests/eucnc24/deploy-scripts/clab-cli-dc1.sh new file mode 100755 index 0000000000000000000000000000000000000000..d0ee18dcfd7eac03b108e163200d14b532d8db8f --- /dev/null +++ b/src/tests/eucnc24/deploy-scripts/clab-cli-dc1.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +docker exec -it clab-eucnc24-dc1 bash diff --git a/src/tests/eucnc24/deploy-scripts/clab-cli-dc2.sh b/src/tests/eucnc24/deploy-scripts/clab-cli-dc2.sh new file mode 100755 index 0000000000000000000000000000000000000000..2867fedcb9051f6d73b78d571b33ed7ae25efd80 --- /dev/null +++ b/src/tests/eucnc24/deploy-scripts/clab-cli-dc2.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +docker exec -it clab-eucnc24-dc2 bash diff --git a/src/tests/eucnc24/deploy-scripts/clab-cli-r1.sh b/src/tests/eucnc24/deploy-scripts/clab-cli-r1.sh new file mode 100755 index 0000000000000000000000000000000000000000..69141a0ae73ee23274b823242b0f864e1527d505 --- /dev/null +++ b/src/tests/eucnc24/deploy-scripts/clab-cli-r1.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +docker exec -it clab-eucnc24-r1 Cli diff --git a/src/tests/eucnc24/deploy-scripts/clab-cli-r2.sh b/src/tests/eucnc24/deploy-scripts/clab-cli-r2.sh new file mode 100755 index 0000000000000000000000000000000000000000..7860d1d21f07a293f3bcbc65575625568a49a41c --- /dev/null +++ b/src/tests/eucnc24/deploy-scripts/clab-cli-r2.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +docker exec -it clab-eucnc24-r2 Cli diff --git a/src/tests/eucnc24/deploy-scripts/clab-cli-r3.sh b/src/tests/eucnc24/deploy-scripts/clab-cli-r3.sh new file mode 100755 index 0000000000000000000000000000000000000000..801c3223d7c59e767bcc64b0cc331553a34bb4b4 --- /dev/null +++ b/src/tests/eucnc24/deploy-scripts/clab-cli-r3.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +docker exec -it clab-eucnc24-r3 Cli diff --git a/src/tests/eucnc24/deploy-scripts/clab-deploy.sh b/src/tests/eucnc24/deploy-scripts/clab-deploy.sh new file mode 100755 index 0000000000000000000000000000000000000000..ae1676ada75ab6a5ce671fae187c0a94ffc62331 --- /dev/null +++ b/src/tests/eucnc24/deploy-scripts/clab-deploy.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cd ~/tfs-ctrl/src/tests/eucnc24 +sudo containerlab deploy --topo eucnc24.clab.yml diff --git a/src/tests/eucnc24/deploy-scripts/clab-destroy.sh b/src/tests/eucnc24/deploy-scripts/clab-destroy.sh new file mode 100755 index 0000000000000000000000000000000000000000..6e58a3c490c196073f4cb259e11d45dd4ff2a1f8 --- /dev/null +++ b/src/tests/eucnc24/deploy-scripts/clab-destroy.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cd ~/tfs-ctrl/src/tests/eucnc24 +sudo containerlab destroy --topo eucnc24.clab.yml +sudo rm -rf clab-eucnc24/ .eucnc24.clab.yml.bak diff --git a/src/tests/eucnc24/deploy-scripts/clab-inspect.sh b/src/tests/eucnc24/deploy-scripts/clab-inspect.sh new file mode 100755 index 0000000000000000000000000000000000000000..0dd6dce12dc60e775edc6ab449d1d448f37bd686 --- /dev/null +++ b/src/tests/eucnc24/deploy-scripts/clab-inspect.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cd ~/tfs-ctrl/src/tests/eucnc24 +sudo containerlab inspect --topo eucnc24.clab.yml diff --git a/src/tests/eucnc24/deploy_specs.sh b/src/tests/eucnc24/deploy_specs.sh new file mode 100755 index 0000000000000000000000000000000000000000..aa72575a97f7b73ce916448307c3e1773fac57c4 --- /dev/null +++ b/src/tests/eucnc24/deploy_specs.sh @@ -0,0 +1,211 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# ----- TeraFlowSDN ------------------------------------------------------------ + +# Set the URL of the internal MicroK8s Docker registry where the images will be uploaded to. +export TFS_REGISTRY_IMAGES="http://localhost:32000/tfs/" + +# Set the list of components, separated by spaces, you want to build images for, and deploy. +#export TFS_COMPONENTS="context device pathcomp service slice nbi webui load_generator" +export TFS_COMPONENTS="context device pathcomp service nbi" + +# Uncomment to activate Monitoring (old) +#export TFS_COMPONENTS="${TFS_COMPONENTS} monitoring" + +# Uncomment to activate Monitoring Framework (new) +#export TFS_COMPONENTS="${TFS_COMPONENTS} kpi_manager kpi_value_writer kpi_value_api telemetry analytics automation" + +# Uncomment to activate QoS Profiles +#export TFS_COMPONENTS="${TFS_COMPONENTS} qos_profile" + +# Uncomment to activate BGP-LS Speaker +#export TFS_COMPONENTS="${TFS_COMPONENTS} bgpls_speaker" + +# Uncomment to activate Optical Controller +# To manage optical connections, "service" requires "opticalcontroller" to be deployed +# before "service", thus we "hack" the TFS_COMPONENTS environment variable prepending the +# "opticalcontroller" only if "service" is already in TFS_COMPONENTS, and re-export it. +#if [[ "$TFS_COMPONENTS" == *"service"* ]]; then +# BEFORE="${TFS_COMPONENTS% service*}" +# AFTER="${TFS_COMPONENTS#* service}" +# export TFS_COMPONENTS="${BEFORE} opticalcontroller service ${AFTER}" +#fi + +# Uncomment to activate ZTP +#export TFS_COMPONENTS="${TFS_COMPONENTS} ztp" + +# Uncomment to activate Policy Manager +#export TFS_COMPONENTS="${TFS_COMPONENTS} policy" + +# Uncomment to activate Optical CyberSecurity +#export TFS_COMPONENTS="${TFS_COMPONENTS} dbscanserving opticalattackmitigator opticalattackdetector opticalattackmanager" + +# Uncomment to activate L3 CyberSecurity +#export TFS_COMPONENTS="${TFS_COMPONENTS} l3_attackmitigator l3_centralizedattackdetector" + +# Uncomment to activate TE +#export TFS_COMPONENTS="${TFS_COMPONENTS} te" + +# Uncomment to activate Forecaster +#export TFS_COMPONENTS="${TFS_COMPONENTS} forecaster" + +# Uncomment to activate E2E Orchestrator +#export TFS_COMPONENTS="${TFS_COMPONENTS} e2e_orchestrator" + +# Uncomment to activate DLT and Interdomain +#export TFS_COMPONENTS="${TFS_COMPONENTS} interdomain dlt" +#if [[ "$TFS_COMPONENTS" == *"dlt"* ]]; then +# export KEY_DIRECTORY_PATH="src/dlt/gateway/keys/priv_sk" +# export CERT_DIRECTORY_PATH="src/dlt/gateway/keys/cert.pem" +# export TLS_CERT_PATH="src/dlt/gateway/keys/ca.crt" +#fi + +# Uncomment to activate QKD App +# To manage QKD Apps, "service" requires "qkd_app" to be deployed +# before "service", thus we "hack" the TFS_COMPONENTS environment variable prepending the +# "qkd_app" only if "service" is already in TFS_COMPONENTS, and re-export it. +#if [[ "$TFS_COMPONENTS" == *"service"* ]]; then +# BEFORE="${TFS_COMPONENTS% service*}" +# AFTER="${TFS_COMPONENTS#* service}" +# export TFS_COMPONENTS="${BEFORE} qkd_app service ${AFTER}" +#fi + + +# Set the tag you want to use for your images. +export TFS_IMAGE_TAG="dev" + +# Set the name of the Kubernetes namespace to deploy TFS to. +export TFS_K8S_NAMESPACE="tfs" + +# Set additional manifest files to be applied after the deployment +export TFS_EXTRA_MANIFESTS="manifests/nginx_ingress_http.yaml" + +# Uncomment to monitor performance of components +#export TFS_EXTRA_MANIFESTS="${TFS_EXTRA_MANIFESTS} manifests/servicemonitors.yaml" + +# Uncomment when deploying Optical CyberSecurity +#export TFS_EXTRA_MANIFESTS="${TFS_EXTRA_MANIFESTS} manifests/cachingservice.yaml" + +# Set the new Grafana admin password +export TFS_GRAFANA_PASSWORD="admin123+" + +# Disable skip-build flag to rebuild the Docker images. +export TFS_SKIP_BUILD="" + + +# ----- CockroachDB ------------------------------------------------------------ + +# Set the namespace where CockroackDB will be deployed. +export CRDB_NAMESPACE="crdb" + +# Set the external port CockroackDB Postgre SQL interface will be exposed to. +export CRDB_EXT_PORT_SQL="26257" + +# Set the external port CockroackDB HTTP Mgmt GUI interface will be exposed to. +export CRDB_EXT_PORT_HTTP="8081" + +# Set the database username to be used by Context. +export CRDB_USERNAME="tfs" + +# Set the database user's password to be used by Context. +export CRDB_PASSWORD="tfs123" + +# Set the database name to be used by Context. +export CRDB_DATABASE="tfs" + +# Set CockroachDB installation mode to 'single'. This option is convenient for development and testing. +# See ./deploy/all.sh or ./deploy/crdb.sh for additional details +export CRDB_DEPLOY_MODE="single" + +# Disable flag for dropping database, if it exists. +export CRDB_DROP_DATABASE_IF_EXISTS="YES" + +# Disable flag for re-deploying CockroachDB from scratch. +export CRDB_REDEPLOY="" + + +# ----- NATS ------------------------------------------------------------------- + +# Set the namespace where NATS will be deployed. +export NATS_NAMESPACE="nats" + +# Set the external port NATS Client interface will be exposed to. +export NATS_EXT_PORT_CLIENT="4222" + +# Set the external port NATS HTTP Mgmt GUI interface will be exposed to. +export NATS_EXT_PORT_HTTP="8222" + +# Set NATS installation mode to 'single'. This option is convenient for development and testing. +# See ./deploy/all.sh or ./deploy/nats.sh for additional details +export NATS_DEPLOY_MODE="single" + +# Disable flag for re-deploying NATS from scratch. +export NATS_REDEPLOY="" + + +# ----- QuestDB ---------------------------------------------------------------- + +# Set the namespace where QuestDB will be deployed. +export QDB_NAMESPACE="qdb" + +# Set the external port QuestDB Postgre SQL interface will be exposed to. +export QDB_EXT_PORT_SQL="8812" + +# Set the external port QuestDB Influx Line Protocol interface will be exposed to. +export QDB_EXT_PORT_ILP="9009" + +# Set the external port QuestDB HTTP Mgmt GUI interface will be exposed to. +export QDB_EXT_PORT_HTTP="9000" + +# Set the database username to be used for QuestDB. +export QDB_USERNAME="admin" + +# Set the database user's password to be used for QuestDB. +export QDB_PASSWORD="quest" + +# Set the table name to be used by Monitoring for KPIs. +export QDB_TABLE_MONITORING_KPIS="tfs_monitoring_kpis" + +# Set the table name to be used by Slice for plotting groups. +export QDB_TABLE_SLICE_GROUPS="tfs_slice_groups" + +# Disable flag for dropping tables if they exist. +export QDB_DROP_TABLES_IF_EXIST="YES" + +# Disable flag for re-deploying QuestDB from scratch. +export QDB_REDEPLOY="" + + +# ----- K8s Observability ------------------------------------------------------ + +# Set the external port Prometheus Mgmt HTTP GUI interface will be exposed to. +export PROM_EXT_PORT_HTTP="9090" + +# Set the external port Grafana HTTP Dashboards will be exposed to. +export GRAF_EXT_PORT_HTTP="3000" + + +# ----- Apache Kafka ----------------------------------------------------------- + +# Set the namespace where Apache Kafka will be deployed. +export KFK_NAMESPACE="kafka" + +# Set the port Apache Kafka server will be exposed to. +export KFK_SERVER_PORT="9092" + +# Set the flag to YES for redeploying of Apache Kafka +export KFK_REDEPLOY="" diff --git a/src/tests/eucnc24/redeploy-tfs.sh b/src/tests/eucnc24/redeploy-tfs.sh new file mode 100755 index 0000000000000000000000000000000000000000..b5ced0d12e026be4b9cb7eefcf343445b776f042 --- /dev/null +++ b/src/tests/eucnc24/redeploy-tfs.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +source ~/tfs-ctrl/src/tests/eucnc24/deploy_specs.sh +./deploy/all.sh diff --git a/src/tests/eucnc24/requirements.in b/src/tests/eucnc24/requirements.in new file mode 100644 index 0000000000000000000000000000000000000000..468af1a17931d6e545647e3e7a057433d74826b3 --- /dev/null +++ b/src/tests/eucnc24/requirements.in @@ -0,0 +1,15 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +requests==2.27.* diff --git a/src/tests/eucnc24/scripts/run-cleanup.sh b/src/tests/eucnc24/scripts/run-cleanup.sh new file mode 100755 index 0000000000000000000000000000000000000000..3f697711a48f8d7fdf9fbd84d8100013ca349272 --- /dev/null +++ b/src/tests/eucnc24/scripts/run-cleanup.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +source /var/teraflow/tfs_runtime_env_vars.sh +export PYTHONPATH=/var/teraflow +pytest --verbose --log-level=INFO \ + --junitxml=/opt/results/report_cleanup.xml \ + /var/teraflow/tests/eucnc24/tests/test_cleanup.py diff --git a/src/tests/eucnc24/scripts/run-onboarding.sh b/src/tests/eucnc24/scripts/run-onboarding.sh new file mode 100755 index 0000000000000000000000000000000000000000..57fc435e417e02edca08ff92d5db03bf9332c0e0 --- /dev/null +++ b/src/tests/eucnc24/scripts/run-onboarding.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +source /var/teraflow/tfs_runtime_env_vars.sh +export PYTHONPATH=/var/teraflow +pytest --verbose --log-level=INFO \ + --junitxml=/opt/results/report_onboarding.xml \ + /var/teraflow/tests/eucnc24/tests/test_onboarding.py diff --git a/src/tests/eucnc24/scripts/run-service-ietf-create.sh b/src/tests/eucnc24/scripts/run-service-ietf-create.sh new file mode 100755 index 0000000000000000000000000000000000000000..d2ec9eef51a1dbe311ddbc052d305ef0d8c92085 --- /dev/null +++ b/src/tests/eucnc24/scripts/run-service-ietf-create.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +source /var/teraflow/tfs_runtime_env_vars.sh +export PYTHONPATH=/var/teraflow +pytest --verbose --log-level=INFO \ + --junitxml=/opt/results/report_service_ietf_create.xml \ + /var/teraflow/tests/eucnc24/tests/test_service_ietf_create.py diff --git a/src/tests/eucnc24/scripts/run-service-ietf-remove.sh b/src/tests/eucnc24/scripts/run-service-ietf-remove.sh new file mode 100755 index 0000000000000000000000000000000000000000..8c52636001fb8163b3a94fa0b229c7ab1264ab13 --- /dev/null +++ b/src/tests/eucnc24/scripts/run-service-ietf-remove.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +source /var/teraflow/tfs_runtime_env_vars.sh +export PYTHONPATH=/var/teraflow +pytest --verbose --log-level=INFO \ + --junitxml=/opt/results/report_service_ietf_remove.xml \ + /var/teraflow/tests/eucnc24/tests/test_service_ietf_remove.py diff --git a/src/tests/eucnc24/scripts/run-service-tfs-create.sh b/src/tests/eucnc24/scripts/run-service-tfs-create.sh new file mode 100755 index 0000000000000000000000000000000000000000..5395f38b3ab82a11361a066f2d80b750e636b010 --- /dev/null +++ b/src/tests/eucnc24/scripts/run-service-tfs-create.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +source /var/teraflow/tfs_runtime_env_vars.sh +export PYTHONPATH=/var/teraflow +pytest --verbose --log-level=INFO \ + --junitxml=/opt/results/report_service_tfs_create.xml \ + /var/teraflow/tests/eucnc24/tests/test_service_tfs_create.py diff --git a/src/tests/eucnc24/scripts/run-service-tfs-remove.sh b/src/tests/eucnc24/scripts/run-service-tfs-remove.sh new file mode 100755 index 0000000000000000000000000000000000000000..d82d6d7738c24039fc4b8e878f48bb3c0c90f3fe --- /dev/null +++ b/src/tests/eucnc24/scripts/run-service-tfs-remove.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +source /var/teraflow/tfs_runtime_env_vars.sh +export PYTHONPATH=/var/teraflow +pytest --verbose --log-level=INFO \ + --junitxml=/opt/results/report_service_tfs_remove.xml \ + /var/teraflow/tests/eucnc24/tests/test_service_tfs_remove.py diff --git a/src/tests/eucnc24/tests/Fixtures.py b/src/tests/eucnc24/tests/Fixtures.py new file mode 100644 index 0000000000000000000000000000000000000000..a9d2ceb24488dbd5b3775c958358df419dec4172 --- /dev/null +++ b/src/tests/eucnc24/tests/Fixtures.py @@ -0,0 +1,43 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +from context.client.ContextClient import ContextClient +from device.client.DeviceClient import DeviceClient +from monitoring.client.MonitoringClient import MonitoringClient +from service.client.ServiceClient import ServiceClient + +@pytest.fixture(scope='session') +def context_client(): + _client = ContextClient() + yield _client + _client.close() + +@pytest.fixture(scope='session') +def device_client(): + _client = DeviceClient() + yield _client + _client.close() + +@pytest.fixture(scope='session') +def monitoring_client(): + _client = MonitoringClient() + yield _client + _client.close() + +@pytest.fixture(scope='session') +def service_client(): + _client = ServiceClient() + yield _client + _client.close() diff --git a/src/tests/eucnc24/tests/Tools.py b/src/tests/eucnc24/tests/Tools.py new file mode 100644 index 0000000000000000000000000000000000000000..e983ffefff3d5aea6b078efa9bb586202957a5e1 --- /dev/null +++ b/src/tests/eucnc24/tests/Tools.py @@ -0,0 +1,109 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import enum, logging, requests +from typing import Any, Dict, List, Optional, Set, Union +from common.Constants import ServiceNameEnum +from common.Settings import get_service_host, get_service_port_http + +NBI_ADDRESS = get_service_host(ServiceNameEnum.NBI) +NBI_PORT = get_service_port_http(ServiceNameEnum.NBI) +NBI_USERNAME = 'admin' +NBI_PASSWORD = 'admin' +NBI_BASE_URL = '' + +class RestRequestMethod(enum.Enum): + GET = 'get' + POST = 'post' + PUT = 'put' + PATCH = 'patch' + DELETE = 'delete' + +EXPECTED_STATUS_CODES : Set[int] = { + requests.codes['OK' ], + requests.codes['CREATED' ], + requests.codes['ACCEPTED' ], + requests.codes['NO_CONTENT'], +} + +def do_rest_request( + method : RestRequestMethod, url : str, body : Optional[Any] = None, timeout : int = 10, + allow_redirects : bool = True, expected_status_codes : Set[int] = EXPECTED_STATUS_CODES, + logger : Optional[logging.Logger] = None +) -> Optional[Union[Dict, List]]: + request_url = 'http://{:s}:{:s}@{:s}:{:d}{:s}{:s}'.format( + NBI_USERNAME, NBI_PASSWORD, NBI_ADDRESS, NBI_PORT, str(NBI_BASE_URL), url + ) + + if logger is not None: + msg = 'Request: {:s} {:s}'.format(str(method.value).upper(), str(request_url)) + if body is not None: msg += ' body={:s}'.format(str(body)) + logger.warning(msg) + reply = requests.request(method.value, request_url, timeout=timeout, json=body, allow_redirects=allow_redirects) + if logger is not None: + logger.warning('Reply: {:s}'.format(str(reply.text))) + assert reply.status_code in expected_status_codes, 'Reply failed with status code {:d}'.format(reply.status_code) + + if reply.content and len(reply.content) > 0: return reply.json() + return None + +def do_rest_get_request( + url : str, body : Optional[Any] = None, timeout : int = 10, + allow_redirects : bool = True, expected_status_codes : Set[int] = EXPECTED_STATUS_CODES, + logger : Optional[logging.Logger] = None +) -> Optional[Union[Dict, List]]: + return do_rest_request( + RestRequestMethod.GET, url, body=body, timeout=timeout, allow_redirects=allow_redirects, + expected_status_codes=expected_status_codes, logger=logger + ) + +def do_rest_post_request( + url : str, body : Optional[Any] = None, timeout : int = 10, + allow_redirects : bool = True, expected_status_codes : Set[int] = EXPECTED_STATUS_CODES, + logger : Optional[logging.Logger] = None +) -> Optional[Union[Dict, List]]: + return do_rest_request( + RestRequestMethod.POST, url, body=body, timeout=timeout, allow_redirects=allow_redirects, + expected_status_codes=expected_status_codes, logger=logger + ) + +def do_rest_put_request( + url : str, body : Optional[Any] = None, timeout : int = 10, + allow_redirects : bool = True, expected_status_codes : Set[int] = EXPECTED_STATUS_CODES, + logger : Optional[logging.Logger] = None +) -> Optional[Union[Dict, List]]: + return do_rest_request( + RestRequestMethod.PUT, url, body=body, timeout=timeout, allow_redirects=allow_redirects, + expected_status_codes=expected_status_codes, logger=logger + ) + +def do_rest_patch_request( + url : str, body : Optional[Any] = None, timeout : int = 10, + allow_redirects : bool = True, expected_status_codes : Set[int] = EXPECTED_STATUS_CODES, + logger : Optional[logging.Logger] = None +) -> Optional[Union[Dict, List]]: + return do_rest_request( + RestRequestMethod.PATCH, url, body=body, timeout=timeout, allow_redirects=allow_redirects, + expected_status_codes=expected_status_codes, logger=logger + ) + +def do_rest_delete_request( + url : str, body : Optional[Any] = None, timeout : int = 10, + allow_redirects : bool = True, expected_status_codes : Set[int] = EXPECTED_STATUS_CODES, + logger : Optional[logging.Logger] = None +) -> Optional[Union[Dict, List]]: + return do_rest_request( + RestRequestMethod.DELETE, url, body=body, timeout=timeout, allow_redirects=allow_redirects, + expected_status_codes=expected_status_codes, logger=logger + ) diff --git a/src/tests/eucnc24/tests/__init__.py b/src/tests/eucnc24/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..3ee6f7071f145e06c3aeaefc09a43ccd88e619e3 --- /dev/null +++ b/src/tests/eucnc24/tests/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/src/tests/eucnc24/tests/test_cleanup.py b/src/tests/eucnc24/tests/test_cleanup.py new file mode 100644 index 0000000000000000000000000000000000000000..7cfed0caf295af9dd5f66fd263339e37a7c887a0 --- /dev/null +++ b/src/tests/eucnc24/tests/test_cleanup.py @@ -0,0 +1,44 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging, os +from common.Constants import DEFAULT_CONTEXT_NAME +from common.proto.context_pb2 import ContextId +from common.tools.descriptor.Loader import DescriptorLoader, validate_empty_scenario +from common.tools.object_factory.Context import json_context_id +from context.client.ContextClient import ContextClient +from device.client.DeviceClient import DeviceClient +from .Fixtures import context_client, device_client # pylint: disable=unused-import + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +DESCRIPTOR_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'data', 'tfs-topology.json') +ADMIN_CONTEXT_ID = ContextId(**json_context_id(DEFAULT_CONTEXT_NAME)) + +def test_scenario_cleanup( + context_client : ContextClient, # pylint: disable=redefined-outer-name + device_client : DeviceClient, # pylint: disable=redefined-outer-name +) -> None: + # Verify the scenario has no services/slices + response = context_client.GetContext(ADMIN_CONTEXT_ID) + assert len(response.service_ids) == 0 + assert len(response.slice_ids) == 0 + + # Load descriptors and validate the base scenario + descriptor_loader = DescriptorLoader( + descriptors_file=DESCRIPTOR_FILE, context_client=context_client, device_client=device_client) + descriptor_loader.validate() + descriptor_loader.unload() + validate_empty_scenario(context_client) diff --git a/src/tests/eucnc24/tests/test_onboarding.py b/src/tests/eucnc24/tests/test_onboarding.py new file mode 100644 index 0000000000000000000000000000000000000000..93f040877a4d0c40e2a9d832872e78c75688cffb --- /dev/null +++ b/src/tests/eucnc24/tests/test_onboarding.py @@ -0,0 +1,67 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging, os, time +from common.Constants import DEFAULT_CONTEXT_NAME +from common.proto.context_pb2 import ContextId, DeviceOperationalStatusEnum, Empty +from common.tools.descriptor.Loader import DescriptorLoader, check_descriptor_load_results, validate_empty_scenario +from common.tools.object_factory.Context import json_context_id +from context.client.ContextClient import ContextClient +from device.client.DeviceClient import DeviceClient +from .Fixtures import context_client, device_client # pylint: disable=unused-import + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +DESCRIPTOR_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'data', 'tfs-topology.json') +ADMIN_CONTEXT_ID = ContextId(**json_context_id(DEFAULT_CONTEXT_NAME)) + +def test_scenario_onboarding( + context_client : ContextClient, # pylint: disable=redefined-outer-name + device_client : DeviceClient, # pylint: disable=redefined-outer-name +) -> None: + validate_empty_scenario(context_client) + + descriptor_loader = DescriptorLoader( + descriptors_file=DESCRIPTOR_FILE, context_client=context_client, device_client=device_client) + results = descriptor_loader.process() + check_descriptor_load_results(results, descriptor_loader) + descriptor_loader.validate() + + # Verify the scenario has no services/slices + response = context_client.GetContext(ADMIN_CONTEXT_ID) + assert len(response.service_ids) == 0 + assert len(response.slice_ids) == 0 + +def test_scenario_devices_enabled( + context_client : ContextClient, # pylint: disable=redefined-outer-name +) -> None: + """ + This test validates that the devices are enabled. + """ + DEVICE_OP_STATUS_ENABLED = DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED + + num_devices = -1 + num_devices_enabled, num_retry = 0, 0 + while (num_devices != num_devices_enabled) and (num_retry < 10): + time.sleep(1.0) + response = context_client.ListDevices(Empty()) + num_devices = len(response.devices) + num_devices_enabled = 0 + for device in response.devices: + if device.device_operational_status != DEVICE_OP_STATUS_ENABLED: continue + num_devices_enabled += 1 + LOGGER.info('Num Devices enabled: {:d}/{:d}'.format(num_devices_enabled, num_devices)) + num_retry += 1 + assert num_devices_enabled == num_devices diff --git a/src/tests/eucnc24/tests/test_service_ietf_create.py b/src/tests/eucnc24/tests/test_service_ietf_create.py new file mode 100644 index 0000000000000000000000000000000000000000..b0785c21a332ddf03021644bc4b415ba86027412 --- /dev/null +++ b/src/tests/eucnc24/tests/test_service_ietf_create.py @@ -0,0 +1,71 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json, logging, os +from typing import Dict +from common.Constants import DEFAULT_CONTEXT_NAME +from common.proto.context_pb2 import ContextId, ServiceStatusEnum, ServiceTypeEnum +from common.tools.grpc.Tools import grpc_message_to_json_string +from common.tools.object_factory.Context import json_context_id +from context.client.ContextClient import ContextClient +from .Fixtures import context_client # pylint: disable=unused-import +from .Tools import do_rest_get_request, do_rest_post_request + + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +REQUEST_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'data', 'ietf-l3vpn-service.json') +ADMIN_CONTEXT_ID = ContextId(**json_context_id(DEFAULT_CONTEXT_NAME)) + + +# pylint: disable=redefined-outer-name, unused-argument +def test_service_ietf_creation( + context_client : ContextClient, +): + # Issue service creation request + with open(REQUEST_FILE, 'r', encoding='UTF-8') as f: + svc1_data = json.load(f) + URL = '/restconf/data/ietf-l3vpn-svc:l3vpn-svc/vpn-services' + do_rest_post_request(URL, body=svc1_data, logger=LOGGER, expected_status_codes={201}) + vpn_id = svc1_data['ietf-l3vpn-svc:l3vpn-svc']['vpn-services']['vpn-service'][0]['vpn-id'] + + URL = '/restconf/data/ietf-l3vpn-svc:l3vpn-svc/vpn-services/vpn-service={:s}/'.format(vpn_id) + service_data = do_rest_get_request(URL, logger=LOGGER, expected_status_codes={200}) + service_uuid = service_data['service-id'] + + # Verify service was created + response = context_client.GetContext(ADMIN_CONTEXT_ID) + assert len(response.service_ids) == 1 + assert len(response.slice_ids) == 0 + + # Check there is 1 service + response = context_client.ListServices(ADMIN_CONTEXT_ID) + LOGGER.warning('Services[{:d}] = {:s}'.format( + len(response.services), grpc_message_to_json_string(response) + )) + assert len(response.services) == 1 + + for service in response.services: + service_id = service.service_id + assert service_id.service_uuid.uuid == service_uuid + assert service.service_status.service_status == ServiceStatusEnum.SERVICESTATUS_ACTIVE + assert service.service_type == ServiceTypeEnum.SERVICETYPE_L3NM + + response = context_client.ListConnections(service_id) + LOGGER.warning(' 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 diff --git a/src/tests/eucnc24/tests/test_service_ietf_remove.py b/src/tests/eucnc24/tests/test_service_ietf_remove.py new file mode 100644 index 0000000000000000000000000000000000000000..f64fc07bfd82c4bd2e50f6a3184f83635cc89cc5 --- /dev/null +++ b/src/tests/eucnc24/tests/test_service_ietf_remove.py @@ -0,0 +1,77 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging, os +from typing import Dict, Set, Tuple +from common.Constants import DEFAULT_CONTEXT_NAME +from common.proto.context_pb2 import ContextId, ServiceStatusEnum, ServiceTypeEnum +from common.tools.grpc.Tools import grpc_message_to_json_string +from common.tools.object_factory.Context import json_context_id +from context.client.ContextClient import ContextClient +from .Fixtures import context_client # pylint: disable=unused-import +from .Tools import do_rest_delete_request + + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +REQUEST_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'data', 'ietf-l3vpn-service.json') +ADMIN_CONTEXT_ID = ContextId(**json_context_id(DEFAULT_CONTEXT_NAME)) + + +# pylint: disable=redefined-outer-name, unused-argument +def test_service_ietf_removal( + context_client : ContextClient, # pylint: disable=redefined-outer-name +): + # Verify the scenario has 1 service and 0 slices + response = context_client.GetContext(ADMIN_CONTEXT_ID) + assert len(response.service_ids) == 1 + assert len(response.slice_ids) == 0 + + # Check there are no slices + response = context_client.ListSlices(ADMIN_CONTEXT_ID) + LOGGER.warning('Slices[{:d}] = {:s}'.format(len(response.slices), grpc_message_to_json_string(response))) + assert len(response.slices) == 0 + + # Check there is 1 service + response = context_client.ListServices(ADMIN_CONTEXT_ID) + LOGGER.warning('Services[{:d}] = {:s}'.format(len(response.services), grpc_message_to_json_string(response))) + assert len(response.services) == 1 + + service_uuids : Set[str] = set() + for service in response.services: + service_id = service.service_id + assert service.service_status.service_status == ServiceStatusEnum.SERVICESTATUS_ACTIVE + assert service.service_type == ServiceTypeEnum.SERVICETYPE_L3NM + + response = context_client.ListConnections(service_id) + LOGGER.warning(' 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 + + service_uuids.add(service_id.service_uuid.uuid) + + # Identify service to delete + assert len(service_uuids) == 1 + service_uuid = set(service_uuids).pop() + + URL = '/restconf/data/ietf-l3vpn-svc:l3vpn-svc/vpn-services/vpn-service={:s}/'.format(service_uuid) + do_rest_delete_request(URL, logger=LOGGER, expected_status_codes={204}) + + # Verify the scenario has no services/slices + response = context_client.GetContext(ADMIN_CONTEXT_ID) + assert len(response.service_ids) == 0 + assert len(response.slice_ids) == 0 diff --git a/src/tests/eucnc24/tests/test_service_tfs_create.py b/src/tests/eucnc24/tests/test_service_tfs_create.py new file mode 100644 index 0000000000000000000000000000000000000000..6e0492f86f21b02c7f047d84c2db49d342710ac7 --- /dev/null +++ b/src/tests/eucnc24/tests/test_service_tfs_create.py @@ -0,0 +1,76 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging, os +from common.Constants import DEFAULT_CONTEXT_NAME +from common.proto.context_pb2 import ContextId, ServiceStatusEnum, ServiceTypeEnum +from common.tools.descriptor.Loader import DescriptorLoader, check_descriptor_load_results +from common.tools.grpc.Tools import grpc_message_to_json_string +from common.tools.object_factory.Context import json_context_id +from context.client.ContextClient import ContextClient +from device.client.DeviceClient import DeviceClient +from service.client.ServiceClient import ServiceClient +from .Fixtures import context_client, device_client, service_client # pylint: disable=unused-import + + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +DESCRIPTOR_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'data', 'tfs-service.json') +ADMIN_CONTEXT_ID = ContextId(**json_context_id(DEFAULT_CONTEXT_NAME)) + + +def test_service_tfs_creation( + context_client : ContextClient, # pylint: disable=redefined-outer-name + device_client : DeviceClient, # pylint: disable=redefined-outer-name + service_client : ServiceClient, # pylint: disable=redefined-outer-name +): + # Load descriptors and validate the base scenario + descriptor_loader = DescriptorLoader( + descriptors_file=DESCRIPTOR_FILE, context_client=context_client, + device_client=device_client, service_client=service_client + ) + results = descriptor_loader.process() + check_descriptor_load_results(results, descriptor_loader) + + # Verify the scenario has 1 service and 0 slices + response = context_client.GetContext(ADMIN_CONTEXT_ID) + assert len(response.service_ids) == 1 + assert len(response.slice_ids) == 0 + + # Check there are no slices + response = context_client.ListSlices(ADMIN_CONTEXT_ID) + LOGGER.warning('Slices[{:d}] = {:s}'.format( + len(response.slices), grpc_message_to_json_string(response) + )) + assert len(response.slices) == 0 + + # Check there is 1 service + response = context_client.ListServices(ADMIN_CONTEXT_ID) + LOGGER.warning('Services[{:d}] = {:s}'.format( + len(response.services), grpc_message_to_json_string(response) + )) + assert len(response.services) == 1 + + for service in response.services: + service_id = service.service_id + assert service.service_status.service_status == ServiceStatusEnum.SERVICESTATUS_ACTIVE + assert service.service_type == ServiceTypeEnum.SERVICETYPE_L3NM + + response = context_client.ListConnections(service_id) + LOGGER.warning(' 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 diff --git a/src/tests/eucnc24/tests/test_service_tfs_remove.py b/src/tests/eucnc24/tests/test_service_tfs_remove.py new file mode 100644 index 0000000000000000000000000000000000000000..76fdf0fc7407ce86bbd87806932eb1a4a45c40d1 --- /dev/null +++ b/src/tests/eucnc24/tests/test_service_tfs_remove.py @@ -0,0 +1,80 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging, os +from typing import Set, Tuple +from common.Constants import DEFAULT_CONTEXT_NAME +from common.proto.context_pb2 import ContextId, ServiceId, ServiceStatusEnum, ServiceTypeEnum +from common.tools.grpc.Tools import grpc_message_to_json_string +from common.tools.object_factory.Context import json_context_id +from common.tools.object_factory.Service import json_service_id +from context.client.ContextClient import ContextClient +from service.client.ServiceClient import ServiceClient +from .Fixtures import context_client, service_client # pylint: disable=unused-import + + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +DESCRIPTOR_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'data', 'tfs-service.json') +ADMIN_CONTEXT_ID = ContextId(**json_context_id(DEFAULT_CONTEXT_NAME)) + + +def test_service_tfs_removal( + context_client : ContextClient, # pylint: disable=redefined-outer-name + service_client : ServiceClient, # pylint: disable=redefined-outer-name +): + # Verify the scenario has 1 service and 0 slices + response = context_client.GetContext(ADMIN_CONTEXT_ID) + assert len(response.service_ids) == 1 + assert len(response.slice_ids) == 0 + + # Check there are no slices + response = context_client.ListSlices(ADMIN_CONTEXT_ID) + LOGGER.warning('Slices[{:d}] = {:s}'.format(len(response.slices), grpc_message_to_json_string(response))) + assert len(response.slices) == 0 + + # Check there is 1 service + response = context_client.ListServices(ADMIN_CONTEXT_ID) + LOGGER.warning('Services[{:d}] = {:s}'.format(len(response.services), grpc_message_to_json_string(response))) + assert len(response.services) == 1 + + context_service_uuids : Set[Tuple[str, str]] = set() + for service in response.services: + service_id = service.service_id + assert service.service_status.service_status == ServiceStatusEnum.SERVICESTATUS_ACTIVE + assert service.service_type == ServiceTypeEnum.SERVICETYPE_L3NM + + response = context_client.ListConnections(service_id) + LOGGER.warning(' 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 + + context_uuid = service_id.context_id.context_uuid.uuid + service_uuid = service_id.service_uuid.uuid + context_service_uuids.add((context_uuid, service_uuid)) + + # Identify service to delete + assert len(context_service_uuids) == 1 + context_uuid, service_uuid = set(context_service_uuids).pop() + + # Delete Service + service_client.DeleteService(ServiceId(**json_service_id(service_uuid, json_context_id(context_uuid)))) + + # Verify the scenario has no services/slices + response = context_client.GetContext(ADMIN_CONTEXT_ID) + assert len(response.service_ids) == 0 + assert len(response.slice_ids) == 0 diff --git a/src/tests/ofc24/.gitlab-ci.yml b/src/tests/ofc24/.gitlab-ci.yml index d7bda488689abe88b8abba9a054307fcf377af46..af4e1aaa6b98beb21a85a4746f61cf9d4916dba7 100644 --- a/src/tests/ofc24/.gitlab-ci.yml +++ b/src/tests/ofc24/.gitlab-ci.yml @@ -138,6 +138,7 @@ end2end_test ofc24: - kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/serviceservice -c server - kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/sliceservice -c server - kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/nbiservice -c server + - kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/webuiservice -c server - kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/opticalcontrollerservice -c server - if docker ps -a | grep ${TEST_NAME}; then docker rm -f ${TEST_NAME}; fi diff --git a/src/tests/ofc24/_old/startExtraNetConfigAgent.sh b/src/tests/ofc24/_old/startExtraNetConfigAgent.sh index 197171d2f5e2653e9c38d51c60502984c4d0b345..706010bd11b940af276b74a8366795c6448df8b7 100755 --- a/src/tests/ofc24/_old/startExtraNetConfigAgent.sh +++ b/src/tests/ofc24/_old/startExtraNetConfigAgent.sh @@ -15,10 +15,14 @@ +docker stop na1 +docker rm na1 +docker stop na2 +docker rm na2 -screen -dmS t1 -T xterm sh -c "docker run -p 10.0.2.4:2023:2022 -v ~/tfs-ctrl/src/tests/ofc24/tempOC/files:/files --name na1 -it asgamb1/oc23bgp.img:latest bash" -screen -dmS t2 -T xterm sh -c "docker run -p 10.0.2.4:2024:2022 -v ~/tfs-ctrl/src/tests/ofc24/tempOC/files:/files --name na2 -it asgamb1/oc23bgp.img:latest bash" +screen -dmS t1 -T xterm sh -c "docker run -p 10.0.2.4:2023:2022 -v ~/tfs-ctrl/src/tests/ofc24/tempOC/files:/files --name na1 -it asgamb1/oc23bgp.img:latest sh" +screen -dmS t2 -T xterm sh -c "docker run -p 10.0.2.4:2024:2022 -v ~/tfs-ctrl/src/tests/ofc24/tempOC/files:/files --name na2 -it asgamb1/oc23bgp.img:latest sh" @@ -26,7 +30,7 @@ sleep 4 echo "starting transponder1 " if [ "$( docker container inspect -f '{{.State.Running}}' na1)" = "true" ]; then - docker exec na1 sh -c " cp /files/platform_t1.xml demoECOC21.xml ; + docker exec na1 bash -c " cp /files/platform_t1.xml demoECOC21.xml ; /confd/examples.confd/OC23/startNetconfAgent.sh;" else @@ -36,7 +40,7 @@ fi echo "starting transponder2 " if [ "$( docker container inspect -f '{{.State.Running}}' na2)" = "true" ]; then - docker exec na2 sh -c " cp /files/platform_t2.xml demoECOC21.xml; + docker exec na2 bash -c " cp /files/platform_t2.xml demoECOC21.xml; /confd/examples.confd/OC23/startNetconfAgent.sh " else diff --git a/src/tests/ofc24/descriptors/topology.json b/src/tests/ofc24/descriptors/topology.json index 85bbad55eb8608d2d2a7abad7fffab8fffdae682..f358bf94cebc5ebe51cef25dd3816622dcdb7b64 100644 --- a/src/tests/ofc24/descriptors/topology.json +++ b/src/tests/ofc24/descriptors/topology.json @@ -7,144 +7,1564 @@ ], "devices": [ { - "device_id": {"device_uuid": {"uuid": "T1"}}, "device_type": "optical-transponder", "device_drivers": [11], + "device_id": {"device_uuid": {"uuid": "T1.1"}}, "device_type": "optical-transponder", "device_drivers": [11], "device_operational_status": 1, - "device_endpoints": [ - {"endpoint_id": { - "device_id": {"device_uuid": {"uuid": "T1"}}, "endpoint_uuid": {"uuid": "1"}, - "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} - }} - ], "device_config": {"config_rules": [ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "172.254.253.101"}}, {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "2022"}}, {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": { "username": "admin", "password": "admin", "force_running": false, "hostkey_verify": false, "look_for_keys": false, "allow_agent": false, "commit_per_rule": false, - "device_params": {"name": "default"}, "manager_params": {"timeout": 120}, - "endpoints": [{"uuid": "1", "type": "optical", "sample_types": [101, 102, 201, 202]}] + "device_params": {"name": "default"}, "manager_params": {"timeout": 120} }}} ]} }, { - "device_id": {"device_uuid": {"uuid": "T2"}}, "device_type": "optical-transponder", "device_drivers": [11], + "device_id": {"device_uuid": {"uuid": "T1.2"}}, "device_type": "optical-transponder", "device_drivers": [11], + "device_operational_status": 1, + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "172.254.253.101"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "2022"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": { + "username": "admin", "password": "admin", "force_running": false, "hostkey_verify": false, + "look_for_keys": false, "allow_agent": false, "commit_per_rule": false, + "device_params": {"name": "default"}, "manager_params": {"timeout": 120} + }}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "T1.3"}}, "device_type": "optical-transponder", "device_drivers": [11], + "device_operational_status": 1, + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "172.254.253.101"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "2022"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": { + "username": "admin", "password": "admin", "force_running": false, "hostkey_verify": false, + "look_for_keys": false, "allow_agent": false, "commit_per_rule": false, + "device_params": {"name": "default"}, "manager_params": {"timeout": 120} + }}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "T2.1"}}, "device_type": "optical-transponder", "device_drivers": [11], + "device_operational_status": 1, + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "172.254.253.102"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "2022"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": { + "username": "admin", "password": "admin", "force_running": false, "hostkey_verify": false, + "look_for_keys": false, "allow_agent": false, "commit_per_rule": false, + "device_params": {"name": "default"}, "manager_params": {"timeout": 120} + }}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "T2.2"}}, "device_type": "optical-transponder", "device_drivers": [11], + "device_operational_status": 1, + "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "172.254.253.102"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "2022"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": { + "username": "admin", "password": "admin", "force_running": false, "hostkey_verify": false, + "look_for_keys": false, "allow_agent": false, "commit_per_rule": false, + "device_params": {"name": "default"}, "manager_params": {"timeout": 120} + }}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "T2.3"}}, "device_type": "optical-transponder", "device_drivers": [11], "device_operational_status": 1, - "device_endpoints": [ - {"endpoint_id": { - "device_id": {"device_uuid": {"uuid": "T2"}}, "endpoint_uuid": {"uuid": "6"}, - "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} - }} - ], "device_config": {"config_rules": [ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "172.254.253.102"}}, {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "2022"}}, {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": { "username": "admin", "password": "admin", "force_running": false, "hostkey_verify": false, "look_for_keys": false, "allow_agent": false, "commit_per_rule": false, - "device_params": {"name": "default"}, "manager_params": {"timeout": 120}, - "endpoints": [{"uuid": "6", "type": "optical", "sample_types": [101, 102, 201, 202]}] + "device_params": {"name": "default"}, "manager_params": {"timeout": 120} }}} ]} }, { "device_id": {"device_uuid": {"uuid": "R1"}}, "device_type": "optical-roadm", "device_drivers": [11], "device_operational_status": 1, - "device_endpoints": [ - {"endpoint_id": { - "device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "2"}, - "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} - }}, - {"endpoint_id": { - "device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "3"}, - "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} - }}, - {"endpoint_id": { - "device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "12"}, - "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} - }}, - {"endpoint_id": { - "device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "13"}, - "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} - }} - ], "device_config": {"config_rules": [ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "172.254.253.201"}}, {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "2022"}}, {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": { "username": "admin", "password": "admin", "force_running": false, "hostkey_verify": false, - "look_for_keys": false, "allow_agent": false, "commit_per_rule": false, - "device_params": {"name": "default"}, "manager_params": {"timeout": 120}, - "endpoints": [ - {"sample_types": [101, 102, 201, 202], "type": "optical", "uuid": "2"}, - {"sample_types": [101, 102, 201, 202], "type": "optical", "uuid": "3"}, - {"sample_types": [101, 102, 201, 202], "type": "optical", "uuid": "12"}, - {"sample_types": [101, 102, 201, 202], "type": "optical", "uuid": "13"} - ]} - }}] - } + "look_for_keys": false, "allow_agent": false, "commit_per_rule": false, "type": "optical-roadm", + "device_params": {"name": "default"}, "manager_params": {"timeout": 120} + }}} + ]} }, { "device_id": {"device_uuid": {"uuid": "R2"}}, "device_type": "optical-roadm", "device_drivers": [11], "device_operational_status": 1, - "device_endpoints": [ - {"endpoint_id": { - "device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "4"}, - "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} - }}, - {"endpoint_id": { - "device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "5"}, - "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} - }}, - {"endpoint_id": { - "device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "14"}, - "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} - }}, - {"endpoint_id": { - "device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "15"}, - "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}} - }} - ], "device_config": {"config_rules": [ {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "172.254.253.202"}}, {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "2022"}}, {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": { "username": "admin", "password": "admin", "force_running": false, "hostkey_verify": false, - "look_for_keys": false, "allow_agent": false, "commit_per_rule": false, - "device_params": {"name": "default"}, "manager_params": {"timeout": 120}, - "endpoints": [ - {"sample_types": [101, 102, 201, 202], "type": "optical", "uuid": "4"}, - {"sample_types": [101, 102, 201, 202], "type": "optical", "uuid": "5"}, - {"sample_types": [101, 102, 201, 202], "type": "optical", "uuid": "14"}, - {"sample_types": [101, 102, 201, 202], "type": "optical", "uuid": "15"} - ] + "look_for_keys": false, "allow_agent": false, "commit_per_rule": false, "type": "optical-roadm", + "device_params": {"name": "default"}, "manager_params": {"timeout": 120} }}} ]} } ], - "links": [ - {"link_id": {"link_uuid": {"uuid": "T1->R1"}}, "link_endpoint_ids": [ - {"device_id": {"device_uuid": {"uuid": "T1"}}, "endpoint_uuid": {"uuid": "1"}}, - {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "12"}} - ]}, - {"link_id": {"link_uuid": {"uuid": "R1->T1"}}, "link_endpoint_ids": [ - {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "2"}}, - {"device_id": {"device_uuid": {"uuid": "T1"}}, "endpoint_uuid": {"uuid": "1"}} - ]}, - {"link_id": {"link_uuid": {"uuid": "R1->R2"}}, "link_endpoint_ids": [ - {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "3"}}, - {"device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "14"}} - ]}, - {"link_id": {"link_uuid": {"uuid": "R2->R1"}}, "link_endpoint_ids": [ - {"device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "4"}}, - {"device_id": {"device_uuid": {"uuid": "R1"}}, "endpoint_uuid": {"uuid": "13"}} - ]}, - {"link_id": {"link_uuid": {"uuid": "T2->R2"}}, "link_endpoint_ids": [ - {"device_id": {"device_uuid": {"uuid": "T2"}}, "endpoint_uuid": {"uuid": "6"}}, - {"device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "15"}} - ]}, - {"link_id": {"link_uuid": {"uuid": "R2->T2"}}, "link_endpoint_ids": [ - {"device_id": {"device_uuid": {"uuid": "R2"}}, "endpoint_uuid": {"uuid": "5"}}, - {"device_id": {"device_uuid": {"uuid": "T2"}}, "endpoint_uuid": {"uuid": "6"}} - ]} - ] + "optical_links": [ + { + "name": "T1.1-R1", + "link_id": { + "link_uuid": { + "uuid": "T1.1->R1" + } + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "T1.1" + } + }, + "endpoint_uuid": { + "uuid": "1" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "R1" + } + }, + "endpoint_uuid": { + "uuid": "12" + } + } + ], + "optical_details": { + "length": 0, + "src_port": "1", + "dst_port": "12", + "local_peer_port": "1", + "remote_peer_port": "2", + "used": false, + "c_slots": { + "1": 1, + "2": 1, + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, + "10": 1, + "11": 1, + "12": 1, + "13": 1, + "14": 1, + "15": 1, + "16": 1, + "17": 1, + "18": 1, + "19": 1, + "20": 1 + }, + "l_slots": { + "101": 1, + "102": 1, + "103": 1, + "104": 1, + "105": 1, + "106": 1, + "107": 1, + "108": 1, + "109": 1, + "110": 1, + "111": 1, + "112": 1, + "113": 1, + "114": 1, + "115": 1, + "116": 1, + "117": 1, + "118": 1, + "119": 1, + "120": 1 + }, + "s_slots": { + "501": 1, + "502": 1, + "503": 1, + "504": 1, + "505": 1, + "506": 1, + "507": 1, + "508": 1, + "509": 1, + "510": 1, + "511": 1, + "512": 1, + "513": 1, + "514": 1, + "515": 1, + "516": 1, + "517": 1, + "518": 1, + "519": 1, + "520": 1 + } + } + }, + { + "name": "T1.2-R1", + "link_id": { + "link_uuid": { + "uuid": "T1.2->R1" + } + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "T1.2" + } + }, + "endpoint_uuid": { + "uuid": "1" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "R1" + } + }, + "endpoint_uuid": { + "uuid": "13" + } + } + ], + "optical_details": { + "length": 0, + "src_port": "1", + "dst_port": "13", + "local_peer_port": "1", + "remote_peer_port": "3", + "used": false, + "c_slots": { + "1": 1, + "2": 1, + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, + "10": 1, + "11": 1, + "12": 1, + "13": 1, + "14": 1, + "15": 1, + "16": 1, + "17": 1, + "18": 1, + "19": 1, + "20": 1 + }, + "l_slots": { + "101": 1, + "102": 1, + "103": 1, + "104": 1, + "105": 1, + "106": 1, + "107": 1, + "108": 1, + "109": 1, + "110": 1, + "111": 1, + "112": 1, + "113": 1, + "114": 1, + "115": 1, + "116": 1, + "117": 1, + "118": 1, + "119": 1, + "120": 1 + }, + "s_slots": { + "501": 1, + "502": 1, + "503": 1, + "504": 1, + "505": 1, + "506": 1, + "507": 1, + "508": 1, + "509": 1, + "510": 1, + "511": 1, + "512": 1, + "513": 1, + "514": 1, + "515": 1, + "516": 1, + "517": 1, + "518": 1, + "519": 1, + "520": 1 + } + } + }, + { + "name": "T1.3-R1", + "link_id": { + "link_uuid": { + "uuid": "T1.3->R1" + } + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "T1.3" + } + }, + "endpoint_uuid": { + "uuid": "1" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "R1" + } + }, + "endpoint_uuid": { + "uuid": "14" + } + } + ], + "optical_details": { + "length": 0, + "src_port": "1", + "dst_port": "14", + "local_peer_port": "1", + "remote_peer_port": "4", + "used": false, + "c_slots": { + "1": 1, + "2": 1, + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, + "10": 1, + "11": 1, + "12": 1, + "13": 1, + "14": 1, + "15": 1, + "16": 1, + "17": 1, + "18": 1, + "19": 1, + "20": 1 + }, + "l_slots": { + "101": 1, + "102": 1, + "103": 1, + "104": 1, + "105": 1, + "106": 1, + "107": 1, + "108": 1, + "109": 1, + "110": 1, + "111": 1, + "112": 1, + "113": 1, + "114": 1, + "115": 1, + "116": 1, + "117": 1, + "118": 1, + "119": 1, + "120": 1 + }, + "s_slots": { + "501": 1, + "502": 1, + "503": 1, + "504": 1, + "505": 1, + "506": 1, + "507": 1, + "508": 1, + "509": 1, + "510": 1, + "511": 1, + "512": 1, + "513": 1, + "514": 1, + "515": 1, + "516": 1, + "517": 1, + "518": 1, + "519": 1, + "520": 1 + } + } + }, + { + "name": "R1-T1.1", + "link_id": { + "link_uuid": { + "uuid": "R1->T1.1" + } + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "R1" + } + }, + "endpoint_uuid": { + "uuid": "2" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "T1.1" + } + }, + "endpoint_uuid": { + "uuid": "1" + } + } + ], + "optical_details": { + "length": 0, + "src_port": "2", + "dst_port": "1", + "local_peer_port": "12", + "remote_peer_port": "1", + "used": false, + "c_slots": { + "1": 1, + "2": 1, + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, + "10": 1, + "11": 1, + "12": 1, + "13": 1, + "14": 1, + "15": 1, + "16": 1, + "17": 1, + "18": 1, + "19": 1, + "20": 1 + }, + "l_slots": { + "101": 1, + "102": 1, + "103": 1, + "104": 1, + "105": 1, + "106": 1, + "107": 1, + "108": 1, + "109": 1, + "110": 1, + "111": 1, + "112": 1, + "113": 1, + "114": 1, + "115": 1, + "116": 1, + "117": 1, + "118": 1, + "119": 1, + "120": 1 + }, + "s_slots": { + "501": 1, + "502": 1, + "503": 1, + "504": 1, + "505": 1, + "506": 1, + "507": 1, + "508": 1, + "509": 1, + "510": 1, + "511": 1, + "512": 1, + "513": 1, + "514": 1, + "515": 1, + "516": 1, + "517": 1, + "518": 1, + "519": 1, + "520": 1 + } + } + }, + { + "name": "R1-T1.2", + "link_id": { + "link_uuid": { + "uuid": "R1->T1.2" + } + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "R1" + } + }, + "endpoint_uuid": { + "uuid": "3" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "T1.2" + } + }, + "endpoint_uuid": { + "uuid": "1" + } + } + ], + "optical_details": { + "length": 0, + "src_port": "3", + "dst_port": "1", + "local_peer_port": "13", + "remote_peer_port": "1", + "used": false, + "c_slots": { + "1": 1, + "2": 1, + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, + "10": 1, + "11": 1, + "12": 1, + "13": 1, + "14": 1, + "15": 1, + "16": 1, + "17": 1, + "18": 1, + "19": 1, + "20": 1 + }, + "l_slots": { + "101": 1, + "102": 1, + "103": 1, + "104": 1, + "105": 1, + "106": 1, + "107": 1, + "108": 1, + "109": 1, + "110": 1, + "111": 1, + "112": 1, + "113": 1, + "114": 1, + "115": 1, + "116": 1, + "117": 1, + "118": 1, + "119": 1, + "120": 1 + }, + "s_slots": { + "501": 1, + "502": 1, + "503": 1, + "504": 1, + "505": 1, + "506": 1, + "507": 1, + "508": 1, + "509": 1, + "510": 1, + "511": 1, + "512": 1, + "513": 1, + "514": 1, + "515": 1, + "516": 1, + "517": 1, + "518": 1, + "519": 1, + "520": 1 + } + } + }, + { + "name": "R1-T1.3", + "link_id": { + "link_uuid": { + "uuid": "R1->T1.3" + } + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "R1" + } + }, + "endpoint_uuid": { + "uuid": "4" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "T1.3" + } + }, + "endpoint_uuid": { + "uuid": "1" + } + } + ], + "optical_details": { + "length": 0, + "src_port": "4", + "dst_port": "1", + "local_peer_port": "14", + "remote_peer_port": "1", + "used": false, + "c_slots": { + "1": 1, + "2": 1, + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, + "10": 1, + "11": 1, + "12": 1, + "13": 1, + "14": 1, + "15": 1, + "16": 1, + "17": 1, + "18": 1, + "19": 1, + "20": 1 + }, + "l_slots": { + "101": 1, + "102": 1, + "103": 1, + "104": 1, + "105": 1, + "106": 1, + "107": 1, + "108": 1, + "109": 1, + "110": 1, + "111": 1, + "112": 1, + "113": 1, + "114": 1, + "115": 1, + "116": 1, + "117": 1, + "118": 1, + "119": 1, + "120": 1 + }, + "s_slots": { + "501": 1, + "502": 1, + "503": 1, + "504": 1, + "505": 1, + "506": 1, + "507": 1, + "508": 1, + "509": 1, + "510": 1, + "511": 1, + "512": 1, + "513": 1, + "514": 1, + "515": 1, + "516": 1, + "517": 1, + "518": 1, + "519": 1, + "520": 1 + } + } + }, + { + "name": "R1-R2", + "link_id": { + "link_uuid": { + "uuid": "R1->R2" + } + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "R1" + } + }, + "endpoint_uuid": { + "uuid": "101" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "R2" + } + }, + "endpoint_uuid": { + "uuid": "111" + } + } + ], + "optical_details": { + "length": 0, + "src_port": "101", + "dst_port": "111", + "local_peer_port": "111", + "remote_peer_port": "101", + "c_slots": { + "1": 1, + "2": 1, + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, + "10": 1, + "11": 1, + "12": 1, + "13": 1, + "14": 1, + "15": 1, + "16": 1, + "17": 1, + "18": 1, + "19": 1, + "20": 1 + }, + "l_slots": { + "101": 1, + "102": 1, + "103": 1, + "104": 1, + "105": 1, + "106": 1, + "107": 1, + "108": 1, + "109": 1, + "110": 1, + "111": 1, + "112": 1, + "113": 1, + "114": 1, + "115": 1, + "116": 1, + "117": 1, + "118": 1, + "119": 1, + "120": 1 + }, + "s_slots": { + "501": 1, + "502": 1, + "503": 1, + "504": 1, + "505": 1, + "506": 1, + "507": 1, + "508": 1, + "509": 1, + "510": 1, + "511": 1, + "512": 1, + "513": 1, + "514": 1, + "515": 1, + "516": 1, + "517": 1, + "518": 1, + "519": 1, + "520": 1 + } + } + }, + { + "name": "R2-R1", + "link_id": { + "link_uuid": { + "uuid": "R2->R1" + } + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "R2" + } + }, + "endpoint_uuid": { + "uuid": "101" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "R1" + } + }, + "endpoint_uuid": { + "uuid": "111" + } + } + ], + "optical_details": { + "length": 0, + "src_port": "101", + "dst_port": "111", + "local_peer_port": "111", + "remote_peer_port": "101", + "c_slots": { + "1": 1, + "2": 1, + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, + "10": 1, + "11": 1, + "12": 1, + "13": 1, + "14": 1, + "15": 1, + "16": 1, + "17": 1, + "18": 1, + "19": 1, + "20": 1 + }, + "l_slots": { + "101": 1, + "102": 1, + "103": 1, + "104": 1, + "105": 1, + "106": 1, + "107": 1, + "108": 1, + "109": 1, + "110": 1, + "111": 1, + "112": 1, + "113": 1, + "114": 1, + "115": 1, + "116": 1, + "117": 1, + "118": 1, + "119": 1, + "120": 1 + }, + "s_slots": { + "501": 1, + "502": 1, + "503": 1, + "504": 1, + "505": 1, + "506": 1, + "507": 1, + "508": 1, + "509": 1, + "510": 1, + "511": 1, + "512": 1, + "513": 1, + "514": 1, + "515": 1, + "516": 1, + "517": 1, + "518": 1, + "519": 1, + "520": 1 + } + } + }, + { + "name": "T2.1-R2", + "link_id": { + "link_uuid": { + "uuid": "T2.1->R2" + } + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "T2.1" + } + }, + "endpoint_uuid": { + "uuid": "6" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "R2" + } + }, + "endpoint_uuid": { + "uuid": "12" + } + } + ], + "optical_details": { + "length": 0, + "src_port": "6", + "dst_port": "12", + "local_peer_port": "6", + "remote_peer_port": "2", + "used": false, + "c_slots": { + "1": 1, + "2": 1, + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, + "10": 1, + "11": 1, + "12": 1, + "13": 1, + "14": 1, + "15": 1, + "16": 1, + "17": 1, + "18": 1, + "19": 1, + "20": 1 + }, + "l_slots": { + "101": 1, + "102": 1, + "103": 1, + "104": 1, + "105": 1, + "106": 1, + "107": 1, + "108": 1, + "109": 1, + "110": 1, + "111": 1, + "112": 1, + "113": 1, + "114": 1, + "115": 1, + "116": 1, + "117": 1, + "118": 1, + "119": 1, + "120": 1 + }, + "s_slots": { + "501": 1, + "502": 1, + "503": 1, + "504": 1, + "505": 1, + "506": 1, + "507": 1, + "508": 1, + "509": 1, + "510": 1, + "511": 1, + "512": 1, + "513": 1, + "514": 1, + "515": 1, + "516": 1, + "517": 1, + "518": 1, + "519": 1, + "520": 1 + } + } + }, + { + "name": "T2.2-R2", + "link_id": { + "link_uuid": { + "uuid": "T2.2->R2" + } + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "T2.2" + } + }, + "endpoint_uuid": { + "uuid": "6" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "R2" + } + }, + "endpoint_uuid": { + "uuid": "13" + } + } + ], + "optical_details": { + "length": 0, + "src_port": "6", + "dst_port": "13", + "local_peer_port": "6", + "remote_peer_port": "3", + "used": false, + "c_slots": { + "1": 1, + "2": 1, + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, + "10": 1, + "11": 1, + "12": 1, + "13": 1, + "14": 1, + "15": 1, + "16": 1, + "17": 1, + "18": 1, + "19": 1, + "20": 1 + }, + "l_slots": { + "101": 1, + "102": 1, + "103": 1, + "104": 1, + "105": 1, + "106": 1, + "107": 1, + "108": 1, + "109": 1, + "110": 1, + "111": 1, + "112": 1, + "113": 1, + "114": 1, + "115": 1, + "116": 1, + "117": 1, + "118": 1, + "119": 1, + "120": 1 + }, + "s_slots": { + "501": 1, + "502": 1, + "503": 1, + "504": 1, + "505": 1, + "506": 1, + "507": 1, + "508": 1, + "509": 1, + "510": 1, + "511": 1, + "512": 1, + "513": 1, + "514": 1, + "515": 1, + "516": 1, + "517": 1, + "518": 1, + "519": 1, + "520": 1 + } + } + }, + { + "name": "T2.3-R2", + "link_id": { + "link_uuid": { + "uuid": "T2.3->R2" + } + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "T2.3" + } + }, + "endpoint_uuid": { + "uuid": "6" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "R2" + } + }, + "endpoint_uuid": { + "uuid": "14" + } + } + ], + "optical_details": { + "length": 0, + "src_port": "6", + "dst_port": "14", + "local_peer_port": "6", + "remote_peer_port": "4", + "used": false, + "c_slots": { + "1": 1, + "2": 1, + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, + "10": 1, + "11": 1, + "12": 1, + "13": 1, + "14": 1, + "15": 1, + "16": 1, + "17": 1, + "18": 1, + "19": 1, + "20": 1 + }, + "l_slots": { + "101": 1, + "102": 1, + "103": 1, + "104": 1, + "105": 1, + "106": 1, + "107": 1, + "108": 1, + "109": 1, + "110": 1, + "111": 1, + "112": 1, + "113": 1, + "114": 1, + "115": 1, + "116": 1, + "117": 1, + "118": 1, + "119": 1, + "120": 1 + }, + "s_slots": { + "501": 1, + "502": 1, + "503": 1, + "504": 1, + "505": 1, + "506": 1, + "507": 1, + "508": 1, + "509": 1, + "510": 1, + "511": 1, + "512": 1, + "513": 1, + "514": 1, + "515": 1, + "516": 1, + "517": 1, + "518": 1, + "519": 1, + "520": 1 + } + } + }, + { + "name": "R2-T2.1", + "link_id": { + "link_uuid": { + "uuid": "R2->T2.1" + } + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "R2" + } + }, + "endpoint_uuid": { + "uuid": "2" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "T2.1" + } + }, + "endpoint_uuid": { + "uuid": "6" + } + } + ], + "optical_details": { + "length": 0, + "src_port": "2", + "dst_port": "6", + "local_peer_port": "12", + "remote_peer_port": "6", + "used": false, + "c_slots": { + "1": 1, + "2": 1, + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, + "10": 1, + "11": 1, + "12": 1, + "13": 1, + "14": 1, + "15": 1, + "16": 1, + "17": 1, + "18": 1, + "19": 1, + "20": 1 + }, + "l_slots": { + "101": 1, + "102": 1, + "103": 1, + "104": 1, + "105": 1, + "106": 1, + "107": 1, + "108": 1, + "109": 1, + "110": 1, + "111": 1, + "112": 1, + "113": 1, + "114": 1, + "115": 1, + "116": 1, + "117": 1, + "118": 1, + "119": 1, + "120": 1 + }, + "s_slots": { + "501": 1, + "502": 1, + "503": 1, + "504": 1, + "505": 1, + "506": 1, + "507": 1, + "508": 1, + "509": 1, + "510": 1, + "511": 1, + "512": 1, + "513": 1, + "514": 1, + "515": 1, + "516": 1, + "517": 1, + "518": 1, + "519": 1, + "520": 1 + } + } + }, + { + "name": "R2-T2.2", + "link_id": { + "link_uuid": { + "uuid": "R1->T2.2" + } + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "R2" + } + }, + "endpoint_uuid": { + "uuid": "3" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "T2.2" + } + }, + "endpoint_uuid": { + "uuid": "6" + } + } + ], + "optical_details": { + "length": 0, + "src_port": "3", + "dst_port": "6", + "local_peer_port": "13", + "remote_peer_port": "6", + "used": false, + "c_slots": { + "1": 1, + "2": 1, + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, + "10": 1, + "11": 1, + "12": 1, + "13": 1, + "14": 1, + "15": 1, + "16": 1, + "17": 1, + "18": 1, + "19": 1, + "20": 1 + }, + "l_slots": { + "101": 1, + "102": 1, + "103": 1, + "104": 1, + "105": 1, + "106": 1, + "107": 1, + "108": 1, + "109": 1, + "110": 1, + "111": 1, + "112": 1, + "113": 1, + "114": 1, + "115": 1, + "116": 1, + "117": 1, + "118": 1, + "119": 1, + "120": 1 + }, + "s_slots": { + "501": 1, + "502": 1, + "503": 1, + "504": 1, + "505": 1, + "506": 1, + "507": 1, + "508": 1, + "509": 1, + "510": 1, + "511": 1, + "512": 1, + "513": 1, + "514": 1, + "515": 1, + "516": 1, + "517": 1, + "518": 1, + "519": 1, + "520": 1 + } + } + }, + { + "name": "R2-T2.3", + "link_id": { + "link_uuid": { + "uuid": "R2->T2.3" + } + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "R2" + } + }, + "endpoint_uuid": { + "uuid": "4" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "T2.3" + } + }, + "endpoint_uuid": { + "uuid": "6" + } + } + ], + "optical_details": { + "length": 0, + "src_port": "4", + "dst_port": "6", + "local_peer_port": "14", + "remote_peer_port": "6", + "used": false, + "c_slots": { + "1": 1, + "2": 1, + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, + "10": 1, + "11": 1, + "12": 1, + "13": 1, + "14": 1, + "15": 1, + "16": 1, + "17": 1, + "18": 1, + "19": 1, + "20": 1 + }, + "l_slots": { + "101": 1, + "102": 1, + "103": 1, + "104": 1, + "105": 1, + "106": 1, + "107": 1, + "108": 1, + "109": 1, + "110": 1, + "111": 1, + "112": 1, + "113": 1, + "114": 1, + "115": 1, + "116": 1, + "117": 1, + "118": 1, + "119": 1, + "120": 1 + }, + "s_slots": { + "501": 1, + "502": 1, + "503": 1, + "504": 1, + "505": 1, + "506": 1, + "507": 1, + "508": 1, + "509": 1, + "510": 1, + "511": 1, + "512": 1, + "513": 1, + "514": 1, + "515": 1, + "516": 1, + "517": 1, + "518": 1, + "519": 1, + "520": 1 + } + } + } + ] } diff --git a/src/tests/ofc24/get_all.sh b/src/tests/ofc24/get_all.sh new file mode 100755 index 0000000000000000000000000000000000000000..ceb96a34c98823e122a65b38ccef57e9ed2f1ec0 --- /dev/null +++ b/src/tests/ofc24/get_all.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License + +~/tfs-ctrl/src/tests/ofc24/get_bands.sh > bands.json +~/tfs-ctrl/src/tests/ofc24/get_links.sh > links.json +~/tfs-ctrl/src/tests/ofc24/get_lightpath.sh > lightpath.json diff --git a/src/tests/ofc24/get_bands.sh b/src/tests/ofc24/get_bands.sh new file mode 100755 index 0000000000000000000000000000000000000000..c63461e44d8363f768fa1770b88b916e4d5689f7 --- /dev/null +++ b/src/tests/ofc24/get_bands.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License + +ip=$(sudo kubectl get all --all-namespaces | grep service/opticalcontrollerservice | awk '{print $4}') +echo $ip + +#push=$(curl -X GET "http://$ip:10060/OpticalTFS/GetTopology/admin/admin") + +links=$(curl -X GET "http://$ip:10060/OpticalTFS/GetOpticalBands") +echo $links diff --git a/src/tests/ofc24/get_lightpath.sh b/src/tests/ofc24/get_lightpath.sh new file mode 100755 index 0000000000000000000000000000000000000000..eefd46c0ed5ee1435e3b0dc5fa601d7d4a8f9243 --- /dev/null +++ b/src/tests/ofc24/get_lightpath.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License + +ip=$(sudo kubectl get all --all-namespaces | grep service/opticalcontrollerservice | awk '{print $4}') +echo $ip + +#push=$(curl -X GET "http://$ip:10060/OpticalTFS/GetTopology/admin/admin") + +links=$(curl -X GET "http://$ip:10060/OpticalTFS/GetLightpaths") +echo $links diff --git a/src/tests/ofc24/get_links.sh b/src/tests/ofc24/get_links.sh new file mode 100755 index 0000000000000000000000000000000000000000..f1146b4eac873114282a58445c3aad1b33002dd9 --- /dev/null +++ b/src/tests/ofc24/get_links.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License + +ip=$(sudo kubectl get all --all-namespaces | grep service/opticalcontrollerservice | awk '{print $4}') +echo $ip + +#push=$(curl -X GET "http://$ip:10060/OpticalTFS/GetTopology/admin/admin") + +links=$(curl -X GET "http://$ip:10060/OpticalTFS/GetLinks") +echo $links diff --git a/src/tests/ofc24/node-agents-config/platform_r1.xml b/src/tests/ofc24/node-agents-config/platform_r1.xml index b9412d1bda3e5eafc17fc6d3233d2bcc1dffec04..c8569cfec736b6d17e76030bd13d2161c588a726 100644 --- a/src/tests/ofc24/node-agents-config/platform_r1.xml +++ b/src/tests/ofc24/node-agents-config/platform_r1.xml @@ -132,5 +132,122 @@ </property> </properties> </component> - </components> -</config> + <component xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:operation="create"> + <name>4</name> + <config> + <name>4</name> + </config> + <properties> + <property> + <name>MG_ON_PORT_TYPE</name> + <config> + <name>MG_ON_PORT_TYPE</name> + <value>MG_ON_OPTICAL_PORT_WAVEBAND</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DIRECTION</name> + <config> + <name>MG_ON_PORT_DIRECTION</name> + <value>OUTPUT</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DEGREE</name> + <config> + <name>MG_ON_PORT_DEGREE</name> + <value>D2</value> + </config> + </property> + </properties> + </component> + <component xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:operation="create"> + <name>14</name> + <config> + <name>14</name> + </config> + <properties> + <property> + <name>MG_ON_PORT_TYPE</name> + <config> + <name>MG_ON_PORT_TYPE</name> + <value>MG_ON_OPTICAL_PORT_WAVEBAND</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DIRECTION</name> + <config> + <name>MG_ON_PORT_DIRECTION</name> + <value>INPUT</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DEGREE</name> + <config> + <name>MG_ON_PORT_DEGREE</name> + <value>D2</value> + </config> + </property> + </properties> + </component> + + <component xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:operation="create"> + <name>101</name> + <config> + <name>101</name> + </config> + <properties> + <property> + <name>MG_ON_PORT_TYPE</name> + <config> + <name>MG_ON_PORT_TYPE</name> + <value>MG_ON_OPTICAL_PORT_WAVEBAND</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DIRECTION</name> + <config> + <name>MG_ON_PORT_DIRECTION</name> + <value>OUTPUT</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DEGREE</name> + <config> + <name>MG_ON_PORT_DEGREE</name> + <value>D2</value> + </config> + </property> + </properties> + </component> + <component xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:operation="create"> + <name>111</name> + <config> + <name>111</name> + </config> + <properties> + <property> + <name>MG_ON_PORT_TYPE</name> + <config> + <name>MG_ON_PORT_TYPE</name> + <value>MG_ON_OPTICAL_PORT_WAVEBAND</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DIRECTION</name> + <config> + <name>MG_ON_PORT_DIRECTION</name> + <value>INPUT</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DEGREE</name> + <config> + <name>MG_ON_PORT_DEGREE</name> + <value>D2</value> + </config> + </property> + </properties> + </component> + </components> +</config> \ No newline at end of file diff --git a/src/tests/ofc24/node-agents-config/platform_r2.xml b/src/tests/ofc24/node-agents-config/platform_r2.xml index 317203483a89348c51e354a6c7973239b494f42d..608941ff47123da942df20fa8bebc42769c780e0 100644 --- a/src/tests/ofc24/node-agents-config/platform_r2.xml +++ b/src/tests/ofc24/node-agents-config/platform_r2.xml @@ -16,6 +16,122 @@ <config xmlns="http://tail-f.com/ns/config/1.0"> <components xmlns="http://openconfig.net/yang/platform"> + <component xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:operation="create"> + <name>2</name> + <config> + <name>2</name> + </config> + <properties> + <property> + <name>MG_ON_PORT_TYPE</name> + <config> + <name>MG_ON_PORT_TYPE</name> + <value>MG_ON_OPTICAL_PORT_WAVEBAND</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DIRECTION</name> + <config> + <name>MG_ON_PORT_DIRECTION</name> + <value>OUTPUT</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DEGREE</name> + <config> + <name>MG_ON_PORT_DEGREE</name> + <value>D1</value> + </config> + </property> + </properties> + </component> + <component xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:operation="create"> + <name>12</name> + <config> + <name>12</name> + </config> + <properties> + <property> + <name>MG_ON_PORT_TYPE</name> + <config> + <name>MG_ON_PORT_TYPE</name> + <value>MG_ON_OPTICAL_PORT_WAVEBAND</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DIRECTION</name> + <config> + <name>MG_ON_PORT_DIRECTION</name> + <value>INPUT</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DEGREE</name> + <config> + <name>MG_ON_PORT_DEGREE</name> + <value>D1</value> + </config> + </property> + </properties> + </component> + <component xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:operation="create"> + <name>3</name> + <config> + <name>3</name> + </config> + <properties> + <property> + <name>MG_ON_PORT_TYPE</name> + <config> + <name>MG_ON_PORT_TYPE</name> + <value>MG_ON_OPTICAL_PORT_WAVEBAND</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DIRECTION</name> + <config> + <name>MG_ON_PORT_DIRECTION</name> + <value>OUTPUT</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DEGREE</name> + <config> + <name>MG_ON_PORT_DEGREE</name> + <value>D2</value> + </config> + </property> + </properties> + </component> + <component xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:operation="create"> + <name>13</name> + <config> + <name>13</name> + </config> + <properties> + <property> + <name>MG_ON_PORT_TYPE</name> + <config> + <name>MG_ON_PORT_TYPE</name> + <value>MG_ON_OPTICAL_PORT_WAVEBAND</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DIRECTION</name> + <config> + <name>MG_ON_PORT_DIRECTION</name> + <value>INPUT</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DEGREE</name> + <config> + <name>MG_ON_PORT_DEGREE</name> + <value>D2</value> + </config> + </property> + </properties> + </component> <component xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:operation="create"> <name>4</name> <config> @@ -40,7 +156,7 @@ <name>MG_ON_PORT_DEGREE</name> <config> <name>MG_ON_PORT_DEGREE</name> - <value>D1</value> + <value>D2</value> </config> </property> </properties> @@ -69,15 +185,16 @@ <name>MG_ON_PORT_DEGREE</name> <config> <name>MG_ON_PORT_DEGREE</name> - <value>D1</value> + <value>D2</value> </config> </property> </properties> </component> + <component xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:operation="create"> - <name>5</name> + <name>101</name> <config> - <name>5</name> + <name>101</name> </config> <properties> <property> @@ -104,9 +221,9 @@ </properties> </component> <component xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:operation="create"> - <name>15</name> + <name>111</name> <config> - <name>15</name> + <name>111</name> </config> <properties> <property> @@ -132,5 +249,5 @@ </property> </properties> </component> - </components> -</config> + </components> +</config> \ No newline at end of file diff --git a/src/tests/ofc24/node-agents-config/transponders_x4.xml b/src/tests/ofc24/node-agents-config/transponders_x4.xml new file mode 100644 index 0000000000000000000000000000000000000000..fb55f02abefa6c5a444d3fecaa2ca049798f9483 --- /dev/null +++ b/src/tests/ofc24/node-agents-config/transponders_x4.xml @@ -0,0 +1,1039 @@ +<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0"> + <components xmlns="http://openconfig.net/yang/platform"> + <component> + <name>device</name> + <config> + <name>device</name> + </config> + <state> + <name>MellanoxSwitch</name> + <mfg-name>SSSA-CNIT</mfg-name> + <hardware-version>1.0.0</hardware-version> + <firmware-version>1.0.0</firmware-version> + <software-version>1.0.0</software-version> + <serial-no>610610</serial-no> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:OPERATING_SYSTEM</type> + </state> + </component> + <component> + <name>channel-1</name> + <config> + <name>channel-1</name> + </config> + <state> + <name>channel-1</name> + <type xmlns:typex="http://openconfig.net/yang/transport-types">typex:OPTICAL_CHANNEL</type> + </state> + <optical-channel xmlns="http://openconfig.net/yang/terminal-device"> + <config> + <frequency>191600000</frequency> + <target-output-power>100</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-1</line-port> + </config> + <state> + <frequency>191600000</frequency> + <target-output-power>0</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-1</line-port> + <group-id>1</group-id> + <output-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </output-power> + <input-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </input-power> + <laser-bias-current> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </laser-bias-current> + <chromatic-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </chromatic-dispersion> + <polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </polarization-mode-dispersion> + <second-order-polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </second-order-polarization-mode-dispersion> + <polarization-dependent-loss> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </polarization-dependent-loss> + </state> + </optical-channel> + </component> + <component> + <name>channel-2</name> + <config> + <name>channel-2</name> + </config> + <state> + <name>channel-2</name> + <type xmlns:typex="http://openconfig.net/yang/transport-types">typex:OPTICAL_CHANNEL</type> + </state> + <optical-channel xmlns="http://openconfig.net/yang/terminal-device"> + <config> + <frequency>191600000</frequency> + <target-output-power>100</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-2</line-port> + </config> + <state> + <frequency>191600000</frequency> + <target-output-power>0</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-2</line-port> + <group-id>1</group-id> + <output-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </output-power> + <input-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </input-power> + <laser-bias-current> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </laser-bias-current> + <chromatic-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </chromatic-dispersion> + <polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </polarization-mode-dispersion> + <second-order-polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </second-order-polarization-mode-dispersion> + <polarization-dependent-loss> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </polarization-dependent-loss> + </state> + </optical-channel> + </component> + <component> + <name>channel-3</name> + <config> + <name>channel-3</name> + </config> + <state> + <name>channel-3</name> + <type xmlns:typex="http://openconfig.net/yang/transport-types">typex:OPTICAL_CHANNEL</type> + </state> + <optical-channel xmlns="http://openconfig.net/yang/terminal-device"> + <config> + <frequency>191600000</frequency> + <target-output-power>100</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-3</line-port> + </config> + <state> + <frequency>191600000</frequency> + <target-output-power>0</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-3</line-port> + <group-id>1</group-id> + <output-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </output-power> + <input-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </input-power> + <laser-bias-current> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </laser-bias-current> + <chromatic-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </chromatic-dispersion> + <polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </polarization-mode-dispersion> + <second-order-polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </second-order-polarization-mode-dispersion> + <polarization-dependent-loss> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </polarization-dependent-loss> + </state> + </optical-channel> + </component> + <component> + <name>channel-4</name> + <config> + <name>channel-4</name> + </config> + <state> + <name>channel-4</name> + <type xmlns:typex="http://openconfig.net/yang/transport-types">typex:OPTICAL_CHANNEL</type> + </state> + <optical-channel xmlns="http://openconfig.net/yang/terminal-device"> + <config> + <frequency>191600000</frequency> + <target-output-power>100</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-4</line-port> + </config> + <state> + <frequency>191600000</frequency> + <target-output-power>0</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-4</line-port> + <group-id>1</group-id> + <output-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </output-power> + <input-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </input-power> + <laser-bias-current> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </laser-bias-current> + <chromatic-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </chromatic-dispersion> + <polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </polarization-mode-dispersion> + <second-order-polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </second-order-polarization-mode-dispersion> + <polarization-dependent-loss> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </polarization-dependent-loss> + </state> + </optical-channel> + </component> + <component> + <name>transceiver-1</name> + <config> + <name>transceiver-1</name> + </config> + <state> + <name>transceiver-1</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:TRANSCEIVER</type> + </state> + <transceiver xmlns="http://openconfig.net/yang/platform/transceiver"> + <config> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + </config> + <state> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + <vendor>Cisco</vendor> + <vendor-part>400zr-QSFP-DD</vendor-part> + <vendor-rev>01</vendor-rev> + <serial-no>1567321</serial-no> + </state> + <physical-channels> + <channel> + <index>1</index> + <config> + <index>1</index> + <associated-optical-channel>channel-1</associated-optical-channel> + </config> + </channel> + </physical-channels> + </transceiver> + </component> + <component> + <name>transceiver-2</name> + <config> + <name>transceiver-2</name> + </config> + <state> + <name>transceiver-2</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:TRANSCEIVER</type> + </state> + <transceiver xmlns="http://openconfig.net/yang/platform/transceiver"> + <config> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + </config> + <state> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + <vendor>Cisco</vendor> + <vendor-part>400zr-QSFP-DD</vendor-part> + <vendor-rev>01</vendor-rev> + <serial-no>1567321</serial-no> + </state> + <physical-channels> + <channel> + <index>1</index> + <config> + <index>1</index> + <associated-optical-channel>channel-2</associated-optical-channel> + </config> + </channel> + </physical-channels> + </transceiver> + </component> + <component> + <name>transceiver-3</name> + <config> + <name>transceiver-3</name> + </config> + <state> + <name>transceiver-3</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:TRANSCEIVER</type> + </state> + <transceiver xmlns="http://openconfig.net/yang/platform/transceiver"> + <config> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + </config> + <state> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + <vendor>Cisco</vendor> + <vendor-part>400zr-QSFP-DD</vendor-part> + <vendor-rev>01</vendor-rev> + <serial-no>1567321</serial-no> + </state> + <physical-channels> + <channel> + <index>1</index> + <config> + <index>1</index> + <associated-optical-channel>channel-3</associated-optical-channel> + </config> + </channel> + </physical-channels> + </transceiver> + </component> + + <component> + <name>transceiver-4</name> + <config> + <name>transceiver-4</name> + </config> + <state> + <name>transceiver-4</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:TRANSCEIVER</type> + </state> + <transceiver xmlns="http://openconfig.net/yang/platform/transceiver"> + <config> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + </config> + <state> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + <vendor>Cisco</vendor> + <vendor-part>400zr-QSFP-DD</vendor-part> + <vendor-rev>01</vendor-rev> + <serial-no>1567321</serial-no> + </state> + <physical-channels> + <channel> + <index>1</index> + <config> + <index>1</index> + <associated-optical-channel>channel-4</associated-optical-channel> + </config> + </channel> + </physical-channels> + </transceiver> + </component> + <component> + <name>port-1</name> + <config> + <name>port-1</name> + </config> + <state> + <name>port-1</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:PORT</type> + </state> + <subcomponents> + <subcomponent> + <name>channel-1</name> + <config> + <name>channel-1</name> + </config> + <state> + <name>channel-1</name> + </state> + </subcomponent> + </subcomponents> + <properties> + <property> + <name>onos-index</name> + <config> + <name>onos-index</name> + <value>1</value> + </config> + <state> + <name>onos-index</name> + <value>1</value> + </state> + </property> + <property> + <name>odtn-port-type</name> + <config> + <name>odtn-port-type</name> + <value>line</value> + </config> + <state> + <name>odtn-port-type</name> + <value>line</value> + </state> + </property> + </properties> + </component> + <component> + <name>port-2</name> + <config> + <name>port-2</name> + </config> + <state> + <name>port-2</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:PORT</type> + </state> + <subcomponents> + <subcomponent> + <name>channel-2</name> + <config> + <name>channel-2</name> + </config> + <state> + <name>channel-2</name> + </state> + </subcomponent> + </subcomponents> + <properties> + <property> + <name>onos-index</name> + <config> + <name>onos-index</name> + <value>2</value> + </config> + <state> + <name>onos-index</name> + <value>2</value> + </state> + </property> + <property> + <name>odtn-port-type</name> + <config> + <name>odtn-port-type</name> + <value>line</value> + </config> + <state> + <name>odtn-port-type</name> + <value>line</value> + </state> + </property> + </properties> + </component> + <component> + <name>port-3</name> + <config> + <name>port-3</name> + </config> + <state> + <name>port-3</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:PORT</type> + </state> + <subcomponents> + <subcomponent> + <name>channel-3</name> + <config> + <name>channel-3</name> + </config> + <state> + <name>channel-3</name> + </state> + </subcomponent> + </subcomponents> + <properties> + <property> + <name>onos-index</name> + <config> + <name>onos-index</name> + <value>3</value> + </config> + <state> + <name>onos-index</name> + <value>3</value> + </state> + </property> + <property> + <name>odtn-port-type</name> + <config> + <name>odtn-port-type</name> + <value>line</value> + </config> + <state> + <name>odtn-port-type</name> + <value>line</value> + </state> + </property> + </properties> + </component> + <component> + <name>port-4</name> + <config> + <name>port-4</name> + </config> + <state> + <name>port-4</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:PORT</type> + </state> + <subcomponents> + <subcomponent> + <name>channel-4</name> + <config> + <name>channel-4</name> + </config> + <state> + <name>channel-4</name> + </state> + </subcomponent> + </subcomponents> + <properties> + <property> + <name>onos-index</name> + <config> + <name>onos-index</name> + <value>4</value> + </config> + <state> + <name>onos-index</name> + <value>4</value> + </state> + </property> + <property> + <name>odtn-port-type</name> + <config> + <name>odtn-port-type</name> + <value>line</value> + </config> + <state> + <name>odtn-port-type</name> + <value>line</value> + </state> + </property> + </properties> + </component> + </components> + <terminal-device xmlns="http://openconfig.net/yang/terminal-device"> + <logical-channels> + <!--Description: Optical logical link--> + <channel> + <!--Description: Line (OTN) Port--> + <index>1</index> + <config> + <index>1</index> + <description>Logical channel 1</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + </config> + <state> + <index>1</index> + <description>Logical channel 1</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + <link-state>UP</link-state> + </state> + <ingress> + <config> + <transceiver>transceiver-1</transceiver> + </config> + <state> + <transceiver>transceiver-1</transceiver> + </state> + </ingress> + <otn> + <config> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + </config> + <state> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + <tti-msg-auto>0</tti-msg-auto> + <tti-msg-recv>0</tti-msg-recv> + <rdi-msg>0</rdi-msg> + <errored-seconds>0</errored-seconds> + <severely-errored-seconds>0</severely-errored-seconds> + <unavailable-seconds>0</unavailable-seconds> + <code-violations>0</code-violations> + <fec-uncorrectable-words>0</fec-uncorrectable-words> + <fec-corrected-bytes>0</fec-corrected-bytes> + <fec-corrected-bits>0</fec-corrected-bits> + <background-block-errors>0</background-block-errors> + <pre-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </pre-fec-ber> + <post-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </post-fec-ber> + <q-value> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </q-value> + <esnr> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </esnr> + </state> + </otn> + <logical-channel-assignments> + <assignment> + <index>1</index> + <config> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-1</optical-channel> + </config> + <state> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-1</optical-channel> + </state> + </assignment> + </logical-channel-assignments> + </channel> + <!--Description: Optical logical link--> + <channel> + <!--Description: Line (OTN) Port--> + <index>2</index> + <config> + <index>2</index> + <description>Logical channel 2</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + </config> + <state> + <index>2</index> + <description>Logical channel 2</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + <link-state>UP</link-state> + </state> + <ingress> + <config> + <transceiver>transceiver-2</transceiver> + </config> + <state> + <transceiver>transceiver-2</transceiver> + </state> + </ingress> + <otn> + <config> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + </config> + <state> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + <tti-msg-auto>0</tti-msg-auto> + <tti-msg-recv>0</tti-msg-recv> + <rdi-msg>0</rdi-msg> + <errored-seconds>0</errored-seconds> + <severely-errored-seconds>0</severely-errored-seconds> + <unavailable-seconds>0</unavailable-seconds> + <code-violations>0</code-violations> + <fec-uncorrectable-words>0</fec-uncorrectable-words> + <fec-corrected-bytes>0</fec-corrected-bytes> + <fec-corrected-bits>0</fec-corrected-bits> + <background-block-errors>0</background-block-errors> + <pre-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </pre-fec-ber> + <post-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </post-fec-ber> + <q-value> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </q-value> + <esnr> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </esnr> + </state> + </otn> + <logical-channel-assignments> + <assignment> + <index>1</index> + <config> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-2</optical-channel> + </config> + <state> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-2</optical-channel> + </state> + </assignment> + </logical-channel-assignments> + </channel> + <!--Description: Optical logical link--> + <channel> + <!--Description: Line (OTN) Port--> + <index>3</index> + <config> + <index>3</index> + <description>Logical channel 3</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + </config> + <state> + <index>3</index> + <description>Logical channel 3</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + <link-state>UP</link-state> + </state> + <ingress> + <config> + <transceiver>transceiver-3</transceiver> + </config> + <state> + <transceiver>transceiver-3</transceiver> + </state> + </ingress> + <otn> + <config> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + </config> + <state> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + <tti-msg-auto>0</tti-msg-auto> + <tti-msg-recv>0</tti-msg-recv> + <rdi-msg>0</rdi-msg> + <errored-seconds>0</errored-seconds> + <severely-errored-seconds>0</severely-errored-seconds> + <unavailable-seconds>0</unavailable-seconds> + <code-violations>0</code-violations> + <fec-uncorrectable-words>0</fec-uncorrectable-words> + <fec-corrected-bytes>0</fec-corrected-bytes> + <fec-corrected-bits>0</fec-corrected-bits> + <background-block-errors>0</background-block-errors> + <pre-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </pre-fec-ber> + <post-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </post-fec-ber> + <q-value> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </q-value> + <esnr> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </esnr> + </state> + </otn> + <logical-channel-assignments> + <assignment> + <index>1</index> + <config> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-3</optical-channel> + </config> + <state> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-3</optical-channel> + </state> + </assignment> + </logical-channel-assignments> + </channel> + <!--Description: Optical logical link--> + <channel> + <!--Description: Line (OTN) Port--> + <index>4</index> + <config> + <index>4</index> + <description>Logical channel 4</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + </config> + <state> + <index>4</index> + <description>Logical channel 4</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + <link-state>UP</link-state> + </state> + <ingress> + <config> + <transceiver>transceiver-4</transceiver> + </config> + <state> + <transceiver>transceiver-4</transceiver> + </state> + </ingress> + <otn> + <config> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + </config> + <state> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + <tti-msg-auto>0</tti-msg-auto> + <tti-msg-recv>0</tti-msg-recv> + <rdi-msg>0</rdi-msg> + <errored-seconds>0</errored-seconds> + <severely-errored-seconds>0</severely-errored-seconds> + <unavailable-seconds>0</unavailable-seconds> + <code-violations>0</code-violations> + <fec-uncorrectable-words>0</fec-uncorrectable-words> + <fec-corrected-bytes>0</fec-corrected-bytes> + <fec-corrected-bits>0</fec-corrected-bits> + <background-block-errors>0</background-block-errors> + <pre-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </pre-fec-ber> + <post-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </post-fec-ber> + <q-value> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </q-value> + <esnr> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </esnr> + </state> + </otn> + <logical-channel-assignments> + <assignment> + <index>1</index> + <config> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-4</optical-channel> + </config> + <state> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-4</optical-channel> + </state> + </assignment> + </logical-channel-assignments> + </channel> + </logical-channels> + <operational-modes> + <mode> + <mode-id>1</mode-id> + <state> + <mode-id>1</mode-id> + <description>FEC1</description> + <vendor-id>Ericsson</vendor-id> + </state> + </mode> + <mode> + <mode-id>2</mode-id> + <state> + <mode-id>2</mode-id> + <description>FEC2</description> + <vendor-id>Ericsson</vendor-id> + </state> + </mode> + </operational-modes> + </terminal-device> +</config> + diff --git a/src/tests/ofc24/node-agents-config/transponders_x4_2.xml b/src/tests/ofc24/node-agents-config/transponders_x4_2.xml new file mode 100644 index 0000000000000000000000000000000000000000..8d10c593b3c2166b16e8ecea383dadd69c3ac067 --- /dev/null +++ b/src/tests/ofc24/node-agents-config/transponders_x4_2.xml @@ -0,0 +1,1039 @@ +<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0"> + <components xmlns="http://openconfig.net/yang/platform"> + <component> + <name>device</name> + <config> + <name>device</name> + </config> + <state> + <name>MellanoxSwitch</name> + <mfg-name>SSSA-CNIT</mfg-name> + <hardware-version>1.0.0</hardware-version> + <firmware-version>1.0.0</firmware-version> + <software-version>1.0.0</software-version> + <serial-no>610610</serial-no> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:OPERATING_SYSTEM</type> + </state> + </component> + <component> + <name>channel-5</name> + <config> + <name>channel-5</name> + </config> + <state> + <name>channel-5</name> + <type xmlns:typex="http://openconfig.net/yang/transport-types">typex:OPTICAL_CHANNEL</type> + </state> + <optical-channel xmlns="http://openconfig.net/yang/terminal-device"> + <config> + <frequency>191600000</frequency> + <target-output-power>100</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-5</line-port> + </config> + <state> + <frequency>191600000</frequency> + <target-output-power>0</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-5</line-port> + <group-id>1</group-id> + <output-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </output-power> + <input-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </input-power> + <laser-bias-current> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </laser-bias-current> + <chromatic-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </chromatic-dispersion> + <polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </polarization-mode-dispersion> + <second-order-polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </second-order-polarization-mode-dispersion> + <polarization-dependent-loss> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </polarization-dependent-loss> + </state> + </optical-channel> + </component> + <component> + <name>channel-6</name> + <config> + <name>channel-6</name> + </config> + <state> + <name>channel-6</name> + <type xmlns:typex="http://openconfig.net/yang/transport-types">typex:OPTICAL_CHANNEL</type> + </state> + <optical-channel xmlns="http://openconfig.net/yang/terminal-device"> + <config> + <frequency>191600000</frequency> + <target-output-power>100</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-6</line-port> + </config> + <state> + <frequency>191600000</frequency> + <target-output-power>0</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-6</line-port> + <group-id>1</group-id> + <output-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </output-power> + <input-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </input-power> + <laser-bias-current> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </laser-bias-current> + <chromatic-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </chromatic-dispersion> + <polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </polarization-mode-dispersion> + <second-order-polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </second-order-polarization-mode-dispersion> + <polarization-dependent-loss> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </polarization-dependent-loss> + </state> + </optical-channel> + </component> + <component> + <name>channel-7</name> + <config> + <name>channel-7</name> + </config> + <state> + <name>channel-7</name> + <type xmlns:typex="http://openconfig.net/yang/transport-types">typex:OPTICAL_CHANNEL</type> + </state> + <optical-channel xmlns="http://openconfig.net/yang/terminal-device"> + <config> + <frequency>191600000</frequency> + <target-output-power>100</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-7</line-port> + </config> + <state> + <frequency>191600000</frequency> + <target-output-power>0</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-7</line-port> + <group-id>1</group-id> + <output-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </output-power> + <input-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </input-power> + <laser-bias-current> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </laser-bias-current> + <chromatic-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </chromatic-dispersion> + <polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </polarization-mode-dispersion> + <second-order-polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </second-order-polarization-mode-dispersion> + <polarization-dependent-loss> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </polarization-dependent-loss> + </state> + </optical-channel> + </component> + <component> + <name>channel-8</name> + <config> + <name>channel-8</name> + </config> + <state> + <name>channel-8</name> + <type xmlns:typex="http://openconfig.net/yang/transport-types">typex:OPTICAL_CHANNEL</type> + </state> + <optical-channel xmlns="http://openconfig.net/yang/terminal-device"> + <config> + <frequency>191600000</frequency> + <target-output-power>100</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-8</line-port> + </config> + <state> + <frequency>191600000</frequency> + <target-output-power>0</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-8</line-port> + <group-id>1</group-id> + <output-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </output-power> + <input-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </input-power> + <laser-bias-current> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </laser-bias-current> + <chromatic-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </chromatic-dispersion> + <polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </polarization-mode-dispersion> + <second-order-polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </second-order-polarization-mode-dispersion> + <polarization-dependent-loss> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </polarization-dependent-loss> + </state> + </optical-channel> + </component> + <component> + <name>transceiver-5</name> + <config> + <name>transceiver-5</name> + </config> + <state> + <name>transceiver-5</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:TRANSCEIVER</type> + </state> + <transceiver xmlns="http://openconfig.net/yang/platform/transceiver"> + <config> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + </config> + <state> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + <vendor>Cisco</vendor> + <vendor-part>400zr-QSFP-DD</vendor-part> + <vendor-rev>01</vendor-rev> + <serial-no>1567321</serial-no> + </state> + <physical-channels> + <channel> + <index>1</index> + <config> + <index>1</index> + <associated-optical-channel>channel-5</associated-optical-channel> + </config> + </channel> + </physical-channels> + </transceiver> + </component> + <component> + <name>transceiver-6</name> + <config> + <name>transceiver-6</name> + </config> + <state> + <name>transceiver-6</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:TRANSCEIVER</type> + </state> + <transceiver xmlns="http://openconfig.net/yang/platform/transceiver"> + <config> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + </config> + <state> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + <vendor>Cisco</vendor> + <vendor-part>400zr-QSFP-DD</vendor-part> + <vendor-rev>01</vendor-rev> + <serial-no>1567321</serial-no> + </state> + <physical-channels> + <channel> + <index>1</index> + <config> + <index>1</index> + <associated-optical-channel>channel-6</associated-optical-channel> + </config> + </channel> + </physical-channels> + </transceiver> + </component> + <component> + <name>transceiver-7</name> + <config> + <name>transceiver-7</name> + </config> + <state> + <name>transceiver-7</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:TRANSCEIVER</type> + </state> + <transceiver xmlns="http://openconfig.net/yang/platform/transceiver"> + <config> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + </config> + <state> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + <vendor>Cisco</vendor> + <vendor-part>400zr-QSFP-DD</vendor-part> + <vendor-rev>01</vendor-rev> + <serial-no>1567321</serial-no> + </state> + <physical-channels> + <channel> + <index>1</index> + <config> + <index>1</index> + <associated-optical-channel>channel-7</associated-optical-channel> + </config> + </channel> + </physical-channels> + </transceiver> + </component> + + <component> + <name>transceiver-8</name> + <config> + <name>transceiver-8</name> + </config> + <state> + <name>transceiver-8</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:TRANSCEIVER</type> + </state> + <transceiver xmlns="http://openconfig.net/yang/platform/transceiver"> + <config> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + </config> + <state> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + <vendor>Cisco</vendor> + <vendor-part>400zr-QSFP-DD</vendor-part> + <vendor-rev>01</vendor-rev> + <serial-no>1567321</serial-no> + </state> + <physical-channels> + <channel> + <index>1</index> + <config> + <index>1</index> + <associated-optical-channel>channel-8</associated-optical-channel> + </config> + </channel> + </physical-channels> + </transceiver> + </component> + <component> + <name>port-5</name> + <config> + <name>port-5</name> + </config> + <state> + <name>port-5</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:PORT</type> + </state> + <subcomponents> + <subcomponent> + <name>channel-5</name> + <config> + <name>channel-5</name> + </config> + <state> + <name>channel-5</name> + </state> + </subcomponent> + </subcomponents> + <properties> + <property> + <name>onos-index</name> + <config> + <name>onos-index</name> + <value>5</value> + </config> + <state> + <name>onos-index</name> + <value>5</value> + </state> + </property> + <property> + <name>odtn-port-type</name> + <config> + <name>odtn-port-type</name> + <value>line</value> + </config> + <state> + <name>odtn-port-type</name> + <value>line</value> + </state> + </property> + </properties> + </component> + <component> + <name>port-6</name> + <config> + <name>port-6</name> + </config> + <state> + <name>port-6</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:PORT</type> + </state> + <subcomponents> + <subcomponent> + <name>channel-6</name> + <config> + <name>channel-6</name> + </config> + <state> + <name>channel-6</name> + </state> + </subcomponent> + </subcomponents> + <properties> + <property> + <name>onos-index</name> + <config> + <name>onos-index</name> + <value>6</value> + </config> + <state> + <name>onos-index</name> + <value>6</value> + </state> + </property> + <property> + <name>odtn-port-type</name> + <config> + <name>odtn-port-type</name> + <value>line</value> + </config> + <state> + <name>odtn-port-type</name> + <value>line</value> + </state> + </property> + </properties> + </component> + <component> + <name>port-7</name> + <config> + <name>port-7</name> + </config> + <state> + <name>port-7</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:PORT</type> + </state> + <subcomponents> + <subcomponent> + <name>channel-7</name> + <config> + <name>channel-7</name> + </config> + <state> + <name>channel-7</name> + </state> + </subcomponent> + </subcomponents> + <properties> + <property> + <name>onos-index</name> + <config> + <name>onos-index</name> + <value>7</value> + </config> + <state> + <name>onos-index</name> + <value>7</value> + </state> + </property> + <property> + <name>odtn-port-type</name> + <config> + <name>odtn-port-type</name> + <value>line</value> + </config> + <state> + <name>odtn-port-type</name> + <value>line</value> + </state> + </property> + </properties> + </component> + <component> + <name>port-8</name> + <config> + <name>port-8</name> + </config> + <state> + <name>port-8</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:PORT</type> + </state> + <subcomponents> + <subcomponent> + <name>channel-8</name> + <config> + <name>channel-8</name> + </config> + <state> + <name>channel-8</name> + </state> + </subcomponent> + </subcomponents> + <properties> + <property> + <name>onos-index</name> + <config> + <name>onos-index</name> + <value>8</value> + </config> + <state> + <name>onos-index</name> + <value>8</value> + </state> + </property> + <property> + <name>odtn-port-type</name> + <config> + <name>odtn-port-type</name> + <value>line</value> + </config> + <state> + <name>odtn-port-type</name> + <value>line</value> + </state> + </property> + </properties> + </component> + </components> + <terminal-device xmlns="http://openconfig.net/yang/terminal-device"> + <logical-channels> + <!--Description: Optical logical link--> + <channel> + <!--Description: Line (OTN) Port--> + <index>5</index> + <config> + <index>5</index> + <description>Logical channel 5</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + </config> + <state> + <index>5</index> + <description>Logical channel 5</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + <link-state>UP</link-state> + </state> + <ingress> + <config> + <transceiver>transceiver-5</transceiver> + </config> + <state> + <transceiver>transceiver-5</transceiver> + </state> + </ingress> + <otn> + <config> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + </config> + <state> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + <tti-msg-auto>0</tti-msg-auto> + <tti-msg-recv>0</tti-msg-recv> + <rdi-msg>0</rdi-msg> + <errored-seconds>0</errored-seconds> + <severely-errored-seconds>0</severely-errored-seconds> + <unavailable-seconds>0</unavailable-seconds> + <code-violations>0</code-violations> + <fec-uncorrectable-words>0</fec-uncorrectable-words> + <fec-corrected-bytes>0</fec-corrected-bytes> + <fec-corrected-bits>0</fec-corrected-bits> + <background-block-errors>0</background-block-errors> + <pre-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </pre-fec-ber> + <post-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </post-fec-ber> + <q-value> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </q-value> + <esnr> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </esnr> + </state> + </otn> + <logical-channel-assignments> + <assignment> + <index>1</index> + <config> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-5</optical-channel> + </config> + <state> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-5</optical-channel> + </state> + </assignment> + </logical-channel-assignments> + </channel> + <!--Description: Optical logical link--> + <channel> + <!--Description: Line (OTN) Port--> + <index>6</index> + <config> + <index>6</index> + <description>Logical channel 6</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + </config> + <state> + <index>6</index> + <description>Logical channel 6</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + <link-state>UP</link-state> + </state> + <ingress> + <config> + <transceiver>transceiver-6</transceiver> + </config> + <state> + <transceiver>transceiver-6</transceiver> + </state> + </ingress> + <otn> + <config> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + </config> + <state> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + <tti-msg-auto>0</tti-msg-auto> + <tti-msg-recv>0</tti-msg-recv> + <rdi-msg>0</rdi-msg> + <errored-seconds>0</errored-seconds> + <severely-errored-seconds>0</severely-errored-seconds> + <unavailable-seconds>0</unavailable-seconds> + <code-violations>0</code-violations> + <fec-uncorrectable-words>0</fec-uncorrectable-words> + <fec-corrected-bytes>0</fec-corrected-bytes> + <fec-corrected-bits>0</fec-corrected-bits> + <background-block-errors>0</background-block-errors> + <pre-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </pre-fec-ber> + <post-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </post-fec-ber> + <q-value> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </q-value> + <esnr> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </esnr> + </state> + </otn> + <logical-channel-assignments> + <assignment> + <index>1</index> + <config> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-6</optical-channel> + </config> + <state> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-6</optical-channel> + </state> + </assignment> + </logical-channel-assignments> + </channel> + <!--Description: Optical logical link--> + <channel> + <!--Description: Line (OTN) Port--> + <index>7</index> + <config> + <index>7</index> + <description>Logical channel 7</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + </config> + <state> + <index>7</index> + <description>Logical channel 7</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + <link-state>UP</link-state> + </state> + <ingress> + <config> + <transceiver>transceiver-7</transceiver> + </config> + <state> + <transceiver>transceiver-7</transceiver> + </state> + </ingress> + <otn> + <config> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + </config> + <state> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + <tti-msg-auto>0</tti-msg-auto> + <tti-msg-recv>0</tti-msg-recv> + <rdi-msg>0</rdi-msg> + <errored-seconds>0</errored-seconds> + <severely-errored-seconds>0</severely-errored-seconds> + <unavailable-seconds>0</unavailable-seconds> + <code-violations>0</code-violations> + <fec-uncorrectable-words>0</fec-uncorrectable-words> + <fec-corrected-bytes>0</fec-corrected-bytes> + <fec-corrected-bits>0</fec-corrected-bits> + <background-block-errors>0</background-block-errors> + <pre-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </pre-fec-ber> + <post-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </post-fec-ber> + <q-value> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </q-value> + <esnr> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </esnr> + </state> + </otn> + <logical-channel-assignments> + <assignment> + <index>1</index> + <config> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-7</optical-channel> + </config> + <state> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-7</optical-channel> + </state> + </assignment> + </logical-channel-assignments> + </channel> + <!--Description: Optical logical link--> + <channel> + <!--Description: Line (OTN) Port--> + <index>8</index> + <config> + <index>8</index> + <description>Logical channel 8</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + </config> + <state> + <index>8</index> + <description>Logical channel 8</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + <link-state>UP</link-state> + </state> + <ingress> + <config> + <transceiver>transceiver-8</transceiver> + </config> + <state> + <transceiver>transceiver-8</transceiver> + </state> + </ingress> + <otn> + <config> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + </config> + <state> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + <tti-msg-auto>0</tti-msg-auto> + <tti-msg-recv>0</tti-msg-recv> + <rdi-msg>0</rdi-msg> + <errored-seconds>0</errored-seconds> + <severely-errored-seconds>0</severely-errored-seconds> + <unavailable-seconds>0</unavailable-seconds> + <code-violations>0</code-violations> + <fec-uncorrectable-words>0</fec-uncorrectable-words> + <fec-corrected-bytes>0</fec-corrected-bytes> + <fec-corrected-bits>0</fec-corrected-bits> + <background-block-errors>0</background-block-errors> + <pre-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </pre-fec-ber> + <post-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </post-fec-ber> + <q-value> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </q-value> + <esnr> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </esnr> + </state> + </otn> + <logical-channel-assignments> + <assignment> + <index>1</index> + <config> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-8</optical-channel> + </config> + <state> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-8</optical-channel> + </state> + </assignment> + </logical-channel-assignments> + </channel> + </logical-channels> + <operational-modes> + <mode> + <mode-id>1</mode-id> + <state> + <mode-id>1</mode-id> + <description>FEC1</description> + <vendor-id>Ericsson</vendor-id> + </state> + </mode> + <mode> + <mode-id>2</mode-id> + <state> + <mode-id>2</mode-id> + <description>FEC2</description> + <vendor-id>Ericsson</vendor-id> + </state> + </mode> + </operational-modes> + </terminal-device> +</config> + diff --git a/src/tests/ofc24/r_t.sh b/src/tests/ofc24/r_t.sh new file mode 100755 index 0000000000000000000000000000000000000000..eef73563fbec2aa60e1133d0790e2821867fe997 --- /dev/null +++ b/src/tests/ofc24/r_t.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License + + +docker stop -t 1 t1 +docker stop -t 1 na3 +docker stop -t 1 t2 +docker stop -t 1 na2 + +docker rm t1 +docker rm na3 + +docker rm t2 +docker rm na2 + +screen -dmS t1 -T xterm sh -c "docker run --name t1 -p 10.0.2.4:2023:2022 -v /home/tfs/tfs-ctrl/src/tests/ofc24/tempOC/files:/files -it asgamb1/oc23bgp.img:latest bash -c 'cp /files/transponders_x4.xml demoECOC21.xml ; ./startNetconfAgent.sh'" +screen -dmS t3 -T xterm sh -c "docker run --name na3 -p 10.0.2.4:2025:2022 -v /home/tfs/tfs-ctrl/src/tests/ofc24/tempOC/files:/files -it asgamb1/flexscale-node.img:latest bash -c 'cp /files/platform_r1.xml init_openconfig-platform.xml ; ./startNetconfAgent.sh'" +screen -dmS t2 -T xterm sh -c "docker run --name t2 -p 10.0.2.4:2024:2022 -v /home/tfs/tfs-ctrl/src/tests/ofc24/tempOC/files:/files -it asgamb1/oc23bgp.img:latest bash -c 'cp /files/transponders_x4_2.xml demoECOC21.xml ; ./startNetconfAgent.sh'" +screen -dmS t4 -T xterm sh -c "docker run --name na2 -p 10.0.2.4:2026:2022 -v /home/tfs/tfs-ctrl/src/tests/ofc24/tempOC/files:/files -it asgamb1/flexscale-node.img:latest bash -c 'cp /files/platform_r2.xml init_openconfig-platform.xml ; ./startNetconfAgent.sh'" diff --git a/src/tests/ofc24/roadms.sh b/src/tests/ofc24/roadms.sh new file mode 100644 index 0000000000000000000000000000000000000000..032615d555fce47c94a83848a9b24f0ed7ce04ae --- /dev/null +++ b/src/tests/ofc24/roadms.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +docker stop -t 1 na3 +docker stop -t 1 na4 + +docker rm na3 +docker rm na4 + + + +screen -dmS t3 -T xterm sh -c "docker run --name na3 -p 10.0.2.4:2025:2022 -v /home/tfs/tfs-ctrl/src/tests/ofc24/tempOC/files:/files -it asgamb1/flexscale-node.img:latest bash -c 'cp /files/platform_r1.xml demoECOC21.xml ; ./startNetconfAgent.sh'" +screen -dmS t4 -T xterm sh -c "docker run --name na4 -p 10.0.2.4:2026:2022 -v /home/tfs/tfs-ctrl/src/tests/ofc24/tempOC/files:/files -it asgamb1/flexscale-node.img:latest bash -c 'cp /files/platform_r2.xml demoECOC21.xml ; ./startNetconfAgent.sh'" \ No newline at end of file diff --git a/src/tests/ofc24/run_test.sh b/src/tests/ofc24/run_test.sh new file mode 100644 index 0000000000000000000000000000000000000000..b1cbe33c9c9169375b2f88fe5590347d85dd5552 --- /dev/null +++ b/src/tests/ofc24/run_test.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License + +docker stop na1 +docker rm na1 +screen -dmS t1 -T xterm sh -c "docker run -p 10.0.2.10:2023:2022 -v ~/tfs-ctrl/src/tests/ofc24/tempOC/files:/files --name na1 -it asgamb1/oc23bgp.img:latest" +sleep 2 +if [ "$( docker container inspect -f '{{.State.Running}}' na1)" = "true" ]; then + docker exec na1 sh -c " cp /files/platform_t1.xml demoECOC21.xml ; /confd/examples.confd/OC23/startNetconfAgent.sh; " +else + echo "your container is not running yet" +fi diff --git a/src/tests/ofc24/tempOC/files/platform_r1.xml b/src/tests/ofc24/tempOC/files/platform_r1.xml new file mode 100644 index 0000000000000000000000000000000000000000..02ea8feb47d4da5a4fce657837eb6e3fa749c545 --- /dev/null +++ b/src/tests/ofc24/tempOC/files/platform_r1.xml @@ -0,0 +1,237 @@ +<config xmlns="http://tail-f.com/ns/config/1.0"> + <components xmlns="http://openconfig.net/yang/platform"> + <component xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:operation="create"> + <name>2</name> + <config> + <name>2</name> + </config> + <properties> + <property> + <name>MG_ON_PORT_TYPE</name> + <config> + <name>MG_ON_PORT_TYPE</name> + <value>MG_ON_OPTICAL_PORT_WAVEBAND</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DIRECTION</name> + <config> + <name>MG_ON_PORT_DIRECTION</name> + <value>OUTPUT</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DEGREE</name> + <config> + <name>MG_ON_PORT_DEGREE</name> + <value>D1</value> + </config> + </property> + </properties> + </component> + <component xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:operation="create"> + <name>12</name> + <config> + <name>12</name> + </config> + <properties> + <property> + <name>MG_ON_PORT_TYPE</name> + <config> + <name>MG_ON_PORT_TYPE</name> + <value>MG_ON_OPTICAL_PORT_WAVEBAND</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DIRECTION</name> + <config> + <name>MG_ON_PORT_DIRECTION</name> + <value>INPUT</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DEGREE</name> + <config> + <name>MG_ON_PORT_DEGREE</name> + <value>D1</value> + </config> + </property> + </properties> + </component> + <component xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:operation="create"> + <name>3</name> + <config> + <name>3</name> + </config> + <properties> + <property> + <name>MG_ON_PORT_TYPE</name> + <config> + <name>MG_ON_PORT_TYPE</name> + <value>MG_ON_OPTICAL_PORT_WAVEBAND</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DIRECTION</name> + <config> + <name>MG_ON_PORT_DIRECTION</name> + <value>OUTPUT</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DEGREE</name> + <config> + <name>MG_ON_PORT_DEGREE</name> + <value>D2</value> + </config> + </property> + </properties> + </component> + <component xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:operation="create"> + <name>13</name> + <config> + <name>13</name> + </config> + <properties> + <property> + <name>MG_ON_PORT_TYPE</name> + <config> + <name>MG_ON_PORT_TYPE</name> + <value>MG_ON_OPTICAL_PORT_WAVEBAND</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DIRECTION</name> + <config> + <name>MG_ON_PORT_DIRECTION</name> + <value>INPUT</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DEGREE</name> + <config> + <name>MG_ON_PORT_DEGREE</name> + <value>D2</value> + </config> + </property> + </properties> + </component> + <component xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:operation="create"> + <name>4</name> + <config> + <name>4</name> + </config> + <properties> + <property> + <name>MG_ON_PORT_TYPE</name> + <config> + <name>MG_ON_PORT_TYPE</name> + <value>MG_ON_OPTICAL_PORT_WAVEBAND</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DIRECTION</name> + <config> + <name>MG_ON_PORT_DIRECTION</name> + <value>OUTPUT</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DEGREE</name> + <config> + <name>MG_ON_PORT_DEGREE</name> + <value>D2</value> + </config> + </property> + </properties> + </component> + <component xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:operation="create"> + <name>14</name> + <config> + <name>14</name> + </config> + <properties> + <property> + <name>MG_ON_PORT_TYPE</name> + <config> + <name>MG_ON_PORT_TYPE</name> + <value>MG_ON_OPTICAL_PORT_WAVEBAND</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DIRECTION</name> + <config> + <name>MG_ON_PORT_DIRECTION</name> + <value>INPUT</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DEGREE</name> + <config> + <name>MG_ON_PORT_DEGREE</name> + <value>D2</value> + </config> + </property> + </properties> + </component> + + <component xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:operation="create"> + <name>101</name> + <config> + <name>101</name> + </config> + <properties> + <property> + <name>MG_ON_PORT_TYPE</name> + <config> + <name>MG_ON_PORT_TYPE</name> + <value>MG_ON_OPTICAL_PORT_WAVEBAND</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DIRECTION</name> + <config> + <name>MG_ON_PORT_DIRECTION</name> + <value>OUTPUT</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DEGREE</name> + <config> + <name>MG_ON_PORT_DEGREE</name> + <value>D2</value> + </config> + </property> + </properties> + </component> + <component xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:operation="create"> + <name>111</name> + <config> + <name>111</name> + </config> + <properties> + <property> + <name>MG_ON_PORT_TYPE</name> + <config> + <name>MG_ON_PORT_TYPE</name> + <value>MG_ON_OPTICAL_PORT_WAVEBAND</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DIRECTION</name> + <config> + <name>MG_ON_PORT_DIRECTION</name> + <value>INPUT</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DEGREE</name> + <config> + <name>MG_ON_PORT_DEGREE</name> + <value>D2</value> + </config> + </property> + </properties> + </component> + </components> +</config> \ No newline at end of file diff --git a/src/tests/ofc24/tempOC/files/platform_r2.xml b/src/tests/ofc24/tempOC/files/platform_r2.xml new file mode 100644 index 0000000000000000000000000000000000000000..04d1d8370ffb72d74d1730094a6591115f7c7d6f --- /dev/null +++ b/src/tests/ofc24/tempOC/files/platform_r2.xml @@ -0,0 +1,237 @@ +<config xmlns="http://tail-f.com/ns/config/1.0"> + <components xmlns="http://openconfig.net/yang/platform"> + <component xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:operation="create"> + <name>2</name> + <config> + <name>2</name> + </config> + <properties> + <property> + <name>MG_ON_PORT_TYPE</name> + <config> + <name>MG_ON_PORT_TYPE</name> + <value>MG_ON_OPTICAL_PORT_WAVEBAND</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DIRECTION</name> + <config> + <name>MG_ON_PORT_DIRECTION</name> + <value>OUTPUT</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DEGREE</name> + <config> + <name>MG_ON_PORT_DEGREE</name> + <value>D1</value> + </config> + </property> + </properties> + </component> + <component xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:operation="create"> + <name>12</name> + <config> + <name>12</name> + </config> + <properties> + <property> + <name>MG_ON_PORT_TYPE</name> + <config> + <name>MG_ON_PORT_TYPE</name> + <value>MG_ON_OPTICAL_PORT_WAVEBAND</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DIRECTION</name> + <config> + <name>MG_ON_PORT_DIRECTION</name> + <value>INPUT</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DEGREE</name> + <config> + <name>MG_ON_PORT_DEGREE</name> + <value>D1</value> + </config> + </property> + </properties> + </component> + <component xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:operation="create"> + <name>3</name> + <config> + <name>3</name> + </config> + <properties> + <property> + <name>MG_ON_PORT_TYPE</name> + <config> + <name>MG_ON_PORT_TYPE</name> + <value>MG_ON_OPTICAL_PORT_WAVEBAND</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DIRECTION</name> + <config> + <name>MG_ON_PORT_DIRECTION</name> + <value>OUTPUT</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DEGREE</name> + <config> + <name>MG_ON_PORT_DEGREE</name> + <value>D2</value> + </config> + </property> + </properties> + </component> + <component xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:operation="create"> + <name>13</name> + <config> + <name>13</name> + </config> + <properties> + <property> + <name>MG_ON_PORT_TYPE</name> + <config> + <name>MG_ON_PORT_TYPE</name> + <value>MG_ON_OPTICAL_PORT_WAVEBAND</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DIRECTION</name> + <config> + <name>MG_ON_PORT_DIRECTION</name> + <value>INPUT</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DEGREE</name> + <config> + <name>MG_ON_PORT_DEGREE</name> + <value>D2</value> + </config> + </property> + </properties> + </component> + <component xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:operation="create"> + <name>4</name> + <config> + <name>4</name> + </config> + <properties> + <property> + <name>MG_ON_PORT_TYPE</name> + <config> + <name>MG_ON_PORT_TYPE</name> + <value>MG_ON_OPTICAL_PORT_WAVEBAND</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DIRECTION</name> + <config> + <name>MG_ON_PORT_DIRECTION</name> + <value>OUTPUT</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DEGREE</name> + <config> + <name>MG_ON_PORT_DEGREE</name> + <value>D2</value> + </config> + </property> + </properties> + </component> + <component xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:operation="create"> + <name>14</name> + <config> + <name>14</name> + </config> + <properties> + <property> + <name>MG_ON_PORT_TYPE</name> + <config> + <name>MG_ON_PORT_TYPE</name> + <value>MG_ON_OPTICAL_PORT_WAVEBAND</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DIRECTION</name> + <config> + <name>MG_ON_PORT_DIRECTION</name> + <value>INPUT</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DEGREE</name> + <config> + <name>MG_ON_PORT_DEGREE</name> + <value>D2</value> + </config> + </property> + </properties> + </component> + + <component xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:operation="create"> + <name>101</name> + <config> + <name>101</name> + </config> + <properties> + <property> + <name>MG_ON_PORT_TYPE</name> + <config> + <name>MG_ON_PORT_TYPE</name> + <value>MG_ON_OPTICAL_PORT_WAVEBAND</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DIRECTION</name> + <config> + <name>MG_ON_PORT_DIRECTION</name> + <value>OUTPUT</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DEGREE</name> + <config> + <name>MG_ON_PORT_DEGREE</name> + <value>D2</value> + </config> + </property> + </properties> + </component> + <component xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:operation="create"> + <name>111</name> + <config> + <name>111</name> + </config> + <properties> + <property> + <name>MG_ON_PORT_TYPE</name> + <config> + <name>MG_ON_PORT_TYPE</name> + <value>MG_ON_OPTICAL_PORT_WAVEBAND</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DIRECTION</name> + <config> + <name>MG_ON_PORT_DIRECTION</name> + <value>INPUT</value> + </config> + </property> + <property> + <name>MG_ON_PORT_DEGREE</name> + <config> + <name>MG_ON_PORT_DEGREE</name> + <value>D2</value> + </config> + </property> + </properties> + </component> + </components> +</config> \ No newline at end of file diff --git a/src/tests/ofc24/tempOC/files/platform_t1.xml b/src/tests/ofc24/tempOC/files/platform_t1.xml new file mode 100644 index 0000000000000000000000000000000000000000..09f316211dcd5fce4a31de45065bddb276c5b268 --- /dev/null +++ b/src/tests/ofc24/tempOC/files/platform_t1.xml @@ -0,0 +1,295 @@ +<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0"> + <components xmlns="http://openconfig.net/yang/platform"> + <component> + <name>device</name> + <config> + <name>device</name> + </config> + <state> + <name>MellanoxSwitch</name> + <mfg-name>SSSA-CNIT</mfg-name> + <hardware-version>1.0.0</hardware-version> + <firmware-version>1.0.0</firmware-version> + <software-version>1.0.0</software-version> + <serial-no>610610</serial-no> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:OPERATING_SYSTEM</type> + </state> + </component> + <component> + <name>channel-1</name> + <config> + <name>channel-1</name> + </config> + <state> + <name>channel-1</name> + <type xmlns:typex="http://openconfig.net/yang/transport-types">typex:OPTICAL_CHANNEL</type> + </state> + <optical-channel xmlns="http://openconfig.net/yang/terminal-device"> + <config> + <frequency>191600000</frequency> + <target-output-power>100</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-1</line-port> + </config> + <state> + <frequency>191600000</frequency> + <target-output-power>0</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-1</line-port> + <group-id>1</group-id> + <output-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </output-power> + <input-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </input-power> + <laser-bias-current> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </laser-bias-current> + <chromatic-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </chromatic-dispersion> + <polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </polarization-mode-dispersion> + <second-order-polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </second-order-polarization-mode-dispersion> + <polarization-dependent-loss> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </polarization-dependent-loss> + </state> + </optical-channel> + </component> + <component> + <name>transceiver-1</name> + <config> + <name>transceiver-1</name> + </config> + <state> + <name>transceiver-1</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:TRANSCEIVER</type> + </state> + <transceiver xmlns="http://openconfig.net/yang/platform/transceiver"> + <config> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + </config> + <state> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + <vendor>Cisco</vendor> + <vendor-part>400zr-QSFP-DD</vendor-part> + <vendor-rev>01</vendor-rev> + <serial-no>1567321</serial-no> + </state> + <physical-channels> + <channel> + <index>1</index> + <config> + <index>1</index> + <associated-optical-channel>channel-1</associated-optical-channel> + </config> + <!--state> + <index>1</index> + <associated-optical-channel>channel-4</associated-optical-channel> + </state--> + </channel> + </physical-channels> + </transceiver> + </component> + <component> + <name>port-1</name> + <config> + <name>port-1</name> + </config> + <state> + <name>port-1</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:PORT</type> + </state> + <subcomponents> + <subcomponent> + <name>channel-1</name> + <config> + <name>channel-1</name> + </config> + <state> + <name>channel-1</name> + </state> + </subcomponent> + </subcomponents> + <properties> + <property> + <name>onos-index</name> + <config> + <name>onos-index</name> + <value>4</value> + </config> + <state> + <name>onos-index</name> + <value>4</value> + </state> + </property> + <property> + <name>odtn-port-type</name> + <config> + <name>odtn-port-type</name> + <value>line</value> + </config> + <state> + <name>odtn-port-type</name> + <value>line</value> + </state> + </property> + </properties> + </component> + </components> + <terminal-device xmlns="http://openconfig.net/yang/terminal-device"> + <logical-channels> + <!--Description: Optical logical link--> + <channel> + + <!--Description: Line (OTN) Port--> + <index>1</index> + <config> + <index>1</index> + <description>Logical channel 1</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + </config> + <state> + <index>1</index> + <description>Logical channel 1</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + <link-state>UP</link-state> + </state> + <ingress> + <config> + <transceiver>transceiver-1</transceiver> + </config> + <state> + <transceiver>transceiver-1</transceiver> + </state> + </ingress> + <otn> + <config> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + </config> + <state> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + <tti-msg-auto>0</tti-msg-auto> + <tti-msg-recv>0</tti-msg-recv> + <rdi-msg>0</rdi-msg> + <errored-seconds>0</errored-seconds> + <severely-errored-seconds>0</severely-errored-seconds> + <unavailable-seconds>0</unavailable-seconds> + <code-violations>0</code-violations> + <fec-uncorrectable-words>0</fec-uncorrectable-words> + <fec-corrected-bytes>0</fec-corrected-bytes> + <fec-corrected-bits>0</fec-corrected-bits> + <background-block-errors>0</background-block-errors> + <pre-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </pre-fec-ber> + <post-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </post-fec-ber> + <q-value> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </q-value> + <esnr> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </esnr> + </state> + </otn> + <logical-channel-assignments> + <assignment> + <index>1</index> + <config> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-1</optical-channel> + </config> + <state> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-1</optical-channel> + </state> + </assignment> + </logical-channel-assignments> + </channel> + </logical-channels> + <operational-modes> + <mode> + <mode-id>1</mode-id> + <state> + <mode-id>1</mode-id> + <description>FEC1</description> + <vendor-id>Ericsson</vendor-id> + </state> + </mode> + <mode> + <mode-id>2</mode-id> + <state> + <mode-id>2</mode-id> + <description>FEC2</description> + <vendor-id>Ericsson</vendor-id> + </state> + </mode> + </operational-modes> + </terminal-device> +</config> diff --git a/src/tests/ofc24/tempOC/files/platform_t2.xml b/src/tests/ofc24/tempOC/files/platform_t2.xml new file mode 100644 index 0000000000000000000000000000000000000000..03c643c91f176b531ec25bda6fbb36c8bdb1c099 --- /dev/null +++ b/src/tests/ofc24/tempOC/files/platform_t2.xml @@ -0,0 +1,295 @@ +<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0"> + <components xmlns="http://openconfig.net/yang/platform"> + <component> + <name>device</name> + <config> + <name>device</name> + </config> + <state> + <name>MellanoxSwitch</name> + <mfg-name>SSSA-CNIT</mfg-name> + <hardware-version>1.0.0</hardware-version> + <firmware-version>1.0.0</firmware-version> + <software-version>1.0.0</software-version> + <serial-no>610610</serial-no> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:OPERATING_SYSTEM</type> + </state> + </component> + <component> + <name>channel-6</name> + <config> + <name>channel-6</name> + </config> + <state> + <name>channel-6</name> + <type xmlns:typex="http://openconfig.net/yang/transport-types">typex:OPTICAL_CHANNEL</type> + </state> + <optical-channel xmlns="http://openconfig.net/yang/terminal-device"> + <config> + <frequency>191600000</frequency> + <target-output-power>100</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-6</line-port> + </config> + <state> + <frequency>191600000</frequency> + <target-output-power>0</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-6</line-port> + <group-id>1</group-id> + <output-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </output-power> + <input-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </input-power> + <laser-bias-current> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </laser-bias-current> + <chromatic-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </chromatic-dispersion> + <polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </polarization-mode-dispersion> + <second-order-polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </second-order-polarization-mode-dispersion> + <polarization-dependent-loss> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </polarization-dependent-loss> + </state> + </optical-channel> + </component> + <component> + <name>transceiver-6</name> + <config> + <name>transceiver-6</name> + </config> + <state> + <name>transceiver-6</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:TRANSCEIVER</type> + </state> + <transceiver xmlns="http://openconfig.net/yang/platform/transceiver"> + <config> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + </config> + <state> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + <vendor>Cisco</vendor> + <vendor-part>400zr-QSFP-DD</vendor-part> + <vendor-rev>01</vendor-rev> + <serial-no>1567321</serial-no> + </state> + <physical-channels> + <channel> + <index>1</index> + <config> + <index>1</index> + <associated-optical-channel>channel-6</associated-optical-channel> + </config> + <!--state> + <index>1</index> + <associated-optical-channel>channel-4</associated-optical-channel> + </state--> + </channel> + </physical-channels> + </transceiver> + </component> + <component> + <name>port-6</name> + <config> + <name>port-6</name> + </config> + <state> + <name>port-6</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:PORT</type> + </state> + <subcomponents> + <subcomponent> + <name>channel-6</name> + <config> + <name>channel-6</name> + </config> + <state> + <name>channel-6</name> + </state> + </subcomponent> + </subcomponents> + <properties> + <property> + <name>onos-index</name> + <config> + <name>onos-index</name> + <value>4</value> + </config> + <state> + <name>onos-index</name> + <value>4</value> + </state> + </property> + <property> + <name>odtn-port-type</name> + <config> + <name>odtn-port-type</name> + <value>line</value> + </config> + <state> + <name>odtn-port-type</name> + <value>line</value> + </state> + </property> + </properties> + </component> + </components> + <terminal-device xmlns="http://openconfig.net/yang/terminal-device"> + <logical-channels> + <!--Description: Optical logical link--> + <channel> + + <!--Description: Line (OTN) Port--> + <index>4</index> + <config> + <index>4</index> + <description>Logical channel 4</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + </config> + <state> + <index>4</index> + <description>Logical channel 4</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + <link-state>UP</link-state> + </state> + <ingress> + <config> + <transceiver>transceiver-6</transceiver> + </config> + <state> + <transceiver>transceiver-6</transceiver> + </state> + </ingress> + <otn> + <config> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + </config> + <state> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + <tti-msg-auto>0</tti-msg-auto> + <tti-msg-recv>0</tti-msg-recv> + <rdi-msg>0</rdi-msg> + <errored-seconds>0</errored-seconds> + <severely-errored-seconds>0</severely-errored-seconds> + <unavailable-seconds>0</unavailable-seconds> + <code-violations>0</code-violations> + <fec-uncorrectable-words>0</fec-uncorrectable-words> + <fec-corrected-bytes>0</fec-corrected-bytes> + <fec-corrected-bits>0</fec-corrected-bits> + <background-block-errors>0</background-block-errors> + <pre-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </pre-fec-ber> + <post-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </post-fec-ber> + <q-value> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </q-value> + <esnr> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </esnr> + </state> + </otn> + <logical-channel-assignments> + <assignment> + <index>1</index> + <config> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-6</optical-channel> + </config> + <state> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-6</optical-channel> + </state> + </assignment> + </logical-channel-assignments> + </channel> + </logical-channels> + <operational-modes> + <mode> + <mode-id>1</mode-id> + <state> + <mode-id>1</mode-id> + <description>FEC1</description> + <vendor-id>Ericsson</vendor-id> + </state> + </mode> + <mode> + <mode-id>2</mode-id> + <state> + <mode-id>2</mode-id> + <description>FEC2</description> + <vendor-id>Ericsson</vendor-id> + </state> + </mode> + </operational-modes> + </terminal-device> +</config> diff --git a/src/tests/ofc24/tempOC/files/startNetconfAgent.sh b/src/tests/ofc24/tempOC/files/startNetconfAgent.sh new file mode 100644 index 0000000000000000000000000000000000000000..35c95b6574bca3e0b620c356ea0f393137ec1b90 --- /dev/null +++ b/src/tests/ofc24/tempOC/files/startNetconfAgent.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License + +make clean +make all +#make init +cp init_openconfig-platform.xml confd-cdb/ +#cp init_flex-scale-mg-on.xml confd-cdb/ +make start2 diff --git a/src/tests/ofc24/tempOC/files/transponders_x4.xml b/src/tests/ofc24/tempOC/files/transponders_x4.xml new file mode 100644 index 0000000000000000000000000000000000000000..fb55f02abefa6c5a444d3fecaa2ca049798f9483 --- /dev/null +++ b/src/tests/ofc24/tempOC/files/transponders_x4.xml @@ -0,0 +1,1039 @@ +<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0"> + <components xmlns="http://openconfig.net/yang/platform"> + <component> + <name>device</name> + <config> + <name>device</name> + </config> + <state> + <name>MellanoxSwitch</name> + <mfg-name>SSSA-CNIT</mfg-name> + <hardware-version>1.0.0</hardware-version> + <firmware-version>1.0.0</firmware-version> + <software-version>1.0.0</software-version> + <serial-no>610610</serial-no> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:OPERATING_SYSTEM</type> + </state> + </component> + <component> + <name>channel-1</name> + <config> + <name>channel-1</name> + </config> + <state> + <name>channel-1</name> + <type xmlns:typex="http://openconfig.net/yang/transport-types">typex:OPTICAL_CHANNEL</type> + </state> + <optical-channel xmlns="http://openconfig.net/yang/terminal-device"> + <config> + <frequency>191600000</frequency> + <target-output-power>100</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-1</line-port> + </config> + <state> + <frequency>191600000</frequency> + <target-output-power>0</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-1</line-port> + <group-id>1</group-id> + <output-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </output-power> + <input-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </input-power> + <laser-bias-current> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </laser-bias-current> + <chromatic-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </chromatic-dispersion> + <polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </polarization-mode-dispersion> + <second-order-polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </second-order-polarization-mode-dispersion> + <polarization-dependent-loss> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </polarization-dependent-loss> + </state> + </optical-channel> + </component> + <component> + <name>channel-2</name> + <config> + <name>channel-2</name> + </config> + <state> + <name>channel-2</name> + <type xmlns:typex="http://openconfig.net/yang/transport-types">typex:OPTICAL_CHANNEL</type> + </state> + <optical-channel xmlns="http://openconfig.net/yang/terminal-device"> + <config> + <frequency>191600000</frequency> + <target-output-power>100</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-2</line-port> + </config> + <state> + <frequency>191600000</frequency> + <target-output-power>0</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-2</line-port> + <group-id>1</group-id> + <output-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </output-power> + <input-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </input-power> + <laser-bias-current> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </laser-bias-current> + <chromatic-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </chromatic-dispersion> + <polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </polarization-mode-dispersion> + <second-order-polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </second-order-polarization-mode-dispersion> + <polarization-dependent-loss> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </polarization-dependent-loss> + </state> + </optical-channel> + </component> + <component> + <name>channel-3</name> + <config> + <name>channel-3</name> + </config> + <state> + <name>channel-3</name> + <type xmlns:typex="http://openconfig.net/yang/transport-types">typex:OPTICAL_CHANNEL</type> + </state> + <optical-channel xmlns="http://openconfig.net/yang/terminal-device"> + <config> + <frequency>191600000</frequency> + <target-output-power>100</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-3</line-port> + </config> + <state> + <frequency>191600000</frequency> + <target-output-power>0</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-3</line-port> + <group-id>1</group-id> + <output-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </output-power> + <input-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </input-power> + <laser-bias-current> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </laser-bias-current> + <chromatic-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </chromatic-dispersion> + <polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </polarization-mode-dispersion> + <second-order-polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </second-order-polarization-mode-dispersion> + <polarization-dependent-loss> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </polarization-dependent-loss> + </state> + </optical-channel> + </component> + <component> + <name>channel-4</name> + <config> + <name>channel-4</name> + </config> + <state> + <name>channel-4</name> + <type xmlns:typex="http://openconfig.net/yang/transport-types">typex:OPTICAL_CHANNEL</type> + </state> + <optical-channel xmlns="http://openconfig.net/yang/terminal-device"> + <config> + <frequency>191600000</frequency> + <target-output-power>100</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-4</line-port> + </config> + <state> + <frequency>191600000</frequency> + <target-output-power>0</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-4</line-port> + <group-id>1</group-id> + <output-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </output-power> + <input-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </input-power> + <laser-bias-current> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </laser-bias-current> + <chromatic-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </chromatic-dispersion> + <polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </polarization-mode-dispersion> + <second-order-polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </second-order-polarization-mode-dispersion> + <polarization-dependent-loss> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </polarization-dependent-loss> + </state> + </optical-channel> + </component> + <component> + <name>transceiver-1</name> + <config> + <name>transceiver-1</name> + </config> + <state> + <name>transceiver-1</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:TRANSCEIVER</type> + </state> + <transceiver xmlns="http://openconfig.net/yang/platform/transceiver"> + <config> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + </config> + <state> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + <vendor>Cisco</vendor> + <vendor-part>400zr-QSFP-DD</vendor-part> + <vendor-rev>01</vendor-rev> + <serial-no>1567321</serial-no> + </state> + <physical-channels> + <channel> + <index>1</index> + <config> + <index>1</index> + <associated-optical-channel>channel-1</associated-optical-channel> + </config> + </channel> + </physical-channels> + </transceiver> + </component> + <component> + <name>transceiver-2</name> + <config> + <name>transceiver-2</name> + </config> + <state> + <name>transceiver-2</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:TRANSCEIVER</type> + </state> + <transceiver xmlns="http://openconfig.net/yang/platform/transceiver"> + <config> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + </config> + <state> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + <vendor>Cisco</vendor> + <vendor-part>400zr-QSFP-DD</vendor-part> + <vendor-rev>01</vendor-rev> + <serial-no>1567321</serial-no> + </state> + <physical-channels> + <channel> + <index>1</index> + <config> + <index>1</index> + <associated-optical-channel>channel-2</associated-optical-channel> + </config> + </channel> + </physical-channels> + </transceiver> + </component> + <component> + <name>transceiver-3</name> + <config> + <name>transceiver-3</name> + </config> + <state> + <name>transceiver-3</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:TRANSCEIVER</type> + </state> + <transceiver xmlns="http://openconfig.net/yang/platform/transceiver"> + <config> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + </config> + <state> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + <vendor>Cisco</vendor> + <vendor-part>400zr-QSFP-DD</vendor-part> + <vendor-rev>01</vendor-rev> + <serial-no>1567321</serial-no> + </state> + <physical-channels> + <channel> + <index>1</index> + <config> + <index>1</index> + <associated-optical-channel>channel-3</associated-optical-channel> + </config> + </channel> + </physical-channels> + </transceiver> + </component> + + <component> + <name>transceiver-4</name> + <config> + <name>transceiver-4</name> + </config> + <state> + <name>transceiver-4</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:TRANSCEIVER</type> + </state> + <transceiver xmlns="http://openconfig.net/yang/platform/transceiver"> + <config> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + </config> + <state> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + <vendor>Cisco</vendor> + <vendor-part>400zr-QSFP-DD</vendor-part> + <vendor-rev>01</vendor-rev> + <serial-no>1567321</serial-no> + </state> + <physical-channels> + <channel> + <index>1</index> + <config> + <index>1</index> + <associated-optical-channel>channel-4</associated-optical-channel> + </config> + </channel> + </physical-channels> + </transceiver> + </component> + <component> + <name>port-1</name> + <config> + <name>port-1</name> + </config> + <state> + <name>port-1</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:PORT</type> + </state> + <subcomponents> + <subcomponent> + <name>channel-1</name> + <config> + <name>channel-1</name> + </config> + <state> + <name>channel-1</name> + </state> + </subcomponent> + </subcomponents> + <properties> + <property> + <name>onos-index</name> + <config> + <name>onos-index</name> + <value>1</value> + </config> + <state> + <name>onos-index</name> + <value>1</value> + </state> + </property> + <property> + <name>odtn-port-type</name> + <config> + <name>odtn-port-type</name> + <value>line</value> + </config> + <state> + <name>odtn-port-type</name> + <value>line</value> + </state> + </property> + </properties> + </component> + <component> + <name>port-2</name> + <config> + <name>port-2</name> + </config> + <state> + <name>port-2</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:PORT</type> + </state> + <subcomponents> + <subcomponent> + <name>channel-2</name> + <config> + <name>channel-2</name> + </config> + <state> + <name>channel-2</name> + </state> + </subcomponent> + </subcomponents> + <properties> + <property> + <name>onos-index</name> + <config> + <name>onos-index</name> + <value>2</value> + </config> + <state> + <name>onos-index</name> + <value>2</value> + </state> + </property> + <property> + <name>odtn-port-type</name> + <config> + <name>odtn-port-type</name> + <value>line</value> + </config> + <state> + <name>odtn-port-type</name> + <value>line</value> + </state> + </property> + </properties> + </component> + <component> + <name>port-3</name> + <config> + <name>port-3</name> + </config> + <state> + <name>port-3</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:PORT</type> + </state> + <subcomponents> + <subcomponent> + <name>channel-3</name> + <config> + <name>channel-3</name> + </config> + <state> + <name>channel-3</name> + </state> + </subcomponent> + </subcomponents> + <properties> + <property> + <name>onos-index</name> + <config> + <name>onos-index</name> + <value>3</value> + </config> + <state> + <name>onos-index</name> + <value>3</value> + </state> + </property> + <property> + <name>odtn-port-type</name> + <config> + <name>odtn-port-type</name> + <value>line</value> + </config> + <state> + <name>odtn-port-type</name> + <value>line</value> + </state> + </property> + </properties> + </component> + <component> + <name>port-4</name> + <config> + <name>port-4</name> + </config> + <state> + <name>port-4</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:PORT</type> + </state> + <subcomponents> + <subcomponent> + <name>channel-4</name> + <config> + <name>channel-4</name> + </config> + <state> + <name>channel-4</name> + </state> + </subcomponent> + </subcomponents> + <properties> + <property> + <name>onos-index</name> + <config> + <name>onos-index</name> + <value>4</value> + </config> + <state> + <name>onos-index</name> + <value>4</value> + </state> + </property> + <property> + <name>odtn-port-type</name> + <config> + <name>odtn-port-type</name> + <value>line</value> + </config> + <state> + <name>odtn-port-type</name> + <value>line</value> + </state> + </property> + </properties> + </component> + </components> + <terminal-device xmlns="http://openconfig.net/yang/terminal-device"> + <logical-channels> + <!--Description: Optical logical link--> + <channel> + <!--Description: Line (OTN) Port--> + <index>1</index> + <config> + <index>1</index> + <description>Logical channel 1</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + </config> + <state> + <index>1</index> + <description>Logical channel 1</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + <link-state>UP</link-state> + </state> + <ingress> + <config> + <transceiver>transceiver-1</transceiver> + </config> + <state> + <transceiver>transceiver-1</transceiver> + </state> + </ingress> + <otn> + <config> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + </config> + <state> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + <tti-msg-auto>0</tti-msg-auto> + <tti-msg-recv>0</tti-msg-recv> + <rdi-msg>0</rdi-msg> + <errored-seconds>0</errored-seconds> + <severely-errored-seconds>0</severely-errored-seconds> + <unavailable-seconds>0</unavailable-seconds> + <code-violations>0</code-violations> + <fec-uncorrectable-words>0</fec-uncorrectable-words> + <fec-corrected-bytes>0</fec-corrected-bytes> + <fec-corrected-bits>0</fec-corrected-bits> + <background-block-errors>0</background-block-errors> + <pre-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </pre-fec-ber> + <post-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </post-fec-ber> + <q-value> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </q-value> + <esnr> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </esnr> + </state> + </otn> + <logical-channel-assignments> + <assignment> + <index>1</index> + <config> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-1</optical-channel> + </config> + <state> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-1</optical-channel> + </state> + </assignment> + </logical-channel-assignments> + </channel> + <!--Description: Optical logical link--> + <channel> + <!--Description: Line (OTN) Port--> + <index>2</index> + <config> + <index>2</index> + <description>Logical channel 2</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + </config> + <state> + <index>2</index> + <description>Logical channel 2</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + <link-state>UP</link-state> + </state> + <ingress> + <config> + <transceiver>transceiver-2</transceiver> + </config> + <state> + <transceiver>transceiver-2</transceiver> + </state> + </ingress> + <otn> + <config> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + </config> + <state> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + <tti-msg-auto>0</tti-msg-auto> + <tti-msg-recv>0</tti-msg-recv> + <rdi-msg>0</rdi-msg> + <errored-seconds>0</errored-seconds> + <severely-errored-seconds>0</severely-errored-seconds> + <unavailable-seconds>0</unavailable-seconds> + <code-violations>0</code-violations> + <fec-uncorrectable-words>0</fec-uncorrectable-words> + <fec-corrected-bytes>0</fec-corrected-bytes> + <fec-corrected-bits>0</fec-corrected-bits> + <background-block-errors>0</background-block-errors> + <pre-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </pre-fec-ber> + <post-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </post-fec-ber> + <q-value> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </q-value> + <esnr> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </esnr> + </state> + </otn> + <logical-channel-assignments> + <assignment> + <index>1</index> + <config> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-2</optical-channel> + </config> + <state> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-2</optical-channel> + </state> + </assignment> + </logical-channel-assignments> + </channel> + <!--Description: Optical logical link--> + <channel> + <!--Description: Line (OTN) Port--> + <index>3</index> + <config> + <index>3</index> + <description>Logical channel 3</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + </config> + <state> + <index>3</index> + <description>Logical channel 3</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + <link-state>UP</link-state> + </state> + <ingress> + <config> + <transceiver>transceiver-3</transceiver> + </config> + <state> + <transceiver>transceiver-3</transceiver> + </state> + </ingress> + <otn> + <config> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + </config> + <state> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + <tti-msg-auto>0</tti-msg-auto> + <tti-msg-recv>0</tti-msg-recv> + <rdi-msg>0</rdi-msg> + <errored-seconds>0</errored-seconds> + <severely-errored-seconds>0</severely-errored-seconds> + <unavailable-seconds>0</unavailable-seconds> + <code-violations>0</code-violations> + <fec-uncorrectable-words>0</fec-uncorrectable-words> + <fec-corrected-bytes>0</fec-corrected-bytes> + <fec-corrected-bits>0</fec-corrected-bits> + <background-block-errors>0</background-block-errors> + <pre-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </pre-fec-ber> + <post-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </post-fec-ber> + <q-value> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </q-value> + <esnr> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </esnr> + </state> + </otn> + <logical-channel-assignments> + <assignment> + <index>1</index> + <config> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-3</optical-channel> + </config> + <state> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-3</optical-channel> + </state> + </assignment> + </logical-channel-assignments> + </channel> + <!--Description: Optical logical link--> + <channel> + <!--Description: Line (OTN) Port--> + <index>4</index> + <config> + <index>4</index> + <description>Logical channel 4</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + </config> + <state> + <index>4</index> + <description>Logical channel 4</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + <link-state>UP</link-state> + </state> + <ingress> + <config> + <transceiver>transceiver-4</transceiver> + </config> + <state> + <transceiver>transceiver-4</transceiver> + </state> + </ingress> + <otn> + <config> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + </config> + <state> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + <tti-msg-auto>0</tti-msg-auto> + <tti-msg-recv>0</tti-msg-recv> + <rdi-msg>0</rdi-msg> + <errored-seconds>0</errored-seconds> + <severely-errored-seconds>0</severely-errored-seconds> + <unavailable-seconds>0</unavailable-seconds> + <code-violations>0</code-violations> + <fec-uncorrectable-words>0</fec-uncorrectable-words> + <fec-corrected-bytes>0</fec-corrected-bytes> + <fec-corrected-bits>0</fec-corrected-bits> + <background-block-errors>0</background-block-errors> + <pre-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </pre-fec-ber> + <post-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </post-fec-ber> + <q-value> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </q-value> + <esnr> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </esnr> + </state> + </otn> + <logical-channel-assignments> + <assignment> + <index>1</index> + <config> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-4</optical-channel> + </config> + <state> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-4</optical-channel> + </state> + </assignment> + </logical-channel-assignments> + </channel> + </logical-channels> + <operational-modes> + <mode> + <mode-id>1</mode-id> + <state> + <mode-id>1</mode-id> + <description>FEC1</description> + <vendor-id>Ericsson</vendor-id> + </state> + </mode> + <mode> + <mode-id>2</mode-id> + <state> + <mode-id>2</mode-id> + <description>FEC2</description> + <vendor-id>Ericsson</vendor-id> + </state> + </mode> + </operational-modes> + </terminal-device> +</config> + diff --git a/src/tests/ofc24/tempOC/files/transponders_x4_2.xml b/src/tests/ofc24/tempOC/files/transponders_x4_2.xml new file mode 100644 index 0000000000000000000000000000000000000000..8d10c593b3c2166b16e8ecea383dadd69c3ac067 --- /dev/null +++ b/src/tests/ofc24/tempOC/files/transponders_x4_2.xml @@ -0,0 +1,1039 @@ +<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0"> + <components xmlns="http://openconfig.net/yang/platform"> + <component> + <name>device</name> + <config> + <name>device</name> + </config> + <state> + <name>MellanoxSwitch</name> + <mfg-name>SSSA-CNIT</mfg-name> + <hardware-version>1.0.0</hardware-version> + <firmware-version>1.0.0</firmware-version> + <software-version>1.0.0</software-version> + <serial-no>610610</serial-no> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:OPERATING_SYSTEM</type> + </state> + </component> + <component> + <name>channel-5</name> + <config> + <name>channel-5</name> + </config> + <state> + <name>channel-5</name> + <type xmlns:typex="http://openconfig.net/yang/transport-types">typex:OPTICAL_CHANNEL</type> + </state> + <optical-channel xmlns="http://openconfig.net/yang/terminal-device"> + <config> + <frequency>191600000</frequency> + <target-output-power>100</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-5</line-port> + </config> + <state> + <frequency>191600000</frequency> + <target-output-power>0</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-5</line-port> + <group-id>1</group-id> + <output-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </output-power> + <input-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </input-power> + <laser-bias-current> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </laser-bias-current> + <chromatic-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </chromatic-dispersion> + <polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </polarization-mode-dispersion> + <second-order-polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </second-order-polarization-mode-dispersion> + <polarization-dependent-loss> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </polarization-dependent-loss> + </state> + </optical-channel> + </component> + <component> + <name>channel-6</name> + <config> + <name>channel-6</name> + </config> + <state> + <name>channel-6</name> + <type xmlns:typex="http://openconfig.net/yang/transport-types">typex:OPTICAL_CHANNEL</type> + </state> + <optical-channel xmlns="http://openconfig.net/yang/terminal-device"> + <config> + <frequency>191600000</frequency> + <target-output-power>100</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-6</line-port> + </config> + <state> + <frequency>191600000</frequency> + <target-output-power>0</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-6</line-port> + <group-id>1</group-id> + <output-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </output-power> + <input-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </input-power> + <laser-bias-current> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </laser-bias-current> + <chromatic-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </chromatic-dispersion> + <polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </polarization-mode-dispersion> + <second-order-polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </second-order-polarization-mode-dispersion> + <polarization-dependent-loss> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </polarization-dependent-loss> + </state> + </optical-channel> + </component> + <component> + <name>channel-7</name> + <config> + <name>channel-7</name> + </config> + <state> + <name>channel-7</name> + <type xmlns:typex="http://openconfig.net/yang/transport-types">typex:OPTICAL_CHANNEL</type> + </state> + <optical-channel xmlns="http://openconfig.net/yang/terminal-device"> + <config> + <frequency>191600000</frequency> + <target-output-power>100</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-7</line-port> + </config> + <state> + <frequency>191600000</frequency> + <target-output-power>0</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-7</line-port> + <group-id>1</group-id> + <output-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </output-power> + <input-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </input-power> + <laser-bias-current> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </laser-bias-current> + <chromatic-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </chromatic-dispersion> + <polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </polarization-mode-dispersion> + <second-order-polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </second-order-polarization-mode-dispersion> + <polarization-dependent-loss> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </polarization-dependent-loss> + </state> + </optical-channel> + </component> + <component> + <name>channel-8</name> + <config> + <name>channel-8</name> + </config> + <state> + <name>channel-8</name> + <type xmlns:typex="http://openconfig.net/yang/transport-types">typex:OPTICAL_CHANNEL</type> + </state> + <optical-channel xmlns="http://openconfig.net/yang/terminal-device"> + <config> + <frequency>191600000</frequency> + <target-output-power>100</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-8</line-port> + </config> + <state> + <frequency>191600000</frequency> + <target-output-power>0</target-output-power> + <operational-mode>0</operational-mode> + <line-port>transceiver-8</line-port> + <group-id>1</group-id> + <output-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </output-power> + <input-power> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </input-power> + <laser-bias-current> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </laser-bias-current> + <chromatic-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </chromatic-dispersion> + <polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </polarization-mode-dispersion> + <second-order-polarization-mode-dispersion> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + </second-order-polarization-mode-dispersion> + <polarization-dependent-loss> + <instant>0</instant> + <avg>0</avg> + <min>0</min> + <max>0</max> + <interval>0</interval> + </polarization-dependent-loss> + </state> + </optical-channel> + </component> + <component> + <name>transceiver-5</name> + <config> + <name>transceiver-5</name> + </config> + <state> + <name>transceiver-5</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:TRANSCEIVER</type> + </state> + <transceiver xmlns="http://openconfig.net/yang/platform/transceiver"> + <config> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + </config> + <state> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + <vendor>Cisco</vendor> + <vendor-part>400zr-QSFP-DD</vendor-part> + <vendor-rev>01</vendor-rev> + <serial-no>1567321</serial-no> + </state> + <physical-channels> + <channel> + <index>1</index> + <config> + <index>1</index> + <associated-optical-channel>channel-5</associated-optical-channel> + </config> + </channel> + </physical-channels> + </transceiver> + </component> + <component> + <name>transceiver-6</name> + <config> + <name>transceiver-6</name> + </config> + <state> + <name>transceiver-6</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:TRANSCEIVER</type> + </state> + <transceiver xmlns="http://openconfig.net/yang/platform/transceiver"> + <config> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + </config> + <state> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + <vendor>Cisco</vendor> + <vendor-part>400zr-QSFP-DD</vendor-part> + <vendor-rev>01</vendor-rev> + <serial-no>1567321</serial-no> + </state> + <physical-channels> + <channel> + <index>1</index> + <config> + <index>1</index> + <associated-optical-channel>channel-6</associated-optical-channel> + </config> + </channel> + </physical-channels> + </transceiver> + </component> + <component> + <name>transceiver-7</name> + <config> + <name>transceiver-7</name> + </config> + <state> + <name>transceiver-7</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:TRANSCEIVER</type> + </state> + <transceiver xmlns="http://openconfig.net/yang/platform/transceiver"> + <config> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + </config> + <state> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + <vendor>Cisco</vendor> + <vendor-part>400zr-QSFP-DD</vendor-part> + <vendor-rev>01</vendor-rev> + <serial-no>1567321</serial-no> + </state> + <physical-channels> + <channel> + <index>1</index> + <config> + <index>1</index> + <associated-optical-channel>channel-7</associated-optical-channel> + </config> + </channel> + </physical-channels> + </transceiver> + </component> + + <component> + <name>transceiver-8</name> + <config> + <name>transceiver-8</name> + </config> + <state> + <name>transceiver-8</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:TRANSCEIVER</type> + </state> + <transceiver xmlns="http://openconfig.net/yang/platform/transceiver"> + <config> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + </config> + <state> + <enabled>true</enabled> + <form-factor-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:QSFP56_DD_TYPE1</form-factor-preconf> + <ethernet-pmd-preconf xmlns:typex="http://openconfig.net/yang/transport-types">typex:ETH_400GBASE_ZR</ethernet-pmd-preconf> + <fec-mode xmlns:typex="http://openconfig.net/yang/platform-types">typex:FEC_AUTO</fec-mode> + <module-functional-type xmlns:typex="http://openconfig.net/yang/transport-types">typex:TYPE_DIGITAL_COHERENT_OPTIC</module-functional-type> + <vendor>Cisco</vendor> + <vendor-part>400zr-QSFP-DD</vendor-part> + <vendor-rev>01</vendor-rev> + <serial-no>1567321</serial-no> + </state> + <physical-channels> + <channel> + <index>1</index> + <config> + <index>1</index> + <associated-optical-channel>channel-8</associated-optical-channel> + </config> + </channel> + </physical-channels> + </transceiver> + </component> + <component> + <name>port-5</name> + <config> + <name>port-5</name> + </config> + <state> + <name>port-5</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:PORT</type> + </state> + <subcomponents> + <subcomponent> + <name>channel-5</name> + <config> + <name>channel-5</name> + </config> + <state> + <name>channel-5</name> + </state> + </subcomponent> + </subcomponents> + <properties> + <property> + <name>onos-index</name> + <config> + <name>onos-index</name> + <value>5</value> + </config> + <state> + <name>onos-index</name> + <value>5</value> + </state> + </property> + <property> + <name>odtn-port-type</name> + <config> + <name>odtn-port-type</name> + <value>line</value> + </config> + <state> + <name>odtn-port-type</name> + <value>line</value> + </state> + </property> + </properties> + </component> + <component> + <name>port-6</name> + <config> + <name>port-6</name> + </config> + <state> + <name>port-6</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:PORT</type> + </state> + <subcomponents> + <subcomponent> + <name>channel-6</name> + <config> + <name>channel-6</name> + </config> + <state> + <name>channel-6</name> + </state> + </subcomponent> + </subcomponents> + <properties> + <property> + <name>onos-index</name> + <config> + <name>onos-index</name> + <value>6</value> + </config> + <state> + <name>onos-index</name> + <value>6</value> + </state> + </property> + <property> + <name>odtn-port-type</name> + <config> + <name>odtn-port-type</name> + <value>line</value> + </config> + <state> + <name>odtn-port-type</name> + <value>line</value> + </state> + </property> + </properties> + </component> + <component> + <name>port-7</name> + <config> + <name>port-7</name> + </config> + <state> + <name>port-7</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:PORT</type> + </state> + <subcomponents> + <subcomponent> + <name>channel-7</name> + <config> + <name>channel-7</name> + </config> + <state> + <name>channel-7</name> + </state> + </subcomponent> + </subcomponents> + <properties> + <property> + <name>onos-index</name> + <config> + <name>onos-index</name> + <value>7</value> + </config> + <state> + <name>onos-index</name> + <value>7</value> + </state> + </property> + <property> + <name>odtn-port-type</name> + <config> + <name>odtn-port-type</name> + <value>line</value> + </config> + <state> + <name>odtn-port-type</name> + <value>line</value> + </state> + </property> + </properties> + </component> + <component> + <name>port-8</name> + <config> + <name>port-8</name> + </config> + <state> + <name>port-8</name> + <type xmlns:typex="http://openconfig.net/yang/platform-types">typex:PORT</type> + </state> + <subcomponents> + <subcomponent> + <name>channel-8</name> + <config> + <name>channel-8</name> + </config> + <state> + <name>channel-8</name> + </state> + </subcomponent> + </subcomponents> + <properties> + <property> + <name>onos-index</name> + <config> + <name>onos-index</name> + <value>8</value> + </config> + <state> + <name>onos-index</name> + <value>8</value> + </state> + </property> + <property> + <name>odtn-port-type</name> + <config> + <name>odtn-port-type</name> + <value>line</value> + </config> + <state> + <name>odtn-port-type</name> + <value>line</value> + </state> + </property> + </properties> + </component> + </components> + <terminal-device xmlns="http://openconfig.net/yang/terminal-device"> + <logical-channels> + <!--Description: Optical logical link--> + <channel> + <!--Description: Line (OTN) Port--> + <index>5</index> + <config> + <index>5</index> + <description>Logical channel 5</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + </config> + <state> + <index>5</index> + <description>Logical channel 5</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + <link-state>UP</link-state> + </state> + <ingress> + <config> + <transceiver>transceiver-5</transceiver> + </config> + <state> + <transceiver>transceiver-5</transceiver> + </state> + </ingress> + <otn> + <config> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + </config> + <state> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + <tti-msg-auto>0</tti-msg-auto> + <tti-msg-recv>0</tti-msg-recv> + <rdi-msg>0</rdi-msg> + <errored-seconds>0</errored-seconds> + <severely-errored-seconds>0</severely-errored-seconds> + <unavailable-seconds>0</unavailable-seconds> + <code-violations>0</code-violations> + <fec-uncorrectable-words>0</fec-uncorrectable-words> + <fec-corrected-bytes>0</fec-corrected-bytes> + <fec-corrected-bits>0</fec-corrected-bits> + <background-block-errors>0</background-block-errors> + <pre-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </pre-fec-ber> + <post-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </post-fec-ber> + <q-value> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </q-value> + <esnr> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </esnr> + </state> + </otn> + <logical-channel-assignments> + <assignment> + <index>1</index> + <config> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-5</optical-channel> + </config> + <state> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-5</optical-channel> + </state> + </assignment> + </logical-channel-assignments> + </channel> + <!--Description: Optical logical link--> + <channel> + <!--Description: Line (OTN) Port--> + <index>6</index> + <config> + <index>6</index> + <description>Logical channel 6</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + </config> + <state> + <index>6</index> + <description>Logical channel 6</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + <link-state>UP</link-state> + </state> + <ingress> + <config> + <transceiver>transceiver-6</transceiver> + </config> + <state> + <transceiver>transceiver-6</transceiver> + </state> + </ingress> + <otn> + <config> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + </config> + <state> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + <tti-msg-auto>0</tti-msg-auto> + <tti-msg-recv>0</tti-msg-recv> + <rdi-msg>0</rdi-msg> + <errored-seconds>0</errored-seconds> + <severely-errored-seconds>0</severely-errored-seconds> + <unavailable-seconds>0</unavailable-seconds> + <code-violations>0</code-violations> + <fec-uncorrectable-words>0</fec-uncorrectable-words> + <fec-corrected-bytes>0</fec-corrected-bytes> + <fec-corrected-bits>0</fec-corrected-bits> + <background-block-errors>0</background-block-errors> + <pre-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </pre-fec-ber> + <post-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </post-fec-ber> + <q-value> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </q-value> + <esnr> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </esnr> + </state> + </otn> + <logical-channel-assignments> + <assignment> + <index>1</index> + <config> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-6</optical-channel> + </config> + <state> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-6</optical-channel> + </state> + </assignment> + </logical-channel-assignments> + </channel> + <!--Description: Optical logical link--> + <channel> + <!--Description: Line (OTN) Port--> + <index>7</index> + <config> + <index>7</index> + <description>Logical channel 7</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + </config> + <state> + <index>7</index> + <description>Logical channel 7</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + <link-state>UP</link-state> + </state> + <ingress> + <config> + <transceiver>transceiver-7</transceiver> + </config> + <state> + <transceiver>transceiver-7</transceiver> + </state> + </ingress> + <otn> + <config> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + </config> + <state> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + <tti-msg-auto>0</tti-msg-auto> + <tti-msg-recv>0</tti-msg-recv> + <rdi-msg>0</rdi-msg> + <errored-seconds>0</errored-seconds> + <severely-errored-seconds>0</severely-errored-seconds> + <unavailable-seconds>0</unavailable-seconds> + <code-violations>0</code-violations> + <fec-uncorrectable-words>0</fec-uncorrectable-words> + <fec-corrected-bytes>0</fec-corrected-bytes> + <fec-corrected-bits>0</fec-corrected-bits> + <background-block-errors>0</background-block-errors> + <pre-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </pre-fec-ber> + <post-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </post-fec-ber> + <q-value> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </q-value> + <esnr> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </esnr> + </state> + </otn> + <logical-channel-assignments> + <assignment> + <index>1</index> + <config> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-7</optical-channel> + </config> + <state> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-7</optical-channel> + </state> + </assignment> + </logical-channel-assignments> + </channel> + <!--Description: Optical logical link--> + <channel> + <!--Description: Line (OTN) Port--> + <index>8</index> + <config> + <index>8</index> + <description>Logical channel 8</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + </config> + <state> + <index>8</index> + <description>Logical channel 8</description> + <admin-state>DISABLED</admin-state> + <logical-channel-type xmlns:type="http://openconfig.net/yang/transport-types">type:PROT_OTN</logical-channel-type> + <loopback-mode>NONE</loopback-mode> + <link-state>UP</link-state> + </state> + <ingress> + <config> + <transceiver>transceiver-8</transceiver> + </config> + <state> + <transceiver>transceiver-8</transceiver> + </state> + </ingress> + <otn> + <config> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + </config> + <state> + <tti-msg-expected>test1</tti-msg-expected> + <tti-msg-transmit>test1</tti-msg-transmit> + <tti-msg-auto>0</tti-msg-auto> + <tti-msg-recv>0</tti-msg-recv> + <rdi-msg>0</rdi-msg> + <errored-seconds>0</errored-seconds> + <severely-errored-seconds>0</severely-errored-seconds> + <unavailable-seconds>0</unavailable-seconds> + <code-violations>0</code-violations> + <fec-uncorrectable-words>0</fec-uncorrectable-words> + <fec-corrected-bytes>0</fec-corrected-bytes> + <fec-corrected-bits>0</fec-corrected-bits> + <background-block-errors>0</background-block-errors> + <pre-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </pre-fec-ber> + <post-fec-ber> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + </post-fec-ber> + <q-value> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </q-value> + <esnr> + <instant>0.0</instant> + <avg>0.0</avg> + <min>0.0</min> + <max>0.0</max> + <interval>0</interval> + </esnr> + </state> + </otn> + <logical-channel-assignments> + <assignment> + <index>1</index> + <config> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-8</optical-channel> + </config> + <state> + <index>1</index> + <description>Optical channel assigned 100</description> + <allocation>100</allocation> + <assignment-type>OPTICAL_CHANNEL</assignment-type> + <optical-channel>channel-8</optical-channel> + </state> + </assignment> + </logical-channel-assignments> + </channel> + </logical-channels> + <operational-modes> + <mode> + <mode-id>1</mode-id> + <state> + <mode-id>1</mode-id> + <description>FEC1</description> + <vendor-id>Ericsson</vendor-id> + </state> + </mode> + <mode> + <mode-id>2</mode-id> + <state> + <mode-id>2</mode-id> + <description>FEC2</description> + <vendor-id>Ericsson</vendor-id> + </state> + </mode> + </operational-modes> + </terminal-device> +</config> + diff --git a/src/tests/ofc24/transponders.sh b/src/tests/ofc24/transponders.sh new file mode 100644 index 0000000000000000000000000000000000000000..17fe4a5e7ebcbc653e1f43291addbc15670c03f6 --- /dev/null +++ b/src/tests/ofc24/transponders.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +docker stop -t 1 tna1 +docker stop -t 1 tna2 + +docker rm tna1 +docker rm tna2 + + + +screen -dmS tt1 -T xterm sh -c "docker run --name tna1 -p 10.0.2.4:2023:2022 -v /home/tfs/tfs-ctrl/src/tests/ofc24/tempOC/files:/files -it asgamb1/oc23bgp.img:latest bash -c 'cp /files/platform_t1.xml demoECOC21.xml ; ./startNetconfAgent.sh'" +screen -dmS tt2 -T xterm sh -c "docker run --name tna2 -p 10.0.2.4:2024:2022 -v /home/tfs/tfs-ctrl/src/tests/ofc24/tempOC/files:/files -it asgamb1/oc23bgp.img:latest bash -c 'cp /files/platform_t2.xml demoECOC21.xml ; ./startNetconfAgent.sh'" diff --git a/src/tests/p4-fwd-l1/README.md b/src/tests/p4-fwd-l1/README.md new file mode 100644 index 0000000000000000000000000000000000000000..b7da0fd88c6b54e4210d5f0d6664ae1ce219fafb --- /dev/null +++ b/src/tests/p4-fwd-l1/README.md @@ -0,0 +1,47 @@ +# Tests for P4 functionality of TeraFlowSDN + +This directory contains the necessary scripts and configurations to run tests for a simple port forwarding TFS program atop software-based P4 switches (using Mininet). + +## Prerequisites + +This test requires some custom monitoring services (latency monitoring and probe). +These services are implemented in Rust and can be found in the `./probe` directory. +To build these services on your target platform, follow the instructions in `./probe/probe-tfs/README.md`. + +## Basic scripts + +To run the experiments you should use the five scripts in the following order: + +```shell +setup.sh +run_test_01_bootstrap.sh +run_test_02_create_service.sh +run_test_03_delete_service.sh +run_test_04_cleanup.sh +``` + +The `setup` script copies the necessary artefacts to the SBI service pod. It should be run just once, after a fresh install of TFS. +The `bootstrap` script registers the context, topology, links, and devices to TFS. +The `create` service script establishes a service between two endpoints. +The `delete` service script deletes the aforementioned service. +The `cleanup` script deletes all the objects (context, topology, links, and devices) from TFS. + +## Objects file + +The above bash scripts make use of the corresponding python scripts found under `./tests/` directory. +More important is the `./tests/Objects.py` file, which contains the definition of the Context, Topology, Devices, Links, Services. **This is the file that needs changes in case of a new topology.** + +Check the `./tests/Objects.py` file before running the experiment to make sure that the switches' details are correct (ip address, port, etc.) + +## Mininet topologies + +In the `./mininet/` directory there are different mininet topology examples. The current `./tests/Objects.py` file corresponds to the `./mininet/8switch3path.py` topology. Additionally there is a backup file `./tests/topologies/6switchObjects.py` which corresponds to the `./mininet/6switch2path.py`. + +## P4 artefacts + +In the `./p4/` directory there are compiled p4 artefacts of the pipeline that will be pushed to the p4 switch, along with the p4-runtime definitions. +The `./setup.sh` script copies from this directory. So if you need to change p4 program, make sure to put the compiled artefacts there. + +## Latency probe + +In the `./probe/` directory there is a little program which calculates latency between two hosts in mininet and sends these measurements to the Monitoring component. For specific instructions, refer to the corresponding `./probe/README.md` file. diff --git a/src/tests/p4-fwd-l1/__init__.py b/src/tests/p4-fwd-l1/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..3ee6f7071f145e06c3aeaefc09a43ccd88e619e3 --- /dev/null +++ b/src/tests/p4-fwd-l1/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/src/tests/p4/deploy_specs.sh b/src/tests/p4-fwd-l1/deploy_specs.sh similarity index 100% rename from src/tests/p4/deploy_specs.sh rename to src/tests/p4-fwd-l1/deploy_specs.sh diff --git a/src/tests/p4/mininet/1switch1path.py b/src/tests/p4-fwd-l1/mininet/1switch1path.py similarity index 100% rename from src/tests/p4/mininet/1switch1path.py rename to src/tests/p4-fwd-l1/mininet/1switch1path.py diff --git a/src/tests/p4/mininet/2switch1path.py b/src/tests/p4-fwd-l1/mininet/2switch1path.py similarity index 100% rename from src/tests/p4/mininet/2switch1path.py rename to src/tests/p4-fwd-l1/mininet/2switch1path.py diff --git a/src/tests/p4/mininet/4switch2path.py b/src/tests/p4-fwd-l1/mininet/4switch2path.py similarity index 100% rename from src/tests/p4/mininet/4switch2path.py rename to src/tests/p4-fwd-l1/mininet/4switch2path.py diff --git a/src/tests/p4/mininet/6switch2path.py b/src/tests/p4-fwd-l1/mininet/6switch2path.py similarity index 100% rename from src/tests/p4/mininet/6switch2path.py rename to src/tests/p4-fwd-l1/mininet/6switch2path.py diff --git a/src/tests/p4/mininet/8switch3path.py b/src/tests/p4-fwd-l1/mininet/8switch3path.py similarity index 100% rename from src/tests/p4/mininet/8switch3path.py rename to src/tests/p4-fwd-l1/mininet/8switch3path.py diff --git a/src/tests/p4/p4/bmv2.json b/src/tests/p4-fwd-l1/p4/bmv2.json similarity index 100% rename from src/tests/p4/p4/bmv2.json rename to src/tests/p4-fwd-l1/p4/bmv2.json diff --git a/src/tests/p4/p4/main.p4 b/src/tests/p4-fwd-l1/p4/main.p4 similarity index 100% rename from src/tests/p4/p4/main.p4 rename to src/tests/p4-fwd-l1/p4/main.p4 diff --git a/src/tests/p4/p4/p4info.txt b/src/tests/p4-fwd-l1/p4/p4info.txt similarity index 100% rename from src/tests/p4/p4/p4info.txt rename to src/tests/p4-fwd-l1/p4/p4info.txt diff --git a/src/tests/p4/probe/README.md b/src/tests/p4-fwd-l1/probe/README.md similarity index 100% rename from src/tests/p4/probe/README.md rename to src/tests/p4-fwd-l1/probe/README.md diff --git a/src/tests/p4/probe/monitoring_kpis.ipynb b/src/tests/p4-fwd-l1/probe/monitoring_kpis.ipynb similarity index 100% rename from src/tests/p4/probe/monitoring_kpis.ipynb rename to src/tests/p4-fwd-l1/probe/monitoring_kpis.ipynb diff --git a/src/tests/p4/probe/monitoring_kpis.py b/src/tests/p4-fwd-l1/probe/monitoring_kpis.py similarity index 100% rename from src/tests/p4/probe/monitoring_kpis.py rename to src/tests/p4-fwd-l1/probe/monitoring_kpis.py diff --git a/src/tests/p4/probe/probe-tfs/.gitignore b/src/tests/p4-fwd-l1/probe/probe-tfs/.gitignore similarity index 100% rename from src/tests/p4/probe/probe-tfs/.gitignore rename to src/tests/p4-fwd-l1/probe/probe-tfs/.gitignore diff --git a/src/tests/p4/probe/probe-tfs/Cargo.toml b/src/tests/p4-fwd-l1/probe/probe-tfs/Cargo.toml similarity index 100% rename from src/tests/p4/probe/probe-tfs/Cargo.toml rename to src/tests/p4-fwd-l1/probe/probe-tfs/Cargo.toml diff --git a/src/tests/p4/probe/probe-tfs/LICENSE b/src/tests/p4-fwd-l1/probe/probe-tfs/LICENSE similarity index 100% rename from src/tests/p4/probe/probe-tfs/LICENSE rename to src/tests/p4-fwd-l1/probe/probe-tfs/LICENSE diff --git a/src/tests/p4-fwd-l1/probe/probe-tfs/README.md b/src/tests/p4-fwd-l1/probe/probe-tfs/README.md new file mode 100644 index 0000000000000000000000000000000000000000..3cff11314ee92db79d7b56c03e38a8a7f2acadec --- /dev/null +++ b/src/tests/p4-fwd-l1/probe/probe-tfs/README.md @@ -0,0 +1,21 @@ +# Rust-tfs + +Client for TFS functionalities written in Rust. + +## Dependencies + +This project requires the cargo Rust compiler: + +```sh +sudo apt-get install protobuf-compiler cargo +``` + +## Build + +Build sources as follows: + +```sh +cargo build +``` + +After a successfuly build, two new executables will be generated in `target/`. diff --git a/src/tests/p4/probe/probe-tfs/build.rs b/src/tests/p4-fwd-l1/probe/probe-tfs/build.rs similarity index 100% rename from src/tests/p4/probe/probe-tfs/build.rs rename to src/tests/p4-fwd-l1/probe/probe-tfs/build.rs diff --git a/src/tests/p4/probe/probe-tfs/connect_to_mininet.sh b/src/tests/p4-fwd-l1/probe/probe-tfs/connect_to_mininet.sh similarity index 100% rename from src/tests/p4/probe/probe-tfs/connect_to_mininet.sh rename to src/tests/p4-fwd-l1/probe/probe-tfs/connect_to_mininet.sh diff --git a/src/tests/p4/probe/probe-tfs/deploy.sh b/src/tests/p4-fwd-l1/probe/probe-tfs/deploy.sh similarity index 100% rename from src/tests/p4/probe/probe-tfs/deploy.sh rename to src/tests/p4-fwd-l1/probe/probe-tfs/deploy.sh diff --git a/src/tests/p4/probe/probe-tfs/proto b/src/tests/p4-fwd-l1/probe/probe-tfs/proto similarity index 100% rename from src/tests/p4/probe/probe-tfs/proto rename to src/tests/p4-fwd-l1/probe/probe-tfs/proto diff --git a/src/tests/p4/probe/probe-tfs/src/agent.rs b/src/tests/p4-fwd-l1/probe/probe-tfs/src/agent.rs similarity index 99% rename from src/tests/p4/probe/probe-tfs/src/agent.rs rename to src/tests/p4-fwd-l1/probe/probe-tfs/src/agent.rs index 749e42a6c81ce0523ae2cf52a29e9c90c43f48e7..81bc1a2a0b40947fd8b77f99ef10be8032eb66f7 100644 --- a/src/tests/p4/probe/probe-tfs/src/agent.rs +++ b/src/tests/p4-fwd-l1/probe/probe-tfs/src/agent.rs @@ -195,6 +195,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { endpoint_id: None, slice_id: None, connection_id: None, + link_id: None, kpi_description: format!( "Latency value for service {}", event_service.service_uuid.unwrap().uuid diff --git a/src/tests/p4/probe/probe-tfs/src/ping.rs b/src/tests/p4-fwd-l1/probe/probe-tfs/src/ping.rs similarity index 100% rename from src/tests/p4/probe/probe-tfs/src/ping.rs rename to src/tests/p4-fwd-l1/probe/probe-tfs/src/ping.rs diff --git a/src/tests/p4/run_test_01_bootstrap.sh b/src/tests/p4-fwd-l1/run_test_01_bootstrap.sh similarity index 100% rename from src/tests/p4/run_test_01_bootstrap.sh rename to src/tests/p4-fwd-l1/run_test_01_bootstrap.sh diff --git a/src/tests/p4/run_test_02_create_service.sh b/src/tests/p4-fwd-l1/run_test_02_create_service.sh similarity index 100% rename from src/tests/p4/run_test_02_create_service.sh rename to src/tests/p4-fwd-l1/run_test_02_create_service.sh diff --git a/src/tests/p4/run_test_03_delete_service.sh b/src/tests/p4-fwd-l1/run_test_03_delete_service.sh similarity index 100% rename from src/tests/p4/run_test_03_delete_service.sh rename to src/tests/p4-fwd-l1/run_test_03_delete_service.sh diff --git a/src/tests/p4/run_test_04_cleanup.sh b/src/tests/p4-fwd-l1/run_test_04_cleanup.sh similarity index 100% rename from src/tests/p4/run_test_04_cleanup.sh rename to src/tests/p4-fwd-l1/run_test_04_cleanup.sh diff --git a/src/tests/p4/setup.sh b/src/tests/p4-fwd-l1/setup.sh similarity index 100% rename from src/tests/p4/setup.sh rename to src/tests/p4-fwd-l1/setup.sh diff --git a/src/tests/p4/tests/.gitignore b/src/tests/p4-fwd-l1/tests/.gitignore similarity index 100% rename from src/tests/p4/tests/.gitignore rename to src/tests/p4-fwd-l1/tests/.gitignore diff --git a/src/tests/p4/tests/BuildDescriptors.py b/src/tests/p4-fwd-l1/tests/BuildDescriptors.py similarity index 100% rename from src/tests/p4/tests/BuildDescriptors.py rename to src/tests/p4-fwd-l1/tests/BuildDescriptors.py diff --git a/src/tests/p4/tests/LoadDescriptors.py b/src/tests/p4-fwd-l1/tests/LoadDescriptors.py similarity index 100% rename from src/tests/p4/tests/LoadDescriptors.py rename to src/tests/p4-fwd-l1/tests/LoadDescriptors.py diff --git a/src/tests/p4/tests/Objects.py b/src/tests/p4-fwd-l1/tests/Objects.py similarity index 100% rename from src/tests/p4/tests/Objects.py rename to src/tests/p4-fwd-l1/tests/Objects.py diff --git a/src/tests/p4-fwd-l1/tests/__init__.py b/src/tests/p4-fwd-l1/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..3ee6f7071f145e06c3aeaefc09a43ccd88e619e3 --- /dev/null +++ b/src/tests/p4-fwd-l1/tests/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/src/tests/p4/tests/test_functional_bootstrap.py b/src/tests/p4-fwd-l1/tests/test_functional_bootstrap.py similarity index 100% rename from src/tests/p4/tests/test_functional_bootstrap.py rename to src/tests/p4-fwd-l1/tests/test_functional_bootstrap.py diff --git a/src/tests/p4/tests/test_functional_cleanup.py b/src/tests/p4-fwd-l1/tests/test_functional_cleanup.py similarity index 100% rename from src/tests/p4/tests/test_functional_cleanup.py rename to src/tests/p4-fwd-l1/tests/test_functional_cleanup.py diff --git a/src/tests/p4/tests/test_functional_create_service.py b/src/tests/p4-fwd-l1/tests/test_functional_create_service.py similarity index 100% rename from src/tests/p4/tests/test_functional_create_service.py rename to src/tests/p4-fwd-l1/tests/test_functional_create_service.py diff --git a/src/tests/p4/tests/test_functional_delete_service.py b/src/tests/p4-fwd-l1/tests/test_functional_delete_service.py similarity index 100% rename from src/tests/p4/tests/test_functional_delete_service.py rename to src/tests/p4-fwd-l1/tests/test_functional_delete_service.py diff --git a/src/tests/p4/tests/topologies/6switchObjects.py b/src/tests/p4-fwd-l1/tests/topologies/6switchObjects.py similarity index 100% rename from src/tests/p4/tests/topologies/6switchObjects.py rename to src/tests/p4-fwd-l1/tests/topologies/6switchObjects.py diff --git a/src/tests/p4/README.md b/src/tests/p4/README.md deleted file mode 100644 index 43920d14d5e99214bb2ad8418cc4babcae5be91c..0000000000000000000000000000000000000000 --- a/src/tests/p4/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# Tests for P4 functionality of TeraFlowSDN - -This directory contains the necessary scripts and configurations to run tests for the P4 functionality of TFS. - -## Basic scripts - -To run the experiments you should use the five scripts in the following order: -``` -setup.sh -run_test_01_bootstrap.sh -run_test_02_create_service.sh -run_test_03_delete_service.sh -run_test_04_cleanup.sh -``` - -The setup script copies the necessary artifacts to the SBI service pod. It should be run just once, after a fresh install of TFS. -The bootstrap script registers the context, topology, links and, devices to TFS. -The create service scripts establishes a service between two endpoints. -The delete service script delete the aforementioned service. -Cleanup script deletes all the objects (context, topology, links, devices) from TFS. - -## Objects file - -The above bash scripts make use of the corresponding python scripts found under `./tests/` directory. -More important is the `./tests/Objects.py` file, which contains the definition of the Context, Topology, Devices, Links, Services. **This is the file that need changes in case of a new topology.** - -Check the `./tests/Objects.py` file before running the experiment to make sure that the switches details are correct (ip address, port, etc.) - -## Mininet topologies - -In the `./mininet/` directory there are different mininet topology examples. The current `./tests/Objects.py` file corresponds to the `./mininet/8switch3path.py` topology. Additionally there is a backup file `./tests/topologies/6switchObjects.py` which corresponds to the `./mininet/6switch2path.py`. - -## P4 artifacts - -In the `./p4/` directory there are the compiled p4 artifacts that contain the pipeline that will be pushed to the p4 switch, along with the p4-runtime definitions. -The `./setup.sh` script copies from this directory. So if you need to change p4 program, make sure to put the compiled artifacts here. - -## Latency probe - -In the `./probe/` directory there is a little program which calculates latency between two hosts in mininet and sends them to the Monitoring component. For specific instructions, refer to the corresponding `./probe/README.md` file. - diff --git a/src/tests/p4/probe/probe-tfs/README.md b/src/tests/p4/probe/probe-tfs/README.md deleted file mode 100644 index f88d7c542dae22ad623797f43750e0589d2473cf..0000000000000000000000000000000000000000 --- a/src/tests/p4/probe/probe-tfs/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# rust-tfs -Client for TFS functionalities written in Rust. diff --git a/src/tests/p4/probe/probe-tfs/target/x86_64-unknown-linux-musl/release/tfsagent b/src/tests/p4/probe/probe-tfs/target/x86_64-unknown-linux-musl/release/tfsagent deleted file mode 100755 index b7cef11a433c6bf2eeb94638fa90d93f25acd3c8..0000000000000000000000000000000000000000 Binary files a/src/tests/p4/probe/probe-tfs/target/x86_64-unknown-linux-musl/release/tfsagent and /dev/null differ diff --git a/src/tests/p4/probe/probe-tfs/target/x86_64-unknown-linux-musl/release/tfsping b/src/tests/p4/probe/probe-tfs/target/x86_64-unknown-linux-musl/release/tfsping deleted file mode 100755 index 6e943d292dd6653e857bf5eea3258d38ad246026..0000000000000000000000000000000000000000 Binary files a/src/tests/p4/probe/probe-tfs/target/x86_64-unknown-linux-musl/release/tfsping and /dev/null differ diff --git a/src/webui/service/__init__.py b/src/webui/service/__init__.py index f137c247e1c4d2f4be5707e72e4140ab75f8e886..4c39e3c033717d1e6e48120a5ebf27fd327ecac9 100644 --- a/src/webui/service/__init__.py +++ b/src/webui/service/__init__.py @@ -21,7 +21,8 @@ from context.client.ContextClient import ContextClient from device.client.DeviceClient import DeviceClient from qkd_app.client.QKDAppClient import QKDAppClient from common.Settings import ( - is_deployed_bgpls, is_deployed_load_gen, is_deployed_policy, is_deployed_qkd_app, is_deployed_slice + is_deployed_bgpls, is_deployed_load_gen, is_deployed_optical, + is_deployed_policy, is_deployed_qkd_app, is_deployed_slice ) def get_working_context() -> str: @@ -86,34 +87,43 @@ def create_app(use_config=None, web_app_root=None): app.register_blueprint(healthz, url_prefix='/healthz') - from webui.service.js.routes import js # pylint: disable=import-outside-toplevel + from webui.service.js.routes import js # pylint: disable=import-outside-toplevel app.register_blueprint(js) - from webui.service.main.routes import main # pylint: disable=import-outside-toplevel + from webui.service.main.routes import main # pylint: disable=import-outside-toplevel app.register_blueprint(main) - from webui.service.load_gen.routes import load_gen # pylint: disable=import-outside-toplevel + from webui.service.load_gen.routes import load_gen # pylint: disable=import-outside-toplevel app.register_blueprint(load_gen) - from webui.service.service.routes import service # pylint: disable=import-outside-toplevel + from webui.service.base_optical.route import base_optical # pylint: disable=import-outside-toplevel + app.register_blueprint(base_optical) + + from webui.service.opticalconfig.routes import opticalconfig # pylint: disable=import-outside-toplevel + app.register_blueprint(opticalconfig) + + from webui.service.optical_link.routes import optical_link # pylint: disable=import-outside-toplevel + app.register_blueprint(optical_link) + + from webui.service.service.routes import service # pylint: disable=import-outside-toplevel app.register_blueprint(service) - from webui.service.slice.routes import slice # pylint: disable=import-outside-toplevel,redefined-builtin + from webui.service.slice.routes import slice # pylint: disable=import-outside-toplevel,redefined-builtin app.register_blueprint(slice) - from webui.service.device.routes import device # pylint: disable=import-outside-toplevel + from webui.service.device.routes import device # pylint: disable=import-outside-toplevel app.register_blueprint(device) - from webui.service.bgpls.routes import bgpls # pylint: disable=import-outside-toplevel + from webui.service.bgpls.routes import bgpls # pylint: disable=import-outside-toplevel app.register_blueprint(bgpls) - from webui.service.link.routes import link # pylint: disable=import-outside-toplevel + from webui.service.link.routes import link # pylint: disable=import-outside-toplevel app.register_blueprint(link) - from webui.service.qkd_app.routes import qkd_app # pylint: disable=import-outside-toplevel + from webui.service.qkd_app.routes import qkd_app # pylint: disable=import-outside-toplevel app.register_blueprint(qkd_app) - from webui.service.policy_rule.routes import policy_rule # pylint: disable=import-outside-toplevel + from webui.service.policy_rule.routes import policy_rule # pylint: disable=import-outside-toplevel app.register_blueprint(policy_rule) app.jinja_env.globals.update({ # pylint: disable=no-member @@ -126,6 +136,7 @@ def create_app(use_config=None, web_app_root=None): 'is_deployed_bgpls' : is_deployed_bgpls, 'is_deployed_load_gen': is_deployed_load_gen, + 'is_deployed_optical' : is_deployed_optical, 'is_deployed_policy' : is_deployed_policy, 'is_deployed_qkd_app' : is_deployed_qkd_app, 'is_deployed_slice' : is_deployed_slice, diff --git a/src/webui/service/base_optical/__init__.py b/src/webui/service/base_optical/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..3ee6f7071f145e06c3aeaefc09a43ccd88e619e3 --- /dev/null +++ b/src/webui/service/base_optical/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/src/webui/service/base_optical/route.py b/src/webui/service/base_optical/route.py new file mode 100644 index 0000000000000000000000000000000000000000..84fb8a97572e9e6f9f2056cae3ad9087a7390720 --- /dev/null +++ b/src/webui/service/base_optical/route.py @@ -0,0 +1,33 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from flask import render_template, Blueprint +#from common.proto.context_pb2 import Empty, OpticalConfigList +#from context.client.ContextClient import ContextClient +#from device.client.DeviceClient import DeviceClient + +base_optical = Blueprint('base_optical', __name__, url_prefix='/base_optical') +#device_client = DeviceClient() +#context_client = ContextClient() + +@base_optical.get('/') +def home(): + # context_client.connect() + # opticalConfig_list:OpticalConfigList = context_client.GetOpticalConfig(Empty()) + # context_client.close() + # device_client.connect() + # device_client.GetDeviceConfiguration(opticalConfig_list) + # device_client.close() + return render_template("base_optical/home.html") diff --git a/src/webui/service/main/routes.py b/src/webui/service/main/routes.py index 52944a31c439472055d65e9e75249465dcbca7f7..97c97331d415b8c2d059f5bb65236ba6563d48c3 100644 --- a/src/webui/service/main/routes.py +++ b/src/webui/service/main/routes.py @@ -153,11 +153,24 @@ def topology(): 'source': link.link_endpoint_ids[0].device_id.device_uuid.uuid, 'target': link.link_endpoint_ids[1].device_id.device_uuid.uuid, }) - - return jsonify({'devices': devices, 'links': links}) + + optical_links = [] + for link in response.optical_links: + if len(link.link_endpoint_ids) != 2: + str_link = grpc_message_to_json_string(link) + LOGGER.warning('Unexpected link with len(endpoints) != 2: {:s}'.format(str_link)) + continue + optical_links.append({ + 'id': link.link_id.link_uuid.uuid, + 'name': link.name, + 'source': link.link_endpoint_ids[0].device_id.device_uuid.uuid, + 'target': link.link_endpoint_ids[1].device_id.device_uuid.uuid, + }) + + return jsonify({'devices': devices, 'links': links, 'optical_links': optical_links}) except: # pylint: disable=bare-except LOGGER.exception('Error retrieving topology') - return jsonify({'devices': [], 'links': []}) + return jsonify({'devices': [], 'links': [], 'optical_links': []}) finally: context_client.close() diff --git a/src/webui/service/optical_link/__init__.py b/src/webui/service/optical_link/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..7c7568fdb6e3b1446aa9412ad32a0a5948ba949b --- /dev/null +++ b/src/webui/service/optical_link/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/src/webui/service/optical_link/routes.py b/src/webui/service/optical_link/routes.py new file mode 100644 index 0000000000000000000000000000000000000000..bc4ed8c6a14d31512398341984029f07fc2bcc6c --- /dev/null +++ b/src/webui/service/optical_link/routes.py @@ -0,0 +1,108 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from flask import current_app, render_template, Blueprint, flash, session, redirect, url_for +from common.proto.context_pb2 import Empty, OpticalLink, LinkId, OpticalLinkList +from common.tools.context_queries.EndPoint import get_endpoint_names +from common.tools.context_queries.Topology import get_topology +from context.client.ContextClient import ContextClient + +optical_link = Blueprint('optical_link', __name__, url_prefix='/optical_link') +context_client = ContextClient() + +@optical_link.get('/') +def home(): + if 'context_uuid' not in session or 'topology_uuid' not in session: + flash("Please select a context!", "warning") + return redirect(url_for("main.home")) + + context_uuid = session['context_uuid'] + topology_uuid = session['topology_uuid'] + + links, endpoint_ids = list(), list() + device_names, endpoints_data = dict(), dict() + + context_client.connect() + grpc_topology = get_topology(context_client, topology_uuid, context_uuid=context_uuid, rw_copy=False) + if grpc_topology is None: + flash('Context({:s})/Topology({:s}) not found'.format(str(context_uuid), str(topology_uuid)), 'danger') + else: + grpc_links : OpticalLinkList = context_client.GetOpticalLinkList(Empty()) + for link_ in grpc_links.optical_links: + links.append(link_) + endpoint_ids.extend(link_.link_endpoint_ids) + device_names, endpoints_data = get_endpoint_names(context_client, endpoint_ids) + context_client.close() + + return render_template( + 'optical_link/home.html', links=links, device_names=device_names, + endpoints_data=endpoints_data + ) + + +@optical_link.route('detail/<path:link_uuid>', methods=('GET', 'POST')) +def detail(link_uuid: str): + context_client.connect() + # pylint: disable=no-member + link_id = LinkId() + link_id.link_uuid.uuid = link_uuid + link_obj = context_client.GetOpticalLink(link_id) + c_slots=s_slots=l_slots=None + if link_obj is None: + flash('Optical Link({:s}) not found'.format(str(link_uuid)), 'danger') + link_obj = OpticalLink() + device_names, endpoints_data = dict(), dict() + else: + device_names, endpoints_data = get_endpoint_names(context_client, link_obj.link_endpoint_ids) + c_slots = link_obj.optical_details.c_slots + l_slots = link_obj.optical_details.l_slots + s_slots = link_obj.optical_details.s_slots + + context_client.close() + + return render_template( + 'optical_link/detail.html', link=link_obj, device_names=device_names, + endpoints_data=endpoints_data, c_slots=c_slots, l_slots=l_slots, s_slots=s_slots + ) + + +@optical_link.get('<path:link_uuid>/delete') +def delete(link_uuid): + try: + request = LinkId() + request.link_uuid.uuid = link_uuid # pylint: disable=no-member + context_client.connect() + context_client.DeleteOpticalLink(request) + context_client.close() + + flash(f'Optical Link "{link_uuid}" deleted successfully!', 'success') + except Exception as e: # pylint: disable=broad-except + flash(f'Problem deleting link "{link_uuid}": {e.details()}', 'danger') + current_app.logger.exception(e) + return redirect(url_for('optical_link.home')) + + +@optical_link.get("delete_all") +def delete_all(): + try: + context_client.connect() + optical_link_list : OpticalLinkList = context_client.GetOpticalLinkList(Empty()) + for optical_link in optical_link_list.optical_links: + context_client.DeleteOpticalLink(optical_link.link_id) + context_client.close() + flash(f"All Optical Link Deleted Successfully",'success') + except Exception as e: + flash(f"Problem in delete all optical link => {e}",'danger') + return redirect(url_for('optical_link.home')) diff --git a/src/webui/service/opticalconfig/__init__.py b/src/webui/service/opticalconfig/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..3ee6f7071f145e06c3aeaefc09a43ccd88e619e3 --- /dev/null +++ b/src/webui/service/opticalconfig/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/src/webui/service/opticalconfig/forms.py b/src/webui/service/opticalconfig/forms.py new file mode 100644 index 0000000000000000000000000000000000000000..e9cb283eb00e07fa6ad10e78c377d921ac4c506d --- /dev/null +++ b/src/webui/service/opticalconfig/forms.py @@ -0,0 +1,41 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from flask_wtf import FlaskForm +from wtforms import StringField, SelectField, SubmitField + +class UpdateDeviceForm(FlaskForm): + power = StringField('Power') + frequency = StringField("Frequency") + operational_mode = StringField("Operational Mode") + line_port = SelectField("Line Port") + submit = SubmitField('Update') + +class AddTrancseiver(FlaskForm): + transceiver = StringField("Transceiver") + submit = SubmitField('Add') + +class UpdateInterfaceForm(FlaskForm): + ip = StringField("IP Address") + prefix_length = StringField("Prefix Length") + +DEVICE_STATUS = [ + ('', 'Select...'), + ('DISABLED', 'DISABLED'), + ('ENABLED', 'ENABLED') +] + +class UpdateStatusForm(FlaskForm): + status = SelectField("Device Status", choices=DEVICE_STATUS) diff --git a/src/webui/service/opticalconfig/routes.py b/src/webui/service/opticalconfig/routes.py new file mode 100644 index 0000000000000000000000000000000000000000..79a487a4e80d96d3f6a6bab1e813202e6679ee66 --- /dev/null +++ b/src/webui/service/opticalconfig/routes.py @@ -0,0 +1,329 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json, logging +from flask import ( + request, redirect, render_template, Blueprint, flash, session, url_for, + current_app, make_response +) +from common.proto.context_pb2 import ( + Empty, OpticalConfig, OpticalConfigId, OpticalConfigList +) +from context.client.ContextClient import ContextClient +from device.client.DeviceClient import DeviceClient +from service.client.ServiceClient import ServiceClient +from slice.client.SliceClient import SliceClient +from .forms import UpdateDeviceForm, AddTrancseiver, UpdateStatusForm +from common.tools.context_queries.OpticalConfig import opticalconfig_get_uuid + + +opticalconfig = Blueprint('opticalconfig', __name__,url_prefix="/opticalconfig") + +context_client = ContextClient() +device_client = DeviceClient() +service_client = ServiceClient() +slice_client = SliceClient() + +LOGGER = logging.getLogger(__name__) + +DESCRIPTOR_LOADER_NUM_WORKERS = 10 + +@opticalconfig.get("/") +def home() : + list_config = [] + channels_num = 0 + if 'context_uuid' not in session or 'topology_uuid' not in session: + flash("Please select a context!", "warning") + return redirect(url_for("main.home")) + context_uuid = session['context_uuid'] + topology_uuid = session['topology_uuid'] + + context_client.connect() + opticalConfig_list : OpticalConfigList = context_client.GetOpticalConfig(Empty()) + for configs in opticalConfig_list.opticalconfigs: + value = json.loads(configs.config) if type(configs.config)==str else configs.config + config_type = value["type"] + if 'channels' in value: + channels_num = len(value['channels']) + value["channels_number"] = channels_num + # value['operationalMode']=value['operational-mode'] + # value['targetOutputPower']=value['target-output-power'] + value['opticalconfig_id']=configs.opticalconfig_id + # value['line_port']=value["line-port"] + list_config.append(value) + + context_client.close() + return render_template('opticalconfig/home.html', config=list_config) + +@opticalconfig.route('<path:config_uuid>/detail',methods=['GET']) +def show_details(config_uuid): + opticalconfigId = OpticalConfigId() + opticalconfigId.opticalconfig_uuid = config_uuid + device_details = [] + + context_client.connect() + response = context_client.SelectOpticalConfig(opticalconfigId) + context_client.close() + if (response and response.opticalconfig_id.opticalconfig_uuid !=''): + opticalConfig = OpticalConfig() + opticalConfig.CopyFrom(response) + + device_name = "" + config = json.loads(opticalConfig.config) + if "device_name" in config: + device_name = config["device_name"] + + config_type = config["type"] + if config_type == 'optical-transponder': + if 'channels' in config: + for channel in config['channels'] : + new_config = { + "name" : channel['name'], + 'operationalMode' : channel['operational-mode'] if 'operational-mode' in channel else '', + 'targetOutputPower': channel['target-output-power'] if 'target-output-power' in channel else '', + "frequency" : channel['frequency'] if 'frequency' in channel else '', + 'line_port' : channel["line-port"] if 'line-port' in channel else '', + "status" : channel['status'] if 'status' in channel else "", + } + device_details.append(new_config) + + if config_type == 'optical-roadm': + if 'channels' in config: + for channel in config['channels'] : + new_config = { + "band_name" : channel['band_name'] if 'band_name' in channel else None, + 'type' : channel['type'] if 'type' in channel else '', + 'src_port' : channel['src_port'] if 'src_port' in channel else '', + 'dest_port' : channel['dest_port'] if 'dest_port' in channel else '', + "lower_frequency" : channel['lower_frequency'] if 'lower_frequency' in channel else '', + "upper_frequency" : channel['upper_frequency'] if 'upper_frequency' in channel else '', + "status" : channel['status'] if 'status' in channel else "", + 'optical_band_parent' : channel['optical_band_parent'] if 'optical_band_parent' in channel else '', + 'channel_index' : channel['channel_index'] if 'channel_index' in channel else '', + } + device_details.append(new_config) + + return render_template( + 'opticalconfig/details.html', device=device_details, config_id=config_uuid, + device_name=device_name, type=config_type + ) + + +@opticalconfig.route('<path:opticalconfig_uuid>/delete', methods=['GET']) +def delete_opitcalconfig (opticalconfig_uuid) : + try : + opticalconfigId = OpticalConfigId() + opticalconfigId.opticalconfig_uuid = opticalconfig_uuid + context_client.connect() + context_client.DeleteOpticalConfig(opticalconfigId) + context_client.close() + flash(f'OpticalConfig "{opticalconfig_uuid}" deleted successfully!', 'success') + except Exception as e: # pylint: disable=broad-except + flash(f'Problem deleting optical config {opticalconfig_uuid}', 'danger') + current_app.logger.exception(e) + return redirect(url_for('opticalconfig.home')) + + +@opticalconfig.route('/update_opticalconfig', methods=['POST']) +def update_externally(): + if (request.method == 'POST'): + data = request.get_json() + devices = data.get('devices') + + myResponse = [] + status_code = '' + for device in devices : + port = device.get("port") + channel_name = f"channel-{port}" + device_name = device.get("device_name") + + if device_name: + opticalconfig_uuid = opticalconfig_get_uuid(device_name=device_name) + opticalconfigId=OpticalConfigId() + opticalconfigId.opticalconfig_uuid = opticalconfig_uuid + context_client.connect() + opticalconfig = context_client.SelectOpticalConfig(opticalconfigId) + context_client.close() + + if opticalconfig and opticalconfig.opticalconfig_id.opticalconfig_uuid != '' : + new_opticalconfig = OpticalConfig() + new_opticalconfig.CopyFrom(opticalconfig) + config = json.loads(opticalconfig.config) + channels = config['channels'] + target_channel = next((item for item in channels if item["name"]['index'] == channel_name) , None) + target_power = device.get( "target-output-power") + freq = device.get("frequency") + mode = device.get("operational-mode") + status = device.get("status","ENABLED") + + if target_channel: + if target_power is not None : + target_channel["target-output-power"] =str(target_power) + if freq is not None : + target_channel["frequency"] = freq + if mode is not None : + target_channel["operational-mode"] = mode + if status is not None : + target_channel["status"] = "ENABLED" + #del target_channel['name'] + config["new_config"]=target_channel + config["new_config"]["channel_name"]=channel_name + config["flow"]=[(port,'0')] + opticalconfig.config =json.dumps(config) + + try: + device_client.connect() + device_client.ConfigureOpticalDevice(opticalconfig) + device_client.close() + + myResponse.append(f"device {device_name} port {port} is updated successfully") + status_code = 200 + except Exception as e: # pylint: disable=broad-except + myResponse.append(f"Problem updating the device. {e}") + status_code = 500 + break + else : + myResponse.append(f"requested channel {channel_name} is not existed") + status_code = 400 + break + else : + myResponse.append(f"requested device {device_name} is not existed") + status_code = 400 + break + + response=make_response(f'{myResponse}') + response.status_code=status_code + return response + + #return redirect(url_for('opticalconfig.show_details',config_uuid=opticalconfig_uuid)) + #return redirect(url_for('opticalconfig.home')) + +@opticalconfig.route('<path:config_uuid>/<path:channel_name>/update', methods=['GET', 'POST']) +def update(config_uuid, channel_name): + form = UpdateDeviceForm() + + opticalconfigId = OpticalConfigId() + opticalconfigId.opticalconfig_uuid = config_uuid + context_client.connect() + response = context_client.SelectOpticalConfig(opticalconfigId) + context_client.close() + + opticalconfig = OpticalConfig() + opticalconfig.CopyFrom(response) + config =json.loads(opticalconfig.config) + new_config={} + for channel in config['channels']: + if (channel["name"] == channel_name): + new_config=channel + form.frequency.default = channel["frequency"] + form.operational_mode.default=channel["operational-mode"] + form.power.default=channel["target-output-power"] + form.line_port.choices = [("","")] + + for transceiver in config["transceivers"]['transceiver']: + form.line_port.choices.append((transceiver,transceiver)) + + # listing enum values + if form.validate_on_submit(): + new_config["target-output-power"] =form.power.data if form.power.data != '' else new_config['target-output-power'] + new_config["frequency"]=form.frequency.data if form.frequency.data != '' else new_config['frequency'] + new_config["operational-mode"]=form.operational_mode.data if form.operational_mode.data != '' else new_config['operational-mode'] + new_config["line-port"]=form.line_port.data if form.line_port.data != '' else new_config['line-port'] + opticalconfig.config =json.dumps(new_config) + + try: + device_client.connect() + device_client.ConfigureOpticalDevice(opticalconfig) + device_client.close() + flash(f' device was updated.', 'success') + return redirect(url_for('opticalconfig.show_details',config_uuid=config_uuid)) + except Exception as e: # pylint: disable=broad-except + flash(f'Problem updating the device. {e}', 'danger') + return render_template('device/update.html', device=response, form=form, submit_text='Update Device',channel_name=channel_name) + + +@opticalconfig.route('refresh_all',methods=['POST','GET']) +def refresh_all (): + context_client.connect() + opticalConfig_list:OpticalConfigList = context_client.GetOpticalConfig(Empty()) + context_client.close() + device_client.connect() + device_client.GetDeviceConfiguration(opticalConfig_list) + device_client.close() + return home() + + +@opticalconfig.route('<path:config_uuid>/add_transceiver', methods=['GET','POST']) +def add_transceiver (config_uuid): + config={} + addtrancseiver=AddTrancseiver() + opticalconfigId=OpticalConfig() + opticalconfigId.opticalconfig_uuid=config_uuid + context_client.connect() + response = context_client.SelectOpticalConfig(opticalconfigId) + context_client.close() + opticlConfig=OpticalConfig() + opticlConfig.CopyFrom(response) + + if addtrancseiver.validate_on_submit(): + config["add_transceiver"]=addtrancseiver.transceiver.data + opticlConfig.config=json.dumps(config) + + try: + device_client.connect() + device_client.ConfigureOpticalDevice(opticlConfig) + device_client.close() + flash(f' device was updated.', 'success') + return redirect(url_for('opticalconfig.update',config_uuid=config_uuid)) + except Exception as e: # pylint: disable=broad-except + flash(f'Problem updating the device. {e}', 'danger') + return render_template('opticalconfig/add_transceiver.html',form=addtrancseiver, submit_text='Add Trancseiver') + + +@opticalconfig.route('<path:config_uuid>/<path:channel_name>/update_status', methods=['GET','POST']) +def update_status (config_uuid,channel_name): + config = {} + form = UpdateStatusForm() + + opticalconfigId=OpticalConfigId() + opticalconfigId.opticalconfig_uuid=config_uuid + context_client.connect() + response = context_client.SelectOpticalConfig(opticalconfigId) + context_client.close() + opticlConfig=OpticalConfig() + opticlConfig.CopyFrom(response) + config=json.loads(opticlConfig.config) + new_config={} + port="" + if form.validate_on_submit(): + if channel_name: + port = channel_name.split('-')[1] + new_config["status"]=form.status.data + new_config["name"]=channel_name + config["flow"]=[(port,'0')] + config["new_config"]=new_config + opticlConfig.config=json.dumps(config) + + try: + device_client.connect() + device_client.ConfigureOpticalDevice(opticlConfig) + device_client.close() + flash(f' device was updated.', 'success') + return redirect(url_for('opticalconfig.show_details',config_uuid=config_uuid)) + except Exception as e: # pylint: disable=broad-except + flash(f'Problem updating the device. {e}', 'danger') + return render_template( + 'opticalconfig/update_status.html', form=form, channel_name=channel_name, + submit_text='Update Device Status' + ) diff --git a/src/webui/service/templates/base.html b/src/webui/service/templates/base.html index 432f1a095be1a682624a45decf2355310e58238b..1402f40c43f3066b0cb09017dfb81f0e4911333f 100644 --- a/src/webui/service/templates/base.html +++ b/src/webui/service/templates/base.html @@ -90,6 +90,16 @@ </li> {% endif %} + {% if is_deployed_optical() %} + <li class="nav-item"> + {% if '/base_optical/' in request.path %} + <a class="nav-link active" aria-current="page" href="{{ url_for('base_optical.home') }}">Optical Config</a> + {% else %} + <a class="nav-link" href="{{ url_for('base_optical.home') }}">Optical Config</a> + {% endif %} + </li> + {% endif %} + {% if is_deployed_policy() %} <li class="nav-item"> {% if '/policy_rule/' in request.path %} diff --git a/src/webui/service/templates/base_optical/home.html b/src/webui/service/templates/base_optical/home.html new file mode 100644 index 0000000000000000000000000000000000000000..6c1e55b140f28e8bfdc60162a77b8756f6cee7a3 --- /dev/null +++ b/src/webui/service/templates/base_optical/home.html @@ -0,0 +1,36 @@ +<!-- + Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + + {% extends 'base.html' %} + + {% block content %} + <h1>Optical Configurations</h1> + + <div class="row"> + <div class="col"> + <a href="{{ url_for('opticalconfig.home') }}" class="btn btn-primary" style="margin-bottom: 10px;"> + + Optical Devices + </a> + </div> + <div class="col"> + <a href="{{ url_for('optical_link.home') }}" class="btn btn-primary" style="margin-bottom: 10px;"> + + Optical Links + </a> + </div> + </div> + {% endblock %} diff --git a/src/webui/service/templates/js/topology.js b/src/webui/service/templates/js/topology.js index caba65fe5359fb64499408886c36c30377cfbbeb..8f5a383c6e90467f8a00df22c8bfb223f3b1e841 100644 --- a/src/webui/service/templates/js/topology.js +++ b/src/webui/service/templates/js/topology.js @@ -51,7 +51,7 @@ const svg = d3.select('#topology') ; // svg objects -var link, node; +var link, node, optical_link; // values for all forces forceProperties = { @@ -85,6 +85,23 @@ d3.json("{{ url_for('main.topology') }}", function(data) { .attr("stroke-dasharray", function(l) { return l.name.toLowerCase().includes('mgmt') ? "5,5" : "0"; }); + + optical_link = svg.append("g").attr("class", "links").style('stroke', '#aaa') + .selectAll("line") + .data(data.optical_links) + .enter() + .append("line") + .attr("opacity", 1) + .attr("stroke", function(l) { + return l.name.toLowerCase().includes('mgmt') ? '#AAAAAA' : '#555555'; + }) + .attr("stroke-width", function(l) { + return l.name.toLowerCase().includes('mgmt') ? 1 : 2; + }) + .attr("stroke-dasharray", function(l) { + return l.name.toLowerCase().includes('mgmt') ? "5,5" : "0"; + }); + node = svg.append("g").attr("class", "devices").attr('r', 20).style('fill', '#69b3a2') .selectAll("circle") .data(data.devices) @@ -101,6 +118,8 @@ d3.json("{{ url_for('main.topology') }}", function(data) { node.append("title").text(function(n) { return n.name; }); // link tooltip link.append("title").text(function(l) { return l.name; }); + // optical link tooltip + optical_link.append("title").text(function(l) { return l.name; }); // link style //link @@ -116,7 +135,14 @@ d3.json("{{ url_for('main.topology') }}", function(data) { .id(function(d) {return d.id;}) .distance(forceProperties.link.distance) .iterations(forceProperties.link.iterations) - .links(forceProperties.link.enabled ? data.links : [])) + .links(forceProperties.link.enabled ? data.links.length>0? data.links :[]:[])) +// ------------------ Experimental : Optical link part + .force("link", d3.forceLink() + .id(function(d) {return d.id;}) + .distance(forceProperties.link.distance) + .iterations(forceProperties.link.iterations) + .links(forceProperties.link.enabled ? data.optical_links.length>0?data.optical_links:[]:[])) + .force("charge", d3.forceManyBody() .strength(forceProperties.charge.strength * forceProperties.charge.enabled) .distanceMin(forceProperties.charge.distanceMin) @@ -147,6 +173,12 @@ function ticked() { .attr('x2', function(d) { return d.target.x; }) .attr('y2', function(d) { return d.target.y; }); + optical_link + .attr('x1', function(d) { return d.source.x; }) + .attr('y1', function(d) { return d.source.y; }) + .attr('x2', function(d) { return d.target.x; }) + .attr('y2', function(d) { return d.target.y; }); + node .attr('x', function(d) { return d.x-icon_width/2; }) .attr('y', function(d) { return d.y-icon_height/2; }); diff --git a/src/webui/service/templates/optical_link/detail.html b/src/webui/service/templates/optical_link/detail.html new file mode 100644 index 0000000000000000000000000000000000000000..d8274e6dfde59b29801bb3d49010285c48eeee25 --- /dev/null +++ b/src/webui/service/templates/optical_link/detail.html @@ -0,0 +1,158 @@ +<!-- + Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +{% extends 'base.html' %} + +{% block content %} +<h1>Link {{ link.name }} ({{ link.link_id.link_uuid.uuid }})</h1> +<div class="row mb-3"> + <div class="col-sm-3"> + <button type="button" class="btn btn-success" onclick="window.location.href='{{ url_for('optical_link.home') }}'"> + <i class="bi bi-box-arrow-in-left"></i> + Back to link list + </button> + </div> + <div class="col-sm-3"> + <!-- <button type="button" class="btn btn-danger"><i class="bi bi-x-square"></i>Delete link</button> --> + <button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#deleteModal"> + <i class="bi bi-x-square"></i> + Delete link + </button> + </div> +</div> + +<br> +<div class="row mb-3"> + <div class="col-sm-4"> + <b>UUID: </b>{{ link.link_id.link_uuid.uuid }}<br> + <b>Name: </b>{{ link.name }}<br> + </div> + <div class="col-sm-8"> + <table class="table table-striped table-hover"> + <thead> + <tr> + <th scope="col">Endpoint UUID</th> + <th scope="col">Name</th> + <th scope="col">Device</th> + <th scope="col">Endpoint Type</th> + </tr> + </thead> + <tbody> + {% for endpoint in link.link_endpoint_ids %} + <tr> + <td> + {{ endpoint.endpoint_uuid.uuid }} + </td> + <td> + {{ endpoints_data.get(endpoint.endpoint_uuid.uuid, (endpoint.endpoint_uuid.uuid, ''))[0] }} + </td> + <td> + <a href="{{ url_for('device.detail', device_uuid=endpoint.device_id.device_uuid.uuid) }}"> + {{ device_names.get(endpoint.device_id.device_uuid.uuid, endpoint.device_id.device_uuid.uuid) }} + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eye" viewBox="0 0 16 16"> + <path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z"/> + <path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z"/> + </svg> + </a> + </td> + <td> + {{ endpoints_data.get(endpoint.endpoint_uuid.uuid, ('', '-'))[1] }} + </td> + </tr> + {% endfor %} + </tbody> + </table> + </div> +</div> + + + +<b>Optical Link Detail:</b> +<table class="table table-striped table-hover"> + <thead> + <tr> + <th scope="col">Key</th> + <th scope="col">Value</th> + </tr> + </thead> + <tbody> + {% for field_descriptor, field_value in link.optical_details.ListFields() %} + {% if field_descriptor.name != "c_slots" and field_descriptor.name != "s_slots" and field_descriptor.name != "l_slots" %} + <tr> + <td> + {{ field_descriptor.name }} + </td> + <td> + {{ field_value }} + </td> + </tr> + {%endif%} + {% endfor %} + {%if c_slots %} + <tr> + <td> + c_slots + </td> + <td> + {{ c_slots }} + </td> + </tr> + {%endif%} + {%if l_slots %} + <tr> + <td> + l_slots + </td> + <td> + {{ l_slots }} + </td> + </tr> + {%endif%} + {%if s_slots %} + <tr> + <td> + s_slots + </td> + <td> + {{ s_slots }} + </td> + </tr> + {%endif%} + </tbody> +</table> +<!-- Modal --> +<div class="modal fade" id="deleteModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" + aria-labelledby="staticBackdropLabel" aria-hidden="true"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title" id="staticBackdropLabel">Delete link?</h5> + <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> + </div> + <div class="modal-body"> + Are you sure you want to delete the link "{{ link.link_id.link_uuid.uuid }}"? + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">No</button> + <a type="button" class="btn btn-danger" + href="{{ url_for('optical_link.delete', link_uuid=link.link_id.link_uuid.uuid) }}"><i + class="bi bi-exclamation-diamond"></i>Yes</a> + </div> + </div> + </div> +</div> + +{% endblock %} diff --git a/src/webui/service/templates/optical_link/home.html b/src/webui/service/templates/optical_link/home.html new file mode 100644 index 0000000000000000000000000000000000000000..2f1b8e4af149bee70524943233800d2eb5a740d5 --- /dev/null +++ b/src/webui/service/templates/optical_link/home.html @@ -0,0 +1,122 @@ +<!-- + Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +{% extends 'base.html' %} + + {% block content %} + <h1>Optical Links</h1> + + <div class="row"> + + <div class="col"> + {{ links | length }} links found in context <i>{{ session['context_uuid'] }}</i> + </div> + <div class="col-sm-3"> + <!-- <button type="button" class="btn btn-danger"><i class="bi bi-x-square"></i>Delete link</button> --> + <button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#deleteModal"> + <i class="bi bi-x-square"></i> + Delete All Optical Links + </button> + </div> + <!-- <div class="col"> + <form> + <div class="input-group"> + <input type="text" aria-label="Search" placeholder="Search..." class="form-control"/> + <button type="submit" class="btn btn-primary">Search</button> + </div> + </form> + </div> --> + </div> + + <table class="table table-striped table-hover"> + <thead> + <tr> + <th scope="col">UUID</th> + <th scope="col">Name</th> + <th scope="col">Endpoints</th> + <th scope="col"></th> + </tr> + </thead> + <tbody> + {% if links %} + {% for link in links %} + <tr> + <td> + {{ link.link_id.link_uuid.uuid }} + </td> + <td> + {{ link.name }} + </td> + + <td> + <ul> + {% for endpoint in link.link_endpoint_ids %} + <li> + {{ endpoints_data.get(endpoint.endpoint_uuid.uuid, (endpoint.endpoint_uuid.uuid, ''))[0] }} / + Device: + <a href="{{ url_for('device.detail', device_uuid=endpoint.device_id.device_uuid.uuid) }}"> + {{ device_names.get(endpoint.device_id.device_uuid.uuid, endpoint.device_id.device_uuid.uuid) }} + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eye" viewBox="0 0 16 16"> + <path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z"/> + <path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z"/> + </svg> + </a> + </li> + {% endfor %} + </ul> + </td> + + <td> + <a href="{{ url_for('optical_link.detail', link_uuid=link.link_id.link_uuid.uuid) }}"> + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eye" viewBox="0 0 16 16"> + <path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z"/> + <path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z"/> + </svg> + </a> + </td> + </tr> + {% endfor %} + {% else %} + <tr> + <td colspan="7">No links found</td> + </tr> + {% endif %} + </tbody> + + </table> + +<div class="modal fade" id="deleteModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" + aria-labelledby="staticBackdropLabel" aria-hidden="true"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title" id="staticBackdropLabel">Delete All Optical links?</h5> + <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> + </div> + <div class="modal-body"> + Are you sure you want to delete all the links ? + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">No</button> + <a type="button" class="btn btn-danger" + href="{{ url_for('optical_link.delete_all') }}"><i + class="bi bi-exclamation-diamond"></i>Yes</a> + </div> + </div> + </div> +</div> + +{% endblock %} diff --git a/src/webui/service/templates/opticalconfig/add_transceiver.html b/src/webui/service/templates/opticalconfig/add_transceiver.html new file mode 100644 index 0000000000000000000000000000000000000000..d10faa7f146fcef18c4f865fe969cf6620e13c15 --- /dev/null +++ b/src/webui/service/templates/opticalconfig/add_transceiver.html @@ -0,0 +1,56 @@ +<!-- + Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +{% extends 'base.html' %} + +{% block content %} +<h1>Add Transceiver </h1> +<br /> +<form id="Add_transceiver" method="POST"> + {{ form.hidden_tag() }} + <fieldset> + <div class="row mb-3 "> + <div class="col-12"> + {{ form.transceiver.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form.transceiver.errors %} + {{ form.transceiver(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form.transceiver.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + + {{ form.transceiver(class="col-sm-7 form-control") }} + + {% endif %} + </div> + + </div> + <button type="submit" class="btn btn-primary"> + <i class="bi bi-plus-circle-fill"></i> + {{ submit_text }} + </button> + <button type="button" class="btn btn-block btn-secondary" onclick="javascript: history.back()"> + <i class="bi bi-box-arrow-in-left"></i> + Cancel + </button> + </div> + </fieldset> +</form> + +{% endblock %} diff --git a/src/webui/service/templates/opticalconfig/details.html b/src/webui/service/templates/opticalconfig/details.html new file mode 100644 index 0000000000000000000000000000000000000000..70b17331097f24733729345d252ce1090e568018 --- /dev/null +++ b/src/webui/service/templates/opticalconfig/details.html @@ -0,0 +1,152 @@ +<!-- + Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + + + +{% extends 'base.html' %} + +{% block content %} + + +<h1>Optical Configurations</h1> + +<div class="row"> + + {% if device %} + <div class="col-sm-12"> + <span>Device ID:</span> + <h5>{{config_id}}</h5> + </div> + <div class="col-sm-12"> + <div class="col-sm-12"> + <div class="row mb-3 "> + + <div class="col-sm-3"> + <button type="button" class="btn btn-success" onclick="window.location.href='{{ url_for('opticalconfig.home') }}'"> + <i class="bi bi-box-arrow-in-left"></i> + Back to device list + </button> + </div> + <div class="col-sm-3"> + <!-- <button type="button" class="btn btn-danger"><i class="bi bi-x-square"></i>Delete device</button> --> + <button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#deleteModal"> + <i class="bi bi-x-square"></i> + Delete Optical Config + </button> + </div> + </div> + </div> + <div class="col-sm-12"> + <span>Device Name:</span> + <span>{{device_name}}</span> + </div> + + </div> + + {% if type == 'optical-transponder' %} + <table class="table table-striped table-hover"> + <thead> + <tr> + <th scope="col">channel name</th> + <th scope="col">Frequency</th> + <th scope="col">Target Output Power</th> + <th scope="col">Operational Mode</th> + <th scope="col">Line Port</th> + <th scope="col">Channel Status</th> + </tr> + </thead> + <tbody> + + {% for channel in device %} + <tr style="background-color:{%if channel.status == 'DISABLED' %} gray {% endif %};"> + <td>{{channel.name.index}}</td> + <td>{{channel.frequency}}</td> + <td> {{channel.targetOutputPower}}</td> + <td>{{channel.operationalMode}}</td> + <td>{{channel.line_port}}</td> + <td> {{channel.status}}</td> + </tr> + {% endfor %} + </tbody> + </table> + {%else%} + <table class="table table-striped table-hover"> + <thead> + <tr> + <th scope="col">Channel Index</th> + <th scope="col">Optical Band Parent</th> + <th scope="col">Band Name</th> + <th scope="col">Lower Frequency</th> + <th scope="col">Upper Frequency</th> + <th scope="col">type</th> + <th scope="col"> Source Port</th> + <th scope="col">Destination Port</th> + <th scope="col">Channel Status </th> + + </tr> + </thead> + <tbody> + + {% for channel in device %} + <tr> + <td>{{channel.channel_index}}</td> + <td>{{channel.optical_band_parent}}</td> + <td> {{channel.band_name}}</td> + <td>{{channel.lower_frequency}}</td> + <td>{{channel.upper_frequency}}</td> + <td> {{channel.type}}</td> + <td>{{channel.src_port}}</td> + <td>{{channel.dest_port}}</td> + <td> {{channel.status}}</td> + + </tr> + {% endfor %} + + </tbody> + </table> + + {% endif%} + +</div> + +{% else %} +<div class="col"> + <h4 colspan="7">No devices found</h4> +</div> +{% endif %} +<div class="modal fade" id="deleteModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" + aria-labelledby="staticBackdropLabel" aria-hidden="true"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title" id="staticBackdropLabel">Delete Optical Config?</h5> + <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> + </div> + <div class="modal-body"> + Are you sure you want to delete the Optical Config "{{config_id}}"? + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">No</button> + <a type="button" class="btn btn-danger" + href="{{ url_for('opticalconfig.delete_opitcalconfig', opticalconfig_uuid=config_id) }}"><i + class="bi bi-exclamation-diamond"></i>Yes</a> + </div> + </div> + </div> +</div> +</div> + +{% endblock %} diff --git a/src/webui/service/templates/opticalconfig/home.html b/src/webui/service/templates/opticalconfig/home.html new file mode 100644 index 0000000000000000000000000000000000000000..a8127de5b3eec45d8e3e09d2561939814b65df46 --- /dev/null +++ b/src/webui/service/templates/opticalconfig/home.html @@ -0,0 +1,113 @@ +<!-- + Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +{% extends 'base.html' %} + +{% block content %} + <h1>Optical Configurations</h1> + {% if config %} + <div class="row"> + <div class="col-sm-12"> + <div class="row mb-3 "> + + <div class="col-sm-3"> + <button type="button" class="btn btn-success" onclick="window.location.href='{{ url_for('base_optical.home') }}'"> + <i class="bi bi-box-arrow-in-left"></i> + Back to main + </button> + </div> + <div class="col-sm-3"> + <!-- <button type="button" class="btn btn-danger"><i class="bi bi-x-square"></i>Delete device</button> --> + <!-- <button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#deleteModal"> + <i class="bi bi-x-square"></i> + Delete All + </button> --> + <button type="button" class="btn btn-info" + onclick="window.location.href='{{ url_for('opticalconfig.refresh_all') }}'"> + <i class="bi bi-arrow-clockwise"></i> + Refresh + </button> + </div> + </div> + </div> + + + <table class="table table-striped table-hover"> + <thead> + <tr> + <th scope="col">UUID</th> + <th scope="col">Device Name</th> + <th scope="col">Device Type</th> + <th scope="col">Channels Number</th> + + </tr> + </thead> + <tbody> + + {% for device in config %} + <tr> + <td>{{device.opticalconfig_id.opticalconfig_uuid}}</td> + <td>{{device.device_name}}</td> + <td> {{device.type}}</td> + {% if device.channels_number %} + <td>{{ device.channels_number }}</td> + {%else%} + <td>__ </td> + {%endif%} + {% if device.opticalconfig_id %} + <td> + <a href="{{ url_for('opticalconfig.show_details', config_uuid=device.opticalconfig_id.opticalconfig_uuid) }}"> + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eye" viewBox="0 0 16 16"> + <path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z"/> + <path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z"/> + </svg> + </a> + </td> + {%endif%} + </tr> + {% endfor %} + + </tbody> + </table> + + </div> + + {% else %} + <div class="col"> + <h4 colspan="7">No devices found</h4> + </div> + {% endif %} + <!-- <div class="col"> + <a href="{{ url_for('service.add') }}" class="btn btn-primary" style="margin-bottom: 10px;"> + <i class="bi bi-plus"></i> + Add New Service + </a> + </div> --> + + <!-- Only display XR service addition button if there are XR constellations. Otherwise it might confuse + user, as other service types do not have GUI to add service yet. --> + + <!-- <div class="col"> + <form> + <div class="input-group"> + <input type="text" aria-label="Search" placeholder="Search..." class="form-control"/> + <button type="submit" class="btn btn-primary">Search</button> + </div> + </form> + </div> --> + </div> + +{% endblock %} diff --git a/src/webui/service/templates/opticalconfig/update_interface.html b/src/webui/service/templates/opticalconfig/update_interface.html new file mode 100644 index 0000000000000000000000000000000000000000..5c9b7b79c419861b287b0e4aae17e60cfbda18cc --- /dev/null +++ b/src/webui/service/templates/opticalconfig/update_interface.html @@ -0,0 +1,90 @@ +<!-- + Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +{% extends 'base.html' %} + +{% block content %} +<div class="col-sm-12 d-flex flex-column"> + + <h1>Update Device {{ device.opticalconfig_id.opticalconfig_uuid }}</h1> + <h2> interface :{{interface_name}}</h2> +</div> +<br /> +<form id="update_device" method="POST"> + {{ form.hidden_tag() }} + <fieldset> + <div class="row mb-3 "> + <div class="col-12"> + {{ form.ip.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form.ip.errors %} + {{ form.ip(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form.ip.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + + {{ form.ip(class="col-sm-7 form-control") }} + + {% endif %} + </div> + </div> + <div class="col-12 "> + {{ form.prefix_length.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form.prefix_length.errors %} + {{ form.prefix_length(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form.prefix_length.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + + + {{ form.prefix_length(class="col-sm-7 form-control") }} + + {% endif %} + </div> + + + </div> + </div> + <div class="col-sm-12"> + <div class="row"> + <div class="col-sm-3"> + + <button type="submit" class="btn btn-primary"> + <i class="bi bi-plus-circle-fill"></i> + update interface + </button> + </div> + <div class="col-sm-3 mx-1"> + + <button type="button" class="btn btn-block btn-secondary" onclick="javascript: history.back()"> + <i class="bi bi-box-arrow-in-left"></i> + Cancel + </button> + </div> + </div> + </div> + </div> + </fieldset> +</form> + +{% endblock %} diff --git a/src/webui/service/templates/opticalconfig/update_status.html b/src/webui/service/templates/opticalconfig/update_status.html new file mode 100644 index 0000000000000000000000000000000000000000..e52bfbebbe29813a49841829c57f66862baf77f8 --- /dev/null +++ b/src/webui/service/templates/opticalconfig/update_status.html @@ -0,0 +1,67 @@ +<!-- + Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +{% extends 'base.html' %} + +{% block content %} +<h1>Update Channel Status </h1> +<br /> +<div> + channel : <span class="font-weight-bold">{{channel_name}}</span> +</div> +<form id="update_status" method="POST"> + {{ form.hidden_tag() }} + <fieldset> + <div class="row mb-3 "> + <div class="col-12"> + {{ form.status.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form.status.errors %} + {{ form.status(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form.status.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + + {{ form.status(class="col-sm-7 form-control") }} + + {% endif %} + </div> + + </div> + <div class="row gx-2"> + <div class="col-5"> + <button type="submit" class="btn btn-primary" > + <i class="bi bi-plus-circle-fill"></i> + {{ submit_text }} + </button> + <div class="col-5"> + + <button type="button" class="btn btn-block btn-secondary" onclick="javascript: history.back()"> + <i class="bi bi-box-arrow-in-left"></i> + Cancel + </button> + </div> + </div> + </div> + + </div> + </fieldset> +</form> + +{% endblock %}