From 02a3ffc0b09f3fe986451c41a48947287a75b9f5 Mon Sep 17 00:00:00 2001 From: Dmitry Ratushnyy Date: Mon, 26 Jun 2023 12:49:11 +0000 Subject: [PATCH] [DPE-1956] Add a backup user --- src/charm.py | 41 +++++++++++++++++++++++++++++++++++++-- tests/unit/test_charm.py | 42 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/src/charm.py b/src/charm.py index e9d545f9d..31e3a90b7 100755 --- a/src/charm.py +++ b/src/charm.py @@ -21,7 +21,13 @@ ) from charms.mongodb.v0.mongodb_provider import MongoDBProvider from charms.mongodb.v0.mongodb_tls import MongoDBTLS -from charms.mongodb.v0.users import CHARM_USERS, MongoDBUser, MonitorUser, OperatorUser +from charms.mongodb.v0.users import ( + CHARM_USERS, + BackupUser, + MongoDBUser, + MonitorUser, + OperatorUser, +) from charms.prometheus_k8s.v0.prometheus_scrape import MetricsEndpointProvider from ops.charm import ( ActionEvent, @@ -134,6 +140,12 @@ def monitor_config(self) -> MongoDBConfiguration: MonitorUser, [self.get_hostname_for_unit(self.unit)] ) + @property + def backup_config(self) -> MongoDBConfiguration: + """Generates a MongoDBConfiguration object for backup.""" + self._check_or_set_user_password(BackupUser) + return self._get_mongodb_config_for_user(BackupUser, BackupUser.get_hosts()) + @property def _monitor_layer(self) -> Layer: """Returns a Pebble configuration layer for mongodb_exporter.""" @@ -261,7 +273,7 @@ def _on_start(self, event) -> None: event.defer() return - if not container.exists(Config.SOCKET_PATH): + if not self._socket_exists(container): logger.debug("The mongod socket is not ready yet.") event.defer() return @@ -475,6 +487,27 @@ def _init_monitor_user(self): self._connect_mongodb_exporter() + @retry( + stop=stop_after_attempt(3), + wait=wait_fixed(5), + reraise=True, + before=before_log(logger, logging.DEBUG), + ) + def _init_backup_user(self): + """Creates the backup user on the MongoDB database.""" + if self._is_user_created(BackupUser): + return + + with MongoDBConnection(self.mongodb_config) as mongo: + # first we must create the necessary roles for the PBM tool + logger.debug("creating the backup user roles...") + mongo.create_role( + role_name=BackupUser.get_mongodb_role(), privileges=BackupUser.get_privileges() + ) + logger.debug("creating the backup user...") + mongo.create_user(self.backup_config) + self._set_user_created(BackupUser) + # END: user management # BEGIN: helper functions @@ -571,6 +604,7 @@ def _initialise_replica_set(self, event: StartEvent) -> None: direct_mongo.init_replset() logger.info("User initialization") self._init_operator_user() + self._init_backup_user() self._init_monitor_user() logger.info("Reconcile relations") self.client_relations.oversee_users(None, event) @@ -745,6 +779,9 @@ def _connect_mongodb_exporter(self) -> None: # Restart changed services and start startup-enabled services. container.replan() + def _socket_exists(self, container) -> bool: + return container.exists(Config.SOCKET_PATH) + # END: helper functions # BEGIN: static methods diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py index 84789e51b..5ae305555 100644 --- a/tests/unit/test_charm.py +++ b/tests/unit/test_charm.py @@ -678,3 +678,45 @@ def test__connect_mongodb_exporter_success( ) expected_uri = uri_template.format(password="mongo123") self.assertEqual(expected_uri, new_uri) + + @patch("charm.MongoDBCharm._init_operator_user") + @patch("charm.MongoDBCharm._init_monitor_user") + @patch("charm.MongoDBCharm._connect_mongodb_exporter") + @patch("charm.MongoDBCharm._socket_exists") + @patch("charm.MongoDBCharm._pull_licenses") + @patch("ops.framework.EventBase.defer") + @patch("charm.MongoDBCharm._set_data_dir_permissions") + @patch("charm.MongoDBConnection") + def test__backup_user_created( + self, + connection, + fix_data_dir, + defer, + pull_licenses, + _socket_exists, + _connect_mongodb_exporter, + _init_operator_user, + _init_monitor_user, + ): + """Tests what backup user was created.""" + container = self.harness.model.unit.get_container("mongod") + self.harness.set_can_connect(container, True) + self.harness.charm.on.start.emit() + password = self.harness.charm.app_peer_data["backup-password"] + self.assertIsNotNone(password) # verify the password is set + + @patch("charm.MongoDBConnection") + def test_set_password_provided(self, connection): + """Tests that a given password is set as the new mongodb password for backup user.""" + container = self.harness.model.unit.get_container("mongod") + self.harness.set_leader(True) + self.harness.set_can_connect(container, True) + self.harness.charm.on.start.emit() + action_event = mock.Mock() + action_event.params = {"password": "canonical123", "username": "backup"} + self.harness.charm._on_set_password(action_event) + new_password = self.harness.charm.app_peer_data["backup-password"] + + # verify app data is updated and results are reported to user + self.assertEqual("canonical123", new_password) + action_event.set_results.assert_called_with({"password": "canonical123"})