OSMENG-1096 OSMENG-1097: Delete application and check application is 03/13603/6
authorDario Faccin <dario.faccin@canonical.com>
Mon, 3 Jul 2023 07:18:04 +0000 (09:18 +0200)
committeraticig <gulsum.atici@canonical.com>
Fri, 7 Jul 2023 12:05:56 +0000 (14:05 +0200)
removed

Change-Id: I76b1c08e987038b0bf48cafc7a258e05d8129553
Signed-off-by: Dario Faccin <dario.faccin@canonical.com>
osm_lcm/nglcm.py
osm_lcm/temporal/juju_paas_activities.py
osm_lcm/tests/test_juju_paas_activities.py

index 38015bf..926fe79 100644 (file)
@@ -36,6 +36,8 @@ from osm_lcm.temporal.juju_paas_activities import (
     CheckCharmStatusImpl,
     DeployCharmImpl,
     TestVimConnectivityImpl,
+    RemoveCharmImpl,
+    CheckCharmIsRemovedImpl,
 )
 
 from osm_lcm.temporal.lcm_activities import NsLcmNoOpImpl, UpdateNsLcmOperationStateImpl
@@ -184,6 +186,8 @@ class NGLcm:
             DeployCharmImpl(paas_connector_instance),
             CheckCharmStatusImpl(paas_connector_instance),
             TestVimConnectivityImpl(paas_connector_instance),
+            RemoveCharmImpl(paas_connector_instance),
+            CheckCharmIsRemovedImpl(paas_connector_instance),
             UpdateVimOperationStateImpl(self.db),
             UpdateVimStateImpl(self.db),
             DeleteVimRecordImpl(self.db),
index 8b51f24..cb96fd9 100644 (file)
@@ -24,8 +24,10 @@ from n2vc.config import EnvironConfig
 from osm_common.temporal.activities.paas import (
     TestVimConnectivity,
     CheckCharmStatus,
+    CheckCharmIsRemoved,
     CreateModel,
     DeployCharm,
+    RemoveCharm,
 )
 from osm_common.temporal.dataclasses_common import (
     CharmInfo,
@@ -266,3 +268,38 @@ class CharmInfoUtils:
         if filtered_image:
             return filtered_image.get("image"), filtered_image.get("version")
         return None, None
+
+
+class RemoveCharmImpl(RemoveCharm):
+    @activity.defn(name=RemoveCharm.__name__)
+    async def __call__(self, activity_input: RemoveCharm.Input) -> None:
+        app_name = activity_input.application_name
+        model_name = activity_input.model_name
+        force_remove = activity_input.force_remove
+        controller: Controller = await self.juju_controller._get_controller(
+            activity_input.vim_uuid
+        )
+        model = await controller.get_model(model_name)
+        if app_name not in model.applications:
+            return
+        await model.remove_application(
+            app_name=app_name,
+            block_until_done=False,
+            force=force_remove,
+            no_wait=force_remove,
+        )
+
+
+class CheckCharmIsRemovedImpl(CheckCharmIsRemoved):
+    @activity.defn(name=CheckCharmIsRemoved.__name__)
+    async def __call__(self, activity_input: CheckCharmIsRemoved.Input) -> None:
+        controller = await self.juju_controller._get_controller(activity_input.vim_uuid)
+        model = await controller.get_model(activity_input.model_name)
+        app_name = activity_input.application_name
+
+        removed = False
+        while not removed:
+            activity.heartbeat()
+            await asyncio.sleep(activity_input.poll_interval)
+            if app_name not in model.applications:
+                removed = True
index bac5619..0f24e82 100644 (file)
@@ -30,6 +30,8 @@ from osm_lcm.temporal.juju_paas_activities import (
     DeployCharmImpl,
     CreateModelImpl,
     CheckCharmStatusImpl,
+    CheckCharmIsRemovedImpl,
+    RemoveCharmImpl,
 )
 from osm_common.temporal.workflows.vdu import VduInstantiateWorkflow
 from osm_common.temporal.dataclasses_common import CharmInfo, VduComputeConstraints
@@ -447,3 +449,100 @@ class TestTestVimConnectivity(TestJujuPaasActivitiesBase):
                 self.test_vim_connectivity,
                 self.test_vim_connectivity_input,
             )
+
+
+class TestRemoveCharm(TestJujuPaasActivitiesBase):
+    app_name = "my_app_name"
+
+    def setUp(self) -> None:
+        super().setUp()
+        self.remove_charm = RemoveCharmImpl(self.juju_paas)
+
+    @parameterized.expand([False, True])
+    async def test_remove_charm__application_exists__is_forced(self, is_forced):
+        remove_charm_input = RemoveCharmImpl.Input(
+            vim_uuid=vim_content["_id"],
+            application_name=self.app_name,
+            model_name=namespace,
+            force_remove=is_forced,
+        )
+        self.add_application(self.app_name)
+        await self.env.run(
+            self.remove_charm,
+            remove_charm_input,
+        )
+        self.model.remove_application.assert_called_once_with(
+            app_name=self.app_name,
+            block_until_done=False,
+            force=is_forced,
+            no_wait=is_forced,
+        )
+
+    async def test_remove_charm__app_does_not_exist(self):
+        remove_charm_input = RemoveCharmImpl.Input(
+            vim_uuid=vim_content["_id"],
+            application_name=self.app_name,
+            model_name=namespace,
+            force_remove=False,
+        )
+        await self.env.run(
+            self.remove_charm,
+            remove_charm_input,
+        )
+        self.model.remove_application.assert_not_called()
+
+    async def test_remove_charm__juju_error_occurred__app_is_not_removed(self):
+        remove_charm_input = RemoveCharmImpl.Input(
+            vim_uuid=vim_content["_id"],
+            application_name=self.app_name,
+            model_name=namespace,
+            force_remove=False,
+        )
+        self.add_application(self.app_name)
+        self.controller.get_model.side_effect = JujuError()
+        with self.assertRaises(JujuError):
+            await self.env.run(
+                self.remove_charm,
+                remove_charm_input,
+            )
+        self.model.remove_application.assert_not_called()
+
+
+class TestCheckCharmIsRemoved(TestJujuPaasActivitiesBase):
+    def setUp(self) -> None:
+        super().setUp()
+        self.env.on_heartbeat = self.on_heartbeat
+        self.heartbeat_count = 0
+        self.heartbeat_maximum = 5
+        self.add_application("application")
+        self.check_charm_is_removed = CheckCharmIsRemovedImpl(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_charm_removed__nominal_case(self):
+        arg = CheckCharmIsRemovedImpl.Input(
+            application_name=self.application_name,
+            model_name="model",
+            vim_uuid="vim-uuid",
+            poll_interval=0,
+        )
+
+        type(self.model).applications = mock.PropertyMock(
+            side_effect=[self.model.applications, {}]
+        )
+
+        await self.env.run(self.check_charm_is_removed.__call__, arg)
+
+    async def test_check_is_charm_removed__cancel(self):
+        arg = CheckCharmIsRemovedImpl.Input(
+            application_name=self.application_name,
+            model_name="model",
+            vim_uuid="vim-uuid",
+            poll_interval=0,
+        )
+
+        with self.assertRaises(asyncio.exceptions.CancelledError):
+            await self.env.run(self.check_charm_is_removed.__call__, arg)