From ec1ab0393fb6807b91f56bef1571e99dfab256bc Mon Sep 17 00:00:00 2001 From: Mia Altieri Date: Fri, 18 Oct 2024 14:20:18 +0000 Subject: [PATCH] make changes for mongos-k8s --- .../mongodb/v0/config_server_interface.py | 36 +++++++++++-- lib/charms/mongodb/v1/mongodb_provider.py | 50 ++++++++++++------- 2 files changed, 64 insertions(+), 22 deletions(-) diff --git a/lib/charms/mongodb/v0/config_server_interface.py b/lib/charms/mongodb/v0/config_server_interface.py index f1e8fd043..2053e0424 100644 --- a/lib/charms/mongodb/v0/config_server_interface.py +++ b/lib/charms/mongodb/v0/config_server_interface.py @@ -8,7 +8,9 @@ """ import logging from typing import Optional +from charms.mongodb.v0.mongo import MongoConnection +from pymongo.errors import PyMongoError from charms.data_platform_libs.v0.data_interfaces import ( DatabaseProvides, DatabaseRequestedEvent, @@ -185,8 +187,6 @@ def _on_relation_broken(self, event) -> None: logger.info("Skipping relation broken event, broken event due to scale down") return - self.charm.client_relations.oversee_users(departed_relation_id, event) - def update_config_server_db(self, event): """Provides related mongos applications with new config server db.""" if not self.pass_hook_checks(event): @@ -314,6 +314,8 @@ def _on_relation_changed(self, event) -> None: # avoid restarting mongos when possible if not updated_keyfile and not updated_config and self.is_mongos_running(): + # mongos-k8s router must update its users on start + self._update_k8s_users(event) return # mongos is not available until it is using new secrets @@ -332,6 +334,18 @@ def _on_relation_changed(self, event) -> None: if self.charm.unit.is_leader(): self.charm.mongos_initialised = True + # mongos-k8s router must update its users on start + self._update_k8s_users(event) + + def _update_k8s_users(self, event) -> None: + # K8s can handle its 1:Many users after being initialized + try: + if self.substrate == Config.Substrate.K8S: + self.charm.client_relations.oversee_users(None, None) + except PyMongoError: + event.defer() + logger.debug("failed to add users on mongos-k8s router, will defer and try again.") + def _on_relation_broken(self, event: RelationBrokenEvent) -> None: # Only relation_deparated events can check if scaling down if not self.charm.has_departed_run(event.relation.id): @@ -345,6 +359,22 @@ def _on_relation_broken(self, event: RelationBrokenEvent) -> None: logger.info("Skipping relation broken event, broken event due to scale down") return + # remove all client mongos-k8s users + if ( + self.charm.unit.is_leader() + and self.charm.client_relations.remove_all_relational_users() + ): + try: + self.charm.client_relations.remove_all_relational_users() + + # now that the client mongos users have been removed we can remove ourself + with MongoConnection(self.charm.mongo_config) as mongo: + mongo.drop_user(self.charm.mongo_config.username) + except PyMongoError: + logger.debug("Trouble removing router users, will defer and try again") + event.defer() + return + self.charm.stop_mongos_service() logger.info("Stopped mongos daemon") @@ -359,7 +389,7 @@ def _on_relation_broken(self, event: RelationBrokenEvent) -> None: if self.substrate == Config.Substrate.VM: self.charm.remove_connection_info() else: - self.db_initialised = False + self.charm.db_initialised = False # BEGIN: helper functions def pass_hook_checks(self, event): diff --git a/lib/charms/mongodb/v1/mongodb_provider.py b/lib/charms/mongodb/v1/mongodb_provider.py index db67c86f6..99cde1585 100644 --- a/lib/charms/mongodb/v1/mongodb_provider.py +++ b/lib/charms/mongodb/v1/mongodb_provider.py @@ -16,13 +16,7 @@ from charms.data_platform_libs.v0.data_interfaces import DatabaseProvides from charms.mongodb.v0.mongo import MongoConfiguration, MongoConnection from charms.mongodb.v1.helpers import generate_password -from ops.charm import ( - CharmBase, - EventBase, - RelationBrokenEvent, - RelationChangedEvent, - RelationEvent, -) +from ops.charm import CharmBase, EventBase, RelationBrokenEvent, RelationChangedEvent from ops.framework import Object from ops.model import Relation from pymongo.errors import PyMongoError @@ -94,11 +88,6 @@ def pass_sanity_hook_checks(self) -> bool: if not self.charm.db_initialised: return False - # Warning: the sanity_hook_checks can pass when the call is - # issued by a config-sever because the config-server is allowed to manage the users - # in MongoDB. This is not well named and does not protect integration of a config-server - # to a client application. The mongos charm however doesn't care - # because it supports only one integration that uses MongoDBProvider. if not self.charm.is_role(Config.Role.MONGOS) and not self.charm.is_relation_feasible( self.get_relation_name() ): @@ -110,14 +99,8 @@ def pass_sanity_hook_checks(self) -> bool: return True - def pass_hook_checks(self, event: RelationEvent) -> bool: + def pass_hook_checks(self, event: EventBase) -> bool: """Runs the pre-hooks checks for MongoDBProvider, returns True if all pass.""" - # First, ensure that the relation is valid, useless to do anything else otherwise - if not self.charm.is_role(Config.Role.MONGOS) and not self.charm.is_relation_feasible( - event.relation.name - ): - return False - if not self.pass_sanity_hook_checks(): return False @@ -218,6 +201,7 @@ def remove_users( Raises: PyMongoError """ + with MongoConnection(self.charm.mongo_config) as mongo: for username in users_being_managed - expected_current_users: logger.info("Remove relation user: %s", username) @@ -227,6 +211,15 @@ def remove_users( ): continue + # for user removal of mongos-k8s router, we let the router remove itself + if ( + self.charm.is_role(Config.Role.CONFIG_SERVER) + and self.substrate == Config.Substrate.K8S + ): + logger.info("K8s routers will remove themselves.") + self._remove_from_relational_users_to_manage(username) + return + mongo.drop_user(username) self._remove_from_relational_users_to_manage(username) @@ -514,6 +507,25 @@ def _add_to_relational_users_to_manage(self, user_to_add: str) -> None: current_users.add(user_to_add) self._update_relational_users_to_manage(current_users) + def remove_all_relational_users(self): + """Removes all users from DB. + + Raises: PyMongoError. + """ + with MongoConnection(self.charm.mongo_config) as mongo: + database_users = mongo.get_users() + + users_being_managed = database_users.intersection(self._get_relational_users_to_manage()) + self.remove_users(users_being_managed, expected_current_users=set()) + + # now we must remove all of their connection info + for relation in self._get_relations(): + fields = self.database_provides.fetch_my_relation_data([relation.id])[relation.id] + self.database_provides.delete_relation_data(relation.id, fields=list(fields)) + + # unforatunately the above doesn't work to remove secrets, so we forcibly remove the rest + relation.data[self.charm.app].clear() + @staticmethod def _get_database_from_relation(relation: Relation) -> Optional[str]: """Return database name from relation."""