Fix 1462
[osm/N2VC.git] / n2vc / n2vc_juju_conn.py
index fff78c9..c09c263 100644 (file)
 ##
 
 import asyncio
-import base64
-import binascii
 import logging
 import os
-import re
 
+from n2vc.config import ModelConfig
 from n2vc.exceptions import (
     N2VCBadArgumentsException,
     N2VCException,
     N2VCConnectionException,
     N2VCExecutionException,
-    N2VCInvalidCertificate,
     # N2VCNotFound,
     MethodNotImplemented,
     JujuK8sProxycharmNotSupported,
@@ -40,6 +37,7 @@ from n2vc.exceptions import (
 from n2vc.n2vc_conn import N2VCConnector
 from n2vc.n2vc_conn import obj_to_dict, obj_to_yaml
 from n2vc.libjuju import Libjuju
+from n2vc.utils import base64_to_cacert
 
 
 class N2VCJujuConnector(N2VCConnector):
@@ -136,21 +134,6 @@ class N2VCJujuConnector(N2VCConnector):
 
         # TODO: Verify ca_cert is valid before using. VCA will crash
         # if the ca_cert isn't formatted correctly.
-        def base64_to_cacert(b64string):
-            """Convert the base64-encoded string containing the VCA CACERT.
-
-            The input string....
-
-            """
-            try:
-                cacert = base64.b64decode(b64string).decode("utf-8")
-
-                cacert = re.sub(r"\\n", r"\n", cacert,)
-            except binascii.Error as e:
-                self.log.debug("Caught binascii.Error: {}".format(e))
-                raise N2VCInvalidCertificate(message="Invalid CA Certificate")
-
-            return cacert
 
         self.ca_cert = vca_config.get("ca_cert")
         if self.ca_cert:
@@ -167,15 +150,7 @@ class N2VCJujuConnector(N2VCConnector):
             )
             self.api_proxy = None
 
-        if "enable_os_upgrade" in vca_config:
-            self.enable_os_upgrade = vca_config["enable_os_upgrade"]
-        else:
-            self.enable_os_upgrade = True
-
-        if "apt_mirror" in vca_config:
-            self.apt_mirror = vca_config["apt_mirror"]
-        else:
-            self.apt_mirror = None
+        model_config = ModelConfig(vca_config)
 
         self.cloud = vca_config.get('cloud')
         self.k8s_cloud = None
@@ -197,8 +172,6 @@ class N2VCJujuConnector(N2VCConnector):
         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,
@@ -206,6 +179,7 @@ class N2VCJujuConnector(N2VCConnector):
             log=self.log,
             db=self.db,
             n2vc=self,
+            model_config=model_config,
         )
 
         # create juju pub key file in lcm container at
@@ -233,12 +207,32 @@ class N2VCJujuConnector(N2VCConnector):
 
         for m in models:
             status[m] = await self.libjuju.get_model_status(m)
-
         if yaml_format:
             return obj_to_yaml(status)
         else:
             return obj_to_dict(status)
 
