Generate names for K8s pods when file or url
[osm/N2VC.git] / n2vc / k8s_juju_conn.py
index 4f62898..1db34b4 100644 (file)
 #     See the License for the specific language governing permissions and
 #     limitations under the License.
 
 #     See the License for the specific language governing permissions and
 #     limitations under the License.
 
+import asyncio
 import concurrent
 from .exceptions import NotImplemented
 
 import concurrent
 from .exceptions import NotImplemented
 
+import io
 import juju
 # from juju.bundle import BundleHandler
 from juju.controller import Controller
 import juju
 # from juju.bundle import BundleHandler
 from juju.controller import Controller
@@ -28,7 +30,6 @@ from n2vc.k8s_conn import K8sConnector
 import os
 # import re
 # import ssl
 import os
 # import re
 # import ssl
-import subprocess
 # from .vnf import N2VC
 
 import uuid
 # from .vnf import N2VC
 
 import uuid
@@ -36,11 +37,15 @@ import yaml
 
 
 class K8sJujuConnector(K8sConnector):
 
 
 class K8sJujuConnector(K8sConnector):
+
     def __init__(
             self,
     def __init__(
             self,
-            fs,
-            kubectl_command='/usr/bin/kubectl',
-            log=None
+            fs: object,
+            db: object,
+            kubectl_command: str = '/usr/bin/kubectl',
+            juju_command: str = '/usr/bin/juju',
+            log=None,
+            on_update_db=None,
     ):
         """
 
     ):
         """
 
@@ -53,25 +58,30 @@ class K8sJujuConnector(K8sConnector):
         # parent class
         K8sConnector.__init__(
             self,
         # parent class
         K8sConnector.__init__(
             self,
-            kubectl_command=kubectl_command,
-            fs=fs,
+            db,
             log=log,
             log=log,
+            on_update_db=on_update_db,
         )
 
         )
 
+        self.fs = fs
         self.info('Initializing K8S Juju connector')
 
         self.authenticated = False
         self.models = {}
         self.log = logging.getLogger(__name__)
         self.info('Initializing K8S Juju connector')
 
         self.authenticated = False
         self.models = {}
         self.log = logging.getLogger(__name__)
+
+        self.juju_command = juju_command
+        self.juju_secret = ""
+
         self.info('K8S Juju connector initialized')
 
     """Initialization"""
     async def init_env(
         self,
         self.info('K8S Juju connector initialized')
 
     """Initialization"""
     async def init_env(
         self,
-        k8s_creds: dict,
+        k8s_creds: str,
         namespace: str = 'kube-system',
         reuse_cluster_uuid: str = None,
         namespace: str = 'kube-system',
         reuse_cluster_uuid: str = None,
-    ) -> str:
+    ) -> (str, bool):
         """Initialize a Kubernetes environment
 
         :param k8s_creds dict: A dictionary containing the Kubernetes cluster
         """Initialize a Kubernetes environment
 
         :param k8s_creds dict: A dictionary containing the Kubernetes cluster
@@ -86,10 +96,6 @@ class K8sJujuConnector(K8sConnector):
         Bootstrapping cannot be done, by design, through the API. We need to
         use the CLI tools.
         """
         Bootstrapping cannot be done, by design, through the API. We need to
         use the CLI tools.
         """
-        # TODO: The path may change
-        jujudir = "/snap/bin"
-
-        self.k8scli = "{}/juju".format(jujudir)
 
         """
         WIP: Workflow
 
         """
         WIP: Workflow
@@ -118,31 +124,28 @@ class K8sJujuConnector(K8sConnector):
 
             cluster_uuid = str(uuid.uuid4())
 
 
             cluster_uuid = str(uuid.uuid4())
 
-            # Add k8s cloud to Juju (unless it's microk8s)
+            # Is a local k8s cluster?
+            localk8s = self.is_local_k8s(k8s_creds)
 
 
-            # Does the kubeconfig contain microk8s?
-            microk8s = self.is_microk8s_by_credentials(k8s_creds)
+            # If the k8s is external, the juju controller needs a loadbalancer
+            loadbalancer = False if localk8s else True
 
 
-            if not microk8s:
-                # Name the new k8s cloud
-                k8s_cloud = "{}-k8s".format(namespace)
-
-                await self.add_k8s(k8s_cloud, k8s_creds)
+            # Name the new k8s cloud
+            k8s_cloud = "{}-k8s".format(namespace)
 
 
-                # Bootstrap Juju controller
-                self.bootstrap(k8s_cloud, cluster_uuid)
-            else:
-                # k8s_cloud = 'microk8s-test'
-                k8s_cloud = "{}-k8s".format(namespace)
+            print("Adding k8s cloud {}".format(k8s_cloud))
+            await self.add_k8s(k8s_cloud, k8s_creds)
 
 
-                await self.add_k8s(k8s_cloud, k8s_creds)
-
-                await self.bootstrap(k8s_cloud, cluster_uuid)
+            # Bootstrap Juju controller
+            print("Bootstrapping...")
+            await self.bootstrap(k8s_cloud, cluster_uuid, loadbalancer)
+            print("Bootstrap done.")
 
             # Get the controller information
 
             # Parse ~/.local/share/juju/controllers.yaml
             # controllers.testing.api-endpoints|ca-cert|uuid
 
             # Get the controller information
 
             # Parse ~/.local/share/juju/controllers.yaml
             # controllers.testing.api-endpoints|ca-cert|uuid
+            print("Getting controller endpoints")
             with open(os.path.expanduser(
                 "~/.local/share/juju/controllers.yaml"
             )) as f:
             with open(os.path.expanduser(
                 "~/.local/share/juju/controllers.yaml"
             )) as f:
@@ -154,6 +157,7 @@ class K8sJujuConnector(K8sConnector):
 
             # Parse ~/.local/share/juju/accounts
             # controllers.testing.user|password
 
             # Parse ~/.local/share/juju/accounts
             # controllers.testing.user|password
+            print("Getting accounts")
             with open(os.path.expanduser(
                 "~/.local/share/juju/accounts.yaml"
             )) as f:
             with open(os.path.expanduser(
                 "~/.local/share/juju/accounts.yaml"
             )) as f:
@@ -178,11 +182,12 @@ class K8sJujuConnector(K8sConnector):
                 'secret': self.juju_secret,
                 'cacert': self.juju_ca_cert,
                 'namespace': namespace,
                 'secret': self.juju_secret,
                 'cacert': self.juju_ca_cert,
                 'namespace': namespace,
-                'microk8s': microk8s,
+                'loadbalancer': loadbalancer,
             }
 
             # Store the cluster configuration so it
             # can be used for subsequent calls
             }
 
             # Store the cluster configuration so it
             # can be used for subsequent calls
