From a9b3baaceae08579c4842ca184db3eb998a889ce Mon Sep 17 00:00:00 2001 From: Ahmad Hassan Date: Thu, 2 Apr 2026 14:46:03 +0500 Subject: [PATCH 1/2] Fix instance recovery disable to purge all related SAAS apps --- .../features/instance_recovery/feature.py | 2 +- .../test_instance_recovery_feature.py | 73 +++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 sunbeam-python/tests/unit/sunbeam/features/test_instance_recovery_feature.py diff --git a/sunbeam-python/sunbeam/features/instance_recovery/feature.py b/sunbeam-python/sunbeam/features/instance_recovery/feature.py index 2571b520a..724e3df18 100644 --- a/sunbeam-python/sunbeam/features/instance_recovery/feature.py +++ b/sunbeam-python/sunbeam/features/instance_recovery/feature.py @@ -204,7 +204,7 @@ def run_disable_plans(self, deployment: Deployment, show_hints: bool) -> None: jhelper, deployment.openstack_machines_model, OPENSTACK_MODEL, - saas_apps_to_delete=["masakari"], + saas_apps_to_delete=self.set_application_names(deployment), ), TerraformInitStep(tfhelper_consul_client), consul.RemoveConsulClientStep(deployment, tfhelper_consul_client, jhelper), diff --git a/sunbeam-python/tests/unit/sunbeam/features/test_instance_recovery_feature.py b/sunbeam-python/tests/unit/sunbeam/features/test_instance_recovery_feature.py new file mode 100644 index 000000000..ee226588c --- /dev/null +++ b/sunbeam-python/tests/unit/sunbeam/features/test_instance_recovery_feature.py @@ -0,0 +1,73 @@ +# SPDX-FileCopyrightText: 2026 - Canonical Ltd +# SPDX-License-Identifier: Apache-2.0 + +from unittest.mock import Mock, patch + +import pytest + +from sunbeam.core.openstack import OPENSTACK_MODEL +from sunbeam.features.instance_recovery.feature import InstanceRecoveryFeature + + +@pytest.fixture +def deployment(): + deploy = Mock() + deploy.openstack_machines_model = "openstack-machines" + deploy.juju_controller = "test-controller" + deploy.get_client.return_value = Mock() + + tfhelper_hypervisor = Mock() + tfhelper_consul_client = Mock() + tfhelper_openstack = Mock() + + def _get_tfhelper(plan: str): + return { + "openstack-plan": tfhelper_openstack, + "hypervisor-plan": tfhelper_hypervisor, + "consul-client-plan": tfhelper_consul_client, + }[plan] + + deploy.get_tfhelper.side_effect = _get_tfhelper + return deploy + + +@patch("sunbeam.features.instance_recovery.feature.JujuHelper") +@patch("sunbeam.features.instance_recovery.feature.RemoveSaasApplicationsStep") +@patch("sunbeam.features.instance_recovery.feature.run_plan") +@patch("sunbeam.features.instance_recovery.consul.ConsulFeature.set_application_names") +@patch.object(InstanceRecoveryFeature, "get_database_topology", return_value="single") +def test_disable_purges_consul_and_masakari_saas_apps( + mock_get_database_topology, + mock_consul_app_names, + mock_run_plan, + mock_remove_saas, + _mock_jhelper_class, + deployment, +): + consul_apps = [ + "consul-management", + "consul-storage", + "consul-tenant", + ] + mock_consul_app_names.return_value = consul_apps.copy() + mock_jhelper = Mock() + _mock_jhelper_class.return_value = mock_jhelper + + feature = InstanceRecoveryFeature() + feature._manifest = Mock() + feature.run_disable_plans(deployment, show_hints=False) + + expected_saas_apps = consul_apps + ["masakari", "masakari-mysql-router"] + + assert mock_run_plan.call_count == 1 + + mock_remove_saas.assert_called_once() + assert ( + mock_remove_saas.call_args.kwargs["saas_apps_to_delete"] == expected_saas_apps + ) + + assert mock_remove_saas.call_args.args[0] == mock_jhelper + assert mock_remove_saas.call_args.args[1] == "openstack-machines" + assert mock_remove_saas.call_args.args[2] == OPENSTACK_MODEL + + mock_get_database_topology.assert_called_once_with(deployment) From d42b42380333db09db033293fa0418726f9b6dae Mon Sep 17 00:00:00 2001 From: Ahmad Hassan Date: Thu, 2 Apr 2026 14:46:03 +0500 Subject: [PATCH 2/2] Fix instance recovery disable to purge all related SAAS apps --- .../test_instance_recovery_feature.py | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/sunbeam-python/tests/unit/sunbeam/features/test_instance_recovery_feature.py b/sunbeam-python/tests/unit/sunbeam/features/test_instance_recovery_feature.py index ee226588c..7922b78b7 100644 --- a/sunbeam-python/tests/unit/sunbeam/features/test_instance_recovery_feature.py +++ b/sunbeam-python/tests/unit/sunbeam/features/test_instance_recovery_feature.py @@ -31,18 +31,25 @@ def _get_tfhelper(plan: str): return deploy +@pytest.mark.parametrize( + "db_topology, expected_extra_masakari_apps", + [ + ("single", ["masakari", "masakari-mysql-router"]), + ("multi", ["masakari", "masakari-mysql-router", "masakari-mysql"]), + ], +) @patch("sunbeam.features.instance_recovery.feature.JujuHelper") @patch("sunbeam.features.instance_recovery.feature.RemoveSaasApplicationsStep") @patch("sunbeam.features.instance_recovery.feature.run_plan") @patch("sunbeam.features.instance_recovery.consul.ConsulFeature.set_application_names") -@patch.object(InstanceRecoveryFeature, "get_database_topology", return_value="single") def test_disable_purges_consul_and_masakari_saas_apps( - mock_get_database_topology, mock_consul_app_names, mock_run_plan, mock_remove_saas, _mock_jhelper_class, deployment, + db_topology, + expected_extra_masakari_apps, ): consul_apps = [ "consul-management", @@ -53,11 +60,18 @@ def test_disable_purges_consul_and_masakari_saas_apps( mock_jhelper = Mock() _mock_jhelper_class.return_value = mock_jhelper - feature = InstanceRecoveryFeature() - feature._manifest = Mock() - feature.run_disable_plans(deployment, show_hints=False) + with patch.object( + InstanceRecoveryFeature, + "get_database_topology", + return_value=db_topology, + ) as mock_get_database_topology: + feature = InstanceRecoveryFeature() + feature._manifest = Mock() + feature.run_disable_plans(deployment, show_hints=False) - expected_saas_apps = consul_apps + ["masakari", "masakari-mysql-router"] + mock_get_database_topology.assert_called_once_with(deployment) + + expected_saas_apps = consul_apps + expected_extra_masakari_apps assert mock_run_plan.call_count == 1 @@ -69,5 +83,3 @@ def test_disable_purges_consul_and_masakari_saas_apps( assert mock_remove_saas.call_args.args[0] == mock_jhelper assert mock_remove_saas.call_args.args[1] == "openstack-machines" assert mock_remove_saas.call_args.args[2] == OPENSTACK_MODEL - - mock_get_database_topology.assert_called_once_with(deployment)