+    async def update_vca_status(self, vcastatus: dict):
+        """
+        Add all configs, actions, executed actions of all applications in a model to vcastatus dict.
+        :param vcastatus: dict containing vcaStatus
+        :return: None
+        """
+        try:
+            for model_name in vcastatus:
+                # Adding executed actions
+                vcastatus[model_name]["executedActions"] = \
+                    await self.libjuju.get_executed_actions(model_name)
+                for application in vcastatus[model_name]["applications"]:
+                    # Adding application actions
+                    vcastatus[model_name]["applications"][application]["actions"] = \
+                        await self.libjuju.get_actions(application, model_name)
+                    # Adding application configs
+                    vcastatus[model_name]["applications"][application]["configs"] = \
+                        await self.libjuju.get_application_configs(model_name, application)
+        except Exception as e:
+            self.log.debug("Error in updating vca status: {}".format(str(e)))
+
     async def create_execution_environment(
         self,
         namespace: str,
@@ -246,6 +240,8 @@ class N2VCJujuConnector(N2VCConnector):
         reuse_ee_id: str = None,
         progress_timeout: float = None,
         total_timeout: float = None,
+        cloud_name: str = None,
+        credential_name: str = None,
     ) -> (str, dict):
 
         self.log.info(
@@ -281,7 +277,13 @@ class N2VCJujuConnector(N2VCConnector):
         # create or reuse a new juju machine
         try:
             if not await self.libjuju.model_exists(model_name):
-                await self.libjuju.add_model(model_name, cloud_name=self.cloud)
+                cloud = cloud_name or self.cloud
+                credential = credential_name or cloud_name if cloud_name else self.cloud
+                await self.libjuju.add_model(
+                    model_name,
+                    cloud_name=cloud,
+                    credential_name=credential
+                )
             machine, new = await self.libjuju.create_machine(
                 model_name=model_name,
                 machine_id=machine_id,
@@ -326,6 +328,8 @@ class N2VCJujuConnector(N2VCConnector):
         db_dict: dict,
         progress_timeout: float = None,
         total_timeout: float = None,
+        cloud_name: str = None,
+        credential_name: str = None,
     ) -> str:
 
         self.log.info(
@@ -368,7 +372,13 @@ class N2VCJujuConnector(N2VCConnector):
         # register machine on juju
         try:
             if not await self.libjuju.model_exists(model_name):
-                await self.libjuju.add_model(model_name, cloud_name=self.cloud)
+                cloud = cloud_name or self.cloud
+                credential = credential_name or cloud_name if cloud_name else self.cloud
+                await self.libjuju.add_model(
+                    model_name,
+                    cloud_name=cloud,
+                    credential_name=credential
+                )
             machine_id = await self.libjuju.provision_machine(
                 model_name=model_name,
                 hostname=hostname,
@@ -490,6 +500,8 @@ class N2VCJujuConnector(N2VCConnector):
         progress_timeout: float = None,
         total_timeout: float = None,
         config: dict = None,
+        cloud_name: str = None,
+        credential_name: str = None,
     ) -> str:
         """
         Install a k8s proxy charm
@@ -508,6 +520,9 @@ class N2VCJujuConnector(N2VCConnector):
         :param float progress_timeout:
         :param float total_timeout:
         :param config: Dictionary with additional configuration
+        :param cloud_name: Cloud Name in which the charms will be deployed
+        :param credential_name: Credential Name to use in the cloud_name.
+                                If not set, cloud_name will be used as credential_name
 
         :returns ee_id: execution environment id.
         """
@@ -540,8 +555,14 @@ class N2VCJujuConnector(N2VCConnector):
 
         _, ns_id, _, _, _ = self._get_namespace_components(namespace=namespace)
         model_name = '{}-k8s'.format(ns_id)
-
-        await self.libjuju.add_model(model_name, self.k8s_cloud)
+        if not await self.libjuju.model_exists(model_name):
+            cloud = cloud_name or self.k8s_cloud
+            credential = credential_name or cloud_name if cloud_name else self.k8s_cloud
+            await self.libjuju.add_model(
+                model_name,
+                cloud_name=cloud,
+                credential_name=credential
+            )
         application_name = self._get_application_name(namespace)
 
         try:
@@ -655,6 +676,9 @@ class N2VCJujuConnector(N2VCConnector):
         # return public key if exists
         return output["pubkey"] if "pubkey" in output else output
 
+    async def get_metrics(self, model_name: str, application_name: str) -> dict:
+        return await self.libjuju.get_metrics(model_name, application_name)
+
     async def add_relation(
         self, ee_id_1: str, ee_id_2: str, endpoint_1: str, endpoint_2: str
     ):
@@ -751,7 +775,8 @@ class N2VCJujuConnector(N2VCConnector):
         self.log.info("Namespace {} deleted".format(namespace))
 
     async def delete_execution_environment(
-        self, ee_id: str, db_dict: dict = None, total_timeout: float = None
+        self, ee_id: str, db_dict: dict = None, total_timeout: float = None,
+            scaling_in: bool = False
     ):
         self.log.info("Deleting execution environment ee_id={}".format(ee_id))
 
@@ -764,12 +789,21 @@ class N2VCJujuConnector(N2VCConnector):
         model_name, application_name, _machine_id = self._get_ee_id_components(
             ee_id=ee_id
         )
-
-        # destroy the application
         try:
-            await self.libjuju.destroy_model(
-                model_name=model_name, total_timeout=total_timeout
-            )
+            if not scaling_in:
+                # destroy the model
+                # TODO: should this be removed?
+                await self.libjuju.destroy_model(
+                    model_name=model_name,
+                    total_timeout=total_timeout,
+                )
+            else:
+                # destroy the application
+                await self.libjuju.destroy_application(
+                    model_name=model_name,
+                    application_name=application_name,
+                    total_timeout=total_timeout,
+                )
         except Exception as e:
             raise N2VCException(
                 message=(
@@ -777,18 +811,6 @@ class N2VCJujuConnector(N2VCConnector):
                 ).format(ee_id, application_name, e)
             )
 
-        # destroy the machine
-        # try:
-        #     await self._juju_destroy_machine(
-        #         model_name=model_name,
-        #         machine_id=machine_id,
-        #         total_timeout=total_timeout
-        #     )
-        # except Exception as e:
-        #     raise N2VCException(
-        #        message='Error deleting execution environment {} (machine {}) : {}'
-        #                .format(ee_id, machine_id, e))
-
         self.log.info("Execution environment {} deleted".format(ee_id))
 
     async def exec_primitive(