+            print("Setting config")
             await self.set_config(cluster_uuid, config)
 
         else:
             await self.set_config(cluster_uuid, config)
 
         else:
@@ -199,17 +204,20 @@ class K8sJujuConnector(K8sConnector):
 
         # Login to the k8s cluster
         if not self.authenticated:
 
         # Login to the k8s cluster
         if not self.authenticated:
-            await self.login()
+            await self.login(cluster_uuid)
 
         # We're creating a new cluster
 
         # We're creating a new cluster
-        print("Getting model {}".format(self.get_namespace(cluster_uuid)))
-        model = await self.get_model(self.get_namespace(cluster_uuid))
+        print("Getting model {}".format(self.get_namespace(cluster_uuid), cluster_uuid=cluster_uuid))
+        model = await self.get_model(
+            self.get_namespace(cluster_uuid),
+            cluster_uuid=cluster_uuid
+        )
 
         # Disconnect from the model
         if model and model.is_connected():
             await model.disconnect()
 
 
         # Disconnect from the model
         if model and model.is_connected():
             await model.disconnect()
 
-        return cluster_uuid
+        return cluster_uuid, True
 
     """Repo Management"""
     async def repo_add(
 
     """Repo Management"""
     async def repo_add(
@@ -231,8 +239,10 @@ class K8sJujuConnector(K8sConnector):
 
     """Reset"""
     async def reset(
 
     """Reset"""
     async def reset(
-        self,
-        cluster_uuid: str,
+            self,
+            cluster_uuid: str,
+            force: bool = False,
+            uninstall_sw: bool = False
     ) -> bool:
         """Reset a cluster
 
     ) -> bool:
         """Reset a cluster
 
@@ -244,7 +254,7 @@ class K8sJujuConnector(K8sConnector):
 
         try:
             if not self.authenticated:
 
         try:
             if not self.authenticated:
-                await self.login()
+                await self.login(cluster_uuid)
 
             if self.controller.is_connected():
                 # Destroy the model
 
             if self.controller.is_connected():
                 # Destroy the model
@@ -264,13 +274,6 @@ class K8sJujuConnector(K8sConnector):
                 print("[reset] Destroying controller")
                 await self.destroy_controller(cluster_uuid)
 
                 print("[reset] Destroying controller")
                 await self.destroy_controller(cluster_uuid)
 
-                """Remove the k8s cloud
-
-                Only remove the k8s cloud if it's not a microk8s cloud,
-                since microk8s is a built-in cloud type.
-                """
-                # microk8s = self.is_microk8s_by_cluster_uuid(cluster_uuid)
-                # if not microk8s:
                 print("[reset] Removing k8s cloud")
                 namespace = self.get_namespace(cluster_uuid)
                 k8s_cloud = "{}-k8s".format(namespace)
                 print("[reset] Removing k8s cloud")
                 namespace = self.get_namespace(cluster_uuid)
                 k8s_cloud = "{}-k8s".format(namespace)
@@ -280,14 +283,16 @@ class K8sJujuConnector(K8sConnector):
             print("Caught exception during reset: {}".format(ex))
 
     """Deployment"""
             print("Caught exception during reset: {}".format(ex))
 
     """Deployment"""
+
     async def install(
         self,
         cluster_uuid: str,
         kdu_model: str,
         atomic: bool = True,
     async def install(
         self,
         cluster_uuid: str,
         kdu_model: str,
         atomic: bool = True,
-        timeout: int = None,
+        timeout: float = 300,
         params: dict = None,
         params: dict = None,
-    ) -> str:
+        db_dict: dict = None
+    ) -> bool:
         """Install a bundle
 
         :param cluster_uuid str: The UUID of the cluster to install to
         """Install a bundle
 
         :param cluster_uuid str: The UUID of the cluster to install to
@@ -303,22 +308,51 @@ class K8sJujuConnector(K8sConnector):
 
         if not self.authenticated:
             print("[install] Logging in to the controller")
 
         if not self.authenticated:
             print("[install] Logging in to the controller")
-            await self.login()
+            await self.login(cluster_uuid)
 
         ##
 
         ##
-        # Get or create the model, based on the namespace the cluster was
-        # instantiated with.
-        namespace = self.get_namespace(cluster_uuid)
-        model = await self.get_model(namespace)
+        # Get or create the model, based on the NS
+        # uuid.
+        model_name = db_dict["filter"]["_id"]
+
+        self.log.debug("Checking for model named {}".format(model_name))
+        model = await self.get_model(model_name, cluster_uuid=cluster_uuid)
         if not model:
             # Create the new model
         if not model:
             # Create the new model
-            model = await self.add_model(namespace)
+            self.log.debug("Adding model: {}".format(model_name))
+            model = await self.add_model(model_name, cluster_uuid=cluster_uuid)
 
         if model:
             # TODO: Instantiation parameters
 
 
         if model:
             # TODO: Instantiation parameters
 
-            print("[install] deploying {}".format(kdu_model))
-            await model.deploy(kdu_model)
+            """
+            "Juju bundle that models the KDU, in any of the following ways:
+                - <juju-repo>/<juju-bundle>
+                - <juju-bundle folder under k8s_models folder in the package>
+                - <juju-bundle tgz file (w/ or w/o extension) under k8s_models folder in the package>
+                - <URL_where_to_fetch_juju_bundle>
+            """
+
+            bundle = kdu_model
+            if kdu_model.startswith("cs:"):
+                bundle = kdu_model
+            elif kdu_model.startswith("http"):
+                # Download the file
+                pass
+            else:
+                # Local file
+
+                # if kdu_model.endswith(".tar.gz") or kdu_model.endswith(".tgz")
+                # Uncompress temporarily
+                # bundle = <uncompressed file>
+                pass
+
+            if not bundle:
+                # Raise named exception that the bundle could not be found
+                raise Exception()
+
+            print("[install] deploying {}".format(bundle))
+            await model.deploy(bundle)
 
             # Get the application
             if atomic:
 
             # Get the application
             if atomic:
@@ -400,10 +434,10 @@ class K8sJujuConnector(K8sConnector):
         initial release.
         """
         namespace = self.get_namespace(cluster_uuid)
         initial release.
         """
         namespace = self.get_namespace(cluster_uuid)
-        model = await self.get_model(namespace)
+        model = await self.get_model(namespace, cluster_uuid=cluster_uuid)
 
         with open(kdu_model, 'r') as f:
 
         with open(kdu_model, 'r') as f:
-            bundle = yaml.load(f, Loader=yaml.FullLoader)
+            bundle = yaml.safe_load(f)
 
             """
             {
 
             """
             {
@@ -478,7 +512,7 @@ class K8sJujuConnector(K8sConnector):
         removed = False
 
         # Remove an application from the model
         removed = False
 
         # Remove an application from the model
-        model = await self.get_model(self.get_namespace(cluster_uuid))
+        model = await self.get_model(self.get_namespace(cluster_uuid), cluster_uuid=cluster_uuid)
 
         if model:
             # Get the application
 
         if model:
             # Get the application
@@ -514,7 +548,7 @@ class K8sJujuConnector(K8sConnector):
 
         kdu = {}
         with open(kdu_model, 'r') as f:
 
         kdu = {}
         with open(kdu_model, 'r') as f:
-            bundle = yaml.load(f, Loader=yaml.FullLoader)
+            bundle = yaml.safe_load(f)
 
             """
             {
 
             """
             {
@@ -580,7 +614,7 @@ class K8sJujuConnector(K8sConnector):
         """
         status = {}
 
         """
         status = {}
 
-        model = await self.get_model(self.get_namespace(cluster_uuid))
+        model = await self.get_model(self.get_namespace(cluster_uuid), cluster_uuid=cluster_uuid)
 
         # model = await self.get_model_by_uuid(cluster_uuid)
         if model:
 
         # model = await self.get_model_by_uuid(cluster_uuid)
         if model:
@@ -602,7 +636,7 @@ class K8sJujuConnector(K8sConnector):
     async def add_k8s(
         self,
         cloud_name: str,
     async def add_k8s(
         self,
         cloud_name: str,
-        credentials: dict,
+        credentials: str,
     ) -> bool:
         """Add a k8s cloud to Juju
 
     ) -> bool:
         """Add a k8s cloud to Juju
 
@@ -615,24 +649,37 @@ class K8sJujuConnector(K8sConnector):
 
         :returns: True if successful, otherwise raises an exception.
         """
 
         :returns: True if successful, otherwise raises an exception.
         """
-        cmd = [self.k8scli, "add-k8s", "--local", cloud_name]
-
-        p = subprocess.run(
-            cmd,
-            stdout=subprocess.PIPE,
-            stderr=subprocess.PIPE,
-            input=yaml.dump(credentials, Dumper=yaml.Dumper),
-            encoding='ascii'
+
+        cmd = [self.juju_command, "add-k8s", "--local", cloud_name]
+        print(cmd)
+
+        process = await asyncio.create_subprocess_exec(
+            *cmd,
+            stdout=asyncio.subprocess.PIPE,
+            stderr=asyncio.subprocess.PIPE,
+            stdin=asyncio.subprocess.PIPE,
         )
         )
-        retcode = p.returncode
 
 
-        if retcode > 0:
-            raise Exception(p.stderr)
+        # Feed the process the credentials
+        process.stdin.write(credentials.encode("utf-8"))
+        await process.stdin.drain()
+        process.stdin.close()
+
+        stdout, stderr = await process.communicate()
+
+        return_code = process.returncode
+
+        print("add-k8s return code: {}".format(return_code))
+
+        if return_code > 0:
+            raise Exception(stderr)
+
         return True
 
     async def add_model(
         self,
         return True
 
     async def add_model(
         self,
-        model_name: str
+        model_name: str,
+        cluster_uuid: str,
     ) -> juju.model.Model:
         """Adds a model to the controller
 
     ) -> juju.model.Model:
         """Adds a model to the controller
 
@@ -643,8 +690,9 @@ class K8sJujuConnector(K8sConnector):
                   raises an exception.
         """
         if not self.authenticated:
                   raises an exception.
         """
         if not self.authenticated:
-            await self.login()
+            await self.login(cluster_uuid)
 
 
+        self.log.debug("Adding model '{}' to cluster_uuid '{}'".format(model_name, cluster_uuid))
         model = await self.controller.add_model(
             model_name,
             config={'authorized-keys': self.juju_public_key}
         model = await self.controller.add_model(
             model_name,
             config={'authorized-keys': self.juju_public_key}
@@ -654,7 +702,8 @@ class K8sJujuConnector(K8sConnector):
     async def bootstrap(
         self,
         cloud_name: str,
     async def bootstrap(
         self,
         cloud_name: str,
-        cluster_uuid: str
+        cluster_uuid: str,
+        loadbalancer: bool
     ) -> bool:
         """Bootstrap a Kubernetes controller
 
     ) -> bool:
         """Bootstrap a Kubernetes controller
 
@@ -662,25 +711,36 @@ class K8sJujuConnector(K8sConnector):
 
         :param cloud_name str: The name of the cloud.
         :param cluster_uuid str: The UUID of the cluster to bootstrap.
 
         :param cloud_name str: The name of the cloud.
         :param cluster_uuid str: The UUID of the cluster to bootstrap.
+        :param loadbalancer bool: If the controller should use loadbalancer or not.
         :returns: True upon success or raises an exception.
         """
         :returns: True upon success or raises an exception.
         """
-        cmd = [self.k8scli, "bootstrap", cloud_name, cluster_uuid]
+
+        if not loadbalancer:
+            cmd = [self.juju_command, "bootstrap", cloud_name, cluster_uuid]
+        else:
+            """
+            For public clusters, specify that the controller service is using a LoadBalancer.
+            """
+            cmd = [self.juju_command, "bootstrap", cloud_name, cluster_uuid, "--config", "controller-service-type=loadbalancer"]
+
         print("Bootstrapping controller {} in cloud {}".format(
             cluster_uuid, cloud_name
         ))
 
         print("Bootstrapping controller {} in cloud {}".format(
             cluster_uuid, cloud_name
         ))
 
-        p = subprocess.run(
-            cmd,
-            stdout=subprocess.PIPE,
-            stderr=subprocess.PIPE,
-            encoding='ascii'
+        process = await asyncio.create_subprocess_exec(
+            *cmd,
+            stdout=asyncio.subprocess.PIPE,
+            stderr=asyncio.subprocess.PIPE,
         )
         )
-        retcode = p.returncode
 
 
-        if retcode > 0:
+        stdout, stderr = await process.communicate()
+
+        return_code = process.returncode
+
+        if return_code > 0:
             #
             #
-            if 'already exists' not in p.stderr:
-                raise Exception(p.stderr)
+            if b'already exists' not in stderr:
+                raise Exception(stderr)
 
         return True
 
 
         return True
 
@@ -696,7 +756,7 @@ class K8sJujuConnector(K8sConnector):
         :returns: True upon success or raises an exception.
         """
         cmd = [
         :returns: True upon success or raises an exception.
         """
         cmd = [
-            self.k8scli,
+            self.juju_command,
             "destroy-controller",
             "--destroy-all-models",
             "--destroy-storage",
             "destroy-controller",
             "--destroy-all-models",
             "--destroy-storage",
@@ -704,18 +764,20 @@ class K8sJujuConnector(K8sConnector):
             cluster_uuid
         ]
 
             cluster_uuid
         ]
 
-        p = subprocess.run(
-            cmd,
-            stdout=subprocess.PIPE,
-            stderr=subprocess.PIPE,
-            encoding='ascii'
+        process = await asyncio.create_subprocess_exec(
+            *cmd,
+            stdout=asyncio.subprocess.PIPE,
+            stderr=asyncio.subprocess.PIPE,
         )
         )
-        retcode = p.returncode
 
 
-        if retcode > 0:
+        stdout, stderr = await process.communicate()
+
+        return_code = process.returncode
+
+        if return_code > 0:
             #
             #
-            if 'already exists' not in p.stderr:
-                raise Exception(p.stderr)
+            if 'already exists' not in stderr:
+                raise Exception(stderr)
 
     def get_config(
         self,
 
     def get_config(
         self,
@@ -731,7 +793,7 @@ class K8sJujuConnector(K8sConnector):
         cluster_config = "{}/{}.yaml".format(self.fs.path, cluster_uuid)
         if os.path.exists(cluster_config):
             with open(cluster_config, 'r') as f:
         cluster_config = "{}/{}.yaml".format(self.fs.path, cluster_uuid)
         if os.path.exists(cluster_config):
             with open(cluster_config, 'r') as f:
-                config = yaml.load(f.read(), Loader=yaml.FullLoader)
+                config = yaml.safe_load(f.read())
                 return config
         else:
             raise Exception(
                 return config
         else:
             raise Exception(
@@ -743,6 +805,7 @@ class K8sJujuConnector(K8sConnector):
     async def get_model(
         self,
         model_name: str,
     async def get_model(
         self,
         model_name: str,
+        cluster_uuid: str,
     ) -> juju.model.Model:
         """Get a model from the Juju Controller.
 
     ) -> juju.model.Model:
         """Get a model from the Juju Controller.
 
@@ -753,12 +816,13 @@ class K8sJujuConnector(K8sConnector):
         :return The juju.model.Model object if found, or None.
         """
         if not self.authenticated:
         :return The juju.model.Model object if found, or None.
         """
         if not self.authenticated:
-            await self.login()
+            await self.login(cluster_uuid)
 
         model = None
         models = await self.controller.list_models()
 
         model = None
         models = await self.controller.list_models()
-
+        self.log.debug(models)
         if model_name in models:
         if model_name in models:
+            self.log.debug("Found model: {}".format(model_name))
             model = await self.controller.get_model(
                 model_name
             )
             model = await self.controller.get_model(
                 model_name
             )
@@ -802,38 +866,30 @@ class K8sJujuConnector(K8sConnector):
             return True
         return False
 
             return True
         return False
 
-    def is_microk8s_by_cluster_uuid(
+    def is_local_k8s(
         self,
         self,
-        cluster_uuid: str,
+        credentials: str,
     ) -> bool:
     ) -> bool:
-        """Check if a cluster is micro8s
-
-        Checks if a cluster is running microk8s
+        """Check if a cluster is local
 
 
-        :param cluster_uuid str: The UUID of the cluster
-        :returns: A boolean if the cluster is running microk8s
-        """
-        config = self.get_config(cluster_uuid)
-        return config['microk8s']
-
-    def is_microk8s_by_credentials(
-        self,
-        credentials: dict,
-    ) -> bool:
-        """Check if a cluster is micro8s
-
-        Checks if a cluster is running microk8s
+        Checks if a cluster is running in the local host
 
         :param credentials dict: A dictionary containing the k8s credentials
 
         :param credentials dict: A dictionary containing the k8s credentials
-        :returns: A boolean if the cluster is running microk8s
+        :returns: A boolean if the cluster is running locally
         """
         """
-        for context in credentials['contexts']:
-            if 'microk8s' in context['name']:
-                return True
+        creds = yaml.safe_load(credentials)
+        if os.getenv("OSMLCM_VCA_APIPROXY"):
+            host_ip = os.getenv("OSMLCM_VCA_APIPROXY")
+
+        if creds and host_ip:
+            for cluster in creds['clusters']:
+                if 'server' in cluster['cluster']:
+                    if host_ip in cluster['cluster']['server']:
+                        return True
 
         return False
 
 
         return False
 
-    async def login(self):
+    async def login(self, cluster_uuid):
         """Login to the Juju controller."""
 
         if self.authenticated:
         """Login to the Juju controller."""
 
         if self.authenticated:
@@ -841,6 +897,15 @@ class K8sJujuConnector(K8sConnector):
 
         self.connecting = True
 
 
         self.connecting = True
 
+        # Test: Make sure we have the credentials loaded
+        config = self.get_config(cluster_uuid)
+
+        self.juju_endpoint = config['endpoint']
+        self.juju_user = config['username']
+        self.juju_secret = config['secret']
+        self.juju_ca_cert = config['cacert']
+        self.juju_public_key = None
+
         self.controller = Controller()
 
         if self.juju_secret:
         self.controller = Controller()
 
         if self.juju_secret:
@@ -901,31 +966,34 @@ class K8sJujuConnector(K8sConnector):
         """
 
         # Remove the bootstrapped controller
         """
 
         # Remove the bootstrapped controller
-        cmd = [self.k8scli, "remove-k8s", "--client", cloud_name]
-        p = subprocess.run(
-            cmd,
-            stdout=subprocess.PIPE,
-            stderr=subprocess.PIPE,
-            encoding='ascii'
+        cmd = [self.juju_command, "remove-k8s", "--client", cloud_name]
+        process = await asyncio.create_subprocess_exec(
+            *cmd,
+            stdout=asyncio.subprocess.PIPE,
+            stderr=asyncio.subprocess.PIPE,
         )
         )
-        retcode = p.returncode
 
 
-        if retcode > 0:
-            raise Exception(p.stderr)
+        stdout, stderr = await process.communicate()
+
+        return_code = process.returncode
+
+        if return_code > 0:
+            raise Exception(stderr)
 
         # Remove the cloud from the local config
 
         # Remove the cloud from the local config
-        cmd = [self.k8scli, "remove-cloud", "--client", cloud_name]
-        p = subprocess.run(
-            cmd,
-            stdout=subprocess.PIPE,
-            stderr=subprocess.PIPE,
-            encoding='ascii'
+        cmd = [self.juju_command, "remove-cloud", "--client", cloud_name]
+        process = await asyncio.create_subprocess_exec(
+            *cmd,
+            stdout=asyncio.subprocess.PIPE,
+            stderr=asyncio.subprocess.PIPE,
         )
         )
-        retcode = p.returncode
 
 
-        if retcode > 0:
-            raise Exception(p.stderr)
+        stdout, stderr = await process.communicate()
+
+        return_code = process.returncode
 
 
+        if return_code > 0:
+            raise Exception(stderr)
 
         return True
 
 
         return True