OSMENG-1090 OSMENG-1091: Remove model and check model is removed 62/13662/8
authorDario Faccin <dario.faccin@canonical.com>
Mon, 10 Jul 2023 07:57:37 +0000 (09:57 +0200)
committerMark Beierl <mark.beierl@canonical.com>
Thu, 20 Jul 2023 18:24:35 +0000 (18:24 +0000)
Change-Id: Id106d8cfe7e4847712b5da79b1c56e72a673fe8e
Signed-off-by: Dario Faccin <dario.faccin@canonical.com>
Signed-off-by: Mark Beierl <mark.beierl@canonical.com>
osm_lcm/temporal/juju_paas_activities.py
osm_lcm/tests/test_juju_paas_activities.py

index 7714470..e770b6e 100644 (file)
@@ -26,9 +26,11 @@ from osm_common.temporal.activities.paas import (
     TestVimConnectivity,
     CheckCharmStatus,
     CheckCharmIsRemoved,
+    CheckModelIsRemoved,
     CreateModel,
     DeployCharm,
     RemoveCharm,
+    RemoveModel,
     ResolveCharmErrors,
 )
 from osm_common.temporal.dataclasses_common import (
@@ -308,6 +310,34 @@ class CheckCharmIsRemovedImpl(CheckCharmIsRemoved):
             await asyncio.sleep(activity_input.poll_interval)
 
 
+@activity.defn(name=RemoveModel.__name__)
+class RemoveModelImpl(RemoveModel):
+    async def __call__(self, activity_input: RemoveModel.Input) -> None:
+        model_name = activity_input.model_name
+        controller: Controller = await self.juju_controller._get_controller(
+            activity_input.vim_uuid
+        )
+        if model_name not in (await controller.list_models()):
+            return
+        await controller.destroy_models(
+            model_name,
+            destroy_storage=True,
+            force=activity_input.force_remove,
+        )
+
+
+@activity.defn(name=CheckModelIsRemoved.__name__)
+class CheckModelIsRemovedImpl(CheckModelIsRemoved):
+    async def __call__(self, activity_input: CheckModelIsRemoved.Input) -> None:
+        model_name = activity_input.model_name
+        controller: Controller = await self.juju_controller._get_controller(
+            activity_input.vim_uuid
+        )
+        while model_name in (await controller.list_models()):
+            activity.heartbeat()
+            await asyncio.sleep(activity_input.poll_interval)
+
+
 @activity.defn(name=ResolveCharmErrors.__name__)
 class ResolveCharmErrorsImpl(ResolveCharmErrors):
     async def __call__(self, activity_input: ResolveCharmErrors.Input) -> None:
index 1381c33..7d5e997 100644 (file)
@@ -31,7 +31,9 @@ from osm_lcm.temporal.juju_paas_activities import (
     CreateModelImpl,
     CheckCharmStatusImpl,
     CheckCharmIsRemovedImpl,
+    CheckModelIsRemovedImpl,
     RemoveCharmImpl,
+    RemoveModelImpl,
     ResolveCharmErrorsImpl,
 )
 from osm_common.temporal.workflows.vdu import VduInstantiateWorkflow
@@ -580,6 +582,93 @@ class TestCheckCharmIsRemoved(TestJujuPaasActivitiesBase):
         self.controller.get_model.assert_not_called()
 
 
+class TestRemoveModel(TestJujuPaasActivitiesBase):
+    def setUp(self) -> None:
+        super().setUp()
+        self.remove_model = RemoveModelImpl(self.juju_paas)
+
+    @parameterized.expand([False, True])
+    async def test_remove_model__nominal_case(self, is_forced):
+        remove_model_input = RemoveModelImpl.Input(
+            vim_uuid=vim_content["_id"],
+            model_name=namespace,
+            force_remove=is_forced,
+        )
+        self.controller.list_models.return_value = [namespace]
+        await self.env.run(
+            self.remove_model,
+            remove_model_input,
+        )
+        self.controller.destroy_models.assert_called_once_with(
+            namespace,
+            destroy_storage=True,
+            force=is_forced,
+        )
+
+    async def test_remove_model__model_does_not_exist(self):
+        remove_model_input = RemoveModelImpl.Input(
+            vim_uuid=vim_content["_id"],
+            model_name="not-existing-model",
+            force_remove=False,
+        )
+        self.controller.list_models.return_value = [namespace]
+        await self.env.run(
+            self.remove_model,
+            remove_model_input,
+        )
+        self.controller.destroy_models.assert_not_called()
+
+    async def test_remove_model__juju_error_occurred__model_is_not_removed(self):
+        remove_model_input = RemoveModelImpl.Input(
+            vim_uuid=vim_content["_id"],
+            model_name=namespace,
+            force_remove=False,
+        )
+        self.controller.list_models.side_effect = JujuError()
+        with self.assertRaises(JujuError):
+            await self.env.run(
+                self.remove_model,
+                remove_model_input,
+            )
+        self.controller.destroy_models.assert_not_called()
+
+
+class TestCheckModelIsRemoved(TestJujuPaasActivitiesBase):
+    def setUp(self) -> None:
+        super().setUp()
+        self.env.on_heartbeat = self.on_heartbeat
+        self.heartbeat_count = 0
+        self.heartbeat_maximum = 5
+        self.check_model_is_removed = CheckModelIsRemovedImpl(self.juju_paas)
+
+    def on_heartbeat(self, *args, **kwargs):
+        self.heartbeat_count += 1
+        if self.heartbeat_count > self.heartbeat_maximum:
+            self.env.cancel()
+
+    async def test_check_is_model_removed__nominal_case(self):
+        arg = CheckModelIsRemovedImpl.Input(
+            model_name=namespace,
+            vim_uuid="vim-uuid",
+            poll_interval=0,
+        )
+        self.controller.list_models.return_value = []
+
+        await self.env.run(self.check_model_is_removed.__call__, arg)
+
+    async def test_check_is_model_removed__cancel(self):
+        arg = CheckModelIsRemovedImpl.Input(
+            model_name=namespace,
+            vim_uuid="vim-uuid",
+            poll_interval=0,
+        )
+
+        self.controller.list_models.return_value = [namespace]
+
+        with self.assertRaises(asyncio.exceptions.CancelledError):
+            await self.env.run(self.check_model_is_removed.__call__, arg)
+
+
 class TestResolveCharmErrors(TestJujuPaasActivitiesBase):
     app_name = "my_app_name"