X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FN2VC.git;a=blobdiff_plain;f=n2vc%2Fk8s_juju_conn.py;h=1db34b4de42bc5a8720572de554e0b8834776779;hp=3acc10e86f6dab54c495f34a4e1ef7fe0a4eab53;hb=8d780a9e0414686f0c91be6dc460275fb0155f89;hpb=eef6893d5f83014dc6299e231590023421a99501 diff --git a/n2vc/k8s_juju_conn.py b/n2vc/k8s_juju_conn.py index 3acc10e..1db34b4 100644 --- a/n2vc/k8s_juju_conn.py +++ b/n2vc/k8s_juju_conn.py @@ -12,9 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +import asyncio import concurrent from .exceptions import NotImplemented +import io 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 subprocess # from .vnf import N2VC import uuid @@ -69,6 +70,7 @@ class K8sJujuConnector(K8sConnector): self.models = {} self.log = logging.getLogger(__name__) + self.juju_command = juju_command self.juju_secret = "" self.info('K8S Juju connector initialized') @@ -94,10 +96,6 @@ class K8sJujuConnector(K8sConnector): Bootstrapping cannot be done, by design, through the API. We need to use the CLI tools. """ - # TODO: The path may change - jujudir = "/usr/local/bin" - - self.k8scli = "{}/juju".format(jujudir) """ WIP: Workflow @@ -126,32 +124,22 @@ class K8sJujuConnector(K8sConnector): cluster_uuid = str(uuid.uuid4()) - # Add k8s cloud to Juju (unless it's microk8s) - - # Convert to a dict - # k8s_creds = yaml.safe_load(k8s_creds) - - # Does the kubeconfig contain microk8s? - microk8s = self.is_microk8s_by_credentials(k8s_creds) + # Is a local k8s cluster? + localk8s = self.is_local_k8s(k8s_creds) - if not microk8s: - # Name the new k8s cloud - k8s_cloud = "{}-k8s".format(namespace) + # If the k8s is external, the juju controller needs a loadbalancer + loadbalancer = False if localk8s else True - print("Adding k8s cloud {}".format(k8s_cloud)) - await self.add_k8s(k8s_cloud, k8s_creds) + # Name the new k8s cloud + k8s_cloud = "{}-k8s".format(namespace) - # Bootstrap Juju controller - print("Bootstrapping...") - await self.bootstrap(k8s_cloud, cluster_uuid) - print("Bootstrap done.") - 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 @@ -194,7 +182,7 @@ class K8sJujuConnector(K8sConnector): 'secret': self.juju_secret, 'cacert': self.juju_ca_cert, 'namespace': namespace, - 'microk8s': microk8s, + 'loadbalancer': loadbalancer, } # Store the cluster configuration so it @@ -221,7 +209,7 @@ class K8sJujuConnector(K8sConnector): # We're creating a new cluster print("Getting model {}".format(self.get_namespace(cluster_uuid), cluster_uuid=cluster_uuid)) model = await self.get_model( - self.get_namespace(cluster_uuid), + self.get_namespace(cluster_uuid), cluster_uuid=cluster_uuid ) @@ -286,13 +274,6 @@ class K8sJujuConnector(K8sConnector): 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) @@ -330,16 +311,16 @@ class K8sJujuConnector(K8sConnector): 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) - namespace = "gitlab-demo" - self.log.debug("Checking for model named {}".format(namespace)) - model = await self.get_model(namespace, cluster_uuid=cluster_uuid) + # 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 - self.log.debug("Adding model: {}".format(namespace)) - model = await self.add_model(namespace, cluster_uuid=cluster_uuid) + self.log.debug("Adding model: {}".format(model_name)) + model = await self.add_model(model_name, cluster_uuid=cluster_uuid) if model: # TODO: Instantiation parameters @@ -668,21 +649,31 @@ class K8sJujuConnector(K8sConnector): :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).encode("utf-8"), - input=credentials.encode("utf-8"), - # 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 - print("add-k8s return code: {}".format(retcode)) - 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( @@ -711,7 +702,8 @@ class K8sJujuConnector(K8sConnector): async def bootstrap( self, cloud_name: str, - cluster_uuid: str + cluster_uuid: str, + loadbalancer: bool ) -> bool: """Bootstrap a Kubernetes controller @@ -719,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 loadbalancer bool: If the controller should use loadbalancer or not. :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 )) - 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 b'already exists' not in p.stderr: - raise Exception(p.stderr) + if b'already exists' not in stderr: + raise Exception(stderr) return True @@ -753,7 +756,7 @@ class K8sJujuConnector(K8sConnector): :returns: True upon success or raises an exception. """ cmd = [ - self.k8scli, + self.juju_command, "destroy-controller", "--destroy-all-models", "--destroy-storage", @@ -761,18 +764,20 @@ class K8sJujuConnector(K8sConnector): 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, @@ -861,36 +866,26 @@ class K8sJujuConnector(K8sConnector): return True return False - def is_microk8s_by_cluster_uuid( - self, - cluster_uuid: str, - ) -> bool: - """Check if a cluster is micro8s - - Checks if a cluster is running microk8s - - :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( + def is_local_k8s( self, credentials: str, ) -> bool: - """Check if a cluster is micro8s + """Check if a cluster is local - 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 - :returns: A boolean if the cluster is running microk8s + :returns: A boolean if the cluster is running locally """ creds = yaml.safe_load(credentials) - if creds: - for context in creds['contexts']: - if 'microk8s' in context['name']: - return True + 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 @@ -971,31 +966,34 @@ class K8sJujuConnector(K8sConnector): """ # 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 - 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