diff --git a/proto/pluggables.proto b/proto/pluggables.proto index 8036a24bef16c673d40dd13f9755c284a00d0d3e..da5e8081bafc5ca4270724807534af05e6caf591 100644 --- a/proto/pluggables.proto +++ b/proto/pluggables.proto @@ -82,6 +82,7 @@ message PluggableConfig { double center_frequency_mhz = 3; // center frequency in MHz int32 operational_mode = 4; // e.g., 0=off and 1=on int32 line_port = 5; // line port number + string channel_name = 6; // channel name repeated DigitalSubcarrierGroupConfig dsc_groups = 10; } diff --git a/scripts/run_tests_locally-service-pluggable.sh b/scripts/run_tests_locally-service-pluggable.sh index 600c1edf77636f5dba85a7eb6607d236347f74c4..e749bd43ca5c30192d755c59d54b492c21733ca7 100755 --- a/scripts/run_tests_locally-service-pluggable.sh +++ b/scripts/run_tests_locally-service-pluggable.sh @@ -17,7 +17,10 @@ PROJECTDIR=`pwd` cd $PROJECTDIR/src RCFILE=$PROJECTDIR/coverage/.coveragerc -python3 -m pytest --log-level=info --log-cli-level=info --verbose \ - pluggables/tests/test_Pluggables.py +# to run integration test: -m integration +python3 -m pytest --log-level=info --log-cli-level=info --verbose -m "not integration" \ + pluggables/tests/test_pluggables_with_SBI.py +# python3 -m pytest --log-level=info --log-cli-level=info --verbose \ +# pluggables/tests/test_Pluggables.py echo "Bye!" diff --git a/src/device/Dockerfile b/src/device/Dockerfile index e33f4e980a46560e07828f071a9486d6f8b76698..d74263414ddea90157c8aa5baf18f5c75cda8985 100644 --- a/src/device/Dockerfile +++ b/src/device/Dockerfile @@ -82,6 +82,8 @@ 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/kpi_manager/__init__.py kpi_manager/__init__.py +COPY src/kpi_manager/client/. kpi_manager/client/ # Clone test mock tools RUN mkdir -p tests/tools/mock_ietf_actn_sdn_ctrl diff --git a/src/device/service/drivers/__init__.py b/src/device/service/drivers/__init__.py index 5768e23101bdf4a2e490aec7b6dab451553259ce..ba5298292fbccb04381050c82c388fa2d3ee39c7 100644 --- a/src/device/service/drivers/__init__.py +++ b/src/device/service/drivers/__init__.py @@ -234,7 +234,6 @@ if LOAD_ALL_DEVICE_DRIVERS: # Real Packet Router, specifying OpenConfig Driver => use OpenConfigDriver FilterFieldEnum.DEVICE_TYPE: [ DeviceTypeEnum.OPEN_ROADM, - ], FilterFieldEnum.DRIVER : DeviceDriverEnum.DEVICEDRIVER_OPENROADM, } diff --git a/src/device/service/drivers/openconfig/templates/Namespace.py b/src/device/service/drivers/openconfig/templates/Namespace.py index 4604481bb666365752e33e9a8ef3bcf8523e6d1c..9455ed3b384452a1f21f192a12233cacae4542f3 100644 --- a/src/device/service/drivers/openconfig/templates/Namespace.py +++ b/src/device/service/drivers/openconfig/templates/Namespace.py @@ -13,22 +13,23 @@ # limitations under the License. -NAMESPACE_NETCONF = 'urn:ietf:params:xml:ns:netconf:base:1.0' +NAMESPACE_NETCONF = 'urn:ietf:params:xml:ns:netconf:base:1.0' -NAMESPACE_ACL = 'http://openconfig.net/yang/acl' -NAMESPACE_BGP_POLICY = 'http://openconfig.net/yang/bgp-policy' -NAMESPACE_INTERFACES = 'http://openconfig.net/yang/interfaces' -NAMESPACE_INTERFACES_IP = 'http://openconfig.net/yang/interfaces/ip' -NAMESPACE_NETWORK_INSTANCE = 'http://openconfig.net/yang/network-instance' -NAMESPACE_NETWORK_INSTANCE_TYPES = 'http://openconfig.net/yang/network-instance-types' -NAMESPACE_OPENCONFIG_TYPES = 'http://openconfig.net/yang/openconfig-types' -NAMESPACE_PLATFORM = 'http://openconfig.net/yang/platform' -NAMESPACE_PLATFORM_PORT = 'http://openconfig.net/yang/platform/port' -NAMESPACE_POLICY_TYPES = 'http://openconfig.net/yang/policy-types' -NAMESPACE_POLICY_TYPES_2 = 'http://openconfig.net/yang/policy_types' -NAMESPACE_ROUTING_POLICY = 'http://openconfig.net/yang/routing-policy' -NAMESPACE_VLAN = 'http://openconfig.net/yang/vlan' -NAMESPACE_PLATFORM_TRANSCEIVER = 'http://openconfig.net/yang/platform/transceiver' +NAMESPACE_ACL = 'http://openconfig.net/yang/acl' +NAMESPACE_BGP_POLICY = 'http://openconfig.net/yang/bgp-policy' +NAMESPACE_INTERFACES = 'http://openconfig.net/yang/interfaces' +NAMESPACE_INTERFACES_IP = 'http://openconfig.net/yang/interfaces/ip' +NAMESPACE_NETWORK_INSTANCE = 'http://openconfig.net/yang/network-instance' +NAMESPACE_NETWORK_INSTANCE_TYPES = 'http://openconfig.net/yang/network-instance-types' +NAMESPACE_OPENCONFIG_TYPES = 'http://openconfig.net/yang/openconfig-types' +NAMESPACE_PLATFORM = 'http://openconfig.net/yang/platform' +NAMESPACE_PLATFORM_PORT = 'http://openconfig.net/yang/platform/port' +NAMESPACE_POLICY_TYPES = 'http://openconfig.net/yang/policy-types' +NAMESPACE_POLICY_TYPES_2 = 'http://openconfig.net/yang/policy_types' +NAMESPACE_ROUTING_POLICY = 'http://openconfig.net/yang/routing-policy' +NAMESPACE_VLAN = 'http://openconfig.net/yang/vlan' +NAMESPACE_PLATFORM_TRANSCEIVER = 'http://openconfig.net/yang/platform/transceiver' +NAMESPACE_TERMINAL_DEVICE_DIGITAL_SUBCARRIERS = 'http://openconfig.net/yang/terminal-device-digital-subcarriers' NAMESPACES = { 'nc' : NAMESPACE_NETCONF, @@ -46,4 +47,5 @@ NAMESPACES = { 'ocrp' : NAMESPACE_ROUTING_POLICY, 'ocv' : NAMESPACE_VLAN, 'ocptr': NAMESPACE_PLATFORM_TRANSCEIVER, + 'octds': NAMESPACE_TERMINAL_DEVICE_DIGITAL_SUBCARRIERS, } diff --git a/src/device/service/drivers/openconfig/templates/__init__.py b/src/device/service/drivers/openconfig/templates/__init__.py index 3704791d86a003d938d1831af8f15b6480c7166a..eca314a842be1f87056267affee7b4229fd5bb51 100644 --- a/src/device/service/drivers/openconfig/templates/__init__.py +++ b/src/device/service/drivers/openconfig/templates/__init__.py @@ -136,6 +136,14 @@ def compose_config( # template generation templates.append(JINJA_ENV.get_template(enable_ingress_filter_path)) templates.append(JINJA_ENV.get_template(acl_entry_path)) templates.append(JINJA_ENV.get_template(acl_ingress_path)) + elif "pluggable" in resource_key: # MANAGING DSCM (Digital Subcarrier Modules) + # Use generic pluggable template for all devices (hub and leaf) + # Resource key format: /pluggable/{template_index}/config + template_name = '{:s}/edit_config.xml'.format(RE_REMOVE_FILTERS.sub('', resource_key)) + templates.append(JINJA_ENV.get_template(template_name)) + LOGGER.info(f"Loading DSCM template: {template_name}") + + data : Dict[str, Any] = json.loads(resource_value) else: template_name = '{:s}/edit_config.xml'.format(RE_REMOVE_FILTERS.sub('', resource_key)) templates.append(JINJA_ENV.get_template(template_name)) diff --git a/src/device/service/drivers/openconfig/templates/pluggable/1/config/edit_config.xml b/src/device/service/drivers/openconfig/templates/pluggable/1/config/edit_config.xml new file mode 100644 index 0000000000000000000000000000000000000000..4149bccdac876b92428b96926727a9335fc87d54 --- /dev/null +++ b/src/device/service/drivers/openconfig/templates/pluggable/1/config/edit_config.xml @@ -0,0 +1,31 @@ + + + {{name}} + + {% if operation is defined and operation != 'delete' %} + + {% if frequency is defined %}{{frequency}}{% endif %} + {% if target_output_power is defined %}{{target_output_power}}{% endif %} + {% if operational_mode is defined %}{{operational_mode}}{% endif %} + + {% if digital_sub_carriers_group is defined and digital_sub_carriers_group %} + {% for group in digital_sub_carriers_group %} + + {{group.digital_sub_carriers_group_id}} + + {% if group.digital_sub_carrier_id is defined and group.digital_sub_carrier_id %} + {% for sub_carrier in group.digital_sub_carrier_id %} + + {{sub_carrier.sub_carrier_id}} + {{sub_carrier.active}} + + {% endfor %} + {% endif %} + + {% endfor %} + {% endif %} + + {% endif %} + + + diff --git a/src/device/tests/dscm/Fixtures.py b/src/device/tests/dscm/Fixtures.py new file mode 100644 index 0000000000000000000000000000000000000000..458d0d78629d8f6102feaab68a5c150dcfc4a3c1 --- /dev/null +++ b/src/device/tests/dscm/Fixtures.py @@ -0,0 +1,58 @@ +# Copyright 2022-2024 ETSI 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 +import logging + +from context.client.ContextClient import ContextClient +from device.client.DeviceClient import DeviceClient +from service.client.ServiceClient import ServiceClient +from kpi_manager.client.KpiManagerClient import KpiManagerClient + + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + + +@pytest.fixture(scope='session') +def context_client(): + _client = ContextClient(host="10.152.183.127") + _client.connect() + LOGGER.info('Yielding Connected ContextClient...') + yield _client + _client.close() + +@pytest.fixture(scope='session') +def device_client(): + _client = DeviceClient(host="10.152.183.95") + _client.connect() + LOGGER.info('Yielding Connected DeviceClient...') + yield _client + _client.close() + +@pytest.fixture(scope='session') +def service_client(): + _client = ServiceClient(host="10.152.183.47") + _client.connect() + LOGGER.info('Yielding Connected DeviceClient...') + yield _client + _client.close() + +@pytest.fixture(scope='session') +def kpi_manager_client(): + _client = KpiManagerClient(host="10.152.183.118") + LOGGER.info('Yielding Connected KpiManagerClient...') + yield _client + _client.close() + LOGGER.info('Closed KpiManagerClient...') diff --git a/src/device/tests/dscm/__init__.py b/src/device/tests/dscm/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..7363515f07a52d996229bcbd72932ce1423258d7 --- /dev/null +++ b/src/device/tests/dscm/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2022-2025 ETSI 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/dscm/test_dscm_netconf_hub_leaf.py b/src/device/tests/dscm/test_dscm_netconf_hub_leaf.py new file mode 100644 index 0000000000000000000000000000000000000000..7c707a7694f9c775b111b72615dafa4dcf381058 --- /dev/null +++ b/src/device/tests/dscm/test_dscm_netconf_hub_leaf.py @@ -0,0 +1,243 @@ +# Copyright 2022-2025 ETSI 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 +from device.service.drivers.netconf_dscm.NetConfDriver import NetConfDriver + +logging.basicConfig(level=logging.DEBUG) +LOGGER = logging.getLogger(__name__) + +DEVICES = { + 'hub': {'address': '10.30.7.7', 'port': 2023, 'settings': {}}, + 'leaf': {'address': '10.30.7.8', 'port': 2023, 'settings': {}} + } + +@pytest.fixture(autouse=True) +def log_each(request): + LOGGER.info(f">>>>>> START {request.node.name} >>>>>>") + yield + LOGGER.info(f"<<<<<< END {request.node.name} <<<<<<") + +@pytest.fixture(scope='session') +def get_driver_hub() -> Tuple[NetConfDriver, None]: # pyright: ignore[reportInvalidTypeForm] + driver = NetConfDriver( + DEVICES['hub']['address'], DEVICES['hub']['port'], **(DEVICES['hub']['settings']) + ) + yield driver # pyright: ignore[reportReturnType] + time.sleep(1) + +@pytest.fixture(scope='session') +def get_driver_leaf() -> Tuple[NetConfDriver, None]: # pyright: ignore[reportInvalidTypeForm] + driver = NetConfDriver( + DEVICES['leaf']['address'], DEVICES['leaf']['port'], **(DEVICES['leaf']['settings']) + ) + yield driver # pyright: ignore[reportReturnType] + time.sleep(1) + + +# --- Directly Testing SBI +def test_get_default_hub_config(get_driver_hub) -> Dict: + data = { + "name": "channel-1", + "frequency": 195000000, + "operational_mode": 1, + "target_output_power": 0.0, + "operation" : "merge", + "digital_sub_carriers_group": [ + { + "digital_sub_carriers_group_id": 1, + "digital_sub_carrier_id": [ + { + "sub_carrier_id": 1, + "active": "true" + }, + { + "sub_carrier_id": 2, + "active": "true" + }, + { + "sub_carrier_id": 3, + "active": "true" + }, + { + "sub_carrier_id": 4, + "active": "true" + } + ] + }, + { + "digital_sub_carriers_group_id": 2, + "digital_sub_carrier_id": [ + { + "sub_carrier_id": 5, + "active": "true" + }, + { + "sub_carrier_id": 6, + "active": "true" + }, + { + "sub_carrier_id": 7, + "active": "true" + }, + { + "sub_carrier_id": 8, + "active": "true" + } + ] + } + ], + } + node = 'T2.1' + result_config = get_driver_hub.SetConfig([(node, data)]) + assert result_config is not None + +def test_get_default_leaves_config(get_driver_leaf) -> Dict: + data = { + "name" : "channel-1", # "channel-1", "channel-3", "channel-5" + "frequency" : 195006250, # "195006250", 195018750, 195031250 + "operational_mode" : 1, + "target_output_power": -99, # should be -99 + "operation" : "merge", + "digital_sub_carriers_group": [ + { + "digital_sub_carriers_group_id": 1, + "digital_sub_carrier_id": [ + { + "sub_carrier_id": 1, + "active": "false" # should be set to false + }, + { + "sub_carrier_id": 2, + "active": "false" + }, + { + "sub_carrier_id": 3, + "active": "false" + }, + { + "sub_carrier_id": 4, + "active": "false" + } + ] + } + ], + } + node = 'T1.1' + result_config = get_driver_leaf.SetConfig([(node, data)]) + assert result_config is not None + +# netconf-console2 --host=10.30.7.7 --port=2023 --tcp --get-config -x '/components/component[name="channel-1"]/optical-channel/state/input-power/instant' + +# def test_get_config(get_driver): +# path = '/components/component[name="channel-1"]/optical-channel/state/input-power/instant' +# result_config = get_driver.GetConfig([path]) +# assert result_config is not None +# LOGGER.info(f"GetConfig result: {result_config}") + +# netconf-console2 --host=10.30.7.7 --port=2023 --tcp --edit-config edit_dscm_hub.xml + +# def test_set_config_hub(get_driver_hub): +# data = { +# "name": "channel-1", +# "frequency": "195000000", +# "target_output_power": "-3.0", +# "operational_mode": "1", +# "operation": "merge", +# "digital_subcarriers_groups": [ +# { "group_id": 1, "digital-subcarrier-id": [{ "subcarrier-id": 1, "active": True}, ]}, +# { "group_id": 2, "digital-subcarrier-id": [{ "subcarrier-id": 2, "active": True}, ]}, +# { "group_id": 3, "digital-subcarrier-id": [{ "subcarrier-id": 3, "active": True}, ]}, +# { "group_id": 4, "digital-subcarrier-id": [{ "subcarrier-id": 4, "active": True}, ]}, +# ], +# } +# node = 'hub' +# result_config = get_driver_hub.SetConfig([(node, data)]) +# assert result_config is not None +# # LOGGER.info(f"SetConfig result: {result_config}") + +# def test_set_config_leaf(get_driver_leaf): +# data = { +# "operation": "merge", +# "channels": +# [ +# { +# "name": "channel-1", +# "frequency": "195006250", +# "target_output_power": "-99.0", +# "operational_mode": "1", +# "digital_subcarriers_groups": +# [{ "group_id": 1 }] +# }, +# { +# "name": "channel-3", +# "frequency": "195018750", +# "target_output_power": "-99.0", +# "operational_mode": "1", +# "digital_subcarriers_groups": +# [{ "group_id": 1 }] +# }, +# { +# "name": "channel-5", +# "frequency": "195031250", +# "target_output_power": "-99.0", +# "operational_mode": "1", +# "digital_subcarriers_groups": +# [{ "group_id": 1 }] +# } +# ] +# } +# node = 'leaf' +# result_config = get_driver_leaf.SetConfig([(node, data)]) +# assert result_config is not None +# # LOGGER.info(f"SetConfig result: {result_config}") + + +# def test_dscm_netconf_hub(drivers): +# path = '/components/component[name="channel-1"]/config' +# data = json.dumps( +# { "name": "channel-1", +# "frequency": "195000000", +# "target_output_power": "-3.0", +# "operational_mode": "1", +# } +# ) +# config_to_set = [(path, data)] +# result_config = drivers['DSCM1'].SetConfig(config_to_set) +# assert result_config is not None +# LOGGER.info(f"SetConfig result: {result_config}") + + + +from common.tools.context_queries.Topology import get_topology +from common.proto.context_pb2 import TopologyId, ContextId +from .Fixtures import context_client +def test_get_and_remove_topology_context(context_client): + response = get_topology(context_client = context_client, topology_uuid = "admin", context_uuid = "admin") + LOGGER.info(f"Topology: {response}") + assert response is not None + # create context_id and topology_id from response + context_id = ContextId() + context_id = response.topology_id.context_id + topology_id = TopologyId() + topology_id = response.topology_id + # Remove Topology + topology_id.context_id.CopyFrom(context_id) + response = context_client.RemoveTopology(topology_id) + LOGGER.info(f"Topology removed Sucessfully") + # Remove Context + response = context_client.RemoveContext(context_id) + LOGGER.info(f"Context removed Sucessfully") diff --git a/src/nbi/Dockerfile b/src/nbi/Dockerfile index 1a08ddbe163f0ea6e0ad2e1679757c34f013bd88..1b0d841f344bb06f871118302f565b61cc26bd1d 100644 --- a/src/nbi/Dockerfile +++ b/src/nbi/Dockerfile @@ -77,18 +77,20 @@ 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/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/osm_client/__init__.py osm_client/__init__.py +COPY src/osm_client/client/. osm_client/client/ +COPY src/pluggables/__init__.py pluggables/__init__.py +COPY src/pluggables/client/. pluggables/client/ COPY src/qkd_app/__init__.py qkd_app/__init__.py COPY src/qkd_app/client/. qkd_app/client/ COPY src/qos_profile/__init__.py qos_profile/__init__.py COPY src/qos_profile/client/. qos_profile/client/ -COPY src/osm_client/__init__.py osm_client/__init__.py -COPY src/osm_client/client/. osm_client/client/ +COPY src/service/__init__.py service/__init__.py +COPY src/service/client/. service/client/ COPY src/simap_connector/__init__.py simap_connector/__init__.py COPY src/simap_connector/client/. simap_connector/client/ +COPY src/slice/__init__.py slice/__init__.py +COPY src/slice/client/. slice/client/ COPY src/vnt_manager/__init__.py vnt_manager/__init__.py COPY src/vnt_manager/client/. vnt_manager/client/ RUN mkdir -p /var/teraflow/tests/tools diff --git a/src/pluggables/service/PluggablesServiceServicerImpl.py b/src/pluggables/service/PluggablesServiceServicerImpl.py index 098c4f9ace620d255033290ae0b9a0d7eb52f259..c34b0999f2538ff828832ade64af2ea379020981 100644 --- a/src/pluggables/service/PluggablesServiceServicerImpl.py +++ b/src/pluggables/service/PluggablesServiceServicerImpl.py @@ -60,42 +60,24 @@ class PluggablesServiceServicerImpl(PluggablesServiceServicer): LOGGER.info(f"Translated pluggable config to NETCONF format: {netconf_config}") - # Step 2: Extract device IP address from _connect/address config rule - device_address = None - for config_rule in device.device_config.config_rules: # type: ignore - if config_rule.custom.resource_key == '_connect/address': # type: ignore - device_address = config_rule.custom.resource_value # type: ignore - break - - # Step 3: Determine the appropriate template based on device IP address (TODO: This need to be updated later) - if device_address == '10.30.7.7': - template_identifier = 'hub' - elif device_address == '10.30.7.8': - template_identifier = 'leaf' - else: - LOGGER.warning(f"Cannot determine device type from IP address {device_address}, defaulting to hub template") - raise InvalidArgumentException( 'Device IP address', device_address, extra_details='Unknown device IP adress') - - LOGGER.info(f"Using template identifier: {template_identifier} for device {device.name} (IP: {device_address})") - - # Step 4: Create configuration rule with template-specific resource key - # For simplicity, we use a fixed pluggable index of 1 for template lookup - template_index = 1 # TODO: This should be dynamic based on actual pluggable index - resource_key = f"/pluggable/{template_index}/config/{template_identifier}" + # Step 2: Create configuration rule with generic pluggable template + # Use template index 1 for standard pluggable configuration + template_index = 1 + resource_key = f"/pluggable/{template_index}/config" # Create config rule dict and convert to protobuf config_json = json.dumps(netconf_config) config_rule_dict = json_config_rule_set(resource_key, config_json) config_rule = create_config_rule_from_dict(config_rule_dict) - # Step 5: Create a minimal Device object with only the DSCM config rule + # Step 3: Create a minimal Device object with only the DSCM config rule config_device = Device() config_device.device_id.device_uuid.uuid = device_uuid # type: ignore config_device.device_config.config_rules.append(config_rule) # type: ignore - LOGGER.info(f"Created minimal device with config rule: resource_key={resource_key}, template={template_identifier}") + LOGGER.info(f"Created minimal device with config rule: resource_key={resource_key}") - # Step 6: Call ConfigureDevice to push the configuration + # Step 4: Call ConfigureDevice to push the configuration try: device_id = self.device_client.ConfigureDevice(config_device) LOGGER.info(f"Successfully configured device {device_id.device_uuid.uuid}") # type: ignore diff --git a/src/pluggables/service/config_translator.py b/src/pluggables/service/config_translator.py index d28e2ae013418827ca9129ec01433f2c92f5b26f..4c0e8c765de40cebf70cd5209c97a78be93f182f 100644 --- a/src/pluggables/service/config_translator.py +++ b/src/pluggables/service/config_translator.py @@ -30,23 +30,29 @@ def create_config_rule_from_dict(config_rule_dict: Dict[str, Any]) -> ConfigRule def translate_pluggable_config_to_netconf( pluggable_config: PluggableConfig, # type: ignore - component_name: str = "channel-1" # channel-1 for HUB and channel-1/3/5 for LEAF + component_name: str = "channel-1" # Fallback if channel_name not provided (channel-1 for HUB and channel-1/3/5 for LEAF) ) -> Dict[str, Any]: """ Translate PluggableConfig protobuf message to the format expected by NetConfDriver. Args: pluggable_config: PluggableConfig message containing DSC groups and subcarriers - component_name: Name of the optical channel component (default: "channel-1") + component_name: Fallback name if channel_name is not specified in config (default: "channel-1") Returns: Dictionary in the format expected by NetConfDriver templates: """ - + if not pluggable_config or not pluggable_config.dsc_groups: LOGGER.warning("Empty pluggable config provided") return { - "name": component_name, + "name": channel_name, "operation": "delete" } + if hasattr(pluggable_config, 'channel_name') and pluggable_config.channel_name: + channel_name = pluggable_config.channel_name + LOGGER.debug(f"Using channel_name from PluggableConfig: {channel_name}") + else: + channel_name = component_name + LOGGER.debug(f"Using fallback component_name: {channel_name}") if not hasattr(pluggable_config, 'center_frequency_mhz') or pluggable_config.center_frequency_mhz <= 0: raise ValueError("center_frequency_mhz is required and must be greater than 0 in PluggableConfig") @@ -88,14 +94,14 @@ def translate_pluggable_config_to_netconf( # Build the final configuration dictionary config = { - "name": component_name, + "name": channel_name, "frequency": center_frequency_mhz, "operational_mode": operational_mode, "target_output_power": target_output_power, "digital_sub_carriers_group": digital_sub_carriers_groups } - LOGGER.info(f"Translated pluggable config to NETCONF format: component={component_name}, " + LOGGER.info(f"Translated pluggable config to NETCONF format: component={channel_name}, " f"frequency={center_frequency_mhz} MHz, groups={len(digital_sub_carriers_groups)}") return config diff --git a/src/pluggables/tests/testmessages.py b/src/pluggables/tests/testmessages.py index c7a7fe934b5183c21548c3fdcd116823e691ce1d..c18924babb9e30dbcf393420d3373da13f641e74 100644 --- a/src/pluggables/tests/testmessages.py +++ b/src/pluggables/tests/testmessages.py @@ -57,6 +57,7 @@ def create_pluggable_request( _request.initial_config.operational_mode = 1 # Operational mode _request.initial_config.target_output_power_dbm = -10.0 # Target output power _request.initial_config.line_port = 1 # Line port number + _request.initial_config.channel_name = "channel-1" # Channel name for component # Add sample DSC group configuration dsc_group = _request.initial_config.dsc_groups.add() @@ -202,6 +203,7 @@ def create_configure_pluggable_request( _request.config.operational_mode = 1 # Operational mode _request.config.target_output_power_dbm = -10.0 # Target output power _request.config.line_port = 1 # Line port number + _request.config.channel_name = "channel-1" # Channel name for component # Add DSC group configuration group_1 = _request.config.dsc_groups.add()