X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;f=n2vc%2Fk8s_helm3_conn.py;h=4baadae203a35455cd3b6bd9729535339f34ae7c;hb=afde3be17b3f596a3d7996b1ebaf50c027bf624e;hp=5544e3c203501c4d585bb424071f580a3519e5aa;hpb=7bd5c6affb2805caba1b832b56d0ad5712396306;p=osm%2FN2VC.git diff --git a/n2vc/k8s_helm3_conn.py b/n2vc/k8s_helm3_conn.py index 5544e3c..4baadae 100644 --- a/n2vc/k8s_helm3_conn.py +++ b/n2vc/k8s_helm3_conn.py @@ -19,6 +19,7 @@ # For those usages not covered by the Apache License, Version 2.0 please # contact with: nfvlabs@tid.es ## +from typing import Union import os import yaml @@ -83,7 +84,12 @@ class K8sHelm3Connector(K8sHelmBaseConnector): """Install a helm chart :param cluster_uuid str: The UUID of the cluster to install to - :param kdu_model str: The name or path of a bundle to install + :param kdu_model str: chart/reference (string), which can be either + of these options: + - a name of chart available via the repos known by OSM + (e.g. stable/openldap, stable/openldap:1.2.4) + - a path to a packaged chart (e.g. mychart.tgz) + - a path to an unpacked chart directory or a URL (e.g. mychart) :param kdu_instance: Kdu instance name :param atomic bool: If set, waits until the model is active and resets the cluster on failure. @@ -97,24 +103,25 @@ class K8sHelm3Connector(K8sHelmBaseConnector): :return: True if successful """ - _, cluster_id = self._get_namespace_cluster_id(cluster_uuid) - self.log.debug("installing {} in cluster {}".format(kdu_model, cluster_id)) + + self.log.debug("installing {} in cluster {}".format(kdu_model, cluster_uuid)) # sync local dir - self.fs.sync(from_path=cluster_id) + self.fs.sync(from_path=cluster_uuid) # init env, paths paths, env = self._init_paths_env( - cluster_name=cluster_id, create_if_not_exist=True + cluster_name=cluster_uuid, create_if_not_exist=True ) # for helm3 if namespace does not exist must create it if namespace and namespace != "kube-system": - if not await self._namespace_exists(cluster_id, namespace): + if not await self._namespace_exists(cluster_uuid, namespace): try: - await self._create_namespace(cluster_id, namespace) + # TODO: refactor to use kubernetes API client + await self._create_namespace(cluster_uuid, namespace) except Exception as e: - if not await self._namespace_exists(cluster_id, namespace): + if not await self._namespace_exists(cluster_uuid, namespace): err_msg = ( "namespace {} does not exist in cluster_id {} " "error message: ".format(namespace, e) @@ -123,7 +130,7 @@ class K8sHelm3Connector(K8sHelmBaseConnector): raise K8sException(err_msg) await self._install_impl( - cluster_id, + cluster_uuid, kdu_model, paths, env, @@ -137,18 +144,17 @@ class K8sHelm3Connector(K8sHelmBaseConnector): ) # sync fs - self.fs.reverse_sync(from_path=cluster_id) + self.fs.reverse_sync(from_path=cluster_uuid) self.log.debug("Returning kdu_instance {}".format(kdu_instance)) return True async def inspect_kdu(self, kdu_model: str, repo_url: str = None) -> str: - self.log.debug( "inspect kdu_model {} from (optional) repo: {}".format(kdu_model, repo_url) ) - return await self._exec_inspect_comand( + return await self._exec_inspect_command( inspect_command="all", kdu_model=kdu_model, repo_url=repo_url ) @@ -244,7 +250,6 @@ class K8sHelm3Connector(K8sHelmBaseConnector): return namespace in namespaces if namespaces else False async def _get_namespaces(self, cluster_id: str): - self.log.debug("get namespaces cluster_id {}".format(cluster_id)) # init config, env @@ -266,7 +271,6 @@ class K8sHelm3Connector(K8sHelmBaseConnector): return namespaces async def _create_namespace(self, cluster_id: str, namespace: str): - self.log.debug(f"create namespace: {cluster_id} for cluster_id: {namespace}") # init config, env @@ -287,7 +291,6 @@ class K8sHelm3Connector(K8sHelmBaseConnector): async def _get_services( self, cluster_id: str, kdu_instance: str, namespace: str, kubeconfig: str ): - # init config, env paths, env = self._init_paths_env( cluster_name=cluster_id, create_if_not_exist=True @@ -312,14 +315,13 @@ class K8sHelm3Connector(K8sHelmBaseConnector): if namespace != "kube-system": namespaces = await self._get_namespaces(cluster_id) if namespace not in namespaces: + # TODO: refactor to use kubernetes API client await self._create_namespace(cluster_id, namespace) - # If default repo is not included add - cluster_uuid = "{}:{}".format(namespace, cluster_id) - repo_list = await self.repo_list(cluster_uuid) + repo_list = await self.repo_list(cluster_id) stable_repo = [repo for repo in repo_list if repo["name"] == "stable"] if not stable_repo and self._stable_repo_url: - await self.repo_add(cluster_uuid, "stable", self._stable_repo_url) + await self.repo_add(cluster_id, "stable", self._stable_repo_url) # Returns False as no software needs to be uninstalled return False @@ -329,7 +331,6 @@ class K8sHelm3Connector(K8sHelmBaseConnector): pass async def _instances_list(self, cluster_id: str): - # init paths, env paths, env = self._init_paths_env( cluster_name=cluster_id, create_if_not_exist=True @@ -347,22 +348,44 @@ class K8sHelm3Connector(K8sHelmBaseConnector): return [] def _get_inspect_command( - self, inspect_command: str, kdu_model: str, repo_str: str, version: str + self, show_command: str, kdu_model: str, repo_str: str, version: str ): + """Generates the command to obtain the information about an Helm Chart package + (´helm show ...´ command) + + Args: + show_command: the second part of the command (`helm show `) + kdu_model: The name or path of an Helm Chart + repo_url: Helm Chart repository url + version: constraint with specific version of the Chart to use + + Returns: + str: the generated Helm Chart command + """ + inspect_command = "{} show {} {}{} {}".format( - self._helm_command, inspect_command, kdu_model, repo_str, version + self._helm_command, show_command, kdu_model, repo_str, version ) return inspect_command + def _get_get_command( + self, get_command: str, kdu_instance: str, namespace: str, kubeconfig: str + ): + get_command = ( + "env KUBECONFIG={} {} get {} {} --namespace={} --output yaml".format( + kubeconfig, self._helm_command, get_command, kdu_instance, namespace + ) + ) + return get_command + async def _status_kdu( self, cluster_id: str, kdu_instance: str, namespace: str = None, + yaml_format: bool = False, show_error_log: bool = False, - return_text: bool = False, - ): - + ) -> Union[str, dict]: self.log.debug( "status of kdu_instance: {}, namespace: {} ".format(kdu_instance, namespace) ) @@ -385,7 +408,7 @@ class K8sHelm3Connector(K8sHelmBaseConnector): env=env, ) - if return_text: + if yaml_format: return str(output) if rc != 0: @@ -396,11 +419,18 @@ class K8sHelm3Connector(K8sHelmBaseConnector): # remove field 'notes' and manifest try: del data.get("info")["notes"] - del data["manifest"] except KeyError: pass - # unable to parse 'resources' as currently it is not included in helm3 + # parse the manifest to a list of dictionaries + if "manifest" in data: + manifest_str = data.get("manifest") + manifest_docs = yaml.load_all(manifest_str, Loader=yaml.SafeLoader) + + data["manifest"] = [] + for doc in manifest_docs: + data["manifest"].append(doc) + return data def _get_install_command( @@ -414,7 +444,6 @@ class K8sHelm3Connector(K8sHelmBaseConnector): timeout: float, kubeconfig: str, ) -> str: - timeout_str = "" if timeout: timeout_str = "--timeout {}s".format(timeout) @@ -449,6 +478,57 @@ class K8sHelm3Connector(K8sHelmBaseConnector): ) return command + def _get_upgrade_scale_command( + self, + kdu_model: str, + kdu_instance: str, + namespace: str, + scale: int, + version: str, + atomic: bool, + replica_str: str, + timeout: float, + resource_name: str, + kubeconfig: str, + ) -> str: + """Generates the command to scale a Helm Chart release + + Args: + kdu_model (str): Kdu model name, corresponding to the Helm local location or repository + kdu_instance (str): KDU instance, corresponding to the Helm Chart release in question + namespace (str): Namespace where this KDU instance is deployed + scale (int): Scale count + version (str): Constraint with specific version of the Chart to use + atomic (bool): If set, upgrade process rolls back changes made in case of failed upgrade. + The --wait flag will be set automatically if --atomic is used + replica_str (str): The key under resource_name key where the scale count is stored + timeout (float): The time, in seconds, to wait + resource_name (str): The KDU's resource to scale + kubeconfig (str): Kubeconfig file path + + Returns: + str: command to scale a Helm Chart release + """ + + # scale + if resource_name: + scale_dict = {"{}.{}".format(resource_name, replica_str): scale} + else: + scale_dict = {replica_str: scale} + + scale_str = self._params_to_set_option(scale_dict) + + return self._get_upgrade_command( + kdu_model=kdu_model, + kdu_instance=kdu_instance, + namespace=namespace, + params_str=scale_str, + version=version, + atomic=atomic, + timeout=timeout, + kubeconfig=kubeconfig, + ) + def _get_upgrade_command( self, kdu_model: str, @@ -459,7 +539,24 @@ class K8sHelm3Connector(K8sHelmBaseConnector): atomic: bool, timeout: float, kubeconfig: str, + force: bool = False, ) -> str: + """Generates the command to upgrade a Helm Chart release + + Args: + kdu_model (str): Kdu model name, corresponding to the Helm local location or repository + kdu_instance (str): KDU instance, corresponding to the Helm Chart release in question + namespace (str): Namespace where this KDU instance is deployed + params_str (str): Params used to upgrade the Helm Chart release + version (str): Constraint with specific version of the Chart to use + atomic (bool): If set, upgrade process rolls back changes made in case of failed upgrade. + The --wait flag will be set automatically if --atomic is used + timeout (float): The time, in seconds, to wait + kubeconfig (str): Kubeconfig file path + force (bool): If set, helm forces resource updates through a replacement strategy. This may recreate pods. + Returns: + str: command to upgrade a Helm Chart release + """ timeout_str = "" if timeout: @@ -470,6 +567,11 @@ class K8sHelm3Connector(K8sHelmBaseConnector): if atomic: atomic_str = "--atomic" + # force + force_str = "" + if force: + force_str = "--force " + # version version_str = "" if version: @@ -481,14 +583,15 @@ class K8sHelm3Connector(K8sHelmBaseConnector): namespace_str = "--namespace {}".format(namespace) command = ( - "env KUBECONFIG={kubeconfig} {helm} upgrade {name} {model} {namespace} {atomic} " - "--output yaml {params} {timeout} {ver}" + "env KUBECONFIG={kubeconfig} {helm} upgrade {name} {model} {namespace} {atomic} {force}" + "--output yaml {params} {timeout} --reuse-values {ver}" ).format( kubeconfig=kubeconfig, helm=self._helm_command, name=kdu_instance, namespace=namespace_str, atomic=atomic_str, + force=force_str, params=params_str, timeout=timeout_str, model=kdu_model, @@ -506,7 +609,6 @@ class K8sHelm3Connector(K8sHelmBaseConnector): def _get_uninstall_command( self, kdu_instance: str, namespace: str, kubeconfig: str ) -> str: - return "env KUBECONFIG={} {} uninstall {} --namespace={}".format( kubeconfig, self._helm_command, kdu_instance, namespace )