From 4fbe89a1c73343967ef719f491c42a7619b8cd1c Mon Sep 17 00:00:00 2001 From: Ahmad Hassan Date: Thu, 9 Apr 2026 14:26:57 +0500 Subject: [PATCH 1/3] feat: generate ibmstorwizesvc charm --- .../backends/ibmstorwizesvc/__init__.py | 4 + .../backends/ibmstorwizesvc/backend.py | 252 ++++++++++++++++++ .../unit/sunbeam/storage/backends/conftest.py | 10 +- .../sunbeam/storage/backends/test_common.py | 16 +- .../storage/backends/test_ibmstorwizesvc.py | 73 +++++ 5 files changed, 352 insertions(+), 3 deletions(-) create mode 100644 sunbeam-python/sunbeam/storage/backends/ibmstorwizesvc/__init__.py create mode 100644 sunbeam-python/sunbeam/storage/backends/ibmstorwizesvc/backend.py create mode 100644 sunbeam-python/tests/unit/sunbeam/storage/backends/test_ibmstorwizesvc.py diff --git a/sunbeam-python/sunbeam/storage/backends/ibmstorwizesvc/__init__.py b/sunbeam-python/sunbeam/storage/backends/ibmstorwizesvc/__init__.py new file mode 100644 index 000000000..fa430f206 --- /dev/null +++ b/sunbeam-python/sunbeam/storage/backends/ibmstorwizesvc/__init__.py @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: 2026 - Canonical Ltd +# SPDX-License-Identifier: Apache-2.0 + +"""IBM Storwize SVC backend for Sunbeam storage.""" diff --git a/sunbeam-python/sunbeam/storage/backends/ibmstorwizesvc/backend.py b/sunbeam-python/sunbeam/storage/backends/ibmstorwizesvc/backend.py new file mode 100644 index 000000000..584a3e5e3 --- /dev/null +++ b/sunbeam-python/sunbeam/storage/backends/ibmstorwizesvc/backend.py @@ -0,0 +1,252 @@ +# SPDX-FileCopyrightText: 2026 - Canonical Ltd +# SPDX-License-Identifier: Apache-2.0 +# ruff: noqa: E501 + +"""StorwizeSVC FC backend implementation using base step classes.""" + +import logging +from enum import StrEnum +from typing import Annotated + +from pydantic import Field +from rich.console import Console + +from sunbeam.core.manifest import StorageBackendConfig +from sunbeam.storage.base import StorageBackendBase +from sunbeam.storage.models import SecretDictField + +LOG = logging.getLogger(__name__) +console = Console() + + +class Protocol(StrEnum): + """Enumeration of valid protocol types.""" + + FC = "fc" + ISCSI = "iscsi" + + +class IbmstorwizesvcConfig(StorageBackendConfig): + """Configuration model for StorwizeSVC FC backend. + + This model includes ALL configuration options for the backend. + Additional configuration can be managed dynamically through the charm. + """ + + # Mandatory connection parameters + san_ip: Annotated[str, Field(description="IP address of SAN controller")] + san_login: Annotated[ + str, + Field(description="Username for SAN controller"), + SecretDictField(field="san-login"), + ] + san_password: Annotated[ + str, + Field(description="Password for SAN controller"), + SecretDictField(field="san-password"), + ] + + # Optional backend configuration + protocol: Annotated[ + Protocol | None, + Field(description="Protocol selector: fc, iscsi."), + ] = None + storwize_svc_volpool_name: Annotated[ + str | None, + Field( + description="Comma separated list of storage system storage pools for volumes." + ), + ] = None + storwize_svc_vol_rsize: Annotated[ + int | None, + Field( + description="Storage system space-efficiency parameter for volumes (percentage)" + ), + ] = None + storwize_svc_vol_warning: Annotated[ + int | None, + Field( + description="Storage system threshold for volume capacity warnings (percentage)" + ), + ] = None + storwize_svc_vol_autoexpand: Annotated[ + bool | None, + Field( + description="Storage system autoexpand parameter for volumes (True/False)" + ), + ] = None + storwize_svc_vol_grainsize: Annotated[ + int | None, + Field( + description="Storage system grain size parameter for volumes (8/32/64/128/256)" + ), + ] = None + storwize_svc_vol_compression: Annotated[ + bool | None, + Field(description="Storage system compression option for volumes"), + ] = None + storwize_svc_vol_easytier: Annotated[ + bool | None, + Field(description="Enable Easy Tier for volumes"), + ] = None + storwize_svc_vol_iogrp: Annotated[ + str | None, + Field( + description="The I/O group in which to allocate volumes. It can be a comma-separated list." + ), + ] = None + storwize_svc_flashcopy_timeout: Annotated[ + int | None, + Field( + description="Maximum number of seconds to wait for FlashCopy to be prepared." + ), + ] = None + storwize_svc_allow_tenant_qos: Annotated[ + bool | None, + Field(description="Allow tenants to specify QOS on create"), + ] = None + storwize_svc_stretched_cluster_partner: Annotated[ + str | None, + Field( + description="If operating in stretched cluster mode, specify the name of the pool in which mirrored copies are stored." + ), + ] = None + storwize_san_secondary_ip: Annotated[ + str | None, + Field( + description="Specifies secondary management IP or hostname to be used if san_ip is invalid or becomes inaccessible." + ), + ] = None + storwize_svc_vol_nofmtdisk: Annotated[ + bool | None, + Field( + description="Specifies that the volume not be formatted during creation." + ), + ] = None + storwize_svc_flashcopy_rate: Annotated[ + int | None, + Field( + description="Specifies the Storwize FlashCopy copy rate to be used when creating a full volume copy." + ), + ] = None + storwize_svc_clean_rate: Annotated[ + int | None, + Field(description="Specifies the Storwize cleaning rate for the mapping."), + ] = None + storwize_svc_mirror_pool: Annotated[ + str | None, + Field( + description="Specifies the name of the pool in which mirrored copy is stored." + ), + ] = None + storwize_svc_aux_mirror_pool: Annotated[ + str | None, + Field( + description="Specifies the name of the pool in which mirrored copy is stored for aux volume." + ), + ] = None + storwize_portset: Annotated[ + str | None, + Field( + description="Specifies the name of the portset in which the host is to be created." + ), + ] = None + storwize_svc_src_child_pool: Annotated[ + str | None, + Field( + description="Specifies the name of the source child pool in which global mirror source change volume is stored." + ), + ] = None + storwize_svc_target_child_pool: Annotated[ + str | None, + Field( + description="Specifies the name of the target child pool in which global mirror auxiliary change volume is stored." + ), + ] = None + storwize_peer_pool: Annotated[ + str | None, + Field( + description="Specifies the name of the peer pool for hyperswap volume, the peer pool must exist on the other site." + ), + ] = None + storwize_preferred_host_site: Annotated[ + str | None, + Field( + description="Specifies the site information for host. One WWPN or multi WWPNs used in the host can be specified." + ), + ] = None + cycle_period_seconds: Annotated[ + int | None, + Field( + description="This defines an optional cycle period that applies to Global Mirror relationships with a cycling mode of multi." + ), + ] = None + storwize_svc_retain_aux_volume: Annotated[ + bool | None, + Field( + description="Enable or disable retaining of aux volume on secondary storage during delete of the volume on primary storage." + ), + ] = None + migrate_from_flashcopy: Annotated[ + bool | None, + Field( + description="Parameter to allow or prevent volumes with legacy FlashCopy mappings to be part of volume_group_enabled and temporary_volume_group_enabled groups." + ), + ] = None + storwize_svc_multipath_enabled: Annotated[ + bool | None, + Field( + description="Connect with multipath (FC only; iSCSI multipath is controlled by Nova)" + ), + ] = None + storwize_svc_iscsi_chap_enabled: Annotated[ + bool | None, + Field( + description="Configure CHAP authentication for iSCSI connections (Default: Enabled)" + ), + ] = None + san_thin_provision: Annotated[ + bool | None, + Field(description="Use thin provisioning for SAN volumes?"), + ] = None + use_multipath_for_image_xfer: Annotated[ + bool | None, + Field(description="Enable multipathing for image transfer operations."), + ] = None + + +class IbmstorwizesvcBackend(StorageBackendBase): + """StorwizeSVC FC backend implementation.""" + + backend_type = "ibmstorwizesvc" + display_name = "StorwizeSVC FC" + generally_available = True + + @property + def charm_name(self) -> str: + """Return the charm application name.""" + return "cinder-volume-ibmstorwizesvc" + + @property + def charm_channel(self) -> str: + """Return the default charm channel.""" + return "latest/edge" + + @property + def charm_revision(self) -> str | None: + """Return a pinned charm revision, if any.""" + return None + + @property + def charm_base(self) -> str: + """Return the target base for this charm.""" + return "ubuntu@24.04" + + @property + def supports_ha(self) -> bool: + """Whether this backend supports HA deployments.""" + return False + + def config_type(self) -> type[StorageBackendConfig]: + """Return the configuration model type for this backend.""" + return IbmstorwizesvcConfig diff --git a/sunbeam-python/tests/unit/sunbeam/storage/backends/conftest.py b/sunbeam-python/tests/unit/sunbeam/storage/backends/conftest.py index 80d3d8ef5..5d8f85222 100644 --- a/sunbeam-python/tests/unit/sunbeam/storage/backends/conftest.py +++ b/sunbeam-python/tests/unit/sunbeam/storage/backends/conftest.py @@ -8,6 +8,7 @@ from sunbeam.storage.backends.dellpowerstore.backend import DellPowerstoreBackend from sunbeam.storage.backends.dellsc.backend import DellSCBackend from sunbeam.storage.backends.hitachi.backend import HitachiBackend +from sunbeam.storage.backends.ibmstorwizesvc.backend import IbmstorwizesvcBackend from sunbeam.storage.backends.purestorage.backend import PureStorageBackend @@ -29,19 +30,26 @@ def dellsc_backend(): return DellSCBackend() +@pytest.fixture +def ibmstorwizesvc_backend(): + """Provide an IBM Storwize SVC backend instance.""" + return IbmstorwizesvcBackend() + + @pytest.fixture def dellpowerstore_backend(): """Provide a Dell PowerStore backend instance.""" return DellPowerstoreBackend() -@pytest.fixture(params=["hitachi", "purestorage", "dellsc", "dellpowerstore"]) +@pytest.fixture(params=["hitachi", "purestorage", "dellsc", "ibmstorwizesvc", "dellpowerstore"]) def any_backend(request): """Parametrized fixture that provides each backend type.""" backends = { "hitachi": HitachiBackend(), "purestorage": PureStorageBackend(), "dellsc": DellSCBackend(), + "ibmstorwizesvc": IbmstorwizesvcBackend(), "dellpowerstore": DellPowerstoreBackend(), } return backends[request.param] diff --git a/sunbeam-python/tests/unit/sunbeam/storage/backends/test_common.py b/sunbeam-python/tests/unit/sunbeam/storage/backends/test_common.py index def51a0e8..2fd8e0fe1 100644 --- a/sunbeam-python/tests/unit/sunbeam/storage/backends/test_common.py +++ b/sunbeam-python/tests/unit/sunbeam/storage/backends/test_common.py @@ -157,13 +157,18 @@ def backend(self, any_backend): def test_all_backends_have_unique_types( - hitachi_backend, purestorage_backend, dellsc_backend, dellpowerstore_backend + hitachi_backend, purestorage_backend, dellsc_backend, ibmstorwizesvc_backend, dellpowerstore_backend ): """Test that all backends have unique type identifiers.""" backends = [ + hitachi_backend, + purestorage_backend, + dellsc_backend, + ibmstorwizesvc_backend, + , dellpowerstore_backend, ] types = [b.backend_type for b in backends] @@ -173,13 +178,18 @@ def test_all_backends_have_unique_types( def test_all_backends_have_unique_charm_names( - hitachi_backend, purestorage_backend, dellsc_backend, dellpowerstore_backend + hitachi_backend, purestorage_backend, dellsc_backend, ibmstorwizesvc_backend, dellpowerstore_backend ): """Test that all backends have unique charm names.""" backends = [ + hitachi_backend, + purestorage_backend, + dellsc_backend, + ibmstorwizesvc_backend, + , dellpowerstore_backend, ] charm_names = [b.charm_name for b in backends] @@ -196,6 +206,7 @@ def test_all_backends_have_unique_charm_names( ("hitachi", "hitachi"), ("purestorage", "purestorage"), ("dellsc", "dellsc"), + ("ibmstorwizesvc", "ibmstorwizesvc"), ("dellpowerstore", "dellpowerstore"), ], ) @@ -211,6 +222,7 @@ def test_backend_types_match_expected(any_backend, backend_type, expected_type): ("hitachi", "cinder-volume-hitachi"), ("purestorage", "cinder-volume-purestorage"), ("dellsc", "cinder-volume-dellsc"), + ("ibmstorwizesvc", "cinder-volume-ibmstorwizesvc"), ("dellpowerstore", "cinder-volume-dellpowerstore"), ], ) diff --git a/sunbeam-python/tests/unit/sunbeam/storage/backends/test_ibmstorwizesvc.py b/sunbeam-python/tests/unit/sunbeam/storage/backends/test_ibmstorwizesvc.py new file mode 100644 index 000000000..e3f4e4c99 --- /dev/null +++ b/sunbeam-python/tests/unit/sunbeam/storage/backends/test_ibmstorwizesvc.py @@ -0,0 +1,73 @@ +# SPDX-FileCopyrightText: 2026 - Canonical Ltd +# SPDX-License-Identifier: Apache-2.0 + +"""Tests for IBM Storwize SVC backend.""" + +import pytest +from pydantic import ValidationError + +from sunbeam.storage.models import SecretDictField +from tests.unit.sunbeam.storage.backends.test_common import BaseBackendTests + + +class TestIbmstorwizesvcBackend(BaseBackendTests): + """Tests for IBM Storwize SVC backend.""" + + @pytest.fixture + def backend(self, ibmstorwizesvc_backend): + """Provide IBM Storwize SVC backend instance.""" + return ibmstorwizesvc_backend + + def test_backend_type_is_ibmstorwizesvc(self, backend): + """Test that backend type is 'ibmstorwizesvc'.""" + assert backend.backend_type == "ibmstorwizesvc" + + def test_charm_name_is_ibmstorwizesvc_charm(self, backend): + """Test that charm name is cinder-volume-ibmstorwizesvc.""" + assert backend.charm_name == "cinder-volume-ibmstorwizesvc" + + def test_config_has_required_fields(self, backend): + """Test that IBM Storwize SVC config has required fields.""" + fields = backend.config_type().model_fields + for field in ("san_ip", "san_login", "san_password", "protocol"): + assert field in fields, f"Required field {field} not found in config" + + def test_san_credentials_are_secret(self, backend): + """Test that SAN login and password are marked as secrets.""" + config_class = backend.config_type() + for field_name in ("san_login", "san_password"): + field = config_class.model_fields.get(field_name) + assert field is not None + assert any(isinstance(m, SecretDictField) for m in field.metadata), ( + f"{field_name} should be marked as secret" + ) + + +class TestIbmstorwizesvcConfigValidation: + """Test IBM Storwize SVC config validation behavior.""" + + def test_protocol_rejects_invalid_value(self, ibmstorwizesvc_backend): + """Test that protocol rejects values other than fc/iscsi.""" + config_class = ibmstorwizesvc_backend.config_type() + with pytest.raises(ValidationError): + config_class.model_validate( + { + "san-ip": "192.168.1.1", + "san-login": "admin", + "san-password": "secret", + "protocol": "nvme", + } + ) + + def test_protocol_accepts_fc(self, ibmstorwizesvc_backend): + """Test that protocol accepts fc.""" + config_class = ibmstorwizesvc_backend.config_type() + config = config_class.model_validate( + { + "san-ip": "192.168.1.1", + "san-login": "admin", + "san-password": "secret", + "protocol": "fc", + } + ) + assert config.protocol == "fc" From d87d2a89da7a5d0be62ef4e6b111c2a2e9918405 Mon Sep 17 00:00:00 2001 From: Ahmad Hassan Date: Thu, 9 Apr 2026 15:17:03 +0500 Subject: [PATCH 2/3] align naming, docs, and lint compliance --- .../backends/ibmstorwizesvc/backend.py | 113 +++++++++++++----- .../unit/sunbeam/storage/backends/conftest.py | 6 +- .../sunbeam/storage/backends/test_common.py | 2 - 3 files changed, 87 insertions(+), 34 deletions(-) diff --git a/sunbeam-python/sunbeam/storage/backends/ibmstorwizesvc/backend.py b/sunbeam-python/sunbeam/storage/backends/ibmstorwizesvc/backend.py index 584a3e5e3..c3b588fd0 100644 --- a/sunbeam-python/sunbeam/storage/backends/ibmstorwizesvc/backend.py +++ b/sunbeam-python/sunbeam/storage/backends/ibmstorwizesvc/backend.py @@ -1,8 +1,7 @@ # SPDX-FileCopyrightText: 2026 - Canonical Ltd # SPDX-License-Identifier: Apache-2.0 -# ruff: noqa: E501 -"""StorwizeSVC FC backend implementation using base step classes.""" +"""Storwize SVC backend implementation using base step classes.""" import logging from enum import StrEnum @@ -26,8 +25,8 @@ class Protocol(StrEnum): ISCSI = "iscsi" -class IbmstorwizesvcConfig(StorageBackendConfig): - """Configuration model for StorwizeSVC FC backend. +class IbmStorwizeSVCConfig(StorageBackendConfig): + """Configuration model for Storwize SVC backend. This model includes ALL configuration options for the backend. Additional configuration can be managed dynamically through the charm. @@ -54,19 +53,25 @@ class IbmstorwizesvcConfig(StorageBackendConfig): storwize_svc_volpool_name: Annotated[ str | None, Field( - description="Comma separated list of storage system storage pools for volumes." + description=( + "Comma-separated list of storage system storage pools for volumes." + ) ), ] = None storwize_svc_vol_rsize: Annotated[ int | None, Field( - description="Storage system space-efficiency parameter for volumes (percentage)" + description=( + "Storage system space-efficiency parameter for volumes (percentage)" + ) ), ] = None storwize_svc_vol_warning: Annotated[ int | None, Field( - description="Storage system threshold for volume capacity warnings (percentage)" + description=( + "Storage system threshold for volume capacity warnings (percentage)" + ) ), ] = None storwize_svc_vol_autoexpand: Annotated[ @@ -78,7 +83,9 @@ class IbmstorwizesvcConfig(StorageBackendConfig): storwize_svc_vol_grainsize: Annotated[ int | None, Field( - description="Storage system grain size parameter for volumes (8/32/64/128/256)" + description=( + "Storage system grain size parameter for volumes (8/32/64/128/256)" + ) ), ] = None storwize_svc_vol_compression: Annotated[ @@ -92,13 +99,18 @@ class IbmstorwizesvcConfig(StorageBackendConfig): storwize_svc_vol_iogrp: Annotated[ str | None, Field( - description="The I/O group in which to allocate volumes. It can be a comma-separated list." + description=( + "The I/O group in which to allocate volumes. " + "It can be a comma-separated list." + ) ), ] = None storwize_svc_flashcopy_timeout: Annotated[ int | None, Field( - description="Maximum number of seconds to wait for FlashCopy to be prepared." + description=( + "Maximum number of seconds to wait for FlashCopy to be prepared." + ) ), ] = None storwize_svc_allow_tenant_qos: Annotated[ @@ -108,13 +120,19 @@ class IbmstorwizesvcConfig(StorageBackendConfig): storwize_svc_stretched_cluster_partner: Annotated[ str | None, Field( - description="If operating in stretched cluster mode, specify the name of the pool in which mirrored copies are stored." + description=( + "If operating in stretched cluster mode, specify " + "the name of the pool in which mirrored copies are stored." + ) ), ] = None storwize_san_secondary_ip: Annotated[ str | None, Field( - description="Specifies secondary management IP or hostname to be used if san_ip is invalid or becomes inaccessible." + description=( + "Specifies secondary management IP or hostname " + "to be used if san_ip is invalid or inaccessible." + ) ), ] = None storwize_svc_vol_nofmtdisk: Annotated[ @@ -126,7 +144,10 @@ class IbmstorwizesvcConfig(StorageBackendConfig): storwize_svc_flashcopy_rate: Annotated[ int | None, Field( - description="Specifies the Storwize FlashCopy copy rate to be used when creating a full volume copy." + description=( + "Specifies the Storwize FlashCopy copy rate to " + "be used when creating a full volume copy." + ) ), ] = None storwize_svc_clean_rate: Annotated[ @@ -136,73 +157,107 @@ class IbmstorwizesvcConfig(StorageBackendConfig): storwize_svc_mirror_pool: Annotated[ str | None, Field( - description="Specifies the name of the pool in which mirrored copy is stored." + description=( + "Specifies the name of the pool in which mirrored copy is stored." + ) ), ] = None storwize_svc_aux_mirror_pool: Annotated[ str | None, Field( - description="Specifies the name of the pool in which mirrored copy is stored for aux volume." + description=( + "Specifies the name of the pool in which mirrored " + "copy is stored for aux volume." + ) ), ] = None storwize_portset: Annotated[ str | None, Field( - description="Specifies the name of the portset in which the host is to be created." + description=( + "Specifies the name of the portset in which the host is to be created." + ) ), ] = None storwize_svc_src_child_pool: Annotated[ str | None, Field( - description="Specifies the name of the source child pool in which global mirror source change volume is stored." + description=( + "Specifies the source child pool for global " + "mirror source change volume storage." + ) ), ] = None storwize_svc_target_child_pool: Annotated[ str | None, Field( - description="Specifies the name of the target child pool in which global mirror auxiliary change volume is stored." + description=( + "Specifies the target child pool for global mirror " + "auxiliary change volume storage." + ) ), ] = None storwize_peer_pool: Annotated[ str | None, Field( - description="Specifies the name of the peer pool for hyperswap volume, the peer pool must exist on the other site." + description=( + "Specifies the peer pool for hyperswap volume; " + "the peer pool must exist on the other site." + ) ), ] = None storwize_preferred_host_site: Annotated[ str | None, Field( - description="Specifies the site information for host. One WWPN or multi WWPNs used in the host can be specified." + description=( + "Specifies site information for host. One WWPN " + "or multiple WWPNs used in the host can be specified." + ) ), ] = None cycle_period_seconds: Annotated[ int | None, Field( - description="This defines an optional cycle period that applies to Global Mirror relationships with a cycling mode of multi." + description=( + "Defines an optional cycle period that applies to " + "Global Mirror relationships with cycling mode multi." + ) ), ] = None storwize_svc_retain_aux_volume: Annotated[ bool | None, Field( - description="Enable or disable retaining of aux volume on secondary storage during delete of the volume on primary storage." + description=( + "Enable or disable retaining aux volume on " + "secondary storage when deleting the primary volume." + ) ), ] = None migrate_from_flashcopy: Annotated[ bool | None, Field( - description="Parameter to allow or prevent volumes with legacy FlashCopy mappings to be part of volume_group_enabled and temporary_volume_group_enabled groups." + description=( + "Allow or prevent volumes with legacy FlashCopy " + "mappings from joining volume group features." + ) ), ] = None storwize_svc_multipath_enabled: Annotated[ bool | None, Field( - description="Connect with multipath (FC only; iSCSI multipath is controlled by Nova)" + description=( + "Connect with multipath (FC only; iSCSI " + "multipath is controlled by Nova)." + ) ), ] = None storwize_svc_iscsi_chap_enabled: Annotated[ bool | None, Field( - description="Configure CHAP authentication for iSCSI connections (Default: Enabled)" + description=( + "Configure CHAP authentication for iSCSI " + "connections (default: enabled)." + ) ), ] = None san_thin_provision: Annotated[ @@ -215,11 +270,11 @@ class IbmstorwizesvcConfig(StorageBackendConfig): ] = None -class IbmstorwizesvcBackend(StorageBackendBase): - """StorwizeSVC FC backend implementation.""" +class IbmStorwizeSVCBackend(StorageBackendBase): + """Storwize SVC backend implementation.""" backend_type = "ibmstorwizesvc" - display_name = "StorwizeSVC FC" + display_name = "IBM Storwize SVC" generally_available = True @property @@ -249,4 +304,4 @@ def supports_ha(self) -> bool: def config_type(self) -> type[StorageBackendConfig]: """Return the configuration model type for this backend.""" - return IbmstorwizesvcConfig + return IbmStorwizeSVCConfig diff --git a/sunbeam-python/tests/unit/sunbeam/storage/backends/conftest.py b/sunbeam-python/tests/unit/sunbeam/storage/backends/conftest.py index 5d8f85222..73c296084 100644 --- a/sunbeam-python/tests/unit/sunbeam/storage/backends/conftest.py +++ b/sunbeam-python/tests/unit/sunbeam/storage/backends/conftest.py @@ -8,7 +8,7 @@ from sunbeam.storage.backends.dellpowerstore.backend import DellPowerstoreBackend from sunbeam.storage.backends.dellsc.backend import DellSCBackend from sunbeam.storage.backends.hitachi.backend import HitachiBackend -from sunbeam.storage.backends.ibmstorwizesvc.backend import IbmstorwizesvcBackend +from sunbeam.storage.backends.ibmstorwizesvc.backend import IbmStorwizeSVCBackend from sunbeam.storage.backends.purestorage.backend import PureStorageBackend @@ -33,7 +33,7 @@ def dellsc_backend(): @pytest.fixture def ibmstorwizesvc_backend(): """Provide an IBM Storwize SVC backend instance.""" - return IbmstorwizesvcBackend() + return IbmStorwizeSVCBackend() @pytest.fixture @@ -49,7 +49,7 @@ def any_backend(request): "hitachi": HitachiBackend(), "purestorage": PureStorageBackend(), "dellsc": DellSCBackend(), - "ibmstorwizesvc": IbmstorwizesvcBackend(), + "ibmstorwizesvc": IbmStorwizeSVCBackend(), "dellpowerstore": DellPowerstoreBackend(), } return backends[request.param] diff --git a/sunbeam-python/tests/unit/sunbeam/storage/backends/test_common.py b/sunbeam-python/tests/unit/sunbeam/storage/backends/test_common.py index 2fd8e0fe1..065f5e492 100644 --- a/sunbeam-python/tests/unit/sunbeam/storage/backends/test_common.py +++ b/sunbeam-python/tests/unit/sunbeam/storage/backends/test_common.py @@ -168,7 +168,6 @@ def test_all_backends_have_unique_types( dellsc_backend, ibmstorwizesvc_backend, - , dellpowerstore_backend, ] types = [b.backend_type for b in backends] @@ -189,7 +188,6 @@ def test_all_backends_have_unique_charm_names( dellsc_backend, ibmstorwizesvc_backend, - , dellpowerstore_backend, ] charm_names = [b.charm_name for b in backends] From cc1b6056952857485a9815415ebd5147b75e0f21 Mon Sep 17 00:00:00 2001 From: Ahmad Hassan Date: Tue, 21 Apr 2026 12:29:35 +0500 Subject: [PATCH 3/3] fix: test/lint issues resolve --- .../unit/sunbeam/storage/backends/conftest.py | 4 +++- .../sunbeam/storage/backends/test_common.py | 18 ++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/sunbeam-python/tests/unit/sunbeam/storage/backends/conftest.py b/sunbeam-python/tests/unit/sunbeam/storage/backends/conftest.py index 73c296084..904fd4888 100644 --- a/sunbeam-python/tests/unit/sunbeam/storage/backends/conftest.py +++ b/sunbeam-python/tests/unit/sunbeam/storage/backends/conftest.py @@ -42,7 +42,9 @@ def dellpowerstore_backend(): return DellPowerstoreBackend() -@pytest.fixture(params=["hitachi", "purestorage", "dellsc", "ibmstorwizesvc", "dellpowerstore"]) +@pytest.fixture( + params=["hitachi", "purestorage", "dellsc", "ibmstorwizesvc", "dellpowerstore"] +) def any_backend(request): """Parametrized fixture that provides each backend type.""" backends = { diff --git a/sunbeam-python/tests/unit/sunbeam/storage/backends/test_common.py b/sunbeam-python/tests/unit/sunbeam/storage/backends/test_common.py index 065f5e492..9348e81c5 100644 --- a/sunbeam-python/tests/unit/sunbeam/storage/backends/test_common.py +++ b/sunbeam-python/tests/unit/sunbeam/storage/backends/test_common.py @@ -157,15 +157,16 @@ def backend(self, any_backend): def test_all_backends_have_unique_types( - hitachi_backend, purestorage_backend, dellsc_backend, ibmstorwizesvc_backend, dellpowerstore_backend + hitachi_backend, + purestorage_backend, + dellsc_backend, + ibmstorwizesvc_backend, + dellpowerstore_backend, ): """Test that all backends have unique type identifiers.""" backends = [ - hitachi_backend, - purestorage_backend, - dellsc_backend, ibmstorwizesvc_backend, dellpowerstore_backend, @@ -177,15 +178,16 @@ def test_all_backends_have_unique_types( def test_all_backends_have_unique_charm_names( - hitachi_backend, purestorage_backend, dellsc_backend, ibmstorwizesvc_backend, dellpowerstore_backend + hitachi_backend, + purestorage_backend, + dellsc_backend, + ibmstorwizesvc_backend, + dellpowerstore_backend, ): """Test that all backends have unique charm names.""" backends = [ - hitachi_backend, - purestorage_backend, - dellsc_backend, ibmstorwizesvc_backend, dellpowerstore_backend,