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",
47 Initializes helm connector for helm v3
49 :param fs: file system for kubernetes and helm configuration
50 :param db: database object to write current operation status
51 :param kubectl_command: path to kubectl executable
52 :param helm_command: path to helm executable
54 :param on_update_db: callback called when k8s connector updates database
58 K8sHelmBaseConnector
.__init
__(self
,
62 kubectl_command
=kubectl_command
,
63 helm_command
=helm_command
,
64 on_update_db
=on_update_db
)
66 self
.log
.info("K8S Helm3 connector initialized")
68 async def inspect_kdu(self
, kdu_model
: str, repo_url
: str = None) -> str:
71 "inspect kdu_model {} from (optional) repo: {}".format(kdu_model
, repo_url
)
74 return await self
._exec
_inspect
_comand
(
75 inspect_command
="all", kdu_model
=kdu_model
, repo_url
=repo_url
79 ####################################################################################
80 ################################### P R I V A T E ##################################
81 ####################################################################################
84 def _init_paths_env(self
, cluster_name
: str, create_if_not_exist
: bool = True):
86 Creates and returns base cluster and kube dirs and returns them.
87 Also created helm3 dirs according to new directory specification, paths are
88 returned and also environment variables that must be provided to execute commands
90 Helm 3 directory specification uses XDG categories for variable support:
91 - Cache: $XDG_CACHE_HOME, for example, ${HOME}/.cache/helm/
92 - Configuration: $XDG_CONFIG_HOME, for example, ${HOME}/.config/helm/
93 - Data: $XDG_DATA_HOME, for example ${HOME}/.local/share/helm
95 The variables assigned for this paths are:
96 (In the documentation the variables names are $HELM_PATH_CACHE, $HELM_PATH_CONFIG,
97 $HELM_PATH_DATA but looking and helm env the variable names are different)
98 - Cache: $HELM_CACHE_HOME
99 - Config: $HELM_CONFIG_HOME
100 - Data: $HELM_DATA_HOME
101 - helm kubeconfig: $KUBECONFIG
103 :param cluster_name: cluster_name
104 :return: Dictionary with config_paths and dictionary with helm environment variables
108 if base
.endswith("/") or base
.endswith("\\"):
111 # base dir for cluster
112 cluster_dir
= base
+ "/" + cluster_name
115 kube_dir
= cluster_dir
+ "/" + ".kube"
116 if create_if_not_exist
and not os
.path
.exists(kube_dir
):
117 self
.log
.debug("Creating dir {}".format(kube_dir
))
118 os
.makedirs(kube_dir
)
120 helm_path_cache
= cluster_dir
+ "/.cache/helm"
121 if create_if_not_exist
and not os
.path
.exists(helm_path_cache
):
122 self
.log
.debug("Creating dir {}".format(helm_path_cache
))
123 os
.makedirs(helm_path_cache
)
125 helm_path_config
= cluster_dir
+ "/.config/helm"
126 if create_if_not_exist
and not os
.path
.exists(helm_path_config
):
127 self
.log
.debug("Creating dir {}".format(helm_path_config
))
128 os
.makedirs(helm_path_config
)
130 helm_path_data
= cluster_dir
+ "/.local/share/helm"
131 if create_if_not_exist
and not os
.path
.exists(helm_path_data
):
132 self
.log
.debug("Creating dir {}".format(helm_path_data
))
133 os
.makedirs(helm_path_data
)
135 config_filename
= kube_dir
+ "/config"
137 # 2 - Prepare dictionary with paths
139 "kube_dir": kube_dir
,
140 "kube_config": config_filename
,
141 "cluster_dir": cluster_dir
144 # 3 - Prepare environment variables
146 "HELM_CACHE_HOME": helm_path_cache
,
147 "HELM_CONFIG_HOME": helm_path_config
,
148 "HELM_DATA_HOME": helm_path_data
,
149 "KUBECONFIG": config_filename
152 for file_name
, file in paths
.items():
153 if "dir" in file_name
and not os
.path
.exists(file):
154 err_msg
= "{} dir does not exist".format(file)
155 self
.log
.error(err_msg
)
156 raise K8sException(err_msg
)
160 async def _get_namespaces(self
,
163 self
.log
.debug("get namespaces cluster_id {}".format(cluster_id
))
166 paths
, env
= self
._init
_paths
_env
(
167 cluster_name
=cluster_id
, create_if_not_exist
=True
170 command
= "{} --kubeconfig={} get namespaces -o=yaml".format(
171 self
.kubectl_command
, paths
["kube_config"]
173 output
, _rc
= await self
._local
_async
_exec
(
174 command
=command
, raise_exception_on_error
=True, env
=env
177 data
= yaml
.load(output
, Loader
=yaml
.SafeLoader
)
178 namespaces
= [item
["metadata"]["name"] for item
in data
["items"]]
179 self
.log
.debug(f
"namespaces {namespaces}")
183 async def _create_namespace(self
,
187 self
.log
.debug(f
"create namespace: {cluster_id} for cluster_id: {namespace}")
190 paths
, env
= self
._init
_paths
_env
(
191 cluster_name
=cluster_id
, create_if_not_exist
=True
194 command
= "{} --kubeconfig={} create namespace {}".format(
195 self
.kubectl_command
, paths
["kube_config"], namespace
197 _
, _rc
= await self
._local
_async
_exec
(
198 command
=command
, raise_exception_on_error
=True, env
=env
200 self
.log
.debug(f
"namespace {namespace} created")
204 async def _get_services(self
, cluster_id
: str, kdu_instance
: str, namespace
: str):
207 paths
, env
= self
._init
_paths
_env
(
208 cluster_name
=cluster_id
, create_if_not_exist
=True
211 command1
= "{} get manifest {} --namespace={}".format(
212 self
._helm
_command
, kdu_instance
, namespace
214 command2
= "{} get --namespace={} -f -".format(
215 self
.kubectl_command
, namespace
217 output
, _rc
= await self
._local
_async
_exec
_pipe
(
218 command1
, command2
, env
=env
, raise_exception_on_error
=True
220 services
= self
._parse
_services
(output
)
224 async def _cluster_init(self
, cluster_id
, namespace
, paths
, env
):
226 Implements the helm version dependent cluster initialization:
227 For helm3 it creates the namespace if it is not created
229 if namespace
!= "kube-system":
230 namespaces
= await self
._get
_namespaces
(cluster_id
)
231 if namespace
not in namespaces
:
232 await self
._create
_namespace
(cluster_id
, namespace
)
234 # If default repo is not included add
235 cluster_uuid
= "{}:{}".format(namespace
, cluster_id
)
236 repo_list
= await self
.repo_list(cluster_uuid
)
237 for repo
in repo_list
:
238 self
.log
.debug("repo")
239 if repo
["name"] == "stable":
240 self
.log
.debug("Default repo already present")
243 await self
.repo_add(cluster_uuid
,
245 "https://kubernetes-charts.storage.googleapis.com/")
247 # Returns False as no software needs to be uninstalled
250 async def _uninstall_sw(self
, cluster_id
: str, namespace
: str):
251 # nothing to do to uninstall sw
254 async def _instances_list(self
, cluster_id
: str):
257 paths
, env
= self
._init
_paths
_env
(
258 cluster_name
=cluster_id
, create_if_not_exist
=True
261 command
= "{} list --all-namespaces --output yaml".format(
264 output
, _rc
= await self
._local
_async
_exec
(
265 command
=command
, raise_exception_on_error
=True, env
=env
268 if output
and len(output
) > 0:
269 self
.log
.debug("instances list output: {}".format(output
))
270 return yaml
.load(output
, Loader
=yaml
.SafeLoader
)
274 def _get_inspect_command(self
, inspect_command
: str, kdu_model
: str, repo_str
: str,
276 inspect_command
= "{} show {} {}{} {}".format(
277 self
._helm
_command
, inspect_command
, kdu_model
, repo_str
, version
279 return inspect_command
281 async def _status_kdu(
285 namespace
: str = None,
286 show_error_log
: bool = False,
287 return_text
: bool = False,
290 self
.log
.debug("status of kdu_instance: {}, namespace: {} ".format(kdu_instance
, namespace
))
293 namespace
= "kube-system"
296 paths
, env
= self
._init
_paths
_env
(
297 cluster_name
=cluster_id
, create_if_not_exist
=True
299 command
= "{} status {} --namespace={} --output yaml".format(
300 self
._helm
_command
, kdu_instance
, namespace
303 output
, rc
= await self
._local
_async
_exec
(
305 raise_exception_on_error
=True,
306 show_error_log
=show_error_log
,
316 data
= yaml
.load(output
, Loader
=yaml
.SafeLoader
)
318 # remove field 'notes' and manifest
320 del data
.get("info")["notes"]
325 # unable to parse 'resources' as currently it is not included in helm3
328 def _get_install_command(self
, kdu_model
: str, kdu_instance
: str, namespace
: str,
329 params_str
: str, version
: str, atomic
: bool, timeout
: float) -> str:
333 timeout_str
= "--timeout {}s".format(timeout
)
338 atomic_str
= "--atomic"
342 namespace_str
= "--namespace {}".format(namespace
)
347 version_str
= "--version {}".format(version
)
350 "{helm} install {name} {atomic} --output yaml "
351 "{params} {timeout} {ns} {model} {ver}".format(
352 helm
=self
._helm
_command
,
364 def _get_upgrade_command(self
, kdu_model
: str, kdu_instance
: str, namespace
: str,
365 params_str
: str, version
: str, atomic
: bool, timeout
: float) -> str:
369 timeout_str
= "--timeout {}s".format(timeout
)
374 atomic_str
= "--atomic"
379 version_str
= "--version {}".format(version
)
384 namespace_str
= "--namespace {}".format(namespace
)
387 "{helm} upgrade {name} {model} {namespace} {atomic} --output yaml {params} "
388 "{timeout} {ver}".format(
389 helm
=self
._helm
_command
,
391 namespace
=namespace_str
,
401 def _get_rollback_command(self
, kdu_instance
: str, namespace
: str, revision
: float) -> str:
402 return "{} rollback {} {} --namespace={} --wait".format(
403 self
._helm
_command
, kdu_instance
, revision
, namespace
406 def _get_uninstall_command(self
, kdu_instance
: str, namespace
: str) -> str:
408 return "{} uninstall {} --namespace={}".format(
409 self
._helm
_command
, kdu_instance
, namespace
)