K8s action support
[osm/N2VC.git] / n2vc / n2vc_juju_conn.py
index 9230e6d..40f46f1 100644 (file)
@@ -32,7 +32,7 @@ from n2vc.n2vc_conn import N2VCConnector
 from n2vc.n2vc_conn import obj_to_dict, obj_to_yaml
 from n2vc.exceptions \
     import N2VCBadArgumentsException, N2VCException, N2VCConnectionException, \
-    N2VCExecutionException, N2VCInvalidCertificate
+    N2VCExecutionException, N2VCInvalidCertificate, N2VCNotFound
 from n2vc.juju_observer import JujuModelObserver
 
 from juju.controller import Controller
@@ -169,7 +169,7 @@ class N2VCJujuConnector(N2VCConnector):
             self.apt_mirror = None
 
         self.cloud = vca_config.get('cloud')
-        self.log.debug('Arguments have been checked')
+        self.log.debug('Arguments have been checked')
 
         # juju data
         self.controller = None         # it will be filled when connect to juju
@@ -338,7 +338,8 @@ class N2VCJujuConnector(N2VCConnector):
         artifact_path: str,
         db_dict: dict,
         progress_timeout: float = None,
-        total_timeout: float = None
+        total_timeout: float = None,
+        config: dict = None,
     ):
 
         self.log.info('Installing configuration sw on ee_id: {}, artifact path: {}, db_dict: {}'
@@ -386,7 +387,8 @@ class N2VCJujuConnector(N2VCConnector):
                 machine_id=machine_id,
                 db_dict=db_dict,
                 progress_timeout=progress_timeout,
-                total_timeout=total_timeout
+                total_timeout=total_timeout,
+                config=config
             )
         except Exception as e:
             raise N2VCException(message='Error desploying charm into ee={} : {}'.format(ee_id, e))
@@ -438,7 +440,7 @@ class N2VCJujuConnector(N2VCConnector):
                 total_timeout=total_timeout
             )
         except Exception as e:
-            self.log.info('Cannot execute action generate-ssh-key: {}\nContinuing...'.format(e))
+            self.log.info('Skipping exception while executing action generate-ssh-key: {}'.format(e))
 
         # execute action: get-ssh-public-key
         try:
@@ -453,7 +455,7 @@ class N2VCJujuConnector(N2VCConnector):
         except Exception as e:
             msg = 'Cannot execute action get-ssh-public-key: {}\n'.format(e)
             self.log.info(msg)
-            raise e
+            raise N2VCException(msg)
 
         # return public key if exists
         return output["pubkey"] if "pubkey" in output else output
@@ -467,7 +469,7 @@ class N2VCJujuConnector(N2VCConnector):
     ):
 
         self.log.debug('adding new relation between {} and {}, endpoints: {}, {}'
-                   .format(ee_id_1, ee_id_2, endpoint_1, endpoint_2))
+                       .format(ee_id_1, ee_id_2, endpoint_1, endpoint_2))
 
         # check arguments
         if not ee_id_1:
@@ -510,7 +512,7 @@ class N2VCJujuConnector(N2VCConnector):
                 relation_2=endpoint_2
             )
         except Exception as e:
-            message = 'Error adding relation between {} and {}'.format(ee_id_1, ee_id_2)
+            message = 'Error adding relation between {} and {}: {}'.format(ee_id_1, ee_id_2, e)
             self.log.error(message)
             raise N2VCException(message=message)
 
@@ -554,6 +556,8 @@ class N2VCJujuConnector(N2VCConnector):
                     model_name=ns_id,
                     total_timeout=total_timeout
                 )
+            except N2VCNotFound:
+                raise
             except Exception as e:
                 raise N2VCException(message='Error deleting namespace {} : {}'.format(namespace, e))
         else:
@@ -700,6 +704,8 @@ class N2VCJujuConnector(N2VCConnector):
                 update_dict=update_dict,
                 fail_on_empty=True
             )
+        except asyncio.CancelledError:
+            raise
         except Exception as e:
             self.log.error('Error writing ee_id to database: {}'.format(e))
 
@@ -954,7 +960,8 @@ class N2VCJujuConnector(N2VCConnector):
             machine_id: str,
             db_dict: dict,
             progress_timeout: float = None,
-            total_timeout: float = None
+            total_timeout: float = None,
+            config: dict = None
     ) -> (Application, int):
 
         # get juju model and observer
@@ -980,7 +987,8 @@ class N2VCJujuConnector(N2VCConnector):
                 channel='stable',
                 num_units=1,
                 series=series,
-                to=machine_id
+                to=machine_id,
+                config=config
             )
 
             # register application with observer
