diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 09f3b1723..87c85737f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -41,10 +41,16 @@ jobs: run: tox run -e unit build: - name: Build charms - uses: canonical/data-platform-workflows/.github/workflows/build_charms_with_cache.yaml@v5 + strategy: + fail-fast: true + matrix: + charms: [".", "tests/integration/ha_tests/application_charm", "tests/integration/relation_tests/application-charm"] + name: Build ${{matrix.charms}} charm + uses: canonical/data-platform-workflows/.github/workflows/build_charm_without_cache.yaml@v5 with: charmcraft-snap-channel: "latest/edge" + path-to-charm-directory: ${{matrix.charms}} + integration-test: strategy: @@ -78,6 +84,17 @@ jobs: uses: actions/download-artifact@v3 with: name: ${{ needs.build.outputs.artifact-name }} + - name: Free disk space + run: | + echo "Free disk space before cleanup" + df -T + # free space in the runner + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf /usr/local/share/boost + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + echo "Free disk space after cleanup" + df -T - name: Select tests id: select-tests run: | diff --git a/.github/workflows/release_5_edge.yaml b/.github/workflows/release_5_edge.yaml index bdbacd78d..040562081 100644 --- a/.github/workflows/release_5_edge.yaml +++ b/.github/workflows/release_5_edge.yaml @@ -3,7 +3,7 @@ name: Release to 5/edge on: push: branches: - - main + - 5/edge jobs: lib-check: diff --git a/src/charm.py b/src/charm.py index cd1854c5d..406f3c2cb 100755 --- a/src/charm.py +++ b/src/charm.py @@ -271,6 +271,21 @@ def db_initialised(self, value): f"'db_initialised' must be a boolean value. Proivded: {value} is of type {type(value)}" ) + @property + def users_initialized(self) -> bool: + """Check if MongoDB users are created.""" + return "users_initialized" in self.app_peer_data + + @users_initialized.setter + def users_initialized(self, value): + """Set the users_initialized flag.""" + if isinstance(value, bool): + self.app_peer_data["users_initialized"] = str(value) + else: + raise ValueError( + f"'users_initialized' must be a boolean value. Proivded: {value} is of type {type(value)}" + ) + # END: properties # BEGIN: generic helper methods @@ -387,6 +402,7 @@ def _on_start(self, event) -> None: return self._initialise_replica_set(event) + self._initialise_users(event) # mongod is now active self.unit.status = ActiveStatus() @@ -590,6 +606,48 @@ def _on_secret_changed(self, event): # END: actions # BEGIN: user management + @retry( + stop=stop_after_attempt(3), + wait=wait_fixed(5), + reraise=True, + before=before_log(logger, logging.DEBUG), + ) + def _initialise_users(self, event: StartEvent) -> None: + """Create users. + + User creation can only be completed after the replica set has + been initialised which requires some time. + In race conditions this can lead to failure to initialise users. + To prevent these race conditions from breaking the code, retry on failure. + """ + if not self.db_initialised: + return + + if self.users_initialized: + return + + # only leader should create users + if not self.unit.is_leader(): + return + + logger.info("User initialization") + + try: + self._init_operator_user() + self._init_backup_user() + self._init_monitor_user() + logger.info("Reconcile relations") + self.client_relations.oversee_users(None, event) + 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() + return + except PyMongoError as e: + logger.error("Deferring on_start since: error=%r", e) + event.defer() + return + @retry( stop=stop_after_attempt(3), wait=wait_fixed(5), @@ -757,12 +815,6 @@ def _initialise_replica_set(self, event: StartEvent) -> None: try: logger.info("Replica Set initialization") 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) except ExecError as e: logger.error( "Deferring on_start: exit code: %i, stderr: %s", e.exit_code, e.stderr diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py index d6221e030..93b75cb22 100644 --- a/tests/unit/test_charm.py +++ b/tests/unit/test_charm.py @@ -295,6 +295,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() @@ -394,7 +395,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") @@ -423,7 +424,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")