Fix bug 1589: Improve deletion of models
[osm/N2VC.git] / n2vc / libjuju.py
index cb2e0ba..3dcb7f9 100644 (file)
@@ -273,7 +273,9 @@ class Libjuju:
         """
         return await controller.get_model(model_name)
 
-    async def model_exists(self, model_name: str, controller: Controller = None) -> bool:
+    async def model_exists(
+        self, model_name: str, controller: Controller = None
+    ) -> bool:
         """
         Check if model exists
 
@@ -343,7 +345,7 @@ class Libjuju:
         db_dict: dict = None,
         progress_timeout: float = None,
         total_timeout: float = None,
-        series: str = "xenial",
+        series: str = "bionic",
         wait: bool = True,
     ) -> (Machine, bool):
         """
@@ -662,11 +664,11 @@ class Libjuju:
         return application
 
     async def scale_application(
-            self,
-            model_name: str,
-            application_name: str,
-            scale: int = 1,
-            total_timeout: float = None,
+        self,
+        model_name: str,
+        application_name: str,
+        scale: int = 1,
+        total_timeout: float = None,
     ):
         """
         Scale application (K8s)
@@ -693,8 +695,7 @@ class Libjuju:
             await application.scale(scale=scale)
             # Wait until application is scaled in model
             self.log.debug(
-                "Waiting for application {} to be scaled in model {}...".format
-                (
+                "Waiting for application {} to be scaled in model {}...".format(
                     application_name, model_name
                 )
             )
@@ -707,7 +708,9 @@ class Libjuju:
                 # wait until application unit count and scale count are equal.
                 # Because there is a delay before scaling triggers in Juju model.
                 if application_scale == scale:
-                    await JujuModelWatcher.wait_for_model(model=model, timeout=total_timeout)
+                    await JujuModelWatcher.wait_for_model(
+                        model=model, timeout=total_timeout
+                    )
                     self.log.debug(
                         "Application {} is scaled in model {}".format(
                             application_name, model_name
@@ -955,7 +958,7 @@ class Libjuju:
             await self.disconnect_model(model)
             await self.disconnect_controller(controller)
 
-    async def destroy_model(self, model_name: str, total_timeout: float):
+    async def destroy_model(self, model_name: str, total_timeout: float = 1800):
         """
         Destroy model
 
@@ -969,42 +972,50 @@ class Libjuju:
             if not await self.model_exists(model_name, controller=controller):
                 return
 
-            model = await self.get_model(controller, model_name)
             self.log.debug("Destroying model {}".format(model_name))
-            uuid = model.info.uuid
 
+            model = await self.get_model(controller, model_name)
             # Destroy machines that are manually provisioned
             # and still are in pending state
             await self._destroy_pending_machines(model, only_manual=True)
-
-            # Disconnect model
             await self.disconnect_model(model)
 
-            await controller.destroy_model(uuid, force=True, max_wait=0)
+            await self._destroy_model(
+                model_name,
+                controller,
+                timeout=total_timeout,
+            )
+        finally:
+            if model:
+                await self.disconnect_model(model)
+            await self.disconnect_controller(controller)
 
-            # Wait until model is destroyed
-            self.log.debug("Waiting for model {} to be destroyed...".format(model_name))
+    async def _destroy_model(
+        self, model_name: str, controller: Controller, timeout: float = 1800
+    ):
+        """
+        Destroy model from controller
 
-            if total_timeout is None:
-                total_timeout = 3600
-            end = time.time() + total_timeout
-            while time.time() < end:
-                models = await controller.list_models()
-                if model_name not in models:
-                    self.log.debug(
-                        "The model {} ({}) was destroyed".format(model_name, uuid)
-                    )
-                    return
+        :param: model: Model name to be removed
+        :param: controller: Controller object
+        :param: timeout: Timeout in seconds
+        """
+
+        async def _destroy_model_loop(model_name: str, controller: Controller):
+            while await self.model_exists(model_name, controller=controller):
+                await controller.destroy_model(
+                    model_name, destroy_storage=True, force=True, max_wait=0
+                )
                 await asyncio.sleep(5)
+
+        try:
+            await asyncio.wait_for(
+                _destroy_model_loop(model_name, controller), timeout=timeout
+            )
+        except asyncio.TimeoutError:
             raise Exception(
                 "Timeout waiting for model {} to be destroyed".format(model_name)
             )
-        except Exception as e:
-            if model:
-                await self.disconnect_model(model)
-            raise e
-        finally:
-            await self.disconnect_controller(controller)
 
     async def destroy_application(
         self, model_name: str, application_name: str, total_timeout: float