From cc4d29dab40af5b3e378b7770fd5da93be47564e Mon Sep 17 00:00:00 2001 From: Ahmad Hassan Date: Tue, 14 Apr 2026 13:07:33 +0500 Subject: [PATCH 1/3] feat: generate prophetstor charm --- .../storage/backends/prophetstor/__init__.py | 4 + .../storage/backends/prophetstor/backend.py | 90 +++++++++++++++++++ .../unit/sunbeam/storage/backends/conftest.py | 10 ++- .../sunbeam/storage/backends/test_common.py | 16 +++- .../storage/backends/test_prophetstor.py | 58 ++++++++++++ 5 files changed, 175 insertions(+), 3 deletions(-) create mode 100644 sunbeam-python/sunbeam/storage/backends/prophetstor/__init__.py create mode 100644 sunbeam-python/sunbeam/storage/backends/prophetstor/backend.py create mode 100644 sunbeam-python/tests/unit/sunbeam/storage/backends/test_prophetstor.py diff --git a/sunbeam-python/sunbeam/storage/backends/prophetstor/__init__.py b/sunbeam-python/sunbeam/storage/backends/prophetstor/__init__.py new file mode 100644 index 000000000..b53fdaa39 --- /dev/null +++ b/sunbeam-python/sunbeam/storage/backends/prophetstor/__init__.py @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: 2026 - Canonical Ltd +# SPDX-License-Identifier: Apache-2.0 + +"""ProphetStor backend for Sunbeam storage.""" diff --git a/sunbeam-python/sunbeam/storage/backends/prophetstor/backend.py b/sunbeam-python/sunbeam/storage/backends/prophetstor/backend.py new file mode 100644 index 000000000..0d478b9e0 --- /dev/null +++ b/sunbeam-python/sunbeam/storage/backends/prophetstor/backend.py @@ -0,0 +1,90 @@ +# SPDX-FileCopyrightText: 2026 - Canonical Ltd +# SPDX-License-Identifier: Apache-2.0 + +"""DPL 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 + +LOG = logging.getLogger(__name__) +console = Console() + + +class Protocol(StrEnum): + """Enumeration of valid protocol types.""" + + FC = "fc" + ISCSI = "iscsi" + + +class ProphetstorConfig(StorageBackendConfig): + """Configuration model for DPL 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="Storage array management IP address or hostname") + ] + + protocol: Annotated[ + Protocol | None, + Field(description="Protocol selector: fc, iscsi."), + ] + + # Optional backend configuration + dpl_pool: Annotated[ + str | None, + Field(description="DPL pool uuid in which DPL volumes are stored."), + ] = None + + dpl_port: Annotated[ + int | None, + Field(description="DPL port number."), + ] = 8357 + + +class ProphetstorBackend(StorageBackendBase): + """DPL FC backend implementation.""" + + backend_type = "prophetstor" + display_name = "DPL FC" + generally_available = True + + @property + def charm_name(self) -> str: + """Return the charm application name.""" + return "cinder-volume-prophetstor" + + @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 ProphetstorConfig diff --git a/sunbeam-python/tests/unit/sunbeam/storage/backends/conftest.py b/sunbeam-python/tests/unit/sunbeam/storage/backends/conftest.py index 80d3d8ef5..371360303 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.prophetstor.backend import ProphetstorBackend from sunbeam.storage.backends.purestorage.backend import PureStorageBackend @@ -29,19 +30,26 @@ def dellsc_backend(): return DellSCBackend() +@pytest.fixture +def prophetstor_backend(): + """Provide a ProphetStor backend instance.""" + return ProphetstorBackend() + + @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", "prophetstor", "dellpowerstore"]) def any_backend(request): """Parametrized fixture that provides each backend type.""" backends = { "hitachi": HitachiBackend(), "purestorage": PureStorageBackend(), "dellsc": DellSCBackend(), + "prophetstor": ProphetstorBackend(), "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..3f4c8566b 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, prophetstor_backend, dellpowerstore_backend ): """Test that all backends have unique type identifiers.""" backends = [ + hitachi_backend, + purestorage_backend, + dellsc_backend, + prophetstor_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, prophetstor_backend, dellpowerstore_backend ): """Test that all backends have unique charm names.""" backends = [ + hitachi_backend, + purestorage_backend, + dellsc_backend, + prophetstor_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"), + ("prophetstor", "prophetstor"), ("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"), + ("prophetstor", "cinder-volume-prophetstor"), ("dellpowerstore", "cinder-volume-dellpowerstore"), ], ) diff --git a/sunbeam-python/tests/unit/sunbeam/storage/backends/test_prophetstor.py b/sunbeam-python/tests/unit/sunbeam/storage/backends/test_prophetstor.py new file mode 100644 index 000000000..0a6b7c054 --- /dev/null +++ b/sunbeam-python/tests/unit/sunbeam/storage/backends/test_prophetstor.py @@ -0,0 +1,58 @@ +# SPDX-FileCopyrightText: 2026 - Canonical Ltd +# SPDX-License-Identifier: Apache-2.0 + +"""Tests for ProphetStor backend.""" + +import pytest +from pydantic import ValidationError + +from tests.unit.sunbeam.storage.backends.test_common import BaseBackendTests + + +class TestProphetstorBackend(BaseBackendTests): + """Tests for ProphetStor backend.""" + + @pytest.fixture + def backend(self, prophetstor_backend): + """Provide ProphetStor backend instance.""" + return prophetstor_backend + + def test_backend_type_is_prophetstor(self, backend): + """Test that backend type is 'prophetstor'.""" + assert backend.backend_type == "prophetstor" + + def test_charm_name_is_prophetstor_charm(self, backend): + """Test that charm name is cinder-volume-prophetstor.""" + assert backend.charm_name == "cinder-volume-prophetstor" + + def test_config_has_required_fields(self, backend): + """Test that ProphetStor config has required fields.""" + fields = backend.config_type().model_fields + for field in ("san_ip", "protocol"): + assert field in fields, f"Required field {field} not found in config" + + +class TestProphetstorConfigValidation: + """Test ProphetStor config validation behavior.""" + + def test_protocol_rejects_invalid_value(self, prophetstor_backend): + """Test that protocol rejects values other than fc/iscsi.""" + config_class = prophetstor_backend.config_type() + with pytest.raises(ValidationError): + config_class.model_validate( + { + "san-ip": "192.168.1.1", + "protocol": "nvme", + } + ) + + def test_protocol_accepts_fc(self, prophetstor_backend): + """Test that protocol accepts fc.""" + config_class = prophetstor_backend.config_type() + config = config_class.model_validate( + { + "san-ip": "192.168.1.1", + "protocol": "fc", + } + ) + assert config.protocol == "fc" From 496d7204c2118f17f761346a2ffbd30e6ce4e5b2 Mon Sep 17 00:00:00 2001 From: Ahmad Hassan Date: Tue, 14 Apr 2026 15:11:46 +0500 Subject: [PATCH 2/3] fix: align backend naming and tighten config validation --- .../storage/backends/prophetstor/backend.py | 18 ++++++------- .../unit/sunbeam/storage/backends/conftest.py | 12 +++------ .../storage/backends/test_prophetstor.py | 25 +++++++++++++++++-- 3 files changed, 35 insertions(+), 20 deletions(-) diff --git a/sunbeam-python/sunbeam/storage/backends/prophetstor/backend.py b/sunbeam-python/sunbeam/storage/backends/prophetstor/backend.py index 0d478b9e0..62494c916 100644 --- a/sunbeam-python/sunbeam/storage/backends/prophetstor/backend.py +++ b/sunbeam-python/sunbeam/storage/backends/prophetstor/backend.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2026 - Canonical Ltd # SPDX-License-Identifier: Apache-2.0 -"""DPL FC backend implementation using base step classes.""" +"""ProphetStor DPL FC backend implementation using base step classes.""" import logging from enum import StrEnum @@ -24,8 +24,8 @@ class Protocol(StrEnum): ISCSI = "iscsi" -class ProphetstorConfig(StorageBackendConfig): - """Configuration model for DPL FC backend. +class ProphetStorConfig(StorageBackendConfig): + """Configuration model for ProphetStor DPL FC backend. This model includes ALL configuration options for the backend. Additional configuration can be managed dynamically through the charm. @@ -37,7 +37,7 @@ class ProphetstorConfig(StorageBackendConfig): ] protocol: Annotated[ - Protocol | None, + Protocol, Field(description="Protocol selector: fc, iscsi."), ] @@ -48,16 +48,16 @@ class ProphetstorConfig(StorageBackendConfig): ] = None dpl_port: Annotated[ - int | None, + int, Field(description="DPL port number."), ] = 8357 -class ProphetstorBackend(StorageBackendBase): - """DPL FC backend implementation.""" +class ProphetStorBackend(StorageBackendBase): + """ProphetStor DPL FC backend implementation.""" backend_type = "prophetstor" - display_name = "DPL FC" + display_name = "ProphetStor FC" generally_available = True @property @@ -87,4 +87,4 @@ def supports_ha(self) -> bool: def config_type(self) -> type[StorageBackendConfig]: """Return the configuration model type for this backend.""" - return ProphetstorConfig + return ProphetStorConfig diff --git a/sunbeam-python/tests/unit/sunbeam/storage/backends/conftest.py b/sunbeam-python/tests/unit/sunbeam/storage/backends/conftest.py index 371360303..8ce3232e5 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.prophetstor.backend import ProphetstorBackend +from sunbeam.storage.backends.prophetstor.backend import ProphetStorBackend from sunbeam.storage.backends.purestorage.backend import PureStorageBackend @@ -36,20 +36,14 @@ def prophetstor_backend(): return ProphetstorBackend() -@pytest.fixture -def dellpowerstore_backend(): - """Provide a Dell PowerStore backend instance.""" - return DellPowerstoreBackend() - - -@pytest.fixture(params=["hitachi", "purestorage", "dellsc", "prophetstor", "dellpowerstore"]) +@pytest.fixture(params=["hitachi", "purestorage", "dellsc", "prophetstor"]) def any_backend(request): """Parametrized fixture that provides each backend type.""" backends = { "hitachi": HitachiBackend(), "purestorage": PureStorageBackend(), "dellsc": DellSCBackend(), - "prophetstor": ProphetstorBackend(), + "prophetstor": ProphetStorBackend(), "dellpowerstore": DellPowerstoreBackend(), } return backends[request.param] diff --git a/sunbeam-python/tests/unit/sunbeam/storage/backends/test_prophetstor.py b/sunbeam-python/tests/unit/sunbeam/storage/backends/test_prophetstor.py index 0a6b7c054..f5926ba80 100644 --- a/sunbeam-python/tests/unit/sunbeam/storage/backends/test_prophetstor.py +++ b/sunbeam-python/tests/unit/sunbeam/storage/backends/test_prophetstor.py @@ -9,7 +9,7 @@ from tests.unit.sunbeam.storage.backends.test_common import BaseBackendTests -class TestProphetstorBackend(BaseBackendTests): +class TestProphetStorBackend(BaseBackendTests): """Tests for ProphetStor backend.""" @pytest.fixture @@ -32,7 +32,7 @@ def test_config_has_required_fields(self, backend): assert field in fields, f"Required field {field} not found in config" -class TestProphetstorConfigValidation: +class TestProphetStorConfigValidation: """Test ProphetStor config validation behavior.""" def test_protocol_rejects_invalid_value(self, prophetstor_backend): @@ -56,3 +56,24 @@ def test_protocol_accepts_fc(self, prophetstor_backend): } ) assert config.protocol == "fc" + + def test_protocol_accepts_iscsi(self, prophetstor_backend): + """Test that protocol accepts iscsi.""" + config_class = prophetstor_backend.config_type() + config = config_class.model_validate( + { + "san-ip": "192.168.1.1", + "protocol": "iscsi", + } + ) + assert config.protocol == "iscsi" + + def test_protocol_is_required(self, prophetstor_backend): + """Test that protocol is required.""" + config_class = prophetstor_backend.config_type() + with pytest.raises(ValidationError): + config_class.model_validate( + { + "san-ip": "192.168.1.1", + } + ) From 099d2f7c5cc7594c3e4d4c6948ad80154e752486 Mon Sep 17 00:00:00 2001 From: Ahmad Hassan Date: Tue, 21 Apr 2026 15:46:04 +0500 Subject: [PATCH 3/3] fix: test/lint issues resolve --- .../unit/sunbeam/storage/backends/conftest.py | 12 +++++++++-- .../sunbeam/storage/backends/test_common.py | 20 +++++++++---------- 2 files changed, 20 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 8ce3232e5..e7e24a595 100644 --- a/sunbeam-python/tests/unit/sunbeam/storage/backends/conftest.py +++ b/sunbeam-python/tests/unit/sunbeam/storage/backends/conftest.py @@ -33,10 +33,18 @@ def dellsc_backend(): @pytest.fixture def prophetstor_backend(): """Provide a ProphetStor backend instance.""" - return ProphetstorBackend() + return ProphetStorBackend() -@pytest.fixture(params=["hitachi", "purestorage", "dellsc", "prophetstor"]) +@pytest.fixture +def dellpowerstore_backend(): + """Provide a Dell PowerStore backend instance.""" + return DellPowerstoreBackend() + + +@pytest.fixture( + params=["hitachi", "purestorage", "dellsc", "prophetstor", "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 3f4c8566b..ad30969bb 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, prophetstor_backend, dellpowerstore_backend + hitachi_backend, + purestorage_backend, + dellsc_backend, + prophetstor_backend, + dellpowerstore_backend, ): """Test that all backends have unique type identifiers.""" backends = [ - hitachi_backend, - purestorage_backend, - dellsc_backend, prophetstor_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, prophetstor_backend, dellpowerstore_backend + hitachi_backend, + purestorage_backend, + dellsc_backend, + prophetstor_backend, + dellpowerstore_backend, ): """Test that all backends have unique charm names.""" backends = [ - hitachi_backend, - purestorage_backend, - dellsc_backend, prophetstor_backend, - , dellpowerstore_backend, ] charm_names = [b.charm_name for b in backends]