Use default Previous Work Dir if there is none
[osm/N2VC.git] / n2vc / n2vc_juju_conn.py
index 0e10337..4545af2 100644 (file)
@@ -48,14 +48,15 @@ from n2vc.juju_observer import JujuModelObserver
 from n2vc.n2vc_conn import N2VCConnector
 from n2vc.n2vc_conn import obj_to_dict, obj_to_yaml
 from n2vc.provisioner import AsyncSSHProvisioner
 from n2vc.n2vc_conn import N2VCConnector
 from n2vc.n2vc_conn import obj_to_dict, obj_to_yaml
 from n2vc.provisioner import AsyncSSHProvisioner
+from n2vc.libjuju import Libjuju
 
 
 class N2VCJujuConnector(N2VCConnector):
 
     """
 
 
 class N2VCJujuConnector(N2VCConnector):
 
     """
-    ####################################################################################
-    ################################### P U B L I C ####################################
-    ####################################################################################
+####################################################################################
+################################### P U B L I C ####################################
+####################################################################################
     """
 
     BUILT_IN_CLOUDS = ["localhost", "microk8s"]
     """
 
     BUILT_IN_CLOUDS = ["localhost", "microk8s"]
@@ -198,6 +199,19 @@ class N2VCJujuConnector(N2VCConnector):
             False  # it will be True when juju connection be stablished
         )
         self._creating_model = False  # True during model creation
             False  # it will be True when juju connection be stablished
         )
         self._creating_model = False  # True during model creation
+        self.libjuju = Libjuju(
+            endpoint=self.url,
+            api_proxy=self.api_proxy,
+            enable_os_upgrade=self.enable_os_upgrade,
+            apt_mirror=self.apt_mirror,
+            username=self.username,
+            password=self.secret,
+            cacert=self.ca_cert,
+            loop=self.loop,
+            log=self.log,
+            db=self.db,
+            n2vc=self,
+        )
 
         # create juju pub key file in lcm container at
         # ./local/share/juju/ssh/juju_id_rsa.pub
 
         # create juju pub key file in lcm container at
         # ./local/share/juju/ssh/juju_id_rsa.pub
@@ -209,9 +223,6 @@ class N2VCJujuConnector(N2VCConnector):
 
         # self.log.info('Getting NS status. namespace: {}'.format(namespace))
 
 
         # self.log.info('Getting NS status. namespace: {}'.format(namespace))
 
-        if not self._authenticated:
-            await self._juju_login()
-
         _nsi_id, ns_id, _vnf_id, _vdu_id, _vdu_count = self._get_namespace_components(
             namespace=namespace
         )
         _nsi_id, ns_id, _vnf_id, _vdu_id, _vdu_count = self._get_namespace_components(
             namespace=namespace
         )
@@ -222,10 +233,7 @@ class N2VCJujuConnector(N2VCConnector):
             self.log.error(msg)
             raise N2VCBadArgumentsException(msg, ["namespace"])
 
             self.log.error(msg)
             raise N2VCBadArgumentsException(msg, ["namespace"])
 
-        # get juju model (create model if needed)
-        model = await self._juju_get_model(model_name=model_name)
-
-        status = await model.get_status()
+        status = await self.libjuju.get_model_status(model_name)
 
         if yaml_format:
             return obj_to_yaml(status)
 
         if yaml_format:
             return obj_to_yaml(status)
@@ -247,9 +255,6 @@ class N2VCJujuConnector(N2VCConnector):
             )
         )
 
             )
         )
 
