diff --git a/lib/charms/rabbitmq_k8s/v0/rabbitmq.py b/lib/charms/rabbitmq_k8s/v0/rabbitmq.py index c7df240..d25356d 100644 --- a/lib/charms/rabbitmq_k8s/v0/rabbitmq.py +++ b/lib/charms/rabbitmq_k8s/v0/rabbitmq.py @@ -74,21 +74,25 @@ def _on_amqp_goneaway(self, event): # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 1 +LIBPATCH = 2 import logging +from typing import ( + List, +) +from ops.charm import ( + RelationEvent, +) from ops.framework import ( - StoredState, EventBase, - ObjectEvents, EventSource, Object, + ObjectEvents, +) +from ops.model import ( + Relation, ) - -from ops.model import Relation - -from typing import List logger = logging.getLogger(__name__) @@ -106,13 +110,13 @@ class RabbitMQReadyEvent(EventBase): class RabbitMQGoneAwayEvent(EventBase): - """RabbitMQ relation has gone-away Event""" + """RabbitMQ relation has gone-away Event.""" pass class RabbitMQServerEvents(ObjectEvents): - """Events class for `on`""" + """Events class for `on`.""" connected = EventSource(RabbitMQConnectedEvent) ready = EventSource(RabbitMQReadyEvent) @@ -120,9 +124,7 @@ class RabbitMQServerEvents(ObjectEvents): class RabbitMQRequires(Object): - """ - RabbitMQRequires class - """ + """RabbitMQRequires class.""" on = RabbitMQServerEvents() @@ -150,19 +152,16 @@ def __init__(self, charm, relation_name: str, username: str, vhost: str): ) def _on_amqp_relation_joined(self, event): - """RabbitMQ relation joined.""" logging.debug("RabbitMQRabbitMQRequires on_joined") self.on.connected.emit() self.request_access(self.username, self.vhost) def _on_amqp_relation_changed(self, event): - """RabbitMQ relation changed.""" logging.debug("RabbitMQRabbitMQRequires on_changed/departed") if self.password: self.on.ready.emit() def _on_amqp_relation_broken(self, event): - """RabbitMQ relation broken.""" logging.debug("RabbitMQRabbitMQRequires on_broken") self.on.goneaway.emit() @@ -178,22 +177,22 @@ def password(self) -> str: @property def hostname(self) -> str: - """Return the hostname from the RabbitMQ relation""" + """Return the hostname from the RabbitMQ relation.""" return self._amqp_rel.data[self._amqp_rel.app].get("hostname") @property def ssl_port(self) -> str: - """Return the SSL port from the RabbitMQ relation""" + """Return the SSL port from the RabbitMQ relation.""" return self._amqp_rel.data[self._amqp_rel.app].get("ssl_port") @property def ssl_ca(self) -> str: - """Return the SSL port from the RabbitMQ relation""" + """Return the SSL port from the RabbitMQ relation.""" return self._amqp_rel.data[self._amqp_rel.app].get("ssl_ca") @property def hostnames(self) -> List[str]: - """Return a list of remote RMQ hosts from the RabbitMQ relation""" + """Return a list of remote RMQ hosts from the RabbitMQ relation.""" _hosts = [] for unit in self._amqp_rel.units: _hosts.append(self._amqp_rel.data[unit].get("ingress-address")) @@ -207,29 +206,34 @@ def request_access(self, username: str, vhost: str) -> None: self._amqp_rel.data[self.charm.app]["vhost"] = vhost -class HasRabbitMQClientsEvent(EventBase): +class HasRabbitMQClientsEvent(RelationEvent): """Has RabbitMQClients Event.""" pass -class ReadyRabbitMQClientsEvent(EventBase): +class ReadyRabbitMQClientsEvent(RelationEvent): """RabbitMQClients Ready Event.""" pass +class GoneAwayRabbitMQClientsEvent(RelationEvent): + """RabbitMQClients GoneAway Event.""" + + pass + + class RabbitMQClientEvents(ObjectEvents): - """Events class for `on`""" + """Events class for `on`.""" has_amqp_clients = EventSource(HasRabbitMQClientsEvent) ready_amqp_clients = EventSource(ReadyRabbitMQClientsEvent) + gone_away_amqp_clients = EventSource(GoneAwayRabbitMQClientsEvent) class RabbitMQProvides(Object): - """ - RabbitMQProvides class - """ + """RabbitMQProvides class.""" on = RabbitMQClientEvents() @@ -253,29 +257,37 @@ def __init__(self, charm, relation_name, callback): def _on_amqp_relation_joined(self, event): """Handle RabbitMQ joined.""" - logging.debug("RabbitMQRabbitMQProvides on_joined data={}" - .format(event.relation.data[event.relation.app])) - self.on.has_amqp_clients.emit() + logging.debug( + "RabbitMQRabbitMQProvides on_joined data={}".format( + event.relation.data[event.relation.app] + ) + ) + self.on.has_amqp_clients.emit(event.relation) def _on_amqp_relation_changed(self, event): """Handle RabbitMQ changed.""" - logging.debug("RabbitMQRabbitMQProvides on_changed data={}" - .format(event.relation.data[event.relation.app])) + logging.debug( + "RabbitMQRabbitMQProvides on_changed data={}".format( + event.relation.data[event.relation.app] + ) + ) # Validate data on the relation if self.username(event) and self.vhost(event): - self.on.ready_amqp_clients.emit() + self.on.ready_amqp_clients.emit(event.relation) if self.charm.unit.is_leader(): self.callback(event, self.username(event), self.vhost(event)) else: - logging.warning("Received RabbitMQ changed event without the " - "expected keys ('username', 'vhost') in the " - "application data bag. Incompatible charm in " - "other end of relation?") + logging.warning( + "Received RabbitMQ changed event without the " + "expected keys ('username', 'vhost') in the " + "application data bag. Incompatible charm in " + "other end of relation?" + ) def _on_amqp_relation_broken(self, event): """Handle RabbitMQ broken.""" logging.debug("RabbitMQRabbitMQProvides on_departed") - # TODO clear data on the relation + self.on.gone_away_amqp_clients.emit(event.relation) def username(self, event): """Return the RabbitMQ username from the client side of the relation.""" diff --git a/src/charm.py b/src/charm.py index 5bacc7e..1cd86db 100755 --- a/src/charm.py +++ b/src/charm.py @@ -131,6 +131,10 @@ def __init__(self, *args): self.amqp_provider.on.ready_amqp_clients, self._on_ready_amqp_clients, ) + self.framework.observe( + self.amqp_provider.on.gone_away_amqp_clients, + self._on_gone_away_amqp_clients, + ) self._stored.set_default(enabled_plugins=[]) self._stored.set_default(rabbitmq_version=None) @@ -461,6 +465,19 @@ def _on_ready_amqp_clients(self, event) -> None: """Event handler on AMQP clients ready.""" self._on_update_status(event) + def _on_gone_away_amqp_clients(self, event) -> None: + """Event handler on AMQP clients goneaway.""" + if not self.unit.is_leader(): + logging.debug("Not a leader unit, nothing to do") + return + + api = self._get_admin_api() + username = self.amqp_provider.username(event) + if username and self.does_user_exist(username): + api.delete_user(username) + + self.peers.delete_user(username) + @property def amqp_rel(self) -> Relation: """AMQP relation.""" diff --git a/src/interface_rabbitmq_peers.py b/src/interface_rabbitmq_peers.py index a4eef25..227d6bb 100644 --- a/src/interface_rabbitmq_peers.py +++ b/src/interface_rabbitmq_peers.py @@ -169,6 +169,11 @@ def store_password(self, username: str, password: str): logging.debug(f"Storing password for {username}") self.peers_rel.data[self.peers_rel.app][username] = password + def delete_user(self, username: str): + """Delete username from application data.""" + if username in self.peers_rel.data[self.peers_rel.app]: + del self.peers_rel.data[self.peers_rel.app][username] + def set_nodename(self, nodename: str): """Advertise nodename to peers.""" logging.debug(f"Setting nodename {nodename}")