Loading scripts/run_tests_locally-pluggable.sh +2 −2 Original line number Diff line number Diff line Loading @@ -18,8 +18,8 @@ cd $PROJECTDIR/src RCFILE=$PROJECTDIR/coverage/.coveragerc # 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 -m "not integration" \ pluggables/tests/test_pluggables_with_SBI.py python3 -m pytest --log-level=info --log-cli-level=info --verbose -m "not integration"\ pluggables/tests/test_pluggables.py Loading src/device/requirements.in +1 −1 Original line number Diff line number Diff line Loading @@ -34,7 +34,7 @@ paramiko==2.11.* pyang==2.6.* git+https://github.com/robshakir/pyangbind.git python-json-logger==2.0.2 #pytz==2021.3 pytz==2021.3 #redis==4.1.2 requests==2.27.1 requests-mock==1.9.3 Loading src/pluggables/service/PluggablesServiceServicerImpl.py +46 −31 Original line number Diff line number Diff line Loading @@ -13,18 +13,19 @@ # limitations under the License. import json, logging, grpc from .config_translator import translate_pluggable_config_to_netconf, create_config_rule_from_dict from common.method_wrappers.Decorator import MetricsPool, safe_and_metered_rpc_method from common.method_wrappers.ServiceExceptions import ( NotFoundException, AlreadyExistsException, InvalidArgumentException, OperationFailedException) from common.proto.context_pb2 import Empty, Device, DeviceId from common.proto.pluggables_pb2_grpc import PluggablesServiceServicer from common.proto.pluggables_pb2 import ( Pluggable, CreatePluggableRequest, ListPluggablesRequest, ListPluggablesResponse, Pluggable, PluggableConfig, CreatePluggableRequest, ListPluggablesRequest, ListPluggablesResponse, GetPluggableRequest, DeletePluggableRequest, ConfigurePluggableRequest) from common.method_wrappers.ServiceExceptions import ( NotFoundException, AlreadyExistsException, InvalidArgumentException, OperationFailedException) from common.proto.pluggables_pb2_grpc import PluggablesServiceServicer from common.tools.object_factory.ConfigRule import json_config_rule_set from context.client.ContextClient import ContextClient from device.client.DeviceClient import DeviceClient from .config_translator import translate_pluggable_config_to_netconf, create_config_rule_from_dict LOGGER = logging.getLogger(__name__) METRICS_POOL = MetricsPool('Pluggables', 'ServicegRPC') Loading Loading @@ -56,9 +57,9 @@ class PluggablesServiceServicerImpl(PluggablesServiceServicer): # Translate pluggable config to NETCONF format component_name = f"channel-{pluggable_index}" netconf_config = translate_pluggable_config_to_netconf(pluggable_config, component_name=component_name) config = translate_pluggable_config_to_netconf(pluggable_config, component_name=component_name) LOGGER.info(f"Translated pluggable config to NETCONF format: {netconf_config}") LOGGER.info(f"Translated pluggable config to NETCONF format: {config}") # Step 2: Create configuration rule with generic pluggable template # Use template index 1 for standard pluggable configuration Loading @@ -66,11 +67,12 @@ class PluggablesServiceServicerImpl(PluggablesServiceServicer): resource_key = f"/pluggable/{template_index}/config" # Create config rule dict and convert to protobuf config_json = json.dumps(netconf_config) config_json = json.dumps(config) config_rule_dict = json_config_rule_set(resource_key, config_json) config_rule = create_config_rule_from_dict(config_rule_dict) # Step 3: Create a minimal Device object with only the DSCM config rule # TODO: In future, if device config merging is needed, fetch existing device config first 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 Loading Loading @@ -195,25 +197,38 @@ class PluggablesServiceServicerImpl(PluggablesServiceServicer): LOGGER.info(f'No matching pluggable found: device={device_uuid}, index={pluggable_index}') raise NotFoundException('Pluggable', pluggable_key) # Remove pluggable config from device # TODO: Verify deletion works with actual hub and leaf devices # # Remove pluggable config from device before deleting from memory # Use empty config to signal deletion to the device driver try: pluggable = self.pluggables[pluggable_key] # Create empty config to trigger deletion from common.proto.pluggables_pb2 import PluggableConfig empty_config = PluggableConfig() empty_config.id.device.device_uuid.uuid = device_uuid empty_config.id.pluggable_index = pluggable_index del_config = PluggableConfig() del_config.id.device.device_uuid.uuid = device_uuid del_config.id.pluggable_index = pluggable_index del_config.operational_mode = 0 # Operational mode del_config.channel_name = "channel-1" # Channel name for component group_1 = del_config.dsc_groups.add() group_1.id.pluggable.device.device_uuid.uuid = device_uuid group_1.id.pluggable.pluggable_index = pluggable_index group_1.id.group_index = 0 subcarrier_1 = group_1.subcarriers.add() subcarrier_1.id.group.pluggable.device.device_uuid.uuid = device_uuid subcarrier_1.id.group.pluggable.pluggable_index = pluggable_index subcarrier_1.id.group.group_index = 0 subcarrier_1.id.subcarrier_index = 0 subcarrier_1.active = False # ADD MORE SUBCARRIERS IF NEEDED LOGGER.info(f"Removing configuration from device {device_uuid}") self._push_config_to_device(device_uuid, pluggable_index, empty_config) except Exception as e: LOGGER.error(f"Failed to remove config from device: {e}") self._push_config_to_device(device_uuid, pluggable_index, del_config) except (grpc.RpcError, InvalidArgumentException, OperationFailedException) as e: LOGGER.warning(f"Failed to remove config from device (continuing with memory deletion): {e}") # Continue with deletion from memory even if device config removal fails # Always delete from memory (even if device operation failed) del self.pluggables[pluggable_key] LOGGER.info(f"Deleted pluggable: device={device_uuid}, index={pluggable_index}") LOGGER.info(f"Deleted pluggable from memory: device={device_uuid}, index={pluggable_index}") return Empty() Loading src/pluggables/service/config_translator.py +4 −5 Original line number Diff line number Diff line Loading @@ -33,18 +33,17 @@ def translate_pluggable_config_to_netconf( 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. Translate PluggableConfig protobuf message to the format expected by OpenConfig. Args: pluggable_config: PluggableConfig message containing DSC groups and subcarriers component_name: Fallback name if channel_name is not specified in config (default: "channel-1") Returns: Dictionary in the format expected by NetConfDriver templates: Dictionary in the format expected by OpenConfig templates: """ if not pluggable_config or not pluggable_config.dsc_groups: LOGGER.warning("Empty pluggable config provided") return { "name": channel_name, "operation": "delete" } if hasattr(pluggable_config, 'channel_name') and pluggable_config.channel_name: Loading @@ -54,11 +53,11 @@ def translate_pluggable_config_to_netconf( 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: if not hasattr(pluggable_config, 'center_frequency_mhz'): raise ValueError("center_frequency_mhz is required and must be greater than 0 in PluggableConfig") center_frequency_mhz = int(pluggable_config.center_frequency_mhz) if not hasattr(pluggable_config, 'operational_mode') or pluggable_config.operational_mode <= 0: if not hasattr(pluggable_config, 'operational_mode'): raise ValueError("operational_mode is required and must be greater than 0 in PluggableConfig") operational_mode = pluggable_config.operational_mode Loading src/pluggables/tests/test_pluggables_with_SBI.py +23 −23 Original line number Diff line number Diff line Loading @@ -83,14 +83,14 @@ def test_create_pluggable_hub_with_config(pluggables_client: PluggablesClient): _pluggable = pluggables_client.CreatePluggable(_request) assert isinstance(_pluggable, Pluggable) assert _pluggable.id.device.device_uuid.uuid == DEVICE_HUB_UUID assert _pluggable.id.pluggable_index == 2 assert len(_pluggable.config.dsc_groups) == 1 # Should be 1, not 2 (check testmessages.py) assert _pluggable.id.device.device_uuid.uuid == DEVICE_HUB_UUID # pyright: ignore[reportAttributeAccessIssue] assert _pluggable.id.pluggable_index == 2 # pyright: ignore[reportAttributeAccessIssue] assert len(_pluggable.config.dsc_groups) == 1 # Should be 1, not 2 (check testmessages.py) # pyright: ignore[reportAttributeAccessIssue] # Verify DSC group configuration dsc_group = _pluggable.config.dsc_groups[0] assert dsc_group.group_size == 4 # From testmessages.py assert len(dsc_group.subcarriers) == 2 dsc_group = _pluggable.config.dsc_groups[0] # pyright: ignore[reportAttributeAccessIssue] assert dsc_group.group_size == 4 # From testmessages.py # pyright: ignore[reportAttributeAccessIssue] assert len(dsc_group.subcarriers) == 2 # pyright: ignore[reportAttributeAccessIssue] LOGGER.info(f'Created Pluggable on Hub with {len(dsc_group.subcarriers)} subcarriers') Loading @@ -112,9 +112,9 @@ def test_create_pluggable_leaf_with_config(pluggables_client: PluggablesClient): _pluggable = pluggables_client.CreatePluggable(_request) assert isinstance(_pluggable, Pluggable) assert _pluggable.id.device.device_uuid.uuid == DEVICE_LEAF_UUID assert _pluggable.id.pluggable_index == 1 # Should be 1, not 0 assert len(_pluggable.config.dsc_groups) == 1 assert _pluggable.id.device.device_uuid.uuid == DEVICE_LEAF_UUID # pyright: ignore[reportAttributeAccessIssue] assert _pluggable.id.pluggable_index == 1 # Should be 1, not 0 # pyright: ignore[reportAttributeAccessIssue] assert len(_pluggable.config.dsc_groups) == 1 # pyright: ignore[reportAttributeAccessIssue] LOGGER.info(f'Created Pluggable on Leaf: {_pluggable.id}') Loading @@ -131,7 +131,7 @@ def test_configure_pluggable_hub(pluggables_client: PluggablesClient): with_initial_config=False ) _created = pluggables_client.CreatePluggable(_create_request) assert _created.id.pluggable_index == 3 assert _created.id.pluggable_index == 3 # pyright: ignore[reportAttributeAccessIssue] # Now configure it _config_request = create_configure_pluggable_request( Loading @@ -143,14 +143,14 @@ def test_configure_pluggable_hub(pluggables_client: PluggablesClient): _configured = pluggables_client.ConfigurePluggable(_config_request) assert isinstance(_configured, Pluggable) assert _configured.id.device.device_uuid.uuid == DEVICE_HUB_UUID assert _configured.id.pluggable_index == 3 assert len(_configured.config.dsc_groups) == 1 assert _configured.id.device.device_uuid.uuid == DEVICE_HUB_UUID # pyright: ignore[reportAttributeAccessIssue] assert _configured.id.pluggable_index == 3 # pyright: ignore[reportAttributeAccessIssue] assert len(_configured.config.dsc_groups) == 1 # pyright: ignore[reportAttributeAccessIssue] # Verify configuration was applied dsc_group = _configured.config.dsc_groups[0] assert dsc_group.group_size == 2 assert len(dsc_group.subcarriers) == 2 dsc_group = _configured.config.dsc_groups[0] # pyright: ignore[reportAttributeAccessIssue] assert dsc_group.group_size == 2 # pyright: ignore[reportAttributeAccessIssue] assert len(dsc_group.subcarriers) == 2 # pyright: ignore[reportAttributeAccessIssue] LOGGER.info(f'Configured Pluggable on Hub with {len(dsc_group.subcarriers)} subcarriers') Loading Loading @@ -181,9 +181,9 @@ def test_get_pluggable(pluggables_client: PluggablesClient): _retrieved = pluggables_client.GetPluggable(_get_request) assert isinstance(_retrieved, Pluggable) assert _retrieved.id.device.device_uuid.uuid == DEVICE_HUB_UUID assert _retrieved.id.pluggable_index == 4 assert len(_retrieved.config.dsc_groups) == len(_created.config.dsc_groups) assert _retrieved.id.device.device_uuid.uuid == DEVICE_HUB_UUID # pyright: ignore[reportAttributeAccessIssue] assert _retrieved.id.pluggable_index == 4 # pyright: ignore[reportAttributeAccessIssue] assert len(_retrieved.config.dsc_groups) == len(_created.config.dsc_groups) # pyright: ignore[reportAttributeAccessIssue] LOGGER.info(f'Retrieved Pluggable: {_retrieved.id}') Loading @@ -203,8 +203,8 @@ def test_list_pluggables(pluggables_client: PluggablesClient): assert len(_response.pluggables) >= 1 # At least one from previous tests for pluggable in _response.pluggables: assert pluggable.id.device.device_uuid.uuid == DEVICE_HUB_UUID LOGGER.info(f'Found Pluggable: index={pluggable.id.pluggable_index}') assert pluggable.id.device.device_uuid.uuid == DEVICE_HUB_UUID # pyright: ignore[reportAttributeAccessIssue] LOGGER.info(f'Found Pluggable: index={pluggable.id.pluggable_index}') # pyright: ignore[reportAttributeAccessIssue] # Number 7. @pytest.mark.integration Loading @@ -222,7 +222,7 @@ def test_delete_pluggable(pluggables_client: PluggablesClient): with_initial_config=True ) _created = pluggables_client.CreatePluggable(_create_request) assert _created.id.pluggable_index == 2 assert _created.id.pluggable_index == 2 # pyright: ignore[reportAttributeAccessIssue] # Delete it _delete_request = create_delete_pluggable_request( Loading Loading @@ -257,7 +257,7 @@ def test_pluggable_already_exists_error(pluggables_client: PluggablesClient): # Create first time - should succeed _pluggable = pluggables_client.CreatePluggable(_request) assert _pluggable.id.pluggable_index == 3 # Should be 3, not 5 assert _pluggable.id.pluggable_index == 3 # Should be 3, not 5 # pyright: ignore[reportAttributeAccessIssue] # Try to create again - should fail with pytest.raises(grpc.RpcError) as e: Loading Loading
scripts/run_tests_locally-pluggable.sh +2 −2 Original line number Diff line number Diff line Loading @@ -18,8 +18,8 @@ cd $PROJECTDIR/src RCFILE=$PROJECTDIR/coverage/.coveragerc # 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 -m "not integration" \ pluggables/tests/test_pluggables_with_SBI.py python3 -m pytest --log-level=info --log-cli-level=info --verbose -m "not integration"\ pluggables/tests/test_pluggables.py Loading
src/device/requirements.in +1 −1 Original line number Diff line number Diff line Loading @@ -34,7 +34,7 @@ paramiko==2.11.* pyang==2.6.* git+https://github.com/robshakir/pyangbind.git python-json-logger==2.0.2 #pytz==2021.3 pytz==2021.3 #redis==4.1.2 requests==2.27.1 requests-mock==1.9.3 Loading
src/pluggables/service/PluggablesServiceServicerImpl.py +46 −31 Original line number Diff line number Diff line Loading @@ -13,18 +13,19 @@ # limitations under the License. import json, logging, grpc from .config_translator import translate_pluggable_config_to_netconf, create_config_rule_from_dict from common.method_wrappers.Decorator import MetricsPool, safe_and_metered_rpc_method from common.method_wrappers.ServiceExceptions import ( NotFoundException, AlreadyExistsException, InvalidArgumentException, OperationFailedException) from common.proto.context_pb2 import Empty, Device, DeviceId from common.proto.pluggables_pb2_grpc import PluggablesServiceServicer from common.proto.pluggables_pb2 import ( Pluggable, CreatePluggableRequest, ListPluggablesRequest, ListPluggablesResponse, Pluggable, PluggableConfig, CreatePluggableRequest, ListPluggablesRequest, ListPluggablesResponse, GetPluggableRequest, DeletePluggableRequest, ConfigurePluggableRequest) from common.method_wrappers.ServiceExceptions import ( NotFoundException, AlreadyExistsException, InvalidArgumentException, OperationFailedException) from common.proto.pluggables_pb2_grpc import PluggablesServiceServicer from common.tools.object_factory.ConfigRule import json_config_rule_set from context.client.ContextClient import ContextClient from device.client.DeviceClient import DeviceClient from .config_translator import translate_pluggable_config_to_netconf, create_config_rule_from_dict LOGGER = logging.getLogger(__name__) METRICS_POOL = MetricsPool('Pluggables', 'ServicegRPC') Loading Loading @@ -56,9 +57,9 @@ class PluggablesServiceServicerImpl(PluggablesServiceServicer): # Translate pluggable config to NETCONF format component_name = f"channel-{pluggable_index}" netconf_config = translate_pluggable_config_to_netconf(pluggable_config, component_name=component_name) config = translate_pluggable_config_to_netconf(pluggable_config, component_name=component_name) LOGGER.info(f"Translated pluggable config to NETCONF format: {netconf_config}") LOGGER.info(f"Translated pluggable config to NETCONF format: {config}") # Step 2: Create configuration rule with generic pluggable template # Use template index 1 for standard pluggable configuration Loading @@ -66,11 +67,12 @@ class PluggablesServiceServicerImpl(PluggablesServiceServicer): resource_key = f"/pluggable/{template_index}/config" # Create config rule dict and convert to protobuf config_json = json.dumps(netconf_config) config_json = json.dumps(config) config_rule_dict = json_config_rule_set(resource_key, config_json) config_rule = create_config_rule_from_dict(config_rule_dict) # Step 3: Create a minimal Device object with only the DSCM config rule # TODO: In future, if device config merging is needed, fetch existing device config first 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 Loading Loading @@ -195,25 +197,38 @@ class PluggablesServiceServicerImpl(PluggablesServiceServicer): LOGGER.info(f'No matching pluggable found: device={device_uuid}, index={pluggable_index}') raise NotFoundException('Pluggable', pluggable_key) # Remove pluggable config from device # TODO: Verify deletion works with actual hub and leaf devices # # Remove pluggable config from device before deleting from memory # Use empty config to signal deletion to the device driver try: pluggable = self.pluggables[pluggable_key] # Create empty config to trigger deletion from common.proto.pluggables_pb2 import PluggableConfig empty_config = PluggableConfig() empty_config.id.device.device_uuid.uuid = device_uuid empty_config.id.pluggable_index = pluggable_index del_config = PluggableConfig() del_config.id.device.device_uuid.uuid = device_uuid del_config.id.pluggable_index = pluggable_index del_config.operational_mode = 0 # Operational mode del_config.channel_name = "channel-1" # Channel name for component group_1 = del_config.dsc_groups.add() group_1.id.pluggable.device.device_uuid.uuid = device_uuid group_1.id.pluggable.pluggable_index = pluggable_index group_1.id.group_index = 0 subcarrier_1 = group_1.subcarriers.add() subcarrier_1.id.group.pluggable.device.device_uuid.uuid = device_uuid subcarrier_1.id.group.pluggable.pluggable_index = pluggable_index subcarrier_1.id.group.group_index = 0 subcarrier_1.id.subcarrier_index = 0 subcarrier_1.active = False # ADD MORE SUBCARRIERS IF NEEDED LOGGER.info(f"Removing configuration from device {device_uuid}") self._push_config_to_device(device_uuid, pluggable_index, empty_config) except Exception as e: LOGGER.error(f"Failed to remove config from device: {e}") self._push_config_to_device(device_uuid, pluggable_index, del_config) except (grpc.RpcError, InvalidArgumentException, OperationFailedException) as e: LOGGER.warning(f"Failed to remove config from device (continuing with memory deletion): {e}") # Continue with deletion from memory even if device config removal fails # Always delete from memory (even if device operation failed) del self.pluggables[pluggable_key] LOGGER.info(f"Deleted pluggable: device={device_uuid}, index={pluggable_index}") LOGGER.info(f"Deleted pluggable from memory: device={device_uuid}, index={pluggable_index}") return Empty() Loading
src/pluggables/service/config_translator.py +4 −5 Original line number Diff line number Diff line Loading @@ -33,18 +33,17 @@ def translate_pluggable_config_to_netconf( 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. Translate PluggableConfig protobuf message to the format expected by OpenConfig. Args: pluggable_config: PluggableConfig message containing DSC groups and subcarriers component_name: Fallback name if channel_name is not specified in config (default: "channel-1") Returns: Dictionary in the format expected by NetConfDriver templates: Dictionary in the format expected by OpenConfig templates: """ if not pluggable_config or not pluggable_config.dsc_groups: LOGGER.warning("Empty pluggable config provided") return { "name": channel_name, "operation": "delete" } if hasattr(pluggable_config, 'channel_name') and pluggable_config.channel_name: Loading @@ -54,11 +53,11 @@ def translate_pluggable_config_to_netconf( 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: if not hasattr(pluggable_config, 'center_frequency_mhz'): raise ValueError("center_frequency_mhz is required and must be greater than 0 in PluggableConfig") center_frequency_mhz = int(pluggable_config.center_frequency_mhz) if not hasattr(pluggable_config, 'operational_mode') or pluggable_config.operational_mode <= 0: if not hasattr(pluggable_config, 'operational_mode'): raise ValueError("operational_mode is required and must be greater than 0 in PluggableConfig") operational_mode = pluggable_config.operational_mode Loading
src/pluggables/tests/test_pluggables_with_SBI.py +23 −23 Original line number Diff line number Diff line Loading @@ -83,14 +83,14 @@ def test_create_pluggable_hub_with_config(pluggables_client: PluggablesClient): _pluggable = pluggables_client.CreatePluggable(_request) assert isinstance(_pluggable, Pluggable) assert _pluggable.id.device.device_uuid.uuid == DEVICE_HUB_UUID assert _pluggable.id.pluggable_index == 2 assert len(_pluggable.config.dsc_groups) == 1 # Should be 1, not 2 (check testmessages.py) assert _pluggable.id.device.device_uuid.uuid == DEVICE_HUB_UUID # pyright: ignore[reportAttributeAccessIssue] assert _pluggable.id.pluggable_index == 2 # pyright: ignore[reportAttributeAccessIssue] assert len(_pluggable.config.dsc_groups) == 1 # Should be 1, not 2 (check testmessages.py) # pyright: ignore[reportAttributeAccessIssue] # Verify DSC group configuration dsc_group = _pluggable.config.dsc_groups[0] assert dsc_group.group_size == 4 # From testmessages.py assert len(dsc_group.subcarriers) == 2 dsc_group = _pluggable.config.dsc_groups[0] # pyright: ignore[reportAttributeAccessIssue] assert dsc_group.group_size == 4 # From testmessages.py # pyright: ignore[reportAttributeAccessIssue] assert len(dsc_group.subcarriers) == 2 # pyright: ignore[reportAttributeAccessIssue] LOGGER.info(f'Created Pluggable on Hub with {len(dsc_group.subcarriers)} subcarriers') Loading @@ -112,9 +112,9 @@ def test_create_pluggable_leaf_with_config(pluggables_client: PluggablesClient): _pluggable = pluggables_client.CreatePluggable(_request) assert isinstance(_pluggable, Pluggable) assert _pluggable.id.device.device_uuid.uuid == DEVICE_LEAF_UUID assert _pluggable.id.pluggable_index == 1 # Should be 1, not 0 assert len(_pluggable.config.dsc_groups) == 1 assert _pluggable.id.device.device_uuid.uuid == DEVICE_LEAF_UUID # pyright: ignore[reportAttributeAccessIssue] assert _pluggable.id.pluggable_index == 1 # Should be 1, not 0 # pyright: ignore[reportAttributeAccessIssue] assert len(_pluggable.config.dsc_groups) == 1 # pyright: ignore[reportAttributeAccessIssue] LOGGER.info(f'Created Pluggable on Leaf: {_pluggable.id}') Loading @@ -131,7 +131,7 @@ def test_configure_pluggable_hub(pluggables_client: PluggablesClient): with_initial_config=False ) _created = pluggables_client.CreatePluggable(_create_request) assert _created.id.pluggable_index == 3 assert _created.id.pluggable_index == 3 # pyright: ignore[reportAttributeAccessIssue] # Now configure it _config_request = create_configure_pluggable_request( Loading @@ -143,14 +143,14 @@ def test_configure_pluggable_hub(pluggables_client: PluggablesClient): _configured = pluggables_client.ConfigurePluggable(_config_request) assert isinstance(_configured, Pluggable) assert _configured.id.device.device_uuid.uuid == DEVICE_HUB_UUID assert _configured.id.pluggable_index == 3 assert len(_configured.config.dsc_groups) == 1 assert _configured.id.device.device_uuid.uuid == DEVICE_HUB_UUID # pyright: ignore[reportAttributeAccessIssue] assert _configured.id.pluggable_index == 3 # pyright: ignore[reportAttributeAccessIssue] assert len(_configured.config.dsc_groups) == 1 # pyright: ignore[reportAttributeAccessIssue] # Verify configuration was applied dsc_group = _configured.config.dsc_groups[0] assert dsc_group.group_size == 2 assert len(dsc_group.subcarriers) == 2 dsc_group = _configured.config.dsc_groups[0] # pyright: ignore[reportAttributeAccessIssue] assert dsc_group.group_size == 2 # pyright: ignore[reportAttributeAccessIssue] assert len(dsc_group.subcarriers) == 2 # pyright: ignore[reportAttributeAccessIssue] LOGGER.info(f'Configured Pluggable on Hub with {len(dsc_group.subcarriers)} subcarriers') Loading Loading @@ -181,9 +181,9 @@ def test_get_pluggable(pluggables_client: PluggablesClient): _retrieved = pluggables_client.GetPluggable(_get_request) assert isinstance(_retrieved, Pluggable) assert _retrieved.id.device.device_uuid.uuid == DEVICE_HUB_UUID assert _retrieved.id.pluggable_index == 4 assert len(_retrieved.config.dsc_groups) == len(_created.config.dsc_groups) assert _retrieved.id.device.device_uuid.uuid == DEVICE_HUB_UUID # pyright: ignore[reportAttributeAccessIssue] assert _retrieved.id.pluggable_index == 4 # pyright: ignore[reportAttributeAccessIssue] assert len(_retrieved.config.dsc_groups) == len(_created.config.dsc_groups) # pyright: ignore[reportAttributeAccessIssue] LOGGER.info(f'Retrieved Pluggable: {_retrieved.id}') Loading @@ -203,8 +203,8 @@ def test_list_pluggables(pluggables_client: PluggablesClient): assert len(_response.pluggables) >= 1 # At least one from previous tests for pluggable in _response.pluggables: assert pluggable.id.device.device_uuid.uuid == DEVICE_HUB_UUID LOGGER.info(f'Found Pluggable: index={pluggable.id.pluggable_index}') assert pluggable.id.device.device_uuid.uuid == DEVICE_HUB_UUID # pyright: ignore[reportAttributeAccessIssue] LOGGER.info(f'Found Pluggable: index={pluggable.id.pluggable_index}') # pyright: ignore[reportAttributeAccessIssue] # Number 7. @pytest.mark.integration Loading @@ -222,7 +222,7 @@ def test_delete_pluggable(pluggables_client: PluggablesClient): with_initial_config=True ) _created = pluggables_client.CreatePluggable(_create_request) assert _created.id.pluggable_index == 2 assert _created.id.pluggable_index == 2 # pyright: ignore[reportAttributeAccessIssue] # Delete it _delete_request = create_delete_pluggable_request( Loading Loading @@ -257,7 +257,7 @@ def test_pluggable_already_exists_error(pluggables_client: PluggablesClient): # Create first time - should succeed _pluggable = pluggables_client.CreatePluggable(_request) assert _pluggable.id.pluggable_index == 3 # Should be 3, not 5 assert _pluggable.id.pluggable_index == 3 # Should be 3, not 5 # pyright: ignore[reportAttributeAccessIssue] # Try to create again - should fail with pytest.raises(grpc.RpcError) as e: Loading