2 # Copyright 2019 Telefonica Investigacion y Desarrollo, S.A.U.
3 # This file is part of OSM
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
10 # http://www.apache.org/licenses/LICENSE-2.0
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
19 # For those usages not covered by the Apache License, Version 2.0 please
20 # contact with: nfvlabs@tid.es
25 from n2vc
.k8s_helm_base_conn
import K8sHelmBaseConnector
26 from n2vc
.exceptions
import K8sException
29 class K8sHelm3Connector(K8sHelmBaseConnector
):
32 ####################################################################################
33 ################################### P U B L I C ####################################
34 ####################################################################################
41 kubectl_command
: str = "/usr/bin/kubectl",
42 helm_command
: str = "/usr/bin/helm3",
45 vca_config
: dict = None,
48 Initializes helm connector for helm v3
50 :param fs: file system for kubernetes and helm configuration
51 :param db: database object to write current operation status
52 :param kubectl_command: path to kubectl executable
53 :param helm_command: path to helm executable
55 :param on_update_db: callback called when k8s connector updates database
59 K8sHelmBaseConnector
.__init
__(self
,
63 kubectl_command
=kubectl_command
,
64 helm_command
=helm_command
,
65 on_update_db
=on_update_db
,
66 vca_config
=vca_config
)
68 self
.log
.info("K8S Helm3 connector initialized")
80 namespace
: str = None,
83 """Install a helm chart
85 :param cluster_uuid str: The UUID of the cluster to install to
86 :param kdu_model str: The name or path of a bundle to install
87 :param kdu_instance: Kdu instance name
88 :param atomic bool: If set, waits until the model is active and resets
89 the cluster on failure.
90 :param timeout int: The time, in seconds, to wait for the install
92 :param params dict: Key-value pairs of instantiation parameters
93 :param kdu_name: Name of the KDU instance to be installed
94 :param namespace: K8s namespace to use for the KDU instance
96 :param kwargs: Additional parameters (None yet)
98 :return: True if successful
100 _
, cluster_id
= self
._get
_namespace
_cluster
_id
(cluster_uuid
)
101 self
.log
.debug("installing {} in cluster {}".format(kdu_model
, cluster_id
))
104 self
.fs
.sync(from_path
=cluster_id
)
107 paths
, env
= self
._init
_paths
_env
(
108 cluster_name
=cluster_id
, create_if_not_exist
=True
111 # for helm3 if namespace does not exist must create it
112 if namespace
and namespace
!= "kube-system":
113 namespaces
= await self
._get
_namespaces
(cluster_id
)
114 if namespace
not in namespaces
:
115 await self
._create
_namespace
(cluster_id
, namespace
)
117 await self
._install
_impl
(
132 self
.fs
.reverse_sync(from_path
=cluster_id
)
134 self
.log
.debug("Returning kdu_instance {}".format(kdu_instance
))
137 async def inspect_kdu(self
, kdu_model
: str, repo_url
: str = None) -> str:
140 "inspect kdu_model {} from (optional) repo: {}".format(kdu_model
, repo_url
)
143 return await self
._exec
_inspect
_comand
(
144 inspect_command
="all", kdu_model
=kdu_model
, repo_url
=repo_url
148 ####################################################################################
149 ################################### P R I V A T E ##################################
150 ####################################################################################
153 def _init_paths_env(self
, cluster_name
: str, create_if_not_exist
: bool = True):
155 Creates and returns base cluster and kube dirs and returns them.
156 Also created helm3 dirs according to new directory specification, paths are
157 returned and also environment variables that must be provided to execute commands
159 Helm 3 directory specification uses XDG categories for variable support:
160 - Cache: $XDG_CACHE_HOME, for example, ${HOME}/.cache/helm/
161 - Configuration: $XDG_CONFIG_HOME, for example, ${HOME}/.config/helm/
162 - Data: $XDG_DATA_HOME, for example ${HOME}/.local/share/helm
164 The variables assigned for this paths are:
165 (In the documentation the variables names are $HELM_PATH_CACHE, $HELM_PATH_CONFIG,
166 $HELM_PATH_DATA but looking and helm env the variable names are different)
167 - Cache: $HELM_CACHE_HOME
168 - Config: $HELM_CONFIG_HOME
169 - Data: $HELM_DATA_HOME
170 - helm kubeconfig: $KUBECONFIG
172 :param cluster_name: cluster_name
173 :return: Dictionary with config_paths and dictionary with helm environment variables
177 if base
.endswith("/") or base
.endswith("\\"):
180 # base dir for cluster
181 cluster_dir
= base
+ "/" + cluster_name
184 kube_dir
= cluster_dir
+ "/" + ".kube"
185 if create_if_not_exist
and not os
.path
.exists(kube_dir
):
186 self
.log
.debug("Creating dir {}".format(kube_dir
))
187 os
.makedirs(kube_dir
)
189 helm_path_cache
= cluster_dir
+ "/.cache/helm"
190 if create_if_not_exist
and not os
.path
.exists(helm_path_cache
):
191 self
.log
.debug("Creating dir {}".format(helm_path_cache
))
192 os
.makedirs(helm_path_cache
)
194 helm_path_config
= cluster_dir
+ "/.config/helm"
195 if create_if_not_exist
and not os
.path
.exists(helm_path_config
):
196 self
.log
.debug("Creating dir {}".format(helm_path_config
))
197 os
.makedirs(helm_path_config
)
199 helm_path_data
= cluster_dir
+ "/.local/share/helm"
200 if create_if_not_exist
and not os
.path
.exists(helm_path_data
):
201 self
.log
.debug("Creating dir {}".format(helm_path_data
))
202 os
.makedirs(helm_path_data
)
204 config_filename
= kube_dir
+ "/config"
206 # 2 - Prepare dictionary with paths
208 "kube_dir": kube_dir
,
209 "kube_config": config_filename
,
210 "cluster_dir": cluster_dir
213 # 3 - Prepare environment variables
215 "HELM_CACHE_HOME": helm_path_cache
,
216 "HELM_CONFIG_HOME": helm_path_config
,
217 "HELM_DATA_HOME": helm_path_data
,
218 "KUBECONFIG": config_filename
221 for file_name
, file in paths
.items():
222 if "dir" in file_name
and not os
.path
.exists(file):
223 err_msg
= "{} dir does not exist".format(file)
224 self
.log
.error(err_msg
)
225 raise K8sException(err_msg
)
229 async def _get_namespaces(self
,
232 self
.log
.debug("get namespaces cluster_id {}".format(cluster_id
))
235 paths
, env
= self
._init
_paths
_env
(
236 cluster_name
=cluster_id
, create_if_not_exist
=True
239 command
= "{} --kubeconfig={} get namespaces -o=yaml".format(
240 self
.kubectl_command
, paths
["kube_config"]
242 output
, _rc
= await self
._local
_async
_exec
(
243 command
=command
, raise_exception_on_error
=True, env
=env
246 data
= yaml
.load(output
, Loader
=yaml
.SafeLoader
)
247 namespaces
= [item
["metadata"]["name"] for item
in data
["items"]]
248 self
.log
.debug(f
"namespaces {namespaces}")
252 async def _create_namespace(self
,
256 self
.log
.debug(f
"create namespace: {cluster_id} for cluster_id: {namespace}")
259 paths
, env
= self
._init
_paths
_env
(
260 cluster_name
=cluster_id
, create_if_not_exist
=True
263 command
= "{} --kubeconfig={} create namespace {}".format(
264 self
.kubectl_command
, paths
["kube_config"], namespace
266 _
, _rc
= await self
._local
_async
_exec
(
267 command
=command
, raise_exception_on_error
=True, env
=env
269 self
.log
.debug(f
"namespace {namespace} created")
273 async def _get_services(self
, cluster_id
: str, kdu_instance
: str, namespace
: str):
276 paths
, env
= self
._init
_paths
_env
(
277 cluster_name
=cluster_id
, create_if_not_exist
=True
280 command1
= "{} get manifest {} --namespace={}".format(
281 self
._helm
_command
, kdu_instance
, namespace
283 command2
= "{} get --namespace={} -f -".format(
284 self
.kubectl_command
, namespace
286 output
, _rc
= await self
._local
_async
_exec
_pipe
(
287 command1
, command2
, env
=env
, raise_exception_on_error
=True
289 services
= self
._parse
_services
(output
)
293 async def _cluster_init(self
, cluster_id
, namespace
, paths
, env
):
295 Implements the helm version dependent cluster initialization:
296 For helm3 it creates the namespace if it is not created
298 if namespace
!= "kube-system":
299 namespaces
= await self
._get
_namespaces
(cluster_id
)
300 if namespace
not in namespaces
:
301 await self
._create
_namespace
(cluster_id
, namespace
)
303 # If default repo is not included add
304 cluster_uuid
= "{}:{}".format(namespace
, cluster_id
)
305 repo_list
= await self
.repo_list(cluster_uuid
)
306 for repo
in repo_list
:
307 self
.log
.debug("repo")
308 if repo
["name"] == "stable":
309 self
.log
.debug("Default repo already present")
312 await self
.repo_add(cluster_uuid
,
314 self
._stable
_repo
_url
)
316 # Returns False as no software needs to be uninstalled
319 async def _uninstall_sw(self
, cluster_id
: str, namespace
: str):
320 # nothing to do to uninstall sw
323 async def _instances_list(self
, cluster_id
: str):
326 paths
, env
= self
._init
_paths
_env
(
327 cluster_name
=cluster_id
, create_if_not_exist
=True
330 command
= "{} list --all-namespaces --output yaml".format(
333 output
, _rc
= await self
._local
_async
_exec
(
334 command
=command
, raise_exception_on_error
=True, env
=env
337 if output
and len(output
) > 0:
338 self
.log
.debug("instances list output: {}".format(output
))
339 return yaml
.load(output
, Loader
=yaml
.SafeLoader
)
343 def _get_inspect_command(self
, inspect_command
: str, kdu_model
: str, repo_str
: str,
345 inspect_command
= "{} show {} {}{} {}".format(
346 self
._helm
_command
, inspect_command
, kdu_model
, repo_str
, version
348 return inspect_command
350 async def _status_kdu(
354 namespace
: str = None,
355 show_error_log
: bool = False,
356 return_text
: bool = False,
359 self
.log
.debug("status of kdu_instance: {}, namespace: {} ".format(kdu_instance
, namespace
))
362 namespace
= "kube-system"
365 paths
, env
= self
._init
_paths
_env
(
366 cluster_name
=cluster_id
, create_if_not_exist
=True
368 command
= "{} status {} --namespace={} --output yaml".format(
369 self
._helm
_command
, kdu_instance
, namespace
372 output
, rc
= await self
._local
_async
_exec
(
374 raise_exception_on_error
=True,
375 show_error_log
=show_error_log
,
385 data
= yaml
.load(output
, Loader
=yaml
.SafeLoader
)
387 # remove field 'notes' and manifest
389 del data
.get("info")["notes"]
394 # unable to parse 'resources' as currently it is not included in helm3
397 def _get_install_command(self
, kdu_model
: str, kdu_instance
: str, namespace
: str,
398 params_str
: str, version
: str, atomic
: bool, timeout
: float) -> str:
402 timeout_str
= "--timeout {}s".format(timeout
)
407 atomic_str
= "--atomic"
411 namespace_str
= "--namespace {}".format(namespace
)
416 version_str
= "--version {}".format(version
)
419 "{helm} install {name} {atomic} --output yaml "
420 "{params} {timeout} {ns} {model} {ver}".format(
421 helm
=self
._helm
_command
,
433 def _get_upgrade_command(self
, kdu_model
: str, kdu_instance
: str, namespace
: str,
434 params_str
: str, version
: str, atomic
: bool, timeout
: float) -> str:
438 timeout_str
= "--timeout {}s".format(timeout
)
443 atomic_str
= "--atomic"
448 version_str
= "--version {}".format(version
)
453 namespace_str
= "--namespace {}".format(namespace
)
456 "{helm} upgrade {name} {model} {namespace} {atomic} --output yaml {params} "
457 "{timeout} {ver}".format(
458 helm
=self
._helm
_command
,
460 namespace
=namespace_str
,
470 def _get_rollback_command(self
, kdu_instance
: str, namespace
: str, revision
: float) -> str:
471 return "{} rollback {} {} --namespace={} --wait".format(
472 self
._helm
_command
, kdu_instance
, revision
, namespace
475 def _get_uninstall_command(self
, kdu_instance
: str, namespace
: str) -> str:
477 return "{} uninstall {} --namespace={}".format(
478 self
._helm
_command
, kdu_instance
, namespace
)
480 def _get_helm_chart_repos_ids(self
, cluster_uuid
) -> list:
482 cluster_filter
= {"_admin.helm-chart-v3.id": cluster_uuid
}
483 cluster
= self
.db
.get_one("k8sclusters", cluster_filter
)
485 repo_ids
= cluster
.get("_admin").get("helm_chart_repos") or []
489 "k8cluster with helm-id : {} not found".format(cluster_uuid
)