From 56fc2b24cb9b8fa79c48511e5ccc00975d572127 Mon Sep 17 00:00:00 2001 From: Ahmad Hassan Date: Tue, 14 Apr 2026 11:57:12 +0500 Subject: [PATCH 1/3] feat: generate netapp charm --- .../storage/backends/netapp/__init__.py | 4 + .../storage/backends/netapp/backend.py | 349 ++++++++++++++++++ .../unit/sunbeam/storage/backends/conftest.py | 10 +- .../sunbeam/storage/backends/test_common.py | 10 +- .../sunbeam/storage/backends/test_netapp.py | 76 ++++ 5 files changed, 444 insertions(+), 5 deletions(-) create mode 100644 sunbeam-python/sunbeam/storage/backends/netapp/__init__.py create mode 100644 sunbeam-python/sunbeam/storage/backends/netapp/backend.py create mode 100644 sunbeam-python/tests/unit/sunbeam/storage/backends/test_netapp.py diff --git a/sunbeam-python/sunbeam/storage/backends/netapp/__init__.py b/sunbeam-python/sunbeam/storage/backends/netapp/__init__.py new file mode 100644 index 000000000..12d9d1c64 --- /dev/null +++ b/sunbeam-python/sunbeam/storage/backends/netapp/__init__.py @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: 2026 - Canonical Ltd +# SPDX-License-Identifier: Apache-2.0 + +"""NetApp backend for Sunbeam storage.""" diff --git a/sunbeam-python/sunbeam/storage/backends/netapp/backend.py b/sunbeam-python/sunbeam/storage/backends/netapp/backend.py new file mode 100644 index 000000000..8688efb44 --- /dev/null +++ b/sunbeam-python/sunbeam/storage/backends/netapp/backend.py @@ -0,0 +1,349 @@ +# SPDX-FileCopyrightText: 2026 - Canonical Ltd +# SPDX-License-Identifier: Apache-2.0 +# ruff: noqa: E501 + +"""NetApp ONTAP backend implementation using base step classes.""" + +import logging +from enum import StrEnum +from typing import Annotated, Literal + +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 Family(StrEnum): + """Enumeration of valid storage family types.""" + + ONTAP_CLUSTER = "ontap_cluster" + + +class TransportType(StrEnum): + """Enumeration of valid transport types.""" + + HTTP = "http" + HTTPS = "https" + + +class LunSpaceReservation(StrEnum): + """Enumeration of valid LUN space reservation options.""" + + ENABLED = "enabled" + DISABLED = "disabled" + + +class NetAppConfig(StorageBackendConfig): + """Configuration model for NetApp ONTAP 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="Storage array management IP address or hostname.") + ] + + # Protocol selection + protocol: Annotated[ + Literal["iscsi", "nvme"], + Field(description="Protocol selector: iscsi, nvme."), + ] + + # Optional backend configuration + netapp_storage_family: Annotated[ + Family | None, + Field(description="The storage family type used on the storage system."), + ] = None + + netapp_storage_protocol: Annotated[ + Literal["iscsi", "fc", "nfs", "nvme"] | None, + Field( + description="The storage protocol to be used on the data path with the storage system." + ), + ] = None + + netapp_server_hostname: Annotated[ + str | None, + Field( + description="The hostname (or IP address) for the storage system or proxy server." + ), + ] = None + + netapp_server_port: Annotated[ + int | None, + Field( + description="The TCP port to use for communication with the storage system or proxy server." + ), + ] = None + + netapp_use_legacy_client: Annotated[ + bool | None, + Field( + description="Select which ONTAP client to use for retrieving and modifying data on the storage." + ), + ] = None + + netapp_async_rest_timeout: Annotated[ + int | None, + Field( + description="The maximum time in seconds to wait for completing a REST asynchronous operation." + ), + ] = None + + netapp_transport_type: Annotated[ + TransportType | None, + Field( + description="The transport protocol used when communicating with the storage system or proxy server." + ), + ] = None + + netapp_ssl_cert_path: Annotated[ + str | None, + Field( + description="The path to a CA_BUNDLE file or directory with certificates of trusted CA." + ), + ] = None + + netapp_login: Annotated[ + str | None, + Field( + description="Administrative user account name used to access the storage system or proxy server." + ), + ] = None + + netapp_password: Annotated[ + str | None, + Field( + description="Password for the administrative user account specified in the netapp_login option." + ), + SecretDictField(field="netapp-password"), + ] = None + + netapp_private_key_file: Annotated[ + str | None, + Field( + description="Absolute path to the file containing the private key associated with the certificate." + ), + SecretDictField(field="netapp-private-key-file"), + ] = None + + netapp_certificate_file: Annotated[ + str | None, + Field( + description="Absolute path to the file containing the digital certificate." + ), + SecretDictField(field="netapp-certificate-file"), + ] = None + + netapp_ca_certificate_file: Annotated[ + str, + Field( + description="Absolute path to the file containing the public key certificate of the trusted CA." + ), + SecretDictField(field="netapp-ca-certificate-file"), + ] + + netapp_certificate_host_validation: Annotated[ + bool | None, + Field(description="Enable certificate verification for host validation."), + ] = None + + netapp_size_multiplier: Annotated[ + str | None, + Field( + description="Multiplier for requested volume size to ensure enough space." + ), + ] = None + + netapp_lun_space_reservation: Annotated[ + LunSpaceReservation | None, + Field( + description="Determines if storage space is reserved for LUN allocation." + ), + ] = None + + netapp_driver_reports_provisioned_capacity: Annotated[ + bool | None, + Field( + description="Enable querying of storage system to calculate volumes provisioned size." + ), + ] = None + + netapp_vserver: Annotated[ + str | None, + Field( + description="Virtual storage server (Vserver) name on the storage cluster." + ), + ] = None + + netapp_disaggregated_platform: Annotated[ + bool | None, + Field(description="Enable ASA r2 workflows for NetApp disaggregated platform."), + ] = None + + netapp_nfs_image_cache_cleanup_interval: Annotated[ + int | None, + Field(description="Time in seconds between NFS image cache cleanup tasks."), + ] = None + + thres_avl_size_perc_start: Annotated[ + int | None, + Field( + description="Percentage of available space for an NFS share to trigger cache cleaning." + ), + ] = None + + thres_avl_size_perc_stop: Annotated[ + int | None, + Field( + description="Percentage of available space on an NFS share to stop cache cleaning." + ), + ] = None + + expiry_thres_minutes: Annotated[ + int | None, + Field( + description="Threshold for last access time for images in the NFS image cache." + ), + ] = None + + netapp_lun_ostype: Annotated[ + str | None, + Field( + description="Type of operating system that will access a LUN exported from Data ONTAP." + ), + ] = None + + netapp_namespace_ostype: Annotated[ + str | None, + Field( + description="Type of operating system that will access a namespace exported from Data ONTAP." + ), + ] = None + + netapp_host_type: Annotated[ + str | None, + Field( + description="Type of operating system for all initiators that can access a LUN." + ), + ] = None + + netapp_pool_name_search_pattern: Annotated[ + str | None, + Field( + description="Regular expression to restrict provisioning to specified pools." + ), + ] = None + + netapp_lun_clone_busy_timeout: Annotated[ + int | None, + Field( + description="Maximum time to retry LUN clone operation when device busy error occurs." + ), + ] = None + + netapp_lun_clone_busy_interval: Annotated[ + int | None, + Field( + description="Time interval to retry LUN clone operation when device busy error occurs." + ), + ] = None + + netapp_dedupe_cache_expiry_duration: Annotated[ + int | None, + Field( + description="Time interval between updates of netapp_dedupe_used_percent for ONTAP backend pools." + ), + ] = None + + netapp_performance_cache_expiry_duration: Annotated[ + int | None, + Field( + description="Time interval between updates of performance utilization for ONTAP backend pools." + ), + ] = None + + netapp_replication_aggregate_map: Annotated[ + str | None, + Field( + description="Aggregate mapping between source and destination back ends for replication." + ), + ] = None + + netapp_snapmirror_quiesce_timeout: Annotated[ + int | None, + Field( + description="Maximum time to wait for existing SnapMirror transfers to complete before aborting." + ), + ] = None + + netapp_replication_volume_online_timeout: Annotated[ + int | None, + Field( + description="Time to wait for a replication volume create to complete and go online." + ), + ] = None + + netapp_replication_policy: Annotated[ + str | None, + Field( + description="Replication policy to be used while creating snapmirror relationship." + ), + ] = None + + netapp_api_trace_pattern: Annotated[ + str | None, + Field(description="Regular expression to limit the API tracing."), + ] = None + + netapp_migrate_volume_timeout: Annotated[ + int | None, + Field( + description="Time to wait for storage assisted volume migration to complete." + ), + ] = None + + +class NetAppBackend(StorageBackendBase): + """NetApp ONTAP backend implementation.""" + + backend_type = "netapp" + display_name = "NetApp ONTAP" + generally_available = True + + @property + def charm_name(self) -> str: + """Return the charm application name.""" + return "cinder-volume-netapp" + + @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 True + + def config_type(self) -> type[StorageBackendConfig]: + """Return the configuration model type for this backend.""" + return NetAppConfig diff --git a/sunbeam-python/tests/unit/sunbeam/storage/backends/conftest.py b/sunbeam-python/tests/unit/sunbeam/storage/backends/conftest.py index 80d3d8ef5..d50f55b01 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.netapp.backend import NetAppBackend from sunbeam.storage.backends.purestorage.backend import PureStorageBackend @@ -29,19 +30,26 @@ def dellsc_backend(): return DellSCBackend() +@pytest.fixture +def netapp_backend(): + """Provide a NetApp backend instance.""" + return NetAppBackend() + + @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", "netapp", "dellpowerstore"]) def any_backend(request): """Parametrized fixture that provides each backend type.""" backends = { "hitachi": HitachiBackend(), "purestorage": PureStorageBackend(), "dellsc": DellSCBackend(), + "netapp": NetAppBackend(), "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..b5569479d 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,13 @@ 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, netapp_backend, dellpowerstore_backend ): """Test that all backends have unique type identifiers.""" backends = [ hitachi_backend, purestorage_backend, - dellsc_backend, + dellsc_backend, netapp_backend, dellpowerstore_backend, ] types = [b.backend_type for b in backends] @@ -173,13 +173,13 @@ 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, netapp_backend, dellpowerstore_backend ): """Test that all backends have unique charm names.""" backends = [ hitachi_backend, purestorage_backend, - dellsc_backend, + dellsc_backend, netapp_backend, dellpowerstore_backend, ] charm_names = [b.charm_name for b in backends] @@ -196,6 +196,7 @@ def test_all_backends_have_unique_charm_names( ("hitachi", "hitachi"), ("purestorage", "purestorage"), ("dellsc", "dellsc"), + ("netapp", "netapp"), ("dellpowerstore", "dellpowerstore"), ], ) @@ -211,6 +212,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"), + ("netapp", "cinder-volume-netapp"), ("dellpowerstore", "cinder-volume-dellpowerstore"), ], ) diff --git a/sunbeam-python/tests/unit/sunbeam/storage/backends/test_netapp.py b/sunbeam-python/tests/unit/sunbeam/storage/backends/test_netapp.py new file mode 100644 index 000000000..ff3484262 --- /dev/null +++ b/sunbeam-python/tests/unit/sunbeam/storage/backends/test_netapp.py @@ -0,0 +1,76 @@ +# SPDX-FileCopyrightText: 2026 - Canonical Ltd +# SPDX-License-Identifier: Apache-2.0 + +"""Tests for NetApp backend.""" + +import pytest +from pydantic import ValidationError + +from sunbeam.storage.models import SecretDictField +from tests.unit.sunbeam.storage.backends.test_common import BaseBackendTests + + +class TestNetAppBackend(BaseBackendTests): + """Tests for NetApp backend.""" + + @pytest.fixture + def backend(self, netapp_backend): + """Provide NetApp backend instance.""" + return netapp_backend + + def test_backend_type_is_netapp(self, backend): + """Test that backend type is 'netapp'.""" + assert backend.backend_type == "netapp" + + def test_charm_name_is_netapp_charm(self, backend): + """Test that charm name is cinder-volume-netapp.""" + assert backend.charm_name == "cinder-volume-netapp" + + def test_config_has_required_fields(self, backend): + """Test that NetApp config has required fields.""" + fields = backend.config_type().model_fields + for field in ("san_ip", "protocol", "netapp_ca_certificate_file"): + assert field in fields, f"Required field {field} not found in config" + + def test_sensitive_fields_are_marked_secret(self, backend): + """Test that certificate/password fields are marked as secrets.""" + config_class = backend.config_type() + for field_name in ( + "netapp_password", + "netapp_private_key_file", + "netapp_certificate_file", + "netapp_ca_certificate_file", + ): + 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 TestNetAppConfigValidation: + """Test NetApp config validation behavior.""" + + def test_protocol_rejects_invalid_value(self, netapp_backend): + """Test that protocol rejects values other than iscsi/nvme.""" + config_class = netapp_backend.config_type() + with pytest.raises(ValidationError): + config_class.model_validate( + { + "san-ip": "192.168.1.1", + "protocol": "fc", + "netapp-ca-certificate-file": "ca.pem", + } + ) + + def test_protocol_accepts_iscsi(self, netapp_backend): + """Test that protocol accepts iscsi.""" + config_class = netapp_backend.config_type() + config = config_class.model_validate( + { + "san-ip": "192.168.1.1", + "protocol": "iscsi", + "netapp-ca-certificate-file": "ca.pem", + } + ) + assert config.protocol == "iscsi" From 20050f061bd136156efada92f4062ca8403ceb2a Mon Sep 17 00:00:00 2001 From: Ahmad Hassan Date: Tue, 14 Apr 2026 12:57:04 +0500 Subject: [PATCH 2/3] fix: remove module E501 ignore and use targeted line-length exceptions --- .../storage/backends/netapp/backend.py | 126 ++++++++++++++---- 1 file changed, 97 insertions(+), 29 deletions(-) diff --git a/sunbeam-python/sunbeam/storage/backends/netapp/backend.py b/sunbeam-python/sunbeam/storage/backends/netapp/backend.py index 8688efb44..49f85de35 100644 --- a/sunbeam-python/sunbeam/storage/backends/netapp/backend.py +++ b/sunbeam-python/sunbeam/storage/backends/netapp/backend.py @@ -1,6 +1,5 @@ # SPDX-FileCopyrightText: 2026 - Canonical Ltd # SPDX-License-Identifier: Apache-2.0 -# ruff: noqa: E501 """NetApp ONTAP backend implementation using base step classes.""" @@ -66,63 +65,87 @@ class NetAppConfig(StorageBackendConfig): netapp_storage_protocol: Annotated[ Literal["iscsi", "fc", "nfs", "nvme"] | None, Field( - description="The storage protocol to be used on the data path with the storage system." + description=( + "The storage protocol to be used on the data path with the " + "storage system." + ) ), ] = None netapp_server_hostname: Annotated[ str | None, Field( - description="The hostname (or IP address) for the storage system or proxy server." + description="The hostname (or IP address) for the storage system or proxy server." # noqa: E501 ), ] = None netapp_server_port: Annotated[ int | None, Field( - description="The TCP port to use for communication with the storage system or proxy server." + description=( + "The TCP port to use for communication with the storage system " + "or proxy server." + ) ), ] = None netapp_use_legacy_client: Annotated[ bool | None, Field( - description="Select which ONTAP client to use for retrieving and modifying data on the storage." + description=( + "Select which ONTAP client to use for retrieving and modifying " + "data on the storage." + ) ), ] = None netapp_async_rest_timeout: Annotated[ int | None, Field( - description="The maximum time in seconds to wait for completing a REST asynchronous operation." + description=( + "The maximum time in seconds to wait for completing a REST " + "asynchronous operation." + ) ), ] = None netapp_transport_type: Annotated[ TransportType | None, Field( - description="The transport protocol used when communicating with the storage system or proxy server." + description=( + "The transport protocol used when communicating with the storage " + "system or proxy server." + ) ), ] = None netapp_ssl_cert_path: Annotated[ str | None, Field( - description="The path to a CA_BUNDLE file or directory with certificates of trusted CA." + description=( + "The path to a CA_BUNDLE file or directory with certificates of " + "trusted CA." + ) ), ] = None netapp_login: Annotated[ str | None, Field( - description="Administrative user account name used to access the storage system or proxy server." + description=( + "Administrative user account name used to access the storage " + "system or proxy server." + ) ), ] = None netapp_password: Annotated[ str | None, Field( - description="Password for the administrative user account specified in the netapp_login option." + description=( + "Password for the administrative user account specified in the " + "netapp_login option." + ) ), SecretDictField(field="netapp-password"), ] = None @@ -130,7 +153,10 @@ class NetAppConfig(StorageBackendConfig): netapp_private_key_file: Annotated[ str | None, Field( - description="Absolute path to the file containing the private key associated with the certificate." + description=( + "Absolute path to the file containing the private key " + "associated with the certificate." + ) ), SecretDictField(field="netapp-private-key-file"), ] = None @@ -146,7 +172,10 @@ class NetAppConfig(StorageBackendConfig): netapp_ca_certificate_file: Annotated[ str, Field( - description="Absolute path to the file containing the public key certificate of the trusted CA." + description=( + "Absolute path to the file containing the public key " + "certificate of the trusted CA." + ) ), SecretDictField(field="netapp-ca-certificate-file"), ] @@ -173,7 +202,10 @@ class NetAppConfig(StorageBackendConfig): netapp_driver_reports_provisioned_capacity: Annotated[ bool | None, Field( - description="Enable querying of storage system to calculate volumes provisioned size." + description=( + "Enable querying of storage system to calculate volumes " + "provisioned size." + ) ), ] = None @@ -197,105 +229,141 @@ class NetAppConfig(StorageBackendConfig): thres_avl_size_perc_start: Annotated[ int | None, Field( - description="Percentage of available space for an NFS share to trigger cache cleaning." + description=( + "Percentage of available space for an NFS share to trigger " + "cache cleaning." + ) ), ] = None thres_avl_size_perc_stop: Annotated[ int | None, Field( - description="Percentage of available space on an NFS share to stop cache cleaning." + description=( + "Percentage of available space on an NFS share to stop cache cleaning." + ) ), ] = None expiry_thres_minutes: Annotated[ int | None, Field( - description="Threshold for last access time for images in the NFS image cache." + description=( + "Threshold for last access time for images in the NFS image cache." + ) ), ] = None netapp_lun_ostype: Annotated[ str | None, Field( - description="Type of operating system that will access a LUN exported from Data ONTAP." + description=( + "Type of operating system that will access a LUN exported from " + "Data ONTAP." + ) ), ] = None netapp_namespace_ostype: Annotated[ str | None, Field( - description="Type of operating system that will access a namespace exported from Data ONTAP." + description=( + "Type of operating system that will access a namespace exported " + "from Data ONTAP." + ) ), ] = None netapp_host_type: Annotated[ str | None, Field( - description="Type of operating system for all initiators that can access a LUN." + description="Type of operating system for all initiators that can access a LUN." # noqa: E501 ), ] = None netapp_pool_name_search_pattern: Annotated[ str | None, Field( - description="Regular expression to restrict provisioning to specified pools." + description=( + "Regular expression to restrict provisioning to specified pools." + ) ), ] = None netapp_lun_clone_busy_timeout: Annotated[ int | None, Field( - description="Maximum time to retry LUN clone operation when device busy error occurs." + description=( + "Maximum time to retry LUN clone operation when device busy " + "error occurs." + ) ), ] = None netapp_lun_clone_busy_interval: Annotated[ int | None, Field( - description="Time interval to retry LUN clone operation when device busy error occurs." + description=( + "Time interval to retry LUN clone operation when device busy " + "error occurs." + ) ), ] = None netapp_dedupe_cache_expiry_duration: Annotated[ int | None, Field( - description="Time interval between updates of netapp_dedupe_used_percent for ONTAP backend pools." + description=( + "Time interval between updates of netapp_dedupe_used_percent " + "for ONTAP backend pools." + ) ), ] = None netapp_performance_cache_expiry_duration: Annotated[ int | None, Field( - description="Time interval between updates of performance utilization for ONTAP backend pools." + description=( + "Time interval between updates of performance utilization for " + "ONTAP backend pools." + ) ), ] = None netapp_replication_aggregate_map: Annotated[ str | None, Field( - description="Aggregate mapping between source and destination back ends for replication." + description=( + "Aggregate mapping between source and destination back ends " + "for replication." + ) ), ] = None netapp_snapmirror_quiesce_timeout: Annotated[ int | None, Field( - description="Maximum time to wait for existing SnapMirror transfers to complete before aborting." + description=( + "Maximum time to wait for existing SnapMirror transfers to " + "complete before aborting." + ) ), ] = None netapp_replication_volume_online_timeout: Annotated[ int | None, Field( - description="Time to wait for a replication volume create to complete and go online." + description=( + "Time to wait for a replication volume create to complete and " + "go online." + ) ), ] = None netapp_replication_policy: Annotated[ str | None, Field( - description="Replication policy to be used while creating snapmirror relationship." + description="Replication policy to be used while creating snapmirror relationship." # noqa: E501 ), ] = None @@ -307,7 +375,7 @@ class NetAppConfig(StorageBackendConfig): netapp_migrate_volume_timeout: Annotated[ int | None, Field( - description="Time to wait for storage assisted volume migration to complete." + description="Time to wait for storage assisted volume migration to complete." # noqa: E501 ), ] = None From d28d0ff5dc9f3b976a29fd6b1fcb6fd231b877b9 Mon Sep 17 00:00:00 2001 From: Ahmad Hassan Date: Tue, 21 Apr 2026 14:41:59 +0500 Subject: [PATCH 3/3] fix: test/lint issues resolve --- .../sunbeam/storage/backends/test_common.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) 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 b5569479d..eb36a4af1 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, netapp_backend, dellpowerstore_backend + hitachi_backend, + purestorage_backend, + dellsc_backend, + netapp_backend, + dellpowerstore_backend, ): """Test that all backends have unique type identifiers.""" backends = [ hitachi_backend, purestorage_backend, - dellsc_backend, netapp_backend, + dellsc_backend, + netapp_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, netapp_backend, dellpowerstore_backend + hitachi_backend, + purestorage_backend, + dellsc_backend, + netapp_backend, + dellpowerstore_backend, ): """Test that all backends have unique charm names.""" backends = [ hitachi_backend, purestorage_backend, - dellsc_backend, netapp_backend, + dellsc_backend, + netapp_backend, dellpowerstore_backend, ] charm_names = [b.charm_name for b in backends]