blob: 6bbc0fa79b0b69eda28060a981441aafcfb440ae [file] [log] [blame]
quilesj26c78a42019-10-28 18:10:42 +01001##
2# Copyright 2019 Telefonica Investigacion y Desarrollo, S.A.U.
3# This file is part of OSM
4# All Rights Reserved.
5#
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
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
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
15# implied.
16# See the License for the specific language governing permissions and
17# limitations under the License.
18#
19# For those usages not covered by the Apache License, Version 2.0 please
20# contact with: nfvlabs@tid.es
21##
quilesj26c78a42019-10-28 18:10:42 +010022import asyncio
beierlmf52cb7c2020-04-21 16:36:35 -040023import os
beierlmf52cb7c2020-04-21 16:36:35 -040024import yaml
quilesj26c78a42019-10-28 18:10:42 +010025
lloretgalleg1c83f2e2020-10-22 09:12:35 +000026from n2vc.k8s_helm_base_conn import K8sHelmBaseConnector
27from n2vc.exceptions import K8sException
quilesj26c78a42019-10-28 18:10:42 +010028
lloretgalleg1c83f2e2020-10-22 09:12:35 +000029
30class K8sHelmConnector(K8sHelmBaseConnector):
quilesj26c78a42019-10-28 18:10:42 +010031
32 """
beierlmf52cb7c2020-04-21 16:36:35 -040033 ####################################################################################
34 ################################### P U B L I C ####################################
35 ####################################################################################
quilesj26c78a42019-10-28 18:10:42 +010036 """
37
38 def __init__(
beierlmf52cb7c2020-04-21 16:36:35 -040039 self,
40 fs: object,
41 db: object,
42 kubectl_command: str = "/usr/bin/kubectl",
43 helm_command: str = "/usr/bin/helm",
44 log: object = None,
45 on_update_db=None,
quilesj26c78a42019-10-28 18:10:42 +010046 ):
47 """
lloretgalleg1c83f2e2020-10-22 09:12:35 +000048 Initializes helm connector for helm v2
quilesj26c78a42019-10-28 18:10:42 +010049
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
54 :param log: logger
55 :param on_update_db: callback called when k8s connector updates database
56 """
57
58 # parent class
lloretgalleg1c83f2e2020-10-22 09:12:35 +000059 K8sHelmBaseConnector.__init__(
60 self,
61 db=db,
62 log=log,
63 fs=fs,
64 kubectl_command=kubectl_command,
65 helm_command=helm_command,
66 on_update_db=on_update_db,
67 )
quilesj26c78a42019-10-28 18:10:42 +010068
lloretgalleg1c83f2e2020-10-22 09:12:35 +000069 self.log.info("Initializing K8S Helm2 connector")
quilesj26c78a42019-10-28 18:10:42 +010070
quilesj1be06302019-11-29 11:17:11 +000071 # initialize helm client-only
beierlmf52cb7c2020-04-21 16:36:35 -040072 self.log.debug("Initializing helm client-only...")
David Garcia4395cfa2021-05-28 16:21:51 +020073 command = "{} init --client-only {} ".format(
74 self._helm_command,
75 "--stable-repo-url {}".format(self._stable_repo_url)
76 if self._stable_repo_url
77 else "--skip-repos",
garciadeblas82b591c2021-03-24 09:22:13 +010078 )
quilesj1be06302019-11-29 11:17:11 +000079 try:
beierlmf52cb7c2020-04-21 16:36:35 -040080 asyncio.ensure_future(
81 self._local_async_exec(command=command, raise_exception_on_error=False)
82 )
quilesj1be06302019-11-29 11:17:11 +000083 # loop = asyncio.get_event_loop()
beierlmf52cb7c2020-04-21 16:36:35 -040084 # loop.run_until_complete(self._local_async_exec(command=command,
85 # raise_exception_on_error=False))
quilesj1be06302019-11-29 11:17:11 +000086 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -040087 self.warning(
88 msg="helm init failed (it was already initialized): {}".format(e)
89 )
quilesj1be06302019-11-29 11:17:11 +000090
lloretgalleg1c83f2e2020-10-22 09:12:35 +000091 self.log.info("K8S Helm2 connector initialized")
quilesj26c78a42019-10-28 18:10:42 +010092
lloretgalleg095392b2020-11-20 11:28:08 +000093 async def install(
garciadeblas82b591c2021-03-24 09:22:13 +010094 self,
95 cluster_uuid: str,
96 kdu_model: str,
97 kdu_instance: str,
98 atomic: bool = True,
99 timeout: float = 300,
100 params: dict = None,
101 db_dict: dict = None,
102 kdu_name: str = None,
103 namespace: str = None,
104 **kwargs,
lloretgalleg095392b2020-11-20 11:28:08 +0000105 ):
David Garciaeb8943a2021-04-12 12:07:37 +0200106 """
107 Deploys of a new KDU instance. It would implicitly rely on the `install` call
108 to deploy the Chart/Bundle properly parametrized (in practice, this call would
109 happen before any _initial-config-primitive_of the VNF is called).
110
111 :param cluster_uuid: UUID of a K8s cluster known by OSM
112 :param kdu_model: chart/ reference (string), which can be either
113 of these options:
114 - a name of chart available via the repos known by OSM
115 - a path to a packaged chart
116 - a path to an unpacked chart directory or a URL
117 :param kdu_instance: Kdu instance name
118 :param atomic: If set, installation process purges chart/bundle on fail, also
119 will wait until all the K8s objects are active
120 :param timeout: Time in seconds to wait for the install of the chart/bundle
121 (defaults to Helm default timeout: 300s)
122 :param params: dictionary of key-value pairs for instantiation parameters
123 (overriding default values)
124 :param dict db_dict: where to write into database when the status changes.
125 It contains a dict with {collection: <str>, filter: {},
126 path: <str>},
127 e.g. {collection: "nsrs", filter:
128 {_id: <nsd-id>, path: "_admin.deployed.K8S.3"}
129 :param kdu_name: Name of the KDU instance to be installed
130 :param namespace: K8s namespace to use for the KDU instance
131 :param kwargs: Additional parameters (None yet)
132 :return: True if successful
133 """
lloretgalleg095392b2020-11-20 11:28:08 +0000134 _, cluster_id = self._get_namespace_cluster_id(cluster_uuid)
135 self.log.debug("installing {} in cluster {}".format(kdu_model, cluster_id))
136
137 # sync local dir
138 self.fs.sync(from_path=cluster_id)
139
140 # init env, paths
141 paths, env = self._init_paths_env(
142 cluster_name=cluster_id, create_if_not_exist=True
143 )
144
David Garciac4da25c2021-02-23 11:47:29 +0100145 await self._install_impl(
146 cluster_id,
147 kdu_model,
148 paths,
149 env,
150 kdu_instance,
151 atomic=atomic,
152 timeout=timeout,
153 params=params,
154 db_dict=db_dict,
155 kdu_name=kdu_name,
156 namespace=namespace,
157 )
lloretgalleg095392b2020-11-20 11:28:08 +0000158
159 # sync fs
160 self.fs.reverse_sync(from_path=cluster_id)
161
162 self.log.debug("Returning kdu_instance {}".format(kdu_instance))
David Garciac4da25c2021-02-23 11:47:29 +0100163 return True
lloretgalleg095392b2020-11-20 11:28:08 +0000164
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000165 async def inspect_kdu(self, kdu_model: str, repo_url: str = None) -> str:
166
167 self.log.debug(
168 "inspect kdu_model {} from (optional) repo: {}".format(kdu_model, repo_url)
169 )
170
171 return await self._exec_inspect_comand(
172 inspect_command="", kdu_model=kdu_model, repo_url=repo_url
173 )
174
175 """
176 ####################################################################################
177 ################################### P R I V A T E ##################################
178 ####################################################################################
179 """
180
181 def _init_paths_env(self, cluster_name: str, create_if_not_exist: bool = True):
tiernoa5728bf2020-06-25 15:48:52 +0000182 """
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000183 Creates and returns base cluster and kube dirs and returns them.
184 Also created helm3 dirs according to new directory specification, paths are
185 returned and also environment variables that must be provided to execute commands
186
187 Helm 2 directory specification uses helm_home dir:
188
189 The variables assigned for this paths are:
190 - Helm hone: $HELM_HOME
191 - helm kubeconfig: $KUBECONFIG
192
193 :param cluster_name: cluster_name
194 :return: Dictionary with config_paths and dictionary with helm environment variables
tiernoa5728bf2020-06-25 15:48:52 +0000195 """
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000196 base = self.fs.path
197 if base.endswith("/") or base.endswith("\\"):
198 base = base[:-1]
tiernoa5728bf2020-06-25 15:48:52 +0000199
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000200 # base dir for cluster
201 cluster_dir = base + "/" + cluster_name
garciadeblas54771fa2019-12-13 13:39:03 +0100202
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000203 # kube dir
204 kube_dir = cluster_dir + "/" + ".kube"
205 if create_if_not_exist and not os.path.exists(kube_dir):
206 self.log.debug("Creating dir {}".format(kube_dir))
207 os.makedirs(kube_dir)
quilesj26c78a42019-10-28 18:10:42 +0100208
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000209 # helm home dir
210 helm_dir = cluster_dir + "/" + ".helm"
211 if create_if_not_exist and not os.path.exists(helm_dir):
212 self.log.debug("Creating dir {}".format(helm_dir))
213 os.makedirs(helm_dir)
quilesj26c78a42019-10-28 18:10:42 +0100214
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000215 config_filename = kube_dir + "/config"
quilesj26c78a42019-10-28 18:10:42 +0100216
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000217 # 2 - Prepare dictionary with paths
218 paths = {
219 "kube_dir": kube_dir,
220 "kube_config": config_filename,
221 "cluster_dir": cluster_dir,
222 "helm_dir": helm_dir,
223 }
224
225 for file_name, file in paths.items():
226 if "dir" in file_name and not os.path.exists(file):
227 err_msg = "{} dir does not exist".format(file)
228 self.log.error(err_msg)
229 raise K8sException(err_msg)
230
231 # 3 - Prepare environment variables
232 env = {"HELM_HOME": helm_dir, "KUBECONFIG": config_filename}
233
234 return paths, env
235
236 async def _get_services(self, cluster_id, kdu_instance, namespace):
237
238 # init config, env
239 paths, env = self._init_paths_env(
tiernoa5728bf2020-06-25 15:48:52 +0000240 cluster_name=cluster_id, create_if_not_exist=True
beierlmf52cb7c2020-04-21 16:36:35 -0400241 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000242
243 command1 = "{} get manifest {} ".format(self._helm_command, kdu_instance)
244 command2 = "{} get --namespace={} -f -".format(self.kubectl_command, namespace)
245 output, _rc = await self._local_async_exec_pipe(
246 command1, command2, env=env, raise_exception_on_error=True
247 )
248 services = self._parse_services(output)
249
250 return services
251
garciadeblas82b591c2021-03-24 09:22:13 +0100252 async def _cluster_init(
253 self, cluster_id: str, namespace: str, paths: dict, env: dict
254 ):
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000255 """
256 Implements the helm version dependent cluster initialization:
257 For helm2 it initialized tiller environment if needed
258 """
quilesj26c78a42019-10-28 18:10:42 +0100259
260 # check if tiller pod is up in cluster
beierlmf52cb7c2020-04-21 16:36:35 -0400261 command = "{} --kubeconfig={} --namespace={} get deployments".format(
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000262 self.kubectl_command, paths["kube_config"], namespace
beierlmf52cb7c2020-04-21 16:36:35 -0400263 )
264 output, _rc = await self._local_async_exec(
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000265 command=command, raise_exception_on_error=True, env=env
beierlmf52cb7c2020-04-21 16:36:35 -0400266 )
quilesj26c78a42019-10-28 18:10:42 +0100267
tierno119f7232020-04-21 13:22:26 +0000268 output_table = self._output_to_table(output=output)
quilesj26c78a42019-10-28 18:10:42 +0100269
270 # find 'tiller' pod in all pods
271 already_initialized = False
272 try:
273 for row in output_table:
beierlmf52cb7c2020-04-21 16:36:35 -0400274 if row[0].startswith("tiller-deploy"):
quilesj26c78a42019-10-28 18:10:42 +0100275 already_initialized = True
276 break
beierlmf52cb7c2020-04-21 16:36:35 -0400277 except Exception:
quilesj26c78a42019-10-28 18:10:42 +0100278 pass
279
280 # helm init
281 n2vc_installed_sw = False
282 if not already_initialized:
beierlmf52cb7c2020-04-21 16:36:35 -0400283 self.log.info(
tiernoa5728bf2020-06-25 15:48:52 +0000284 "Initializing helm in client and server: {}".format(cluster_id)
beierlmf52cb7c2020-04-21 16:36:35 -0400285 )
tiernoa5728bf2020-06-25 15:48:52 +0000286 command = "{} --kubeconfig={} --namespace kube-system create serviceaccount {}".format(
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000287 self.kubectl_command, paths["kube_config"], self.service_account
288 )
289 _, _rc = await self._local_async_exec(
290 command=command, raise_exception_on_error=False, env=env
291 )
tiernoa5728bf2020-06-25 15:48:52 +0000292
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000293 command = (
294 "{} --kubeconfig={} create clusterrolebinding osm-tiller-cluster-rule "
295 "--clusterrole=cluster-admin --serviceaccount=kube-system:{}"
296 ).format(self.kubectl_command, paths["kube_config"], self.service_account)
297 _, _rc = await self._local_async_exec(
298 command=command, raise_exception_on_error=False, env=env
299 )
tiernoa5728bf2020-06-25 15:48:52 +0000300
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000301 command = (
302 "{} --kubeconfig={} --tiller-namespace={} --home={} --service-account {} "
David Garcia4395cfa2021-05-28 16:21:51 +0200303 " {} init"
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000304 ).format(
305 self._helm_command,
306 paths["kube_config"],
307 namespace,
308 paths["helm_dir"],
309 self.service_account,
David Garcia4395cfa2021-05-28 16:21:51 +0200310 "--stable-repo-url {}".format(self._stable_repo_url)
311 if self._stable_repo_url
312 else "--skip-repos",
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000313 )
314 _, _rc = await self._local_async_exec(
315 command=command, raise_exception_on_error=True, env=env
316 )
quilesj26c78a42019-10-28 18:10:42 +0100317 n2vc_installed_sw = True
318 else:
319 # check client helm installation
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000320 check_file = paths["helm_dir"] + "/repository/repositories.yaml"
321 if not self._check_file_exists(
322 filename=check_file, exception_if_not_exists=False
323 ):
tiernoa5728bf2020-06-25 15:48:52 +0000324 self.log.info("Initializing helm in client: {}".format(cluster_id))
beierlmf52cb7c2020-04-21 16:36:35 -0400325 command = (
326 "{} --kubeconfig={} --tiller-namespace={} "
David Garcia4395cfa2021-05-28 16:21:51 +0200327 "--home={} init --client-only {} "
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000328 ).format(
329 self._helm_command,
330 paths["kube_config"],
331 namespace,
332 paths["helm_dir"],
David Garcia4395cfa2021-05-28 16:21:51 +0200333 "--stable-repo-url {}".format(self._stable_repo_url)
334 if self._stable_repo_url
335 else "--skip-repos",
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000336 )
beierlmf52cb7c2020-04-21 16:36:35 -0400337 output, _rc = await self._local_async_exec(
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000338 command=command, raise_exception_on_error=True, env=env
beierlmf52cb7c2020-04-21 16:36:35 -0400339 )
quilesj26c78a42019-10-28 18:10:42 +0100340 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400341 self.log.info("Helm client already initialized")
quilesj26c78a42019-10-28 18:10:42 +0100342
lloretgalleg83e55892020-12-17 12:42:11 +0000343 # remove old stable repo and add new one
344 cluster_uuid = "{}:{}".format(namespace, cluster_id)
345 repo_list = await self.repo_list(cluster_uuid)
346 for repo in repo_list:
347 if repo["name"] == "stable" and repo["url"] != self._stable_repo_url:
348 self.log.debug("Add new stable repo url: {}")
garciadeblas82b591c2021-03-24 09:22:13 +0100349 await self.repo_remove(cluster_uuid, "stable")
David Garcia4395cfa2021-05-28 16:21:51 +0200350 if self._stable_repo_url:
351 await self.repo_add(cluster_uuid, "stable", self._stable_repo_url)
lloretgalleg83e55892020-12-17 12:42:11 +0000352 break
353
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000354 return n2vc_installed_sw
lloretgallege308c712020-09-02 09:40:38 +0000355
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000356 async def _uninstall_sw(self, cluster_id: str, namespace: str):
357 # uninstall Tiller if necessary
quilesj26c78a42019-10-28 18:10:42 +0100358
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000359 self.log.debug("Uninstalling tiller from cluster {}".format(cluster_id))
quilesj26c78a42019-10-28 18:10:42 +0100360
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000361 # init paths, env
362 paths, env = self._init_paths_env(
tiernoa5728bf2020-06-25 15:48:52 +0000363 cluster_name=cluster_id, create_if_not_exist=True
beierlmf52cb7c2020-04-21 16:36:35 -0400364 )
quilesj26c78a42019-10-28 18:10:42 +0100365
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000366 if not namespace:
367 # find namespace for tiller pod
368 command = "{} --kubeconfig={} get deployments --all-namespaces".format(
369 self.kubectl_command, paths["kube_config"]
370 )
371 output, _rc = await self._local_async_exec(
372 command=command, raise_exception_on_error=False, env=env
373 )
374 output_table = self._output_to_table(output=output)
375 namespace = None
376 for r in output_table:
377 try:
378 if "tiller-deploy" in r[1]:
379 namespace = r[0]
380 break
381 except Exception:
382 pass
quilesj26c78a42019-10-28 18:10:42 +0100383 else:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000384 msg = "Tiller deployment not found in cluster {}".format(cluster_id)
385 self.log.error(msg)
quilesj26c78a42019-10-28 18:10:42 +0100386
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000387 self.log.debug("namespace for tiller: {}".format(namespace))
quilesj26c78a42019-10-28 18:10:42 +0100388
tierno53555f62020-04-07 11:08:16 +0000389 if namespace:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000390 # uninstall tiller from cluster
391 self.log.debug("Uninstalling tiller from cluster {}".format(cluster_id))
392 command = "{} --kubeconfig={} --home={} reset".format(
393 self._helm_command, paths["kube_config"], paths["helm_dir"]
beierlmf52cb7c2020-04-21 16:36:35 -0400394 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000395 self.log.debug("resetting: {}".format(command))
396 output, _rc = await self._local_async_exec(
397 command=command, raise_exception_on_error=True, env=env
quilesj26c78a42019-10-28 18:10:42 +0100398 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000399 # Delete clusterrolebinding and serviceaccount.
400 # Ignore if errors for backward compatibility
401 command = (
402 "{} --kubeconfig={} delete clusterrolebinding.rbac.authorization.k8s."
403 "io/osm-tiller-cluster-rule"
404 ).format(self.kubectl_command, paths["kube_config"])
405 output, _rc = await self._local_async_exec(
406 command=command, raise_exception_on_error=False, env=env
quilesj26c78a42019-10-28 18:10:42 +0100407 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000408 command = "{} --kubeconfig={} --namespace kube-system delete serviceaccount/{}".format(
409 self.kubectl_command, paths["kube_config"], self.service_account
410 )
411 output, _rc = await self._local_async_exec(
412 command=command, raise_exception_on_error=False, env=env
413 )
quilesj26c78a42019-10-28 18:10:42 +0100414
415 else:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000416 self.log.debug("namespace not found")
quilesj26c78a42019-10-28 18:10:42 +0100417
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000418 async def _instances_list(self, cluster_id):
quilesj26c78a42019-10-28 18:10:42 +0100419
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000420 # init paths, env
421 paths, env = self._init_paths_env(
tiernoa5728bf2020-06-25 15:48:52 +0000422 cluster_name=cluster_id, create_if_not_exist=True
beierlmf52cb7c2020-04-21 16:36:35 -0400423 )
quilesj26c78a42019-10-28 18:10:42 +0100424
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000425 command = "{} list --output yaml".format(self._helm_command)
quilesj26c78a42019-10-28 18:10:42 +0100426
beierlmf52cb7c2020-04-21 16:36:35 -0400427 output, _rc = await self._local_async_exec(
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000428 command=command, raise_exception_on_error=True, env=env
beierlmf52cb7c2020-04-21 16:36:35 -0400429 )
quilesj26c78a42019-10-28 18:10:42 +0100430
431 if output and len(output) > 0:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000432 # parse yaml and update keys to lower case to unify with helm3
433 instances = yaml.load(output, Loader=yaml.SafeLoader).get("Releases")
434 new_instances = []
435 for instance in instances:
436 new_instance = dict((k.lower(), v) for k, v in instance.items())
437 new_instances.append(new_instance)
438 return new_instances
quilesj26c78a42019-10-28 18:10:42 +0100439 else:
440 return []
441
garciadeblas82b591c2021-03-24 09:22:13 +0100442 def _get_inspect_command(
443 self, show_command: str, kdu_model: str, repo_str: str, version: str
444 ):
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000445 inspect_command = "{} inspect {} {}{} {}".format(
446 self._helm_command, show_command, kdu_model, repo_str, version
beierlmf52cb7c2020-04-21 16:36:35 -0400447 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000448 return inspect_command
quilesj1be06302019-11-29 11:17:11 +0000449
quilesj26c78a42019-10-28 18:10:42 +0100450 async def _status_kdu(
beierlmf52cb7c2020-04-21 16:36:35 -0400451 self,
tiernoa5728bf2020-06-25 15:48:52 +0000452 cluster_id: str,
beierlmf52cb7c2020-04-21 16:36:35 -0400453 kdu_instance: str,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000454 namespace: str = None,
beierlmf52cb7c2020-04-21 16:36:35 -0400455 show_error_log: bool = False,
456 return_text: bool = False,
quilesj26c78a42019-10-28 18:10:42 +0100457 ):
458
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000459 self.log.debug(
460 "status of kdu_instance: {}, namespace: {} ".format(kdu_instance, namespace)
461 )
quilesj26c78a42019-10-28 18:10:42 +0100462
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000463 # init config, env
464 paths, env = self._init_paths_env(
tiernoa5728bf2020-06-25 15:48:52 +0000465 cluster_name=cluster_id, create_if_not_exist=True
beierlmf52cb7c2020-04-21 16:36:35 -0400466 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000467 command = "{} status {} --output yaml".format(self._helm_command, kdu_instance)
quilesj26c78a42019-10-28 18:10:42 +0100468 output, rc = await self._local_async_exec(
469 command=command,
470 raise_exception_on_error=True,
beierlmf52cb7c2020-04-21 16:36:35 -0400471 show_error_log=show_error_log,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000472 env=env,
quilesj26c78a42019-10-28 18:10:42 +0100473 )
474
quilesj1be06302019-11-29 11:17:11 +0000475 if return_text:
476 return str(output)
477
quilesj26c78a42019-10-28 18:10:42 +0100478 if rc != 0:
479 return None
480
481 data = yaml.load(output, Loader=yaml.SafeLoader)
482
483 # remove field 'notes'
484 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400485 del data.get("info").get("status")["notes"]
quilesj26c78a42019-10-28 18:10:42 +0100486 except KeyError:
487 pass
488
489 # parse field 'resources'
490 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400491 resources = str(data.get("info").get("status").get("resources"))
quilesj26c78a42019-10-28 18:10:42 +0100492 resource_table = self._output_to_table(resources)
beierlmf52cb7c2020-04-21 16:36:35 -0400493 data.get("info").get("status")["resources"] = resource_table
494 except Exception:
quilesj26c78a42019-10-28 18:10:42 +0100495 pass
496
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000497 # set description to lowercase (unify with helm3)
498 try:
499 data.get("info")["description"] = data.get("info").pop("Description")
500 except KeyError:
501 pass
502
quilesj26c78a42019-10-28 18:10:42 +0100503 return data
504
lloretgalleg095392b2020-11-20 11:28:08 +0000505 def _get_helm_chart_repos_ids(self, cluster_uuid) -> list:
506 repo_ids = []
507 cluster_filter = {"_admin.helm-chart.id": cluster_uuid}
508 cluster = self.db.get_one("k8sclusters", cluster_filter)
509 if cluster:
510 repo_ids = cluster.get("_admin").get("helm_chart_repos") or []
511 return repo_ids
512 else:
513 raise K8sException(
514 "k8cluster with helm-id : {} not found".format(cluster_uuid)
515 )
516
tiernoa5728bf2020-06-25 15:48:52 +0000517 async def _is_install_completed(self, cluster_id: str, kdu_instance: str) -> bool:
quilesj26c78a42019-10-28 18:10:42 +0100518
beierlmf52cb7c2020-04-21 16:36:35 -0400519 status = await self._status_kdu(
tiernoa5728bf2020-06-25 15:48:52 +0000520 cluster_id=cluster_id, kdu_instance=kdu_instance, return_text=False
beierlmf52cb7c2020-04-21 16:36:35 -0400521 )
quilesj26c78a42019-10-28 18:10:42 +0100522
523 # extract info.status.resources-> str
524 # format:
525 # ==> v1/Deployment
526 # NAME READY UP-TO-DATE AVAILABLE AGE
527 # halting-horse-mongodb 0/1 1 0 0s
528 # halting-petit-mongodb 1/1 1 0 0s
529 # blank line
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000530 resources = K8sHelmBaseConnector._get_deep(
531 status, ("info", "status", "resources")
532 )
quilesj26c78a42019-10-28 18:10:42 +0100533
534 # convert to table
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000535 resources = K8sHelmBaseConnector._output_to_table(resources)
quilesj26c78a42019-10-28 18:10:42 +0100536
537 num_lines = len(resources)
538 index = 0
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000539 ready = True
quilesj26c78a42019-10-28 18:10:42 +0100540 while index < num_lines:
541 try:
542 line1 = resources[index]
543 index += 1
544 # find '==>' in column 0
beierlmf52cb7c2020-04-21 16:36:35 -0400545 if line1[0] == "==>":
quilesj26c78a42019-10-28 18:10:42 +0100546 line2 = resources[index]
547 index += 1
548 # find READY in column 1
beierlmf52cb7c2020-04-21 16:36:35 -0400549 if line2[1] == "READY":
quilesj26c78a42019-10-28 18:10:42 +0100550 # read next lines
551 line3 = resources[index]
552 index += 1
553 while len(line3) > 1 and index < num_lines:
554 ready_value = line3[1]
beierlmf52cb7c2020-04-21 16:36:35 -0400555 parts = ready_value.split(sep="/")
quilesj26c78a42019-10-28 18:10:42 +0100556 current = int(parts[0])
557 total = int(parts[1])
558 if current < total:
beierlmf52cb7c2020-04-21 16:36:35 -0400559 self.log.debug("NOT READY:\n {}".format(line3))
quilesj26c78a42019-10-28 18:10:42 +0100560 ready = False
561 line3 = resources[index]
562 index += 1
563
beierlmf52cb7c2020-04-21 16:36:35 -0400564 except Exception:
quilesj26c78a42019-10-28 18:10:42 +0100565 pass
566
567 return ready
568
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000569 def _get_install_command(
570 self, kdu_model, kdu_instance, namespace, params_str, version, atomic, timeout
571 ) -> str:
lloretgallegd99f3f22020-06-29 14:18:30 +0000572
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000573 timeout_str = ""
574 if timeout:
575 timeout_str = "--timeout {}".format(timeout)
lloretgallegd99f3f22020-06-29 14:18:30 +0000576
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000577 # atomic
578 atomic_str = ""
579 if atomic:
580 atomic_str = "--atomic"
581 # namespace
582 namespace_str = ""
583 if namespace:
584 namespace_str = "--namespace {}".format(namespace)
lloretgallegd99f3f22020-06-29 14:18:30 +0000585
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000586 # version
587 version_str = ""
588 if version:
589 version_str = version_str = "--version {}".format(version)
lloretgallegd99f3f22020-06-29 14:18:30 +0000590
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000591 command = (
592 "{helm} install {atomic} --output yaml "
593 "{params} {timeout} --name={name} {ns} {model} {ver}".format(
594 helm=self._helm_command,
595 atomic=atomic_str,
596 params=params_str,
597 timeout=timeout_str,
598 name=kdu_instance,
599 ns=namespace_str,
600 model=kdu_model,
601 ver=version_str,
beierlmf52cb7c2020-04-21 16:36:35 -0400602 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000603 )
604 return command
quilesj26c78a42019-10-28 18:10:42 +0100605
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000606 def _get_upgrade_command(
607 self, kdu_model, kdu_instance, namespace, params_str, version, atomic, timeout
608 ) -> str:
quilesj26c78a42019-10-28 18:10:42 +0100609
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000610 timeout_str = ""
611 if timeout:
612 timeout_str = "--timeout {}".format(timeout)
quilesj26c78a42019-10-28 18:10:42 +0100613
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000614 # atomic
615 atomic_str = ""
616 if atomic:
617 atomic_str = "--atomic"
quilesj26c78a42019-10-28 18:10:42 +0100618
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000619 # version
620 version_str = ""
621 if version:
622 version_str = "--version {}".format(version)
quilesj26c78a42019-10-28 18:10:42 +0100623
garciadeblas82b591c2021-03-24 09:22:13 +0100624 command = "{helm} upgrade {atomic} --output yaml {params} {timeout} {name} {model} {ver}".format(
625 helm=self._helm_command,
626 atomic=atomic_str,
627 params=params_str,
628 timeout=timeout_str,
629 name=kdu_instance,
630 model=kdu_model,
631 ver=version_str,
632 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000633 return command
quilesj26c78a42019-10-28 18:10:42 +0100634
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000635 def _get_rollback_command(self, kdu_instance, namespace, revision) -> str:
636 return "{} rollback {} {} --wait".format(
637 self._helm_command, kdu_instance, revision
638 )
quilesj26c78a42019-10-28 18:10:42 +0100639
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000640 def _get_uninstall_command(self, kdu_instance: str, namespace: str) -> str:
641 return "{} delete --purge {}".format(self._helm_command, kdu_instance)