From 81d3a2854873b4b8b0239fc485f1b1c83eee1923 Mon Sep 17 00:00:00 2001 From: Ahmad Hassan Date: Tue, 7 Apr 2026 17:45:39 +0500 Subject: [PATCH 1/3] feat: generate charm dellpowervault --- .../backends/dellpowervault/__init__.py | 4 + .../backends/dellpowervault/backend.py | 97 +++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 sunbeam-python/sunbeam/storage/backends/dellpowervault/__init__.py create mode 100644 sunbeam-python/sunbeam/storage/backends/dellpowervault/backend.py diff --git a/sunbeam-python/sunbeam/storage/backends/dellpowervault/__init__.py b/sunbeam-python/sunbeam/storage/backends/dellpowervault/__init__.py new file mode 100644 index 000000000..dc1a1fa20 --- /dev/null +++ b/sunbeam-python/sunbeam/storage/backends/dellpowervault/__init__.py @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: 2026 - Canonical Ltd +# SPDX-License-Identifier: Apache-2.0 + +"""Dell PowerVault backend for Sunbeam storage.""" diff --git a/sunbeam-python/sunbeam/storage/backends/dellpowervault/backend.py b/sunbeam-python/sunbeam/storage/backends/dellpowervault/backend.py new file mode 100644 index 000000000..87374f91b --- /dev/null +++ b/sunbeam-python/sunbeam/storage/backends/dellpowervault/backend.py @@ -0,0 +1,97 @@ +# SPDX-FileCopyrightText: 2026 - Canonical Ltd +# SPDX-License-Identifier: Apache-2.0 + +"""Dell PowerVault 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 + +LOG = logging.getLogger(__name__) +console = Console() + + +class Protocol(StrEnum): + """Enumeration of valid protocol types.""" + + FC = "fc" + ISCSI = "iscsi" + + +class DellpowervaultConfig(StorageBackendConfig): + """Configuration model for Dell PowerVault 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: Annotated[ + Literal["fc", "iscsi"], + Field(description="Protocol selector: fc, iscsi."), + ] + + # Optional backend configuration + driver_ssl_cert: Annotated[ + str | None, + Field( + description="PEM-encoded SSL certificate for HTTPS connections to the storage array." # noqa: E501 + ), + ] = None + + pvme_pool_name: Annotated[ + str | None, + Field(description="Pool or Vdisk name to use for volume creation."), + ] = None + + pvme_iscsi_ips: Annotated[ + str | None, + Field(description="List of comma-separated target iSCSI IP addresses."), + ] = None + + +class DellpowervaultBackend(StorageBackendBase): + """Dell PowerVault backend implementation.""" + + backend_type = "dellpowervault" + display_name = "Dell PowerVault" + generally_available = True + + @property + def charm_name(self) -> str: + """Return the charm application name.""" + return "cinder-volume-dellpowervault" + + @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 DellpowervaultConfig From 21e66ea4a199ec721729909ba838a24879b70d8a Mon Sep 17 00:00:00 2001 From: Ahmad Hassan Date: Wed, 8 Apr 2026 17:54:59 +0500 Subject: [PATCH 2/3] apply fixes of co-pilot review --- .../backends/dellpowervault/backend.py | 12 +++--- .../unit/sunbeam/storage/backends/conftest.py | 10 ++++- .../sunbeam/storage/backends/test_common.py | 16 +++++++- .../storage/backends/test_dellpowervault.py | 37 +++++++++++++++++++ 4 files changed, 66 insertions(+), 9 deletions(-) create mode 100644 sunbeam-python/tests/unit/sunbeam/storage/backends/test_dellpowervault.py diff --git a/sunbeam-python/sunbeam/storage/backends/dellpowervault/backend.py b/sunbeam-python/sunbeam/storage/backends/dellpowervault/backend.py index 87374f91b..5074cc34b 100644 --- a/sunbeam-python/sunbeam/storage/backends/dellpowervault/backend.py +++ b/sunbeam-python/sunbeam/storage/backends/dellpowervault/backend.py @@ -5,7 +5,7 @@ import logging from enum import StrEnum -from typing import Annotated, Literal +from typing import Annotated from pydantic import Field from rich.console import Console @@ -24,7 +24,7 @@ class Protocol(StrEnum): ISCSI = "iscsi" -class DellpowervaultConfig(StorageBackendConfig): +class DellPowerVaultConfig(StorageBackendConfig): """Configuration model for Dell PowerVault backend. This model includes ALL configuration options for the backend. @@ -37,7 +37,7 @@ class DellpowervaultConfig(StorageBackendConfig): ] protocol: Annotated[ - Literal["fc", "iscsi"], + Protocol, Field(description="Protocol selector: fc, iscsi."), ] @@ -60,12 +60,12 @@ class DellpowervaultConfig(StorageBackendConfig): ] = None -class DellpowervaultBackend(StorageBackendBase): +class DellPowerVaultBackend(StorageBackendBase): """Dell PowerVault backend implementation.""" backend_type = "dellpowervault" display_name = "Dell PowerVault" - generally_available = True + generally_available = False @property def charm_name(self) -> str: @@ -94,4 +94,4 @@ def supports_ha(self) -> bool: def config_type(self) -> type[StorageBackendConfig]: """Return the configuration model type for this backend.""" - return DellpowervaultConfig + return DellPowerVaultConfig diff --git a/sunbeam-python/tests/unit/sunbeam/storage/backends/conftest.py b/sunbeam-python/tests/unit/sunbeam/storage/backends/conftest.py index 80d3d8ef5..61cfbe85e 100644 --- a/sunbeam-python/tests/unit/sunbeam/storage/backends/conftest.py +++ b/sunbeam-python/tests/unit/sunbeam/storage/backends/conftest.py @@ -5,6 +5,7 @@ import pytest +from sunbeam.storage.backends.dellpowervault.backend import DellPowerVaultBackend from sunbeam.storage.backends.dellpowerstore.backend import DellPowerstoreBackend from sunbeam.storage.backends.dellsc.backend import DellSCBackend from sunbeam.storage.backends.hitachi.backend import HitachiBackend @@ -29,19 +30,26 @@ def dellsc_backend(): return DellSCBackend() +@pytest.fixture +def dellpowervault_backend(): + """Provide a Dell PowerVault backend instance.""" + return DellPowerVaultBackend() + + @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", "dellpowervault", "dellpowerstore"]) def any_backend(request): """Parametrized fixture that provides each backend type.""" backends = { "hitachi": HitachiBackend(), "purestorage": PureStorageBackend(), "dellsc": DellSCBackend(), + "dellpowervault": DellPowerVaultBackend(), "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..701286d8d 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, dellpowervault_backend, dellpowerstore_backend ): """Test that all backends have unique type identifiers.""" backends = [ + hitachi_backend, + purestorage_backend, + dellsc_backend, + dellpowervault_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, dellpowervault_backend, dellpowerstore_backend ): """Test that all backends have unique charm names.""" backends = [ + hitachi_backend, + purestorage_backend, + dellsc_backend, + dellpowervault_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"), + ("dellpowervault", "dellpowervault"), ("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"), + ("dellpowervault", "cinder-volume-dellpowervault"), ("dellpowerstore", "cinder-volume-dellpowerstore"), ], ) diff --git a/sunbeam-python/tests/unit/sunbeam/storage/backends/test_dellpowervault.py b/sunbeam-python/tests/unit/sunbeam/storage/backends/test_dellpowervault.py new file mode 100644 index 000000000..94910364e --- /dev/null +++ b/sunbeam-python/tests/unit/sunbeam/storage/backends/test_dellpowervault.py @@ -0,0 +1,37 @@ +# SPDX-FileCopyrightText: 2026 - Canonical Ltd +# SPDX-License-Identifier: Apache-2.0 + +"""Tests for Dell PowerVault backend.""" + +import pytest + +from sunbeam.storage.backends.dellpowervault.backend import Protocol +from tests.unit.sunbeam.storage.backends.test_common import BaseBackendTests + + +class TestDellPowerVaultBackend(BaseBackendTests): + """Tests for Dell PowerVault backend.""" + + @pytest.fixture + def backend(self, dellpowervault_backend): + """Provide Dell PowerVault backend instance.""" + return dellpowervault_backend + + def test_backend_type_is_dellpowervault(self, backend): + """Test that backend type is 'dellpowervault'.""" + assert backend.backend_type == "dellpowervault" + + def test_display_name_mentions_powervault(self, backend): + """Test that display name mentions PowerVault.""" + assert "powervault" in backend.display_name.lower() + + def test_charm_name_is_dellpowervault_charm(self, backend): + """Test that charm name is cinder-volume-dellpowervault.""" + assert backend.charm_name == "cinder-volume-dellpowervault" + + def test_dellpowervault_protocol_uses_enum(self, backend): + """Test protocol field is typed using the Protocol enum.""" + config_class = backend.config_type() + protocol_field = config_class.model_fields.get("protocol") + assert protocol_field is not None + assert protocol_field.annotation is Protocol From f101524325352da74002d42d11946db18bad7717 Mon Sep 17 00:00:00 2001 From: Ahmad Hassan Date: Wed, 22 Apr 2026 12:33:29 +0500 Subject: [PATCH 3/3] fix: test/lint issues resolve --- .../unit/sunbeam/storage/backends/conftest.py | 6 ++++-- .../sunbeam/storage/backends/test_common.py | 20 +++++++++---------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/sunbeam-python/tests/unit/sunbeam/storage/backends/conftest.py b/sunbeam-python/tests/unit/sunbeam/storage/backends/conftest.py index 61cfbe85e..341120cc6 100644 --- a/sunbeam-python/tests/unit/sunbeam/storage/backends/conftest.py +++ b/sunbeam-python/tests/unit/sunbeam/storage/backends/conftest.py @@ -5,8 +5,8 @@ import pytest -from sunbeam.storage.backends.dellpowervault.backend import DellPowerVaultBackend from sunbeam.storage.backends.dellpowerstore.backend import DellPowerstoreBackend +from sunbeam.storage.backends.dellpowervault.backend import DellPowerVaultBackend from sunbeam.storage.backends.dellsc.backend import DellSCBackend from sunbeam.storage.backends.hitachi.backend import HitachiBackend from sunbeam.storage.backends.purestorage.backend import PureStorageBackend @@ -42,7 +42,9 @@ def dellpowerstore_backend(): return DellPowerstoreBackend() -@pytest.fixture(params=["hitachi", "purestorage", "dellsc", "dellpowervault", "dellpowerstore"]) +@pytest.fixture( + params=["hitachi", "purestorage", "dellsc", "dellpowervault", "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 701286d8d..3a5dd3d8a 100644 --- a/sunbeam-python/tests/unit/sunbeam/storage/backends/test_common.py +++ b/sunbeam-python/tests/unit/sunbeam/storage/backends/test_common.py @@ -157,18 +157,18 @@ def backend(self, any_backend): def test_all_backends_have_unique_types( - hitachi_backend, purestorage_backend, dellsc_backend, dellpowervault_backend, dellpowerstore_backend + hitachi_backend, + purestorage_backend, + dellsc_backend, + dellpowervault_backend, + dellpowerstore_backend, ): """Test that all backends have unique type identifiers.""" backends = [ - hitachi_backend, - purestorage_backend, - dellsc_backend, dellpowervault_backend, - , dellpowerstore_backend, ] types = [b.backend_type for b in backends] @@ -178,18 +178,18 @@ def test_all_backends_have_unique_types( def test_all_backends_have_unique_charm_names( - hitachi_backend, purestorage_backend, dellsc_backend, dellpowervault_backend, dellpowerstore_backend + hitachi_backend, + purestorage_backend, + dellsc_backend, + dellpowervault_backend, + dellpowerstore_backend, ): """Test that all backends have unique charm names.""" backends = [ - hitachi_backend, - purestorage_backend, - dellsc_backend, dellpowervault_backend, - , dellpowerstore_backend, ] charm_names = [b.charm_name for b in backends]