@@ -1107,6 +1115,8 @@ class N2VCJujuConnector(N2VCConnector):
                 )
                 self.log.debug('Result: {}, output: {}'.format(ok, output))
                 return True
+            except asyncio.CancelledError:
+                raise
             except Exception as e:
                 self.log.debug('Error executing verify-ssh-credentials: {}. Retrying...'.format(e))
                 await asyncio.sleep(retry_timeout)
@@ -1257,16 +1267,22 @@ class N2VCJujuConnector(N2VCConnector):
         if machine_id in machines:
             machine = model.machines[machine_id]
             observer.unregister_machine(machine_id)
-            await machine.destroy(force=True)
-            # max timeout
-            end = time.time() + total_timeout
-            # wait for machine removal
-            machines = await model.get_machines()
-            while machine_id in machines and time.time() < end:
-                self.log.debug('Waiting for machine {} is destroyed'.format(machine_id))
-                await asyncio.sleep(0.5)
+            # TODO: change this by machine.is_manual when this is upstreamed: https://github.com/juju/python-libjuju/pull/396
+            if "instance-id" in machine.safe_data and machine.safe_data[
+                "instance-id"
+            ].startswith("manual:"):
+                self.log.debug("machine.destroy(force=True) started.")
+                await machine.destroy(force=True)
+                self.log.debug("machine.destroy(force=True) passed.")
+                # max timeout
+                end = time.time() + total_timeout
+                # wait for machine removal
                 machines = await model.get_machines()
-            self.log.debug('Machine destroyed: {}'.format(machine_id))
+                while machine_id in machines and time.time() < end:
+                    self.log.debug("Waiting for machine {} is destroyed".format(machine_id))
+                    await asyncio.sleep(0.5)
+                    machines = await model.get_machines()
+                self.log.debug("Machine destroyed: {}".format(machine_id))
         else:
             self.log.debug('Machine not found: {}'.format(machine_id))
 
@@ -1280,40 +1296,63 @@ class N2VCJujuConnector(N2VCConnector):
 
         if total_timeout is None:
             total_timeout = 3600
+        end = time.time() + total_timeout
 
         model = await self._juju_get_model(model_name=model_name)
+
+        if not model:
+            raise N2VCNotFound(
+                message="Model {} does not exist".format(model_name)
+            )
+
         uuid = model.info.uuid
 
+        # destroy applications
+        for application_name in model.applications:
+            try:
+                await self._juju_destroy_application(model_name=model_name, application_name=application_name)
+            except Exception as e:
+                self.log.error(
+                    "Error destroying application {} in model {}: {}".format(
+                        application_name,
+                        model_name,
+                        e
+                    )
+                )
+
         # destroy machines
         machines = await model.get_machines()
         for machine_id in machines:
             try:
                 await self._juju_destroy_machine(model_name=model_name, machine_id=machine_id)
+            except asyncio.CancelledError:
+                raise
             except Exception as e:
                 # ignore exceptions destroying machine
                 pass
 
         await self._juju_disconnect_model(model_name=model_name)
-        self.juju_models[model_name] = None
-        self.juju_observers[model_name] = None
 
         self.log.debug('destroying model {}...'.format(model_name))
         await self.controller.destroy_model(uuid)
-        self.log.debug('model destroy requested {}'.format(model_name))
+        self.log.debug('model destroy requested {}'.format(model_name))
 
         # wait for model is completely destroyed
-        end = time.time() + total_timeout
+        self.log.debug('Waiting for model {} to be destroyed...'.format(model_name))
+        last_exception = ''
         while time.time() < end:
-            self.log.debug('Waiting for model is destroyed...')
             try:
                 # await self.controller.get_model(uuid)
                 models = await self.controller.list_models()
                 if model_name not in models:
                     self.log.debug('The model {} ({}) was destroyed'.format(model_name, uuid))
                     return
+            except asyncio.CancelledError:
+                raise
             except Exception as e:
-                pass
-            await asyncio.sleep(1.0)
+                last_exception = e
+            await asyncio.sleep(5)
+        raise N2VCException("Timeout waiting for model {} to be destroyed {}".format(model_name, last_exception))
 
     async def _juju_login(self):
         """Connect to juju controller
@@ -1336,8 +1375,8 @@ class N2VCJujuConnector(N2VCConnector):
         try:
             self._connecting = True
             self.log.info(
-                'connecting to juju controller: {} {}:{} ca_cert: {}'
-                .format(self.url, self.username, self.secret, '\n'+self.ca_cert if self.ca_cert else 'None'))
+                'connecting to juju controller: {} {}:{}{}'
+                .format(self.url, self.username, self.secret[:8] + '...', ' with ca_cert' if self.ca_cert else ''))
 
             # Create controller object
             self.controller = Controller(loop=self.loop)