From d00fd7f938b8389cdd9f7344cc500c84054e9b96 Mon Sep 17 00:00:00 2001 From: "Amanda H. L. de Andrade Katz" Date: Tue, 24 Sep 2024 09:07:02 -0300 Subject: [PATCH] chores: remove reset-instance action (#535) --- actions.yaml | 5 - docs/how-to/contribute.md | 3 +- src-docs/charm.py.md | 22 +-- src-docs/pebble.py.md | 26 ---- src/actions/__init__.py | 1 - src/actions/reset_instance.py | 61 -------- src/charm.py | 44 +----- src/charm_state.py | 2 +- src/pebble.py | 28 ---- src/synapse/__init__.py | 1 - src/synapse/api.py | 2 +- src/synapse/workload.py | 30 +--- tests/integration/conftest.py | 1 + tests/integration/test_charm.py | 81 +--------- tests/integration/test_s3.py | 2 +- tests/unit/test_backup_observer.py | 2 +- tests/unit/test_register_user_action.py | 2 +- tests/unit/test_reset_instance_action.py | 181 ----------------------- 18 files changed, 22 insertions(+), 472 deletions(-) delete mode 100644 src/actions/reset_instance.py delete mode 100644 tests/unit/test_reset_instance_action.py diff --git a/actions.yaml b/actions.yaml index 97febaaf..574188a2 100644 --- a/actions.yaml +++ b/actions.yaml @@ -11,11 +11,6 @@ anonymize-user: type: string required: - username -reset-instance: - description: | - Set a new server_name before running this action. - Once a server_name is configured, you must start a new instance if you wish a different one. - This actions will erase all data and create a instance with the new server_name. register-user: description: | Registers a user for the Synapse server. diff --git a/docs/how-to/contribute.md b/docs/how-to/contribute.md index e8c09776..82e5d21a 100644 --- a/docs/how-to/contribute.md +++ b/docs/how-to/contribute.md @@ -101,8 +101,7 @@ juju deploy ./synapse_ubuntu-22.04-amd64.charm \ ### Configure `server_name` Synapse requires a `server_name` to be set before starting. Note that this cannot -be changed later so if you want a different server name, will need to run the -action `reset-instance` to re-create everything. +be changed later. The following command will configure the `server_name` mychat.test.com: diff --git a/src-docs/charm.py.md b/src-docs/charm.py.md index 78fbe36e..9d8c3195 100644 --- a/src-docs/charm.py.md +++ b/src-docs/charm.py.md @@ -74,7 +74,7 @@ Unit that this execution is responsible for. --- - + ### function `build_charm_state` @@ -91,7 +91,7 @@ Build charm state. --- - + ### function `get_main_unit` @@ -108,7 +108,7 @@ Get main unit. --- - + ### function `get_main_unit_address` @@ -125,7 +125,7 @@ Get main unit address. If main unit is None, use unit name. --- - + ### function `get_signing_key` @@ -142,7 +142,7 @@ Get signing key from secret. --- - + ### function `get_unit_number` @@ -166,7 +166,7 @@ Get unit number from unit name. --- - + ### function `instance_map` @@ -183,7 +183,7 @@ Build instance_map config. --- - + ### function `is_main` @@ -201,7 +201,7 @@ Verify if this unit is the main. --- - + ### function `peer_units_total` @@ -218,7 +218,7 @@ Get peer units total. --- - + ### function `reconcile` @@ -238,7 +238,7 @@ This is the main entry for changes that require a restart. --- - + ### function `set_main_unit` @@ -256,7 +256,7 @@ Create/Renew an admin access token and put it in the peer relation. --- - + ### function `set_signing_key` diff --git a/src-docs/pebble.py.md b/src-docs/pebble.py.md index fd139ec1..a98a96c4 100644 --- a/src-docs/pebble.py.md +++ b/src-docs/pebble.py.md @@ -250,32 +250,6 @@ This is the main entry for changes that require a restart done via Pebble. -**Raises:** - - - `PebbleServiceError`: if something goes wrong while interacting with Pebble. - - ---- - - - -## function `reset_instance` - -```python -reset_instance(charm_state: CharmState, container: Container) → None -``` - -Reset instance. - - - -**Args:** - - - `charm_state`: Instance of CharmState - - `container`: Charm container. - - - **Raises:** - `PebbleServiceError`: if something goes wrong while interacting with Pebble. diff --git a/src/actions/__init__.py b/src/actions/__init__.py index ec86e6eb..8ac23cc3 100644 --- a/src/actions/__init__.py +++ b/src/actions/__init__.py @@ -5,4 +5,3 @@ # Exporting methods to be used for another modules from .register_user import RegisterUserError, register_user # noqa: F401 -from .reset_instance import ResetInstanceError, reset_instance # noqa: F401 diff --git a/src/actions/reset_instance.py b/src/actions/reset_instance.py deleted file mode 100644 index 7cf93985..00000000 --- a/src/actions/reset_instance.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2024 Canonical Ltd. -# See LICENSE file for licensing details. - -"""Module to interact with Reset Instance action.""" - -import logging -import typing - -import ops -import psycopg2 - -import synapse -from charm_state import CharmState -from database_client import DatabaseClient, DatasourcePostgreSQL - -logger = logging.getLogger(__name__) - - -class ResetInstanceError(Exception): - """Exception raised when something fails while running reset-instance. - - Attrs: - msg (str): Explanation of the error. - """ - - def __init__(self, msg: str): - """Initialize a new instance of the ResetInstanceError exception. - - Args: - msg (str): Explanation of the error. - """ - self.msg = msg - - -def reset_instance( - container: ops.Container, - charm_state: CharmState, - datasource: typing.Optional[DatasourcePostgreSQL], -) -> None: - """Run reset instance action. - - Args: - container: Container of the charm. - charm_state: charm state from the charm. - datasource: datasource to interact with the database. - - Raises: - ResetInstanceError: if something goes wrong while resetting the instance. - """ - try: - if datasource is not None: - logger.info("Erase Synapse database") - # Connecting to template1 to make it possible to erase the database. - # Otherwise PostgreSQL will prevent it if there are open connections. - db_client = DatabaseClient(datasource=datasource, alternative_database="template1") - db_client.erase() - synapse.execute_migrate_config(container=container, charm_state=charm_state) - except (psycopg2.Error, synapse.WorkloadError) as exc: - raise ResetInstanceError(str(exc)) from exc diff --git a/src/charm.py b/src/charm.py index 45b64b00..5b12d9a2 100755 --- a/src/charm.py +++ b/src/charm.py @@ -92,7 +92,6 @@ def __init__(self, *args: typing.Any) -> None: self.framework.observe( self.on[synapse.SYNAPSE_PEER_RELATION_NAME].relation_changed, self._on_relation_changed ) - self.framework.observe(self.on.reset_instance_action, self._on_reset_instance_action) self.framework.observe(self.on.synapse_pebble_ready, self._on_synapse_pebble_ready) self.framework.observe( self.on.synapse_nginx_pebble_ready, self._on_synapse_nginx_pebble_ready @@ -497,50 +496,11 @@ def _on_synapse_nginx_pebble_ready(self, _: ops.HookEvent) -> None: pebble.restart_nginx(container, self.get_main_unit_address()) self._set_unit_status() - @inject_charm_state - def _on_reset_instance_action(self, event: ActionEvent, charm_state: CharmState) -> None: - """Reset instance and report action result. - - Args: - event: Event triggering the reset instance action. - charm_state: The charm state. - """ - results = { - "reset-instance": False, - } - if not self.model.unit.is_leader(): - event.fail("Only the juju leader unit can run reset instance action") - return - container = self.unit.get_container(synapse.SYNAPSE_CONTAINER_NAME) - if not container.can_connect(): - event.fail("Failed to connect to the container") - return - try: - self.model.unit.status = ops.MaintenanceStatus("Resetting Synapse instance") - try: - container.stop(pebble.STATS_EXPORTER_SERVICE_NAME) - except (ops.pebble.Error, RuntimeError) as e: - event.fail(f"Failed to stop Synapse Stats Exporter: {str(e)}") - pebble.reset_instance(charm_state, container) - datasource = self._database.get_relation_as_datasource() - actions.reset_instance( - container=container, charm_state=charm_state, datasource=datasource - ) - logger.info("Start Synapse") - pebble.restart_synapse(charm_state, container, self.is_main()) - results["reset-instance"] = True - except (pebble.PebbleServiceError, actions.ResetInstanceError) as exc: - self.model.unit.status = ops.BlockedStatus(str(exc)) - event.fail(str(exc)) - return - event.set_results(results) - self.model.unit.status = ops.ActiveStatus() - def _on_register_user_action(self, event: ActionEvent) -> None: - """Reset instance and report action result. + """Register user and report action result. Args: - event: Event triggering the reset instance action. + event: Event triggering the register user instance action. """ container = self.unit.get_container(synapse.SYNAPSE_CONTAINER_NAME) if not container.can_connect(): diff --git a/src/charm_state.py b/src/charm_state.py index 2ef0465b..36ef6983 100644 --- a/src/charm_state.py +++ b/src/charm_state.py @@ -381,7 +381,7 @@ def proxy(self) -> "ProxyConfig": # from_charm receives configuration from all integration so too many arguments. @classmethod - def from_charm( # pylint: disable=too-many-arguments + def from_charm( # pylint: disable=too-many-arguments,too-many-positional-arguments cls, charm: ops.CharmBase, datasource: typing.Optional[DatasourcePostgreSQL], diff --git a/src/pebble.py b/src/pebble.py index 77be8821..aa10013f 100644 --- a/src/pebble.py +++ b/src/pebble.py @@ -395,34 +395,6 @@ def reconcile( # noqa: C901 pylint: disable=too-many-branches,too-many-statemen raise PebbleServiceError(str(exc)) from exc -def reset_instance(charm_state: CharmState, container: ops.model.Container) -> None: - """Reset instance. - - Args: - charm_state: Instance of CharmState - container: Charm container. - - Raises: - PebbleServiceError: if something goes wrong while interacting with Pebble. - """ - # This is needed in the case of relation with Postgresql. - # If there is open connections it won't be possible to drop the database. - try: - logger.info("Replan service to not restart") - container.add_layer( - synapse.SYNAPSE_CONTAINER_NAME, - _pebble_layer_without_restart(charm_state), - combine=True, - ) - container.replan() - logger.info("Stop Synapse instance") - container.stop(synapse.SYNAPSE_SERVICE_NAME) - logger.info("Erase Synapse data") - synapse.reset_instance(container) - except ops.pebble.PathError as exc: - raise PebbleServiceError(str(exc)) from exc - - def _pebble_layer(charm_state: CharmState, is_main: bool = True) -> ops.pebble.LayerDict: """Return a dictionary representing a Pebble layer. diff --git a/src/synapse/__init__.py b/src/synapse/__init__.py index d95ec9bd..f363e41e 100644 --- a/src/synapse/__init__.py +++ b/src/synapse/__init__.py @@ -67,7 +67,6 @@ get_environment, get_media_store_path, get_registration_shared_secret, - reset_instance, validate_config, ) from .workload_configuration import ( # noqa: F401 diff --git a/src/synapse/api.py b/src/synapse/api.py index e99ba86f..e8241605 100644 --- a/src/synapse/api.py +++ b/src/synapse/api.py @@ -234,7 +234,7 @@ def register_user( raise RegisterUserError(str(exc)) from exc -def _generate_mac( +def _generate_mac( # pylint: disable=too-many-positional-arguments shared_secret: str, nonce: str, user: str, diff --git a/src/synapse/workload.py b/src/synapse/workload.py index 6ced4cc4..f0293cb6 100644 --- a/src/synapse/workload.py +++ b/src/synapse/workload.py @@ -182,9 +182,7 @@ def _check_server_name(container: ops.Container, charm_state: CharmState) -> Non ): msg = ( f"server_name {charm_state.synapse_config.server_name} is different from the existing " - f"one {configured_server_name}. Please revert the config or run the action " - "reset-instance if you want to erase the existing instance and start a new " - "one." + f"one {configured_server_name}. Please revert the config." ) logger.error(msg) raise ServerNameModifiedError( @@ -302,32 +300,6 @@ def validate_config(container: ops.Container) -> None: raise WorkloadError("Validate config failed, please check the logs") -def reset_instance(container: ops.Container) -> None: - """Erase data and config server_name. - - Args: - container: Container of the charm. - - Raises: - PathError: if somethings goes wrong while erasing the Synapse directory. - """ - logging.debug("Erasing directory %s", SYNAPSE_CONFIG_DIR) - try: - container.remove_path(SYNAPSE_CONFIG_DIR, recursive=True) - except PathError as path_error: - # The error "unlinkat //data: device or resource busy" is expected - # when removing the entire directory because it's a volume mount. - # The files will be removed but SYNAPSE_CONFIG_DIR directory will - # remain. - if "device or resource busy" in str(path_error): - pass - else: - logger.exception( - "exception while erasing directory %s: %r", SYNAPSE_CONFIG_DIR, path_error - ) - raise - - def generate_nginx_config(container: ops.Container, main_unit_address: str) -> None: """Generate NGINX configuration based on templates. diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 055fd108..ab375aa4 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -97,6 +97,7 @@ def synapse_app_charmhub_name_fixture() -> str: return "synapse-charmhub" +# pylint: disable=too-many-positional-arguments @pytest_asyncio.fixture(scope="module", name="synapse_app") async def synapse_app_fixture( ops_test: OpsTest, diff --git a/tests/integration/test_charm.py b/tests/integration/test_charm.py index 5bdbf464..3ab7a8d7 100644 --- a/tests/integration/test_charm.py +++ b/tests/integration/test_charm.py @@ -132,31 +132,6 @@ async def test_synapse_scale_blocked(synapse_app: Application): ) -async def test_reset_instance_action( - model: Model, another_synapse_app: Application, another_server_name: str -): - """ - arrange: a deployed Synapse charm in a blocked state due to a server_name change. - act: call the reset_instance action. - assert: the old instance is deleted and the new one configured. - """ - unit = model.applications[another_synapse_app.name].units[0] - # Status string defined in Juju - # https://github.com/juju/juju/blob/2.9/core/status/status.go#L150 - assert unit.workload_status == "blocked" - assert "server_name modification is not allowed" in unit.workload_status_message - action_reset_instance: Action = await another_synapse_app.units[0].run_action( # type: ignore - "reset-instance" - ) - await action_reset_instance.wait() - assert action_reset_instance.status == "completed" - assert action_reset_instance.results["reset-instance"] - assert unit.workload_status == "active" - config = await model.applications[another_synapse_app.name].get_config() - current_server_name = config.get("server_name", {}).get("value") - assert current_server_name == another_server_name - - @pytest.mark.asyncio async def test_workload_version( ops_test: OpsTest, @@ -412,6 +387,7 @@ async def test_synapse_enable_mjolnir( assert res.status_code == 200 +# pylint: disable=too-many-positional-arguments @pytest.mark.mjolnir async def test_synapse_with_mjolnir_from_refresh_is_up( ops_test: OpsTest, @@ -461,58 +437,3 @@ async def test_synapse_with_mjolnir_from_refresh_is_up( f"http://{synapse_ip}:{synapse.MJOLNIR_HEALTH_PORT}/healthz", timeout=5 ) assert mjolnir_response.status_code == 200 - - -async def test_admin_token_refresh(model: Model, synapse_app: Application): - """ - arrange: Build and deploy the Synapse charm from charmhub. - Create a user. - Promote it to admin (forces to get the admin token). - Reset the instance (wipes database and so admin token is invalid). - Create another user. - act: Promote the second user to admin. - assert: It should not fail as the admin token is refreshed. - """ - action_register_initial_user: Action = await synapse_app.units[0].run_action( - "register-user", username="initial_user", admin=False - ) - await action_register_initial_user.wait() - assert action_register_initial_user.status == "completed" - assert action_register_initial_user.results.get("register-user") - password = action_register_initial_user.results.get("user-password") - assert password - action_promote_initial_user: Action = await synapse_app.units[0].run_action( # type: ignore - "promote-user-admin", username="initial_user" - ) - await action_promote_initial_user.wait() - assert action_promote_initial_user.status == "completed" - - new_server_name = f"test-admin-token-refresh{token_hex(6)}" - await synapse_app.set_config({"server_name": new_server_name}) - await model.wait_for_idle() - - unit = model.applications[synapse_app.name].units[0] - assert unit.workload_status == "blocked" - assert "server_name modification is not allowed" in unit.workload_status_message - action_reset_instance: Action = await synapse_app.units[0].run_action( # type: ignore - "reset-instance" - ) - await action_reset_instance.wait() - assert action_reset_instance.status == "completed" - assert action_reset_instance.results["reset-instance"] - assert unit.workload_status == "active" - - action_register_after_reset: Action = await synapse_app.units[0].run_action( - "register-user", username="user2", admin=False - ) - await action_register_after_reset.wait() - assert action_register_after_reset.status == "completed" - assert action_register_after_reset.results.get("register-user") - password = action_register_after_reset.results.get("user-password") - assert password - - action_promote_after_reset: Action = await synapse_app.units[0].run_action( # type: ignore - "promote-user-admin", username="user2" - ) - await action_promote_after_reset.wait() - assert action_promote_after_reset.status == "completed" diff --git a/tests/integration/test_s3.py b/tests/integration/test_s3.py index 87522f69..c30c3ea0 100644 --- a/tests/integration/test_s3.py +++ b/tests/integration/test_s3.py @@ -247,7 +247,7 @@ async def test_synapse_backup_delete( @pytest.mark.s3 @pytest.mark.usefixtures("s3_media_bucket") -async def test_synapse_enable_media( +async def test_synapse_enable_media( # pylint: disable=too-many-positional-arguments model: Model, synapse_app: Application, get_unit_ips: typing.Callable[[str], typing.Awaitable[tuple[str, ...]]], diff --git a/tests/unit/test_backup_observer.py b/tests/unit/test_backup_observer.py index b1f4637d..86778af6 100644 --- a/tests/unit/test_backup_observer.py +++ b/tests/unit/test_backup_observer.py @@ -74,7 +74,7 @@ ), ], ) -def test_on_s3_credentials_changed( +def test_on_s3_credentials_changed( # pylint: disable=too-many-positional-arguments harness: Harness, monkeypatch: pytest.MonkeyPatch, relation_data: dict, diff --git a/tests/unit/test_register_user_action.py b/tests/unit/test_register_user_action.py index a0137090..5872ebad 100644 --- a/tests/unit/test_register_user_action.py +++ b/tests/unit/test_register_user_action.py @@ -21,7 +21,7 @@ def test_register_user_action(harness: Harness, monkeypatch: pytest.MonkeyPatch) """ arrange: start the Synapse charm, set Synapse container to be ready and set server_name. act: run register-user action. - assert: Synapse charm should reset the instance. + assert: User is created and the charm is active. """ harness.begin_with_initial_hooks() get_registration_mock = unittest.mock.Mock(return_value="shared_secret") diff --git a/tests/unit/test_reset_instance_action.py b/tests/unit/test_reset_instance_action.py deleted file mode 100644 index 34e98a20..00000000 --- a/tests/unit/test_reset_instance_action.py +++ /dev/null @@ -1,181 +0,0 @@ -# Copyright 2024 Canonical Ltd. -# See LICENSE file for licensing details. - -"""Reset instance action unit tests.""" - -# pylint: disable=protected-access - -import io -import unittest.mock - -import ops -import pytest -from ops.testing import Harness - -import actions -import synapse -from database_client import DatabaseClient - -from .conftest import TEST_SERVER_NAME - - -def test_reset_instance_action(harness: Harness) -> None: - """ - arrange: start the Synapse charm, set Synapse container to be ready and set server_name. - act: run reset-instance action. - assert: Synapse charm should reset the instance. - """ - harness.set_leader(True) - harness.begin() - event = unittest.mock.Mock() - - # Calling to test the action since is not possible calling via harness - harness.charm._on_reset_instance_action(event) - - # Disable no-member to allow tests on generated mock attributes - # pylint: disable=no-member - assert event.set_results.call_count == 1 - event.set_results.assert_called_with({"reset-instance": True}) - assert isinstance(harness.model.unit.status, ops.ActiveStatus) - - -def test_reset_instance_action_container_down(harness: Harness) -> None: - """ - arrange: start the Synapse charm, set Synapse container to be ready and set server_name. - act: run reset-instance action. - assert: Synapse charm should reset the instance. - """ - harness.set_leader(True) - harness.begin() - harness.set_can_connect(harness.model.unit.containers[synapse.SYNAPSE_CONTAINER_NAME], False) - event = unittest.mock.Mock() - - # Calling to test the action since is not possible calling via harness - harness.charm._on_reset_instance_action(event) - - assert event.set_results.call_count == 0 - assert event.fail.call_count == 1 - assert "Failed to connect to the container" == event.fail.call_args[0][0] - - -@pytest.mark.parametrize( - "harness", - [ - pytest.param(1, id="harness_exit_code"), - ], - indirect=True, -) -def test_reset_instance_action_failed(harness: Harness) -> None: - """ - arrange: start the Synapse charm, set Synapse container to be ready and set server_name. - act: change server_name and run reset-instance action. - assert: Synapse charm should be blocked by error on migrate_config command. - """ - harness.set_leader(True) - harness.begin() - event = unittest.mock.Mock() - - # Calling to test the action since is not possible calling via harness - harness.charm._on_reset_instance_action(event) - - assert event.set_results.call_count == 0 - assert isinstance(harness.model.unit.status, ops.BlockedStatus) - assert "Migrate config failed" in str(harness.model.unit.status) - - -def test_reset_instance_action_path_error_blocked( - container_with_path_error_blocked: unittest.mock.MagicMock, - harness: Harness, - monkeypatch: pytest.MonkeyPatch, -) -> None: - """ - arrange: start the Synapse charm, set Synapse container to be ready and set server_name. - act: change server_name and run reset-instance action. - assert: Synapse charm should be blocked by error on remove_path. - """ - harness.set_leader(True) - harness.begin() - harness.charm.unit.get_container = unittest.mock.MagicMock( - return_value=container_with_path_error_blocked - ) - event = unittest.mock.MagicMock() - monkeypatch.setattr(DatabaseClient, "erase", unittest.mock.MagicMock()) - - # Calling to test the action since is not possible calling via harness - harness.charm._on_reset_instance_action(event) - - assert container_with_path_error_blocked.remove_path.call_count == 1 - assert isinstance(harness.model.unit.status, ops.BlockedStatus) - assert "Error erasing" in str(harness.model.unit.status) - - -def test_reset_instance_action_path_error_pass( - container_with_path_error_pass: unittest.mock.MagicMock, - harness: Harness, - monkeypatch: pytest.MonkeyPatch, -) -> None: - """ - arrange: start the Synapse charm, set Synapse container to be ready and set server_name. - act: change server_name and run reset-instance action. - assert: Synapse charm should reset the instance. - """ - harness.set_leader(True) - harness.begin() - content = io.StringIO(f'server_name: "{TEST_SERVER_NAME}"') - pull_mock = unittest.mock.MagicMock(return_value=content) - monkeypatch.setattr(container_with_path_error_pass, "pull", pull_mock) - harness.charm.unit.get_container = unittest.mock.MagicMock( - return_value=container_with_path_error_pass - ) - event = unittest.mock.MagicMock() - monkeypatch.setattr(DatabaseClient, "erase", unittest.mock.MagicMock()) - - # Calling to test the action since is not possible calling via harness - harness.charm._on_reset_instance_action(event) - - assert container_with_path_error_pass.remove_path.call_count == 1 - assert isinstance(harness.model.unit.status, ops.ActiveStatus) - - -def test_reset_instance_action_no_leader( - harness: Harness, -) -> None: - """ - arrange: start the Synapse charm, set Synapse container to be ready and set server_name. - act: change server_name and run reset-instance action. - assert: Synapse charm should take no action if no leader. - """ - harness.begin() - harness.set_leader(False) - - event = unittest.mock.MagicMock() - # Calling to test the action since is not possible calling via harness - harness.charm._on_reset_instance_action(event) - - # Disable no-member to allow tests on generated mock attributes - # pylint: disable=no-member - assert event.fail.call_count == 1 - assert "Only the juju leader unit can run reset instance action" == event.fail.call_args[0][0] - - -def test_reset_instance_action_erase_database( - harness: Harness, - monkeypatch: pytest.MonkeyPatch, -) -> None: - """ - arrange: start the Synapse charm, set Synapse container to be ready and set server_name. - act: run reset-instance action. - assert: since there is a datasource, erase should be called. - """ - harness.begin() - db_erase_mock = unittest.mock.MagicMock() - monkeypatch.setattr(DatabaseClient, "erase", db_erase_mock) - monkeypatch.setattr("synapse.execute_migrate_config", unittest.mock.MagicMock()) - - actions.reset_instance( - container=unittest.mock.MagicMock(), - charm_state=harness.charm.build_charm_state(), - datasource=unittest.mock.MagicMock(), - ) - - db_erase_mock.assert_called_once()