Give a warning when removing a cloud that does not exist
[osm/N2VC.git] / n2vc / libjuju.py
index ce1c9df..1cb1e9e 100644 (file)
@@ -18,7 +18,7 @@ import typing
 
 import time
 
-from juju.errors import JujuAPIError
+import juju.errors
 from juju.model import Model
 from juju.machine import Machine
 from juju.application import Application
@@ -174,7 +174,7 @@ class Libjuju:
                     cloud_name=cloud.name,
                     credential_name=cloud.credential_name,
                 )
-        except JujuAPIError as e:
+        except juju.errors.JujuAPIError as e:
             if "already exists" in e.message:
                 pass
             else:
@@ -345,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):
         """
@@ -759,7 +759,7 @@ class Libjuju:
         db_dict: dict = None,
         progress_timeout: float = None,
         total_timeout: float = None,
-        **kwargs
+        **kwargs,
     ):
         """Execute action
 
@@ -921,7 +921,7 @@ class Libjuju:
         # Add relation
         try:
             await model.add_relation(endpoint_1, endpoint_2)
-        except JujuAPIError as e:
+        except juju.errors.JujuAPIError as e:
             if "not found" in e.message:
                 self.log.warning("Relation not found: {}".format(e.message))
                 return
@@ -958,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
 
@@ -972,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
@@ -1294,6 +1302,11 @@ class Libjuju:
         controller = await self.get_controller()
         try:
             await controller.remove_cloud(name)
+        except juju.errors.JujuError as e:
+            if len(e.errors) == 1 and f'cloud "{name}" not found' == e.errors[0]:
+                self.log.warning(f"Cloud {name} not found, so it could not be deleted.")
+            else:
+                raise e
         finally:
             await self.disconnect_controller(controller)