Fix 1462 99/10399/9
authoraktas <emin.aktas@ulakhaberlesme.com.tr>
Fri, 26 Feb 2021 12:32:39 +0000 (15:32 +0300)
committerDavid Garcia <david.garcia@canonical.com>
Mon, 26 Apr 2021 16:22:42 +0000 (18:22 +0200)
Wait until Juju application is destroyed in
n2vc.libjuju.Libjuju.destroy_application()

Change-Id: Ie7992f229872ffffbea87ca3e8e20825bfec7b6d
Signed-off-by: aktas <emin.aktas@ulakhaberlesme.com.tr>
n2vc/libjuju.py
n2vc/n2vc_juju_conn.py
n2vc/tests/unit/test_libjuju.py

index 0fb82e7..eb0fa72 100644 (file)
@@ -944,23 +944,59 @@ class Libjuju:
         finally:
             await self.disconnect_controller(controller)
 
-    async def destroy_application(self, model: Model, application_name: str):
+    async def destroy_application(
+        self, model_name: str, application_name: str, total_timeout: float
+    ):
         """
         Destroy application
 
-        :param: model:              Model object
+        :param: model_name:         Model name
         :param: application_name:   Application name
+        :param: total_timeout:      Timeout
         """
-        self.log.debug(
-            "Destroying application {} in model {}".format(
-                application_name, model.info.name
+
+        controller = await self.get_controller()
+        model = None
+
+        try:
+            model = await self.get_model(controller, model_name)
+            self.log.debug(
+                "Destroying application {} in model {}".format(
+                    application_name, model_name
+                )
             )
-        )
-        application = model.applications.get(application_name)
-        if application:
-            await application.destroy()
-        else:
-            self.log.warning("Application not found: {}".format(application_name))
+            application = self._get_application(model, application_name)
+            if application:
+                await application.destroy()
+            else:
+                self.log.warning("Application not found: {}".format(application_name))
+
+            self.log.debug(
+                "Waiting for application {} to be destroyed in model {}...".format(
+                    application_name, model_name
+                )
+            )
+            if total_timeout is None:
+                total_timeout = 3600
+            end = time.time() + total_timeout
+            while time.time() < end:
+                if not self._get_application(model, application_name):
+                    self.log.debug(
+                        "The application {} was destroyed in model {} ".format(
+                            application_name, model_name
+                        )
+                    )
+                    return
+                await asyncio.sleep(5)
+            raise Exception(
+                "Timeout waiting for application {} to be destroyed in model {}".format(
+                    application_name, model_name
+                )
+            )
+        finally:
+            if model is not None:
+                await self.disconnect_model(model)
+            await self.disconnect_controller(controller)
 
     async def _destroy_pending_machines(self, model: Model, only_manual: bool = False):
         """
index d31c169..c09c263 100644 (file)
@@ -794,15 +794,16 @@ class N2VCJujuConnector(N2VCConnector):
                 # destroy the model
                 # TODO: should this be removed?
                 await self.libjuju.destroy_model(
-                    model_name=model_name, total_timeout=total_timeout
+                    model_name=model_name,
+                    total_timeout=total_timeout,
                 )
             else:
-                # get juju model and observer
-                controller = await self.libjuju.get_controller()
-                model = await self.libjuju.get_model(controller, model_name)
                 # destroy the application
                 await self.libjuju.destroy_application(
-                    model=model, application_name=application_name)
+                    model_name=model_name,
+                    application_name=application_name,
+                    total_timeout=total_timeout,
+                )
         except Exception as e:
             raise N2VCException(
                 message=(
index 36110f6..29bcb7b 100644 (file)
@@ -1177,6 +1177,78 @@ class AddRelationTest(LibjujuTestCase):
 # TODO destroy_model testcase
 
 
+@asynctest.mock.patch("n2vc.libjuju.Libjuju.get_controller")
+@asynctest.mock.patch("n2vc.libjuju.Libjuju.get_model")
+@asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_controller")
+@asynctest.mock.patch("n2vc.libjuju.Libjuju._get_application")
+@asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_model")
+class DestroyApplicationTest(LibjujuTestCase):
+    def setUp(self):
+        super(DestroyApplicationTest, self).setUp()
+
+    def test_success(
+            self,
+            mock_get_controller,
+            mock_get_model,
+            mock_disconnect_controller,
+            mock_get_application,
+            mock_disconnect_model,
+    ):
+        mock_get_application.return_value = FakeApplication()
+        mock_get_model.return_value = None
+        self.loop.run_until_complete(
+            self.libjuju.destroy_application(
+                "existing_model",
+                "existing_app",
+                3600,
+            )
+        )
+        mock_get_application.assert_called()
+        mock_disconnect_controller.assert_called_once()
+        mock_disconnect_model.assert_called_once()
+
+    def test_no_application(
+            self,
+            mock_get_controller,
+            mock_get_model,
+            mock_disconnect_controller,
+            mock_get_application,
+            mock_disconnect_model,
+    ):
+        mock_get_model.return_value = None
+        mock_get_application.return_value = None
+
+        self.loop.run_until_complete(
+            self.libjuju.destroy_application(
+                "existing_model",
+                "existing_app",
+                3600,
+            )
+        )
+        mock_get_application.assert_called()
+
+    def test_exception(
+            self,
+            mock_get_controller,
+            mock_get_model,
+            mock_disconnect_controller,
+            mock_get_application,
+            mock_disconnect_model,
+    ):
+        mock_get_application.return_value = FakeApplication
+        mock_get_model.return_value = None
+
+        with self.assertRaises(Exception):
+            self.loop.run_until_complete(
+                self.libjuju.destroy_application(
+                    "existing_model",
+                    "existing_app",
+                    0,
+                )
+            )
+            mock_get_application.assert_called_once()
+
+
 # @asynctest.mock.patch("juju.model.Model.get_machines")
 # @asynctest.mock.patch("logging.Logger.debug")
 # class DestroyMachineTest(LibjujuTestCase):