From a7ac636d28556567e879874b98f992a5076724c4 Mon Sep 17 00:00:00 2001 From: gnuoy Date: Tue, 19 Sep 2023 15:28:23 +0000 Subject: [PATCH] Revert "Revert "Add relation handler for ceph-access relation"" This reverts commit 8f18062509d092330e303f94d84a8a4a80d7404f. Reason for revert: This revert should have been targetted at the stable/2023.1.2 branch not main Change-Id: Id446a017e3b38bf58c651102f97f72702da1347b --- ops-sunbeam/fetch-libs.sh | 1 + ops-sunbeam/ops_sunbeam/charm.py | 8 + ops-sunbeam/ops_sunbeam/relation_handlers.py | 67 +++++ ops-sunbeam/ops_sunbeam/test_utils.py | 20 ++ .../charms/cinder_ceph_k8s/v0/ceph_access.py | 265 ++++++++++++++++++ ops-sunbeam/unit_tests/test_charms.py | 2 + ops-sunbeam/unit_tests/test_core.py | 7 + 7 files changed, 370 insertions(+) create mode 100644 ops-sunbeam/unit_tests/lib/charms/cinder_ceph_k8s/v0/ceph_access.py diff --git a/ops-sunbeam/fetch-libs.sh b/ops-sunbeam/fetch-libs.sh index 557337d4..fe4f743d 100755 --- a/ops-sunbeam/fetch-libs.sh +++ b/ops-sunbeam/fetch-libs.sh @@ -13,5 +13,6 @@ charmcraft fetch-lib charms.rabbitmq_k8s.v0.rabbitmq charmcraft fetch-lib charms.ovn_central_k8s.v0.ovsdb charmcraft fetch-lib charms.traefik_k8s.v1.ingress charmcraft fetch-lib charms.ceilometer_k8s.v0.ceilometer_service +charmcraft fetch-lib charms.cinder_ceph_k8s.v0.ceph_access echo "Copying libs to to unit_test dir" rsync --recursive --delete lib/ unit_tests/lib/ diff --git a/ops-sunbeam/ops_sunbeam/charm.py b/ops-sunbeam/ops_sunbeam/charm.py index e8c89f74..c2d2287f 100644 --- a/ops-sunbeam/ops_sunbeam/charm.py +++ b/ops-sunbeam/ops_sunbeam/charm.py @@ -178,6 +178,14 @@ class OSBaseOperatorCharm(ops.charm.CharmBase): "identity-credentials" in self.mandatory_relations, ) handlers.append(self.ccreds) + if self.can_add_handler("ceph-access", handlers): + self.ceph_access = sunbeam_rhandlers.CephAccessRequiresHandler( + self, + "ceph-access", + self.configure_charm, + "ceph-access" in self.mandatory_relations, + ) + handlers.append(self.ceph_access) return handlers def get_sans(self) -> List[str]: diff --git a/ops-sunbeam/ops_sunbeam/relation_handlers.py b/ops-sunbeam/ops_sunbeam/relation_handlers.py index 796a7526..4b9f7010 100644 --- a/ops-sunbeam/ops_sunbeam/relation_handlers.py +++ b/ops-sunbeam/ops_sunbeam/relation_handlers.py @@ -1200,3 +1200,70 @@ class CeilometerServiceRequiresHandler(RelationHandler): return bool(self.interface.telemetry_secret) except (AttributeError, KeyError): return False + + +class CephAccessRequiresHandler(RelationHandler): + """Handles the ceph access relation on the requires side.""" + + def __init__( + self, + charm: ops.charm.CharmBase, + relation_name: str, + callback_f: Callable, + mandatory: bool = False, + ) -> None: + """Create a new ceph-access handler. + + Create a new CephAccessRequiresHandler that handles initial + events from the relation and invokes the provided callbacks based on + the event raised. + + :param charm: the Charm class the handler is for + :type charm: ops.charm.CharmBase + :param relation_name: the relation the handler is bound to + :type relation_name: str + :param callback_f: the function to call when the nodes are connected + :type callback_f: Callable + """ + super().__init__(charm, relation_name, callback_f, mandatory) + + def setup_event_handler(self) -> ops.charm.Object: + """Configure event handlers for ceph-access relation.""" + import charms.cinder_ceph_k8s.v0.ceph_access as ceph_access + + logger.debug("Setting up the ceph-access event handler") + ceph_access = ceph_access.CephAccessRequires( + self.charm, + self.relation_name, + ) + self.framework.observe(ceph_access.on.ready, self._ceph_access_ready) + self.framework.observe( + ceph_access.on.goneaway, self._ceph_access_goneaway + ) + return ceph_access + + def _ceph_access_ready(self, event: ops.framework.EventBase) -> None: + """React to credential ready event.""" + self.callback_f(event) + + def _ceph_access_goneaway(self, event: ops.framework.EventBase) -> None: + """React to credential goneaway event.""" + self.callback_f(event) + if self.mandatory: + self.status.set(BlockedStatus("integration missing")) + + @property + def ready(self) -> bool: + """Whether handler is ready for use.""" + try: + return bool(self.interface.ready) + except (AttributeError, KeyError): + return False + + def context(self) -> dict: + """Context containing Ceph access data.""" + ctxt = super().context() + data = self.interface.ceph_access_data + ctxt["key"] = data.get("key") + ctxt["uuid"] = data.get("uuid") + return ctxt diff --git a/ops-sunbeam/ops_sunbeam/test_utils.py b/ops-sunbeam/ops_sunbeam/test_utils.py index 0ff999e5..d2234bd7 100644 --- a/ops-sunbeam/ops_sunbeam/test_utils.py +++ b/ops-sunbeam/ops_sunbeam/test_utils.py @@ -344,6 +344,26 @@ def add_amqp_relation_credentials(harness: Harness, rel_id: str) -> None: ) +def add_base_ceph_access_relation(harness: Harness) -> str: + """Add ceph-access relation.""" + rel_id = harness.add_relation( + "ceph-access", "cinder-ceph", app_data={"a": "b"} + ) + return rel_id + + +def add_ceph_access_relation_response(harness: Harness, rel_id: str) -> None: + """Add secret data to cinder-access relation.""" + credentials_content = {"uuid": "svcuser1", "key": "svcpass1"} + credentials_id = harness.add_model_secret( + "cinder-ceph", credentials_content + ) + harness.grant_secret(credentials_id, harness.charm.app.name) + harness.update_relation_data( + rel_id, "cinder-ceph", {"access-credentials": credentials_id} + ) + + def add_base_identity_service_relation(harness: Harness) -> str: """Add identity-service relation.""" rel_id = harness.add_relation("identity-service", "keystone") diff --git a/ops-sunbeam/unit_tests/lib/charms/cinder_ceph_k8s/v0/ceph_access.py b/ops-sunbeam/unit_tests/lib/charms/cinder_ceph_k8s/v0/ceph_access.py new file mode 100644 index 00000000..53534305 --- /dev/null +++ b/ops-sunbeam/unit_tests/lib/charms/cinder_ceph_k8s/v0/ceph_access.py @@ -0,0 +1,265 @@ +"""CephAccess Provides and Requires module. + +This library contains the Requires and Provides classes for handling +the ceph-access interface. + +Import `CephAccessRequires` in your charm, with the charm object and the +relation name: + - self + - "ceph_access" + +Three events are also available to respond to: + - connected + - ready + - goneaway + +A basic example showing the usage of this relation follows: + +``` +from charms.cinder_ceph_k8s.v0.ceph_access import CephAccessRequires + +class CephAccessClientCharm(CharmBase): + def __init__(self, *args): + super().__init__(*args) + # CephAccess Requires + self.ceph_access = CephAccessRequires( + self, + relation_name="ceph_access", + ) + self.framework.observe( + self.ceph_access.on.connected, self._on_ceph_access_connected) + self.framework.observe( + self.ceph_access.on.ready, self._on_ceph_access_ready) + self.framework.observe( + self.ceph_access.on.goneaway, self._on_ceph_access_goneaway) + + def _on_ceph_access_connected(self, event): + '''React to the CephAccess connected event. + + This event happens when n CephAccess relation is added to the + model before credentials etc have been provided. + ''' + # Do something before the relation is complete + pass + + def _on_ceph_access_ready(self, event): + '''React to the CephAccess ready event. + + This event happens when an CephAccess relation is removed. + ''' + # IdentityService Relation has goneaway. shutdown services or suchlike + pass + +``` + +""" + +# The unique Charmhub library identifier, never change it +LIBID = "7fa8d4f8407c4f31ab1deb51c0c046f1" + +# Increment this major API version when introducing breaking changes +LIBAPI = 0 + +# Increment this PATCH version before using `charmcraft publish-lib` or reset +# to 0 if you are raising the major API version +LIBPATCH = 1 + +import logging +from typing import Optional +from ops import ( + RelationEvent +) +from ops.model import ( + Relation, + Secret, + SecretNotFoundError, +) +from ops.framework import ( + EventBase, + ObjectEvents, + EventSource, + Object, +) +logger = logging.getLogger(__name__) + +class CephAccessConnectedEvent(EventBase): + """CephAccess connected Event.""" + + pass + + +class CephAccessReadyEvent(EventBase): + """CephAccess ready for use Event.""" + + pass + + +class CephAccessGoneAwayEvent(EventBase): + """CephAccess relation has gone-away Event""" + + pass + + +class CephAccessServerEvents(ObjectEvents): + """Events class for `on`""" + + connected = EventSource(CephAccessConnectedEvent) + ready = EventSource(CephAccessReadyEvent) + goneaway = EventSource(CephAccessGoneAwayEvent) + + +class CephAccessRequires(Object): + """ + CephAccessRequires class + """ + + + on = CephAccessServerEvents() + + def __init__(self, charm, relation_name: str): + super().__init__(charm, relation_name) + self.charm = charm + self.relation_name = relation_name + self.framework.observe( + self.charm.on[relation_name].relation_joined, + self._on_ceph_access_relation_joined, + ) + self.framework.observe( + self.charm.on[relation_name].relation_changed, + self._on_ceph_access_relation_changed, + ) + self.framework.observe( + self.charm.on[relation_name].relation_departed, + self._on_ceph_access_relation_changed, + ) + self.framework.observe( + self.charm.on[relation_name].relation_broken, + self._on_ceph_access_relation_broken, + ) + + @property + def _ceph_access_rel(self) -> Relation: + """The CephAccess relation.""" + return self.framework.model.get_relation(self.relation_name) + + def get_remote_app_data(self, key: str) -> Optional[str]: + """Return the value for the given key from remote app data.""" + data = self._ceph_access_rel.data[self._ceph_access_rel.app] + return data.get(key) + + def _on_ceph_access_relation_joined(self, event): + """CephAccess relation joined.""" + logging.debug("CephAccess on_joined") + self.on.connected.emit() + + def _on_ceph_access_relation_changed(self, event): + """CephAccess relation changed.""" + logging.debug("CephAccess on_changed") + try: + if self.ready: + self.on.ready.emit() + except (AttributeError, KeyError): + pass + + def _on_ceph_access_relation_broken(self, event): + """CephAccess relation broken.""" + logging.debug("CephAccess on_broken") + self.on.goneaway.emit() + + def _retrieve_secret(self) -> Optional[Secret]: + try: + credentials_id = self.get_remote_app_data('access-credentials') + if not credentials_id: + return None + credentials = self.charm.model.get_secret(id=credentials_id) + return credentials + except SecretNotFoundError: + logger.warning(f"Secret {credentials_id} not found") + return None + + @property + def ceph_access_data(self) -> dict: + """Return the service_password.""" + secret = self._retrieve_secret() + if not secret: + return {} + return secret.get_content() + + @property + def ready(self) -> bool: + """Return the service_password.""" + return all(k in self.ceph_access_data for k in ["uuid", "key"]) + +class HasCephAccessClientsEvent(EventBase): + """Has CephAccessClients Event.""" + + pass + +class ReadyCephAccessClientsEvent(RelationEvent): + """Has ReadyCephAccessClients Event.""" + + pass + +class CephAccessClientEvents(ObjectEvents): + """Events class for `on`""" + + has_ceph_access_clients = EventSource(HasCephAccessClientsEvent) + ready_ceph_access_clients = EventSource(ReadyCephAccessClientsEvent) + + +class CephAccessProvides(Object): + """ + CephAccessProvides class + """ + + on = CephAccessClientEvents() + + def __init__(self, charm, relation_name): + super().__init__(charm, relation_name) + self.charm = charm + self.relation_name = relation_name + self.framework.observe( + self.charm.on[relation_name].relation_joined, + self._on_ceph_access_relation_joined, + ) + self.framework.observe( + self.charm.on[relation_name].relation_changed, + self._on_ceph_access_relation_changed, + ) + self.framework.observe( + self.charm.on[relation_name].relation_broken, + self._on_ceph_access_relation_broken, + ) + + def _on_ceph_access_relation_joined(self, event): + """Handle CephAccess joined.""" + logging.debug("CephAccess on_joined") + self.on.has_ceph_access_clients.emit() + + def _on_ceph_access_relation_changed(self, event): + """Handle CephAccess joined.""" + logging.debug("CephAccess on_changed") + self.on.ready_ceph_access_clients.emit( + event.relation, + app=event.app, + unit=event.unit) + + def _on_ceph_access_relation_broken(self, event): + """Handle CephAccess broken.""" + logging.debug("CephAccessProvides on_broken") + + def set_ceph_access_credentials(self, relation_name: int, + relation_id: str, + access_credentials: str): + + logging.debug("Setting ceph_access connection information.") + _ceph_access_rel = None + for relation in self.framework.model.relations[relation_name]: + if relation.id == relation_id: + _ceph_access_rel = relation + if not _ceph_access_rel: + # Relation has disappeared so skip send of data + return + app_data = _ceph_access_rel.data[self.charm.app] + logging.debug(access_credentials) + app_data["access-credentials"] = access_credentials diff --git a/ops-sunbeam/unit_tests/test_charms.py b/ops-sunbeam/unit_tests/test_charms.py index fd1a78bd..fc2202df 100644 --- a/ops-sunbeam/unit_tests/test_charms.py +++ b/ops-sunbeam/unit_tests/test_charms.py @@ -116,6 +116,8 @@ requires: identity-credentials: interface: keystone-credentials limit: 1 + ceph-access: + interface: cinder-ceph-key peers: peers: diff --git a/ops-sunbeam/unit_tests/test_core.py b/ops-sunbeam/unit_tests/test_core.py index ea4f50de..04d7283c 100644 --- a/ops-sunbeam/unit_tests/test_core.py +++ b/ops-sunbeam/unit_tests/test_core.py @@ -365,6 +365,13 @@ class TestOSBaseOperatorAPICharm(_TestOSBaseOperatorAPICharm): test_utils.add_ingress_relation_data( self.harness, ingress_rel_id, "public" ) + + ceph_access_rel_id = test_utils.add_base_ceph_access_relation( + self.harness + ) + test_utils.add_ceph_access_relation_response( + self.harness, ceph_access_rel_id + ) self.assertTrue(self.harness.charm.relation_handlers_ready()) # Add an optional relation and test if relation_handlers_ready