From: Dominik Fleischmann Date: Tue, 9 Jun 2020 09:57:14 +0000 (+0200) Subject: 8716 - K8s Proxy Charms X-Git-Tag: v8.0.0rc2~4 X-Git-Url: https://osm.etsi.org/gitweb/?a=commitdiff_plain;h=refs%2Fchanges%2F29%2F9229%2F1;p=osm%2FN2VC.git 8716 - K8s Proxy Charms This commit enables the possibility to deploy Proxy Charms on Kubernetes. It depends on a new LCM environment variable called `k8s_cloud` which will state the k8s name in VCA that will be used for deploying the proxy charms. It will also check the k8s model for status and delete the k8s model if it exists. Finally it will now retry the verify ssh credential action when the action fails, instead of only retrying when a excpetion is launched. Change-Id: I09510dcec00f747da945f88d54c2e25c1402d56c Signed-off-by: Dominik Fleischmann (cherry picked from commit b95133489d1cd16e1692085b1193d24d52c858fc) --- diff --git a/n2vc/exceptions.py b/n2vc/exceptions.py index 061cd7a..59bcc1a 100644 --- a/n2vc/exceptions.py +++ b/n2vc/exceptions.py @@ -37,6 +37,10 @@ class JujuMachineNotFound(Exception): """The machine cannot be found.""" +class JujuK8sProxycharmNotSupported(Exception): + """K8s Proxy Charms not supported in this installation.""" + + class N2VCPrimitiveExecutionFailed(Exception): """Something failed while attempting to execute a primitive.""" diff --git a/n2vc/libjuju.py b/n2vc/libjuju.py index b0e1358..aa8a355 100644 --- a/n2vc/libjuju.py +++ b/n2vc/libjuju.py @@ -944,3 +944,20 @@ class Libjuju: finally: await self.disconnect_controller(controller) await asyncio.sleep(interval) + + async def list_models(self, contains: str = None) -> [str]: + """List models with certain names + + :param: contains: String that is contained in model name + + :retur: [models] Returns list of model names + """ + + controller = await self.get_controller() + try: + models = await controller.list_models() + if contains: + models = [model for model in models if contains in model] + return models + finally: + await self.disconnect_controller(controller) diff --git a/n2vc/n2vc_conn.py b/n2vc/n2vc_conn.py index 55cc7e1..656eec7 100644 --- a/n2vc/n2vc_conn.py +++ b/n2vc/n2vc_conn.py @@ -245,6 +245,38 @@ class N2VCConnector(abc.ABC, Loggable): :param float total_timeout: """ + @abc.abstractmethod + async def install_k8s_proxy_charm( + self, + charm_name: str, + namespace: str, + artifact_path: str, + db_dict: dict, + progress_timeout: float = None, + total_timeout: float = None, + config: dict = None, + ) -> str: + """ + Install a k8s proxy charm + + :param charm_name: Name of the charm being deployed + :param namespace: collection of all the uuids related to the charm. + :param str artifact_path: where to locate the artifacts (parent folder) using + the self.fs + the final artifact path will be a combination of this artifact_path and + additional string from the config_dict (e.g. charm name) + :param dict db_dict: where to write into database when the status changes. + It contains a dict with + {collection: , filter: {}, path: }, + e.g. {collection: "nsrs", filter: + {_id: , path: "_admin.deployed.VCA.3"} + :param float progress_timeout: + :param float total_timeout: + :param config: Dictionary with additional configuration + + :returns ee_id: execution environment id. + """ + @abc.abstractmethod async def get_ee_ssh_public__key( self, diff --git a/n2vc/n2vc_juju_conn.py b/n2vc/n2vc_juju_conn.py index 75194aa..ed1250d 100644 --- a/n2vc/n2vc_juju_conn.py +++ b/n2vc/n2vc_juju_conn.py @@ -43,6 +43,7 @@ from n2vc.exceptions import ( N2VCInvalidCertificate, N2VCNotFound, MethodNotImplemented, + JujuK8sProxycharmNotSupported, ) from n2vc.juju_observer import JujuModelObserver from n2vc.n2vc_conn import N2VCConnector @@ -185,8 +186,11 @@ class N2VCJujuConnector(N2VCConnector): else: self.apt_mirror = None - self.cloud = vca_config.get("cloud") - # self.log.debug('Arguments have been checked') + self.cloud = vca_config.get('cloud') + self.k8s_cloud = None + if "k8s_cloud" in vca_config: + self.k8s_cloud = vca_config.get("k8s_cloud") + self.log.debug('Arguments have been checked') # juju data self.controller = None # it will be filled when connect to juju @@ -233,7 +237,11 @@ class N2VCJujuConnector(N2VCConnector): self.log.error(msg) raise N2VCBadArgumentsException(msg, ["namespace"]) - status = await self.libjuju.get_model_status(model_name) + status = {} + models = await self.libjuju.list_models(contains=ns_id) + + for m in models: + status[m] = self.libjuju.get_model_status(m) if yaml_format: return obj_to_yaml(status) @@ -486,6 +494,91 @@ class N2VCJujuConnector(N2VCConnector): self.log.info("Configuration sw installed") + async def install_k8s_proxy_charm( + self, + charm_name: str, + namespace: str, + artifact_path: str, + db_dict: dict, + progress_timeout: float = None, + total_timeout: float = None, + config: dict = None, + ) -> str: + """ + Install a k8s proxy charm + + :param charm_name: Name of the charm being deployed + :param namespace: collection of all the uuids related to the charm. + :param str artifact_path: where to locate the artifacts (parent folder) using + the self.fs + the final artifact path will be a combination of this artifact_path and + additional string from the config_dict (e.g. charm name) + :param dict db_dict: where to write into database when the status changes. + It contains a dict with + {collection: , filter: {}, path: }, + e.g. {collection: "nsrs", filter: + {_id: , path: "_admin.deployed.VCA.3"} + :param float progress_timeout: + :param float total_timeout: + :param config: Dictionary with additional configuration + + :returns ee_id: execution environment id. + """ + self.log.info('Installing k8s proxy charm: {}, artifact path: {}, db_dict: {}' + .format(charm_name, artifact_path, db_dict)) + + if not self.k8s_cloud: + raise JujuK8sProxycharmNotSupported("There is not k8s_cloud available") + + if artifact_path is None or len(artifact_path) == 0: + raise N2VCBadArgumentsException( + message="artifact_path is mandatory", bad_args=["artifact_path"] + ) + if db_dict is None: + raise N2VCBadArgumentsException(message='db_dict is mandatory', bad_args=['db_dict']) + + # remove // in charm path + while artifact_path.find('//') >= 0: + artifact_path = artifact_path.replace('//', '/') + + # check charm path + if not self.fs.file_exists(artifact_path, mode="dir"): + msg = 'artifact path does not exist: {}'.format(artifact_path) + raise N2VCBadArgumentsException(message=msg, bad_args=['artifact_path']) + + if artifact_path.startswith('/'): + full_path = self.fs.path + artifact_path + else: + full_path = self.fs.path + '/' + artifact_path + + _, ns_id, _, _, _ = self._get_namespace_components(namespace=namespace) + model_name = '{}-k8s'.format(ns_id) + + await self.libjuju.add_model(model_name, self.k8s_cloud) + application_name = self._get_application_name(namespace) + + try: + await self.libjuju.deploy_charm( + model_name=model_name, + application_name=application_name, + path=full_path, + machine_id=None, + db_dict=db_dict, + progress_timeout=progress_timeout, + total_timeout=total_timeout, + config=config + ) + except Exception as e: + raise N2VCException(message='Error deploying charm: {}'.format(e)) + + self.log.info('K8s proxy charm installed') + ee_id = N2VCJujuConnector._build_ee_id( + model_name=model_name, + application_name=application_name, + machine_id="k8s", + ) + return ee_id + async def get_ee_ssh_public__key( self, ee_id: str, @@ -653,13 +746,11 @@ class N2VCJujuConnector(N2VCConnector): ) if ns_id is not None: try: - 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: - raise + models = await self.libjuju.list_models(contains=ns_id) + for model in models: + await self.libjuju.destroy_model( + model_name=model, total_timeout=total_timeout + ) except Exception as e: raise N2VCException( message="Error deleting namespace {} : {}".format(namespace, e) @@ -785,17 +876,18 @@ class N2VCJujuConnector(N2VCConnector): progress_timeout=progress_timeout, total_timeout=total_timeout, ) + + if ok == "failed": + self.log.debug( + "Error executing verify-ssh-credentials: {}. Retrying..." + ) + await asyncio.sleep(retry_timeout) + + continue 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( @@ -1370,7 +1462,6 @@ class N2VCJujuConnector(N2VCConnector): # get juju model names from juju model_list = await self.controller.list_models() - if model_name not in model_list: self.log.info( "Model {} does not exist. Creating new model...".format(model_name)