-        if not self._authenticated:
-            await self._juju_login()
-
         machine_id = None
         if reuse_ee_id:
             model_name, application_name, machine_id = self._get_ee_id_components(
         machine_id = None
         if reuse_ee_id:
             model_name, application_name, machine_id = self._get_ee_id_components(
@@ -276,30 +281,36 @@ class N2VCJujuConnector(N2VCConnector):
 
         # create or reuse a new juju machine
         try:
 
         # create or reuse a new juju machine
         try:
-            machine = await self._juju_create_machine(
+            if not await self.libjuju.model_exists(model_name):
+                await self.libjuju.add_model(model_name, cloud_name=self.cloud)
+            machine, new = await self.libjuju.create_machine(
                 model_name=model_name,
                 model_name=model_name,
-                application_name=application_name,
                 machine_id=machine_id,
                 db_dict=db_dict,
                 progress_timeout=progress_timeout,
                 total_timeout=total_timeout,
             )
                 machine_id=machine_id,
                 db_dict=db_dict,
                 progress_timeout=progress_timeout,
                 total_timeout=total_timeout,
             )
+            # id for the execution environment
+            ee_id = N2VCJujuConnector._build_ee_id(
+                model_name=model_name,
+                application_name=application_name,
+                machine_id=str(machine.entity_id),
+            )
+            self.log.debug("ee_id: {}".format(ee_id))
+
+            if new:
+                # write ee_id in database
+                self._write_ee_id_db(db_dict=db_dict, ee_id=ee_id)
+
         except Exception as e:
             message = "Error creating machine on juju: {}".format(e)
             self.log.error(message)
             raise N2VCException(message=message)
 
         except Exception as e:
             message = "Error creating machine on juju: {}".format(e)
             self.log.error(message)
             raise N2VCException(message=message)
 
-        # id for the execution environment
-        ee_id = N2VCJujuConnector._build_ee_id(
-            model_name=model_name,
-            application_name=application_name,
-            machine_id=str(machine.entity_id),
-        )
-        self.log.debug("ee_id: {}".format(ee_id))
-
         # new machine credentials
         # new machine credentials
-        credentials = dict()
-        credentials["hostname"] = machine.dns_name
+        credentials = {
+            "hostname": machine.dns_name,
+        }
 
         self.log.info(
             "Execution environment created. ee_id: {}, credentials: {}".format(
 
         self.log.info(
             "Execution environment created. ee_id: {}, credentials: {}".format(
@@ -318,9 +329,6 @@ class N2VCJujuConnector(N2VCConnector):
         total_timeout: float = None,
     ) -> str:
 
         total_timeout: float = None,
     ) -> str:
 
-        if not self._authenticated:
-            await self._juju_login()
-
         self.log.info(
             "Registering execution environment. namespace={}, credentials={}".format(
                 namespace, credentials
         self.log.info(
             "Registering execution environment. namespace={}, credentials={}".format(
                 namespace, credentials
@@ -360,7 +368,13 @@ class N2VCJujuConnector(N2VCConnector):
 
         # register machine on juju
         try:
 
         # register machine on juju
         try:
-            machine_id = await self._juju_provision_machine(
+            if not self.api_proxy:
+                msg = "Cannot provision machine: api_proxy is not defined"
+                self.log.error(msg=msg)
+                raise N2VCException(message=msg)
+            if not await self.libjuju.model_exists(model_name):
+                await self.libjuju.add_model(model_name, cloud_name=self.cloud)
+            machine_id = await self.libjuju.provision_machine(
                 model_name=model_name,
                 hostname=hostname,
                 username=username,
                 model_name=model_name,
                 hostname=hostname,
                 username=username,
@@ -405,9 +419,6 @@ class N2VCJujuConnector(N2VCConnector):
             ).format(ee_id, artifact_path, db_dict)
         )
 
             ).format(ee_id, artifact_path, db_dict)
         )
 
-        if not self._authenticated:
-            await self._juju_login()
-
         # check arguments
         if ee_id is None or len(ee_id) == 0:
             raise N2VCBadArgumentsException(
         # check arguments
         if ee_id is None or len(ee_id) == 0:
             raise N2VCBadArgumentsException(
@@ -456,10 +467,10 @@ class N2VCJujuConnector(N2VCConnector):
             full_path = self.fs.path + "/" + artifact_path
 
         try:
             full_path = self.fs.path + "/" + artifact_path
 
         try:
-            await self._juju_deploy_charm(
+            await self.libjuju.deploy_charm(
                 model_name=model_name,
                 application_name=application_name,
                 model_name=model_name,
                 application_name=application_name,
-                charm_path=full_path,
+                path=full_path,
                 machine_id=machine_id,
                 db_dict=db_dict,
                 progress_timeout=progress_timeout,
                 machine_id=machine_id,
                 db_dict=db_dict,
                 progress_timeout=progress_timeout,
@@ -487,9 +498,6 @@ class N2VCJujuConnector(N2VCConnector):
             ).format(ee_id, db_dict)
         )
 
             ).format(ee_id, db_dict)
         )
 
-        if not self._authenticated:
-            await self._juju_login()
-
         # check arguments
         if ee_id is None or len(ee_id) == 0:
             raise N2VCBadArgumentsException(
         # check arguments
         if ee_id is None or len(ee_id) == 0:
             raise N2VCBadArgumentsException(
@@ -525,9 +533,11 @@ class N2VCJujuConnector(N2VCConnector):
 
         output = None
 
 
         output = None
 
+        application_name = N2VCJujuConnector._format_app_name(application_name)
+
         # execute action: generate-ssh-key
         try:
         # execute action: generate-ssh-key
         try:
-            output, _status = await self._juju_execute_action(
+            output, _status = await self.libjuju.execute_action(
                 model_name=model_name,
                 application_name=application_name,
                 action_name="generate-ssh-key",
                 model_name=model_name,
                 application_name=application_name,
                 action_name="generate-ssh-key",
@@ -544,7 +554,7 @@ class N2VCJujuConnector(N2VCConnector):
 
         # execute action: get-ssh-public-key
         try:
 
         # execute action: get-ssh-public-key
         try:
-            output, _status = await self._juju_execute_action(
+            output, _status = await self.libjuju.execute_action(
                 model_name=model_name,
                 application_name=application_name,
                 action_name="get-ssh-public-key",
                 model_name=model_name,
                 application_name=application_name,
                 action_name="get-ssh-public-key",
@@ -555,7 +565,7 @@ class N2VCJujuConnector(N2VCConnector):
         except Exception as e:
             msg = "Cannot execute action get-ssh-public-key: {}\n".format(e)
             self.log.info(msg)
         except Exception as e:
             msg = "Cannot execute action get-ssh-public-key: {}\n".format(e)
             self.log.info(msg)
-            raise N2VCException(msg)
+            raise N2VCExecutionException(e, primitive_name="get-ssh-public-key")
 
         # return public key if exists
         return output["pubkey"] if "pubkey" in output else output
 
         # return public key if exists
         return output["pubkey"] if "pubkey" in output else output
@@ -588,9 +598,6 @@ class N2VCJujuConnector(N2VCConnector):
             self.log.error(message)
             raise N2VCBadArgumentsException(message=message, bad_args=["endpoint_2"])
 
             self.log.error(message)
             raise N2VCBadArgumentsException(message=message, bad_args=["endpoint_2"])
 
-        if not self._authenticated:
-            await self._juju_login()
-
         # get the model, the applications and the machines from the ee_id's
         model_1, app_1, _machine_1 = self._get_ee_id_components(ee_id_1)
         model_2, app_2, _machine_2 = self._get_ee_id_components(ee_id_2)
         # get the model, the applications and the machines from the ee_id's
         model_1, app_1, _machine_1 = self._get_ee_id_components(ee_id_1)
         model_2, app_2, _machine_2 = self._get_ee_id_components(ee_id_2)
@@ -605,7 +612,7 @@ class N2VCJujuConnector(N2VCConnector):
 
         # add juju relations between two applications
         try:
 
         # add juju relations between two applications
         try:
-            await self._juju_add_relation(
+            await self.libjuju.add_relation(
                 model_name=model_1,
                 application_name_1=app_1,
                 application_name_2=app_2,
                 model_name=model_1,
                 application_name_1=app_1,
                 application_name_2=app_2,
@@ -620,16 +627,11 @@ class N2VCJujuConnector(N2VCConnector):
             raise N2VCException(message=message)
 
     async def remove_relation(self):
             raise N2VCException(message=message)
 
     async def remove_relation(self):
-        if not self._authenticated:
-            await self._juju_login()
         # TODO
         self.log.info("Method not implemented yet")
         raise MethodNotImplemented()
 
     async def deregister_execution_environments(self):
         # TODO
         self.log.info("Method not implemented yet")
         raise MethodNotImplemented()
 
     async def deregister_execution_environments(self):
-        if not self._authenticated:
-            await self._juju_login()
-        # TODO
         self.log.info("Method not implemented yet")
         raise MethodNotImplemented()
 
         self.log.info("Method not implemented yet")
         raise MethodNotImplemented()
 
@@ -638,9 +640,6 @@ class N2VCJujuConnector(N2VCConnector):
     ):
         self.log.info("Deleting namespace={}".format(namespace))
 
     ):
         self.log.info("Deleting namespace={}".format(namespace))
 
-        if not self._authenticated:
-            await self._juju_login()
-
         # check arguments
         if namespace is None:
             raise N2VCBadArgumentsException(
         # check arguments
         if namespace is None:
             raise N2VCBadArgumentsException(
@@ -652,7 +651,9 @@ class N2VCJujuConnector(N2VCConnector):
         )
         if ns_id is not None:
             try:
         )
         if ns_id is not None:
             try:
-                await self._juju_destroy_model(
+                if not await self.libjuju.model_exists(ns_id):
+                    raise N2VCNotFound(message="Model {} does not exist".format(ns_id))
+                await self.libjuju.destroy_model(
                     model_name=ns_id, total_timeout=total_timeout
                 )
             except N2VCNotFound:
                     model_name=ns_id, total_timeout=total_timeout
                 )
             except N2VCNotFound:
@@ -673,9 +674,6 @@ class N2VCJujuConnector(N2VCConnector):
     ):
         self.log.info("Deleting execution environment ee_id={}".format(ee_id))
 
     ):
         self.log.info("Deleting execution environment ee_id={}".format(ee_id))
 
-        if not self._authenticated:
-            await self._juju_login()
-
         # check arguments
         if ee_id is None:
             raise N2VCBadArgumentsException(
         # check arguments
         if ee_id is None:
             raise N2VCBadArgumentsException(
@@ -688,8 +686,8 @@ class N2VCJujuConnector(N2VCConnector):
 
         # destroy the application
         try:
 
         # destroy the application
         try:
-            await self._juju_destroy_application(
-                model_name=model_name, application_name=application_name
+            await self.libjuju.destroy_model(
+                model_name=model_name, total_timeout=total_timeout
             )
         except Exception as e:
             raise N2VCException(
             )
         except Exception as e:
             raise N2VCException(
@@ -728,9 +726,6 @@ class N2VCJujuConnector(N2VCConnector):
             )
         )
 
             )
         )
 
-        if not self._authenticated:
-            await self._juju_login()
-
         # check arguments
         if ee_id is None or len(ee_id) == 0:
             raise N2VCBadArgumentsException(
         # check arguments
         if ee_id is None or len(ee_id) == 0:
             raise N2VCBadArgumentsException(
@@ -760,14 +755,56 @@ class N2VCJujuConnector(N2VCConnector):
         if primitive_name == "config":
             # Special case: config primitive
             try:
         if primitive_name == "config":
             # Special case: config primitive
             try:
-                await self._juju_configure_application(
+                await self.libjuju.configure_application(
                     model_name=model_name,
                     application_name=application_name,
                     config=params_dict,
                     model_name=model_name,
                     application_name=application_name,
                     config=params_dict,
-                    db_dict=db_dict,
-                    progress_timeout=progress_timeout,
-                    total_timeout=total_timeout,
                 )
                 )
+                actions = await self.libjuju.get_actions(
+                    application_name=application_name, model_name=model_name,
+                )
+                self.log.debug(
+                    "Application {} has these actions: {}".format(
+                        application_name, actions
+                    )
+                )
+                if "verify-ssh-credentials" in actions:
+                    # execute verify-credentials
+                    num_retries = 20
+                    retry_timeout = 15.0
+                    for _ in range(num_retries):
+                        try:
+                            self.log.debug("Executing action verify-ssh-credentials...")
+                            output, ok = await self.libjuju.execute_action(
+                                model_name=model_name,
+                                application_name=application_name,
+                                action_name="verify-ssh-credentials",
+                                db_dict=db_dict,
+                                progress_timeout=progress_timeout,
+                                total_timeout=total_timeout,
+                            )
+                            self.log.debug("Result: {}, output: {}".format(ok, output))
+                            break
+                        except asyncio.CancelledError:
+                            raise
+                        except Exception as e:
+                            self.log.debug(
+                                "Error executing verify-ssh-credentials: {}. Retrying...".format(
+                                    e
+                                )
+                            )
+                            await asyncio.sleep(retry_timeout)
+                    else:
+                        self.log.error(
+                            "Error executing verify-ssh-credentials after {} retries. ".format(
+                                num_retries
+                            )
+                        )
+                else:
+                    msg = "Action verify-ssh-credentials does not exist in application {}".format(
+                        application_name
+                    )
+                    self.log.debug(msg=msg)
             except Exception as e:
                 self.log.error("Error configuring juju application: {}".format(e))
                 raise N2VCExecutionException(
             except Exception as e:
                 self.log.error("Error configuring juju application: {}".format(e))
                 raise N2VCExecutionException(
@@ -779,7 +816,7 @@ class N2VCJujuConnector(N2VCConnector):
             return "CONFIG OK"
         else:
             try:
             return "CONFIG OK"
         else:
             try:
-                output, status = await self._juju_execute_action(
+                output, status = await self.libjuju.execute_action(
                     model_name=model_name,
                     application_name=application_name,
                     action_name=primitive_name,
                     model_name=model_name,
                     application_name=application_name,
                     action_name=primitive_name,
@@ -805,12 +842,17 @@ class N2VCJujuConnector(N2VCConnector):
 
     async def disconnect(self):
         self.log.info("closing juju N2VC...")
 
     async def disconnect(self):
         self.log.info("closing juju N2VC...")
-        await self._juju_logout()
+        try:
+            await self.libjuju.disconnect()
+        except Exception as e:
+            raise N2VCConnectionException(
+                message="Error disconnecting controller: {}".format(e), url=self.url
+            )
 
     """
 
     """
-    ####################################################################################
-    ################################### P R I V A T E ##################################
-    ####################################################################################
+####################################################################################
+################################### P R I V A T E ##################################
+####################################################################################
     """
 
     def _write_ee_id_db(self, db_dict: dict, ee_id: str):
     """
 
     def _write_ee_id_db(self, db_dict: dict, ee_id: str):
@@ -1034,7 +1076,7 @@ class N2VCJujuConnector(N2VCConnector):
         results = await client_facade.AddMachines(params=[params])
         error = results.machines[0].error
         if error:
         results = await client_facade.AddMachines(params=[params])
         error = results.machines[0].error
         if error:
-            msg = "Error adding machine: {}}".format(error.message)
+            msg = "Error adding machine: {}".format(error.message)
             self.log.error(msg=msg)
             raise ValueError(msg)
 
             self.log.error(msg=msg)
             raise ValueError(msg)
 
@@ -1113,14 +1155,14 @@ class N2VCJujuConnector(N2VCConnector):
                 )
             )
             self.log.debug("charm: {}".format(charm_path))
                 )
             )
             self.log.debug("charm: {}".format(charm_path))
-            series = "xenial"
+            machine = model.machines[machine_id]
             # series = None
             application = await model.deploy(
                 entity_url=charm_path,
                 application_name=application_name,
                 channel="stable",
                 num_units=1,
             # series = None
             application = await model.deploy(
                 entity_url=charm_path,
                 application_name=application_name,
                 channel="stable",
                 num_units=1,
-                series=series,
+                series=machine.series,
                 to=machine_id,
                 config=config,
             )
                 to=machine_id,
                 config=config,
             )
@@ -1348,7 +1390,7 @@ class N2VCJujuConnector(N2VCConnector):
                         model_name=model_name,
                         config=config_dict,
                         cloud_name=self.cloud,
                         model_name=model_name,
                         config=config_dict,
                         cloud_name=self.cloud,
-                        credential_name="admin",
+                        credential_name=self.cloud,
                     )
                 self.log.info("New model created, name={}".format(model_name))
             else:
                     )
                 self.log.info("New model created, name={}".format(model_name))
             else: