diff --git a/src/charm.py b/src/charm.py index b8dc50277..50a0e2bc8 100755 --- a/src/charm.py +++ b/src/charm.py @@ -63,7 +63,7 @@ ServiceInfo, ) from pymongo.errors import PyMongoError -from tenacity import before_log, retry, stop_after_attempt, wait_fixed +from tenacity import RetryError, before_log, retry, stop_after_attempt, wait_fixed from config import Config from exceptions import AdminUserCreationError, MissingSecretError @@ -406,7 +406,11 @@ def _on_start(self, event) -> None: return self._initialise_replica_set(event) - self._initialise_users(event) + try: + self._initialise_users(event) + except RetryError: + event.defer() + return # mongod is now active self.unit.status = ActiveStatus() @@ -613,7 +617,6 @@ def _on_secret_changed(self, event): @retry( stop=stop_after_attempt(USER_CREATING_MAX_ATTEMPTS), wait=wait_fixed(USER_CREATION_COOLDOWN), - reraise=True, before=before_log(logger, logging.DEBUG), ) def _initialise_users(self, event: StartEvent) -> None: @@ -644,16 +647,14 @@ def _initialise_users(self, event: StartEvent) -> None: self.users_initialized = True except ExecError as e: logger.error("Deferring on_start: exit code: %i, stderr: %s", e.exit_code, e.stderr) - event.defer() - raise e # we need to raise to make retry work + raise # we need to raise to make retry work except PyMongoError as e: logger.error("Deferring on_start since: error=%r", e) - event.defer() - raise e # we need to raise to make retry work - except AdminUserCreationError as e: + raise # we need to raise to make retry work + except AdminUserCreationError: logger.error("Deferring on_start: Failed to create operator user.") event.defer() - raise e # we need to raise to make retry work + raise # we need to raise to make retry work @retry( stop=stop_after_attempt(3), diff --git a/tests/integration/test_charm.py b/tests/integration/test_charm.py index 28c606ea1..6b37639d1 100644 --- a/tests/integration/test_charm.py +++ b/tests/integration/test_charm.py @@ -183,7 +183,7 @@ async def test_no_password_change_on_invalid_password(ops_test: OpsTest) -> None password1 = await get_password(ops_test, unit_id=leader_id, username="monitor") # The password has to be minimum 3 characters - await set_password(ops_test, unit_id=leader_id, username="monitor", password="ca" * 2048) + await set_password(ops_test, unit_id=leader_id, username="monitor", password="ca" * 1000000) password2 = await get_password(ops_test, unit_id=leader_id, username="monitor") # The password didn't change diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py index a19824010..d28cf3d00 100644 --- a/tests/unit/test_charm.py +++ b/tests/unit/test_charm.py @@ -18,8 +18,9 @@ OperationFailure, PyMongoError, ) +from tenacity import RetryError -from charm import MongoDBCharm, NotReadyError +from charm import USER_CREATING_MAX_ATTEMPTS, MongoDBCharm, NotReadyError from .helpers import patch_network_get @@ -295,6 +296,7 @@ def test_start_already_initialised(self, connection, init_user, provider, defer) self.harness.charm.unit.get_container = mock_container self.harness.charm.app_peer_data["db_initialised"] = "True" + self.harness.charm.app_peer_data["users_initialized"] = "True" self.harness.charm.on.start.emit() @@ -395,7 +397,7 @@ def test_start_mongod_error_initalising_user(self, connection, init_user, provid defer.assert_called() # verify app data - self.assertEqual("db_initialised" in self.harness.charm.app_peer_data, False) + self.assertEqual("users_initialized" in self.harness.charm.app_peer_data, False) @patch("ops.framework.EventBase.defer") @patch("charm.MongoDBProvider") @@ -428,7 +430,7 @@ def test_start_mongod_error_overseeing_users(self, connection, init_user, provid defer.assert_called() # verify app data - self.assertEqual("db_initialised" in self.harness.charm.app_peer_data, False) + self.assertEqual("users_initialized" in self.harness.charm.app_peer_data, False) @patch("ops.framework.EventBase.defer") @patch("charm.MongoDBConnection") @@ -595,14 +597,13 @@ def test_reconfigure_add_member_failure(self, connection, defer): connection.return_value.__enter__.return_value.add_replset_member.assert_called() defer.assert_called() + @patch("charm.MongoDBCharm._initialise_users") @patch("ops.framework.EventBase.defer") @patch("charm.MongoDBProvider.oversee_users") @patch("charm.MongoDBConnection") - @patch("tenacity.nap.time.sleep", MagicMock()) - @patch("charm.USER_CREATING_MAX_ATTEMPTS", 1) - @patch("charm.USER_CREATION_COOLDOWN", 1) - @patch("charm.REPLICA_SET_INIT_CHECK_TIMEOUT", 1) - def test_start_init_operator_user_after_second_call(self, connection, oversee_users, defer): + def test_start_init_operator_user_after_second_call( + self, connection, oversee_users, defer, _initialise_users + ): """Tests that the creation of the admin user is only performed once. Verifies that if the user is already set up, that no attempts to set it up again are @@ -619,6 +620,7 @@ def test_start_init_operator_user_after_second_call(self, connection, oversee_us connection.return_value.__enter__.return_value.is_ready = True oversee_users.side_effect = PyMongoError() + _initialise_users.side_effect = RetryError(last_attempt=USER_CREATING_MAX_ATTEMPTS) self.harness.charm.on.start.emit() @@ -910,6 +912,10 @@ def test__connect_mongodb_exporter_success( expected_uri = uri_template.format(password="mongo123") self.assertEqual(expected_uri, new_uri) + @patch("tenacity.nap.time.sleep", MagicMock()) + @patch("charm.USER_CREATING_MAX_ATTEMPTS", 1) + @patch("charm.USER_CREATION_COOLDOWN", 1) + @patch("charm.REPLICA_SET_INIT_CHECK_TIMEOUT", 1) @patch("charm.MongoDBCharm._init_operator_user") @patch("charm.MongoDBCharm._init_monitor_user") @patch("charm.MongoDBCharm._connect_mongodb_exporter") @@ -918,10 +924,6 @@ def test__connect_mongodb_exporter_success( @patch("ops.framework.EventBase.defer") @patch("charm.MongoDBCharm._set_data_dir_permissions") @patch("charm.MongoDBConnection") - @patch("tenacity.nap.time.sleep", MagicMock()) - @patch("charm.USER_CREATING_MAX_ATTEMPTS", 1) - @patch("charm.USER_CREATION_COOLDOWN", 1) - @patch("charm.REPLICA_SET_INIT_CHECK_TIMEOUT", 1) def test__backup_user_created( self, connection, @@ -936,6 +938,7 @@ def test__backup_user_created( """Tests what backup user was created.""" container = self.harness.model.unit.get_container("mongod") self.harness.set_can_connect(container, True) + self.harness.charm.app_peer_data["db_initialised"] = "True" self.harness.charm.on.start.emit() password = self.harness.charm.get_secret("app", "backup-password") self.assertIsNotNone(password) # verify the password is set