blob: c8c95ee466a73d1d8a7d2ce9e8e594ccc0219c97 [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
Pedro Escaleirad901a802022-04-05 17:32:13 +010023from typing import Union
beierlmf52cb7c2020-04-21 16:36:35 -040024import os
beierlmf52cb7c2020-04-21 16:36:35 -040025import yaml
quilesj26c78a42019-10-28 18:10:42 +010026
lloretgalleg1c83f2e2020-10-22 09:12:35 +000027from n2vc.k8s_helm_base_conn import K8sHelmBaseConnector
28from n2vc.exceptions import K8sException
quilesj26c78a42019-10-28 18:10:42 +010029
lloretgalleg1c83f2e2020-10-22 09:12:35 +000030
31class K8sHelmConnector(K8sHelmBaseConnector):
quilesj26c78a42019-10-28 18:10:42 +010032
33 """
beierlmf52cb7c2020-04-21 16:36:35 -040034 ####################################################################################
35 ################################### P U B L I C ####################################
36 ####################################################################################
quilesj26c78a42019-10-28 18:10:42 +010037 """
38
39 def __init__(
beierlmf52cb7c2020-04-21 16:36:35 -040040 self,
41 fs: object,
42 db: object,
43 kubectl_command: str = "/usr/bin/kubectl",
44 helm_command: str = "/usr/bin/helm",
45 log: object = None,
46 on_update_db=None,
quilesj26c78a42019-10-28 18:10:42 +010047 ):
48 """
lloretgalleg1c83f2e2020-10-22 09:12:35 +000049 Initializes helm connector for helm v2
quilesj26c78a42019-10-28 18:10:42 +010050
51 :param fs: file system for kubernetes and helm configuration
52 :param db: database object to write current operation status
53 :param kubectl_command: path to kubectl executable
54 :param helm_command: path to helm executable
55 :param log: logger
56 :param on_update_db: callback called when k8s connector updates database
57 """
58
59 # parent class
lloretgalleg1c83f2e2020-10-22 09:12:35 +000060 K8sHelmBaseConnector.__init__(
61 self,
62 db=db,
63 log=log,
64 fs=fs,
65 kubectl_command=kubectl_command,
66 helm_command=helm_command,
67 on_update_db=on_update_db,
68 )
quilesj26c78a42019-10-28 18:10:42 +010069
lloretgalleg1c83f2e2020-10-22 09:12:35 +000070 self.log.info("Initializing K8S Helm2 connector")
quilesj26c78a42019-10-28 18:10:42 +010071
quilesj1be06302019-11-29 11:17:11 +000072 # initialize helm client-only
beierlmf52cb7c2020-04-21 16:36:35 -040073 self.log.debug("Initializing helm client-only...")
David Garciadd322062021-05-28 16:21:51 +020074 command = "{} init --client-only {} ".format(
75 self._helm_command,
76 "--stable-repo-url {}".format(self._stable_repo_url)
77 if self._stable_repo_url
78 else "--skip-repos",
garciadeblas82b591c2021-03-24 09:22:13 +010079 )
quilesj1be06302019-11-29 11:17:11 +000080 try:
beierlmf52cb7c2020-04-21 16:36:35 -040081 asyncio.ensure_future(
82 self._local_async_exec(command=command, raise_exception_on_error=False)
83 )
quilesj1be06302019-11-29 11:17:11 +000084 # loop = asyncio.get_event_loop()
beierlmf52cb7c2020-04-21 16:36:35 -040085 # loop.run_until_complete(self._local_async_exec(command=command,
86 # raise_exception_on_error=False))
quilesj1be06302019-11-29 11:17:11 +000087 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -040088 self.warning(
89 msg="helm init failed (it was already initialized): {}".format(e)
90 )
quilesj1be06302019-11-29 11:17:11 +000091
lloretgalleg1c83f2e2020-10-22 09:12:35 +000092 self.log.info("K8S Helm2 connector initialized")
quilesj26c78a42019-10-28 18:10:42 +010093
lloretgalleg095392b2020-11-20 11:28:08 +000094 async def install(
garciadeblas82b591c2021-03-24 09:22:13 +010095 self,
96 cluster_uuid: str,
97 kdu_model: str,
98 kdu_instance: str,
99 atomic: bool = True,
100 timeout: float = 300,
101 params: dict = None,
102 db_dict: dict = None,
103 kdu_name: str = None,
104 namespace: str = None,
105 **kwargs,
lloretgalleg095392b2020-11-20 11:28:08 +0000106 ):
David Garciaeb8943a2021-04-12 12:07:37 +0200107 """
108 Deploys of a new KDU instance. It would implicitly rely on the `install` call
109 to deploy the Chart/Bundle properly parametrized (in practice, this call would
110 happen before any _initial-config-primitive_of the VNF is called).
111
112 :param cluster_uuid: UUID of a K8s cluster known by OSM
113 :param kdu_model: chart/ reference (string), which can be either
114 of these options:
115 - a name of chart available via the repos known by OSM
116 - a path to a packaged chart
117 - a path to an unpacked chart directory or a URL
118 :param kdu_instance: Kdu instance name
119 :param atomic: If set, installation process purges chart/bundle on fail, also
120 will wait until all the K8s objects are active
121 :param timeout: Time in seconds to wait for the install of the chart/bundle
122 (defaults to Helm default timeout: 300s)
123 :param params: dictionary of key-value pairs for instantiation parameters
124 (overriding default values)
125 :param dict db_dict: where to write into database when the status changes.
126 It contains a dict with {collection: <str>, filter: {},
127 path: <str>},
128 e.g. {collection: "nsrs", filter:
129 {_id: <nsd-id>, path: "_admin.deployed.K8S.3"}
130 :param kdu_name: Name of the KDU instance to be installed
131 :param namespace: K8s namespace to use for the KDU instance
132 :param kwargs: Additional parameters (None yet)
133 :return: True if successful
134 """
David Garcia2a10e432022-06-17 14:27:54 +0200135 _, cluster_id = self._get_namespace_cluster_id(cluster_uuid)
136 self.log.debug("installing {} in cluster {}".format(kdu_model, cluster_id))
lloretgalleg095392b2020-11-20 11:28:08 +0000137
138 # sync local dir
David Garcia2a10e432022-06-17 14:27:54 +0200139 self.fs.sync(from_path=cluster_id)
lloretgalleg095392b2020-11-20 11:28:08 +0000140
141 # init env, paths
142 paths, env = self._init_paths_env(
David Garcia2a10e432022-06-17 14:27:54 +0200143 cluster_name=cluster_id, create_if_not_exist=True
lloretgalleg095392b2020-11-20 11:28:08 +0000144 )
145
David Garciac4da25c2021-02-23 11:47:29 +0100146 await self._install_impl(
David Garcia2a10e432022-06-17 14:27:54 +0200147 cluster_id,
David Garciac4da25c2021-02-23 11:47:29 +0100148 kdu_model,
149 paths,
150 env,
151 kdu_instance,
152 atomic=atomic,
153 timeout=timeout,
154 params=params,
155 db_dict=db_dict,
156 kdu_name=kdu_name,
157 namespace=namespace,
158 )
lloretgalleg095392b2020-11-20 11:28:08 +0000159
160 # sync fs
David Garcia2a10e432022-06-17 14:27:54 +0200161 self.fs.reverse_sync(from_path=cluster_id)
lloretgalleg095392b2020-11-20 11:28:08 +0000162
163 self.log.debug("Returning kdu_instance {}".format(kdu_instance))
David Garciac4da25c2021-02-23 11:47:29 +0100164 return True
lloretgalleg095392b2020-11-20 11:28:08 +0000165
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000166 async def inspect_kdu(self, kdu_model: str, repo_url: str = None) -> str:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000167 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
David Garcia05bccf72022-02-02 11:35:20 +0100236 async def _get_services(self, cluster_id, kdu_instance, namespace, kubeconfig):
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000237 # init config, env
238 paths, env = self._init_paths_env(
tiernoa5728bf2020-06-25 15:48:52 +0000239 cluster_name=cluster_id, create_if_not_exist=True
beierlmf52cb7c2020-04-21 16:36:35 -0400240 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000241
David Garcia05bccf72022-02-02 11:35:20 +0100242 command1 = "env KUBECONFIG={} {} get manifest {} ".format(
243 kubeconfig, self._helm_command, kdu_instance
244 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000245 command2 = "{} get --namespace={} -f -".format(self.kubectl_command, namespace)
246 output, _rc = await self._local_async_exec_pipe(
247 command1, command2, env=env, raise_exception_on_error=True
248 )
249 services = self._parse_services(output)
250
251 return services
252
garciadeblas82b591c2021-03-24 09:22:13 +0100253 async def _cluster_init(
254 self, cluster_id: str, namespace: str, paths: dict, env: dict
255 ):
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000256 """
257 Implements the helm version dependent cluster initialization:
258 For helm2 it initialized tiller environment if needed
259 """
quilesj26c78a42019-10-28 18:10:42 +0100260
261 # check if tiller pod is up in cluster
beierlmf52cb7c2020-04-21 16:36:35 -0400262 command = "{} --kubeconfig={} --namespace={} get deployments".format(
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000263 self.kubectl_command, paths["kube_config"], namespace
beierlmf52cb7c2020-04-21 16:36:35 -0400264 )
265 output, _rc = await self._local_async_exec(
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000266 command=command, raise_exception_on_error=True, env=env
beierlmf52cb7c2020-04-21 16:36:35 -0400267 )
quilesj26c78a42019-10-28 18:10:42 +0100268
tierno119f7232020-04-21 13:22:26 +0000269 output_table = self._output_to_table(output=output)
quilesj26c78a42019-10-28 18:10:42 +0100270
271 # find 'tiller' pod in all pods
272 already_initialized = False
273 try:
274 for row in output_table:
beierlmf52cb7c2020-04-21 16:36:35 -0400275 if row[0].startswith("tiller-deploy"):
quilesj26c78a42019-10-28 18:10:42 +0100276 already_initialized = True
277 break
beierlmf52cb7c2020-04-21 16:36:35 -0400278 except Exception:
quilesj26c78a42019-10-28 18:10:42 +0100279 pass
280
281 # helm init
282 n2vc_installed_sw = False
283 if not already_initialized:
beierlmf52cb7c2020-04-21 16:36:35 -0400284 self.log.info(
tiernoa5728bf2020-06-25 15:48:52 +0000285 "Initializing helm in client and server: {}".format(cluster_id)
beierlmf52cb7c2020-04-21 16:36:35 -0400286 )
tiernoa5728bf2020-06-25 15:48:52 +0000287 command = "{} --kubeconfig={} --namespace kube-system create serviceaccount {}".format(
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000288 self.kubectl_command, paths["kube_config"], self.service_account
289 )
290 _, _rc = await self._local_async_exec(
291 command=command, raise_exception_on_error=False, env=env
292 )
tiernoa5728bf2020-06-25 15:48:52 +0000293
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000294 command = (
295 "{} --kubeconfig={} create clusterrolebinding osm-tiller-cluster-rule "
296 "--clusterrole=cluster-admin --serviceaccount=kube-system:{}"
297 ).format(self.kubectl_command, paths["kube_config"], self.service_account)
298 _, _rc = await self._local_async_exec(
299 command=command, raise_exception_on_error=False, env=env
300 )
tiernoa5728bf2020-06-25 15:48:52 +0000301
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000302 command = (
Gabriel Cuba5749c532022-07-05 15:07:33 -0500303 "{} init --kubeconfig={} --tiller-namespace={} --home={} --service-account {} "
304 " {}"
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000305 ).format(
306 self._helm_command,
307 paths["kube_config"],
308 namespace,
309 paths["helm_dir"],
310 self.service_account,
David Garciadd322062021-05-28 16:21:51 +0200311 "--stable-repo-url {}".format(self._stable_repo_url)
312 if self._stable_repo_url
313 else "--skip-repos",
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000314 )
315 _, _rc = await self._local_async_exec(
316 command=command, raise_exception_on_error=True, env=env
317 )
quilesj26c78a42019-10-28 18:10:42 +0100318 n2vc_installed_sw = True
319 else:
320 # check client helm installation
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000321 check_file = paths["helm_dir"] + "/repository/repositories.yaml"
322 if not self._check_file_exists(
323 filename=check_file, exception_if_not_exists=False
324 ):
tiernoa5728bf2020-06-25 15:48:52 +0000325 self.log.info("Initializing helm in client: {}".format(cluster_id))
beierlmf52cb7c2020-04-21 16:36:35 -0400326 command = (
Gabriel Cuba5749c532022-07-05 15:07:33 -0500327 "{} init --kubeconfig={} --tiller-namespace={} "
328 "--home={} --client-only {} "
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000329 ).format(
330 self._helm_command,
331 paths["kube_config"],
332 namespace,
333 paths["helm_dir"],
David Garciadd322062021-05-28 16:21:51 +0200334 "--stable-repo-url {}".format(self._stable_repo_url)
335 if self._stable_repo_url
336 else "--skip-repos",
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000337 )
beierlmf52cb7c2020-04-21 16:36:35 -0400338 output, _rc = await self._local_async_exec(
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000339 command=command, raise_exception_on_error=True, env=env
beierlmf52cb7c2020-04-21 16:36:35 -0400340 )
quilesj26c78a42019-10-28 18:10:42 +0100341 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400342 self.log.info("Helm client already initialized")
quilesj26c78a42019-10-28 18:10:42 +0100343
David Garcia2a10e432022-06-17 14:27:54 +0200344 # remove old stable repo and add new one
345 cluster_uuid = "{}:{}".format(namespace, cluster_id)
346 repo_list = await self.repo_list(cluster_uuid)
lloretgalleg83e55892020-12-17 12:42:11 +0000347 for repo in repo_list:
348 if repo["name"] == "stable" and repo["url"] != self._stable_repo_url:
349 self.log.debug("Add new stable repo url: {}")
David Garcia2a10e432022-06-17 14:27:54 +0200350 await self.repo_remove(cluster_uuid, "stable")
David Garciadd322062021-05-28 16:21:51 +0200351 if self._stable_repo_url:
David Garcia2a10e432022-06-17 14:27:54 +0200352 await self.repo_add(cluster_uuid, "stable", self._stable_repo_url)
lloretgalleg83e55892020-12-17 12:42:11 +0000353 break
354
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000355 return n2vc_installed_sw
lloretgallege308c712020-09-02 09:40:38 +0000356
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000357 async def _uninstall_sw(self, cluster_id: str, namespace: str):
358 # uninstall Tiller if necessary
quilesj26c78a42019-10-28 18:10:42 +0100359
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000360 self.log.debug("Uninstalling tiller from cluster {}".format(cluster_id))
quilesj26c78a42019-10-28 18:10:42 +0100361
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000362 # init paths, env
363 paths, env = self._init_paths_env(
tiernoa5728bf2020-06-25 15:48:52 +0000364 cluster_name=cluster_id, create_if_not_exist=True
beierlmf52cb7c2020-04-21 16:36:35 -0400365 )
quilesj26c78a42019-10-28 18:10:42 +0100366
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000367 if not namespace:
368 # find namespace for tiller pod
369 command = "{} --kubeconfig={} get deployments --all-namespaces".format(
370 self.kubectl_command, paths["kube_config"]
371 )
372 output, _rc = await self._local_async_exec(
373 command=command, raise_exception_on_error=False, env=env
374 )
375 output_table = self._output_to_table(output=output)
376 namespace = None
377 for r in output_table:
378 try:
379 if "tiller-deploy" in r[1]:
380 namespace = r[0]
381 break
382 except Exception:
383 pass
quilesj26c78a42019-10-28 18:10:42 +0100384 else:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000385 msg = "Tiller deployment not found in cluster {}".format(cluster_id)
386 self.log.error(msg)
quilesj26c78a42019-10-28 18:10:42 +0100387
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000388 self.log.debug("namespace for tiller: {}".format(namespace))
quilesj26c78a42019-10-28 18:10:42 +0100389
tierno53555f62020-04-07 11:08:16 +0000390 if namespace:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000391 # uninstall tiller from cluster
392 self.log.debug("Uninstalling tiller from cluster {}".format(cluster_id))
393 command = "{} --kubeconfig={} --home={} reset".format(
394 self._helm_command, paths["kube_config"], paths["helm_dir"]
beierlmf52cb7c2020-04-21 16:36:35 -0400395 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000396 self.log.debug("resetting: {}".format(command))
397 output, _rc = await self._local_async_exec(
398 command=command, raise_exception_on_error=True, env=env
quilesj26c78a42019-10-28 18:10:42 +0100399 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000400 # Delete clusterrolebinding and serviceaccount.
401 # Ignore if errors for backward compatibility
402 command = (
403 "{} --kubeconfig={} delete clusterrolebinding.rbac.authorization.k8s."
404 "io/osm-tiller-cluster-rule"
405 ).format(self.kubectl_command, paths["kube_config"])
406 output, _rc = await self._local_async_exec(
407 command=command, raise_exception_on_error=False, env=env
quilesj26c78a42019-10-28 18:10:42 +0100408 )
David Garcia2a10e432022-06-17 14:27:54 +0200409 command = "{} --kubeconfig={} --namespace kube-system delete serviceaccount/{}".format(
410 self.kubectl_command, paths["kube_config"], self.service_account
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000411 )
412 output, _rc = await self._local_async_exec(
413 command=command, raise_exception_on_error=False, env=env
414 )
quilesj26c78a42019-10-28 18:10:42 +0100415
416 else:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000417 self.log.debug("namespace not found")
quilesj26c78a42019-10-28 18:10:42 +0100418
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000419 async def _instances_list(self, cluster_id):
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,
Pedro Escaleirad901a802022-04-05 17:32:13 +0100455 yaml_format: bool = False,
beierlmf52cb7c2020-04-21 16:36:35 -0400456 show_error_log: bool = False,
Pedro Escaleirad901a802022-04-05 17:32:13 +0100457 ) -> Union[str, dict]:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000458 self.log.debug(
459 "status of kdu_instance: {}, namespace: {} ".format(kdu_instance, namespace)
460 )
quilesj26c78a42019-10-28 18:10:42 +0100461
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000462 # init config, env
463 paths, env = self._init_paths_env(
tiernoa5728bf2020-06-25 15:48:52 +0000464 cluster_name=cluster_id, create_if_not_exist=True
beierlmf52cb7c2020-04-21 16:36:35 -0400465 )
David Garcia05bccf72022-02-02 11:35:20 +0100466 command = ("env KUBECONFIG={} {} status {} --output yaml").format(
467 paths["kube_config"], self._helm_command, kdu_instance
468 )
quilesj26c78a42019-10-28 18:10:42 +0100469 output, rc = await self._local_async_exec(
470 command=command,
471 raise_exception_on_error=True,
beierlmf52cb7c2020-04-21 16:36:35 -0400472 show_error_log=show_error_log,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000473 env=env,
quilesj26c78a42019-10-28 18:10:42 +0100474 )
475
Pedro Escaleirad901a802022-04-05 17:32:13 +0100476 if yaml_format:
quilesj1be06302019-11-29 11:17:11 +0000477 return str(output)
478
quilesj26c78a42019-10-28 18:10:42 +0100479 if rc != 0:
480 return None
481
482 data = yaml.load(output, Loader=yaml.SafeLoader)
483
484 # remove field 'notes'
485 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400486 del data.get("info").get("status")["notes"]
quilesj26c78a42019-10-28 18:10:42 +0100487 except KeyError:
488 pass
489
Pedro Escaleirabeaac1e2022-04-03 13:51:46 +0100490 # parse the manifest to a list of dictionaries
491 if "manifest" in data:
492 manifest_str = data.get("manifest")
493 manifest_docs = yaml.load_all(manifest_str, Loader=yaml.SafeLoader)
494
495 data["manifest"] = []
496 for doc in manifest_docs:
497 data["manifest"].append(doc)
498
quilesj26c78a42019-10-28 18:10:42 +0100499 # parse field 'resources'
500 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400501 resources = str(data.get("info").get("status").get("resources"))
quilesj26c78a42019-10-28 18:10:42 +0100502 resource_table = self._output_to_table(resources)
beierlmf52cb7c2020-04-21 16:36:35 -0400503 data.get("info").get("status")["resources"] = resource_table
504 except Exception:
quilesj26c78a42019-10-28 18:10:42 +0100505 pass
506
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000507 # set description to lowercase (unify with helm3)
508 try:
509 data.get("info")["description"] = data.get("info").pop("Description")
510 except KeyError:
511 pass
512
quilesj26c78a42019-10-28 18:10:42 +0100513 return data
514
lloretgalleg095392b2020-11-20 11:28:08 +0000515 def _get_helm_chart_repos_ids(self, cluster_uuid) -> list:
516 repo_ids = []
517 cluster_filter = {"_admin.helm-chart.id": cluster_uuid}
518 cluster = self.db.get_one("k8sclusters", cluster_filter)
519 if cluster:
520 repo_ids = cluster.get("_admin").get("helm_chart_repos") or []
521 return repo_ids
522 else:
523 raise K8sException(
524 "k8cluster with helm-id : {} not found".format(cluster_uuid)
525 )
526
tiernoa5728bf2020-06-25 15:48:52 +0000527 async def _is_install_completed(self, cluster_id: str, kdu_instance: str) -> bool:
David Garcia05bccf72022-02-02 11:35:20 +0100528 # init config, env
529 paths, env = self._init_paths_env(
530 cluster_name=cluster_id, create_if_not_exist=True
531 )
quilesj26c78a42019-10-28 18:10:42 +0100532
beierlmf52cb7c2020-04-21 16:36:35 -0400533 status = await self._status_kdu(
Pedro Escaleirad901a802022-04-05 17:32:13 +0100534 cluster_id=cluster_id, kdu_instance=kdu_instance, yaml_format=False
beierlmf52cb7c2020-04-21 16:36:35 -0400535 )
quilesj26c78a42019-10-28 18:10:42 +0100536
537 # extract info.status.resources-> str
538 # format:
539 # ==> v1/Deployment
540 # NAME READY UP-TO-DATE AVAILABLE AGE
541 # halting-horse-mongodb 0/1 1 0 0s
542 # halting-petit-mongodb 1/1 1 0 0s
543 # blank line
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000544 resources = K8sHelmBaseConnector._get_deep(
545 status, ("info", "status", "resources")
546 )
quilesj26c78a42019-10-28 18:10:42 +0100547
548 # convert to table
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000549 resources = K8sHelmBaseConnector._output_to_table(resources)
quilesj26c78a42019-10-28 18:10:42 +0100550
551 num_lines = len(resources)
552 index = 0
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000553 ready = True
quilesj26c78a42019-10-28 18:10:42 +0100554 while index < num_lines:
555 try:
556 line1 = resources[index]
557 index += 1
558 # find '==>' in column 0
beierlmf52cb7c2020-04-21 16:36:35 -0400559 if line1[0] == "==>":
quilesj26c78a42019-10-28 18:10:42 +0100560 line2 = resources[index]
561 index += 1
562 # find READY in column 1
beierlmf52cb7c2020-04-21 16:36:35 -0400563 if line2[1] == "READY":
quilesj26c78a42019-10-28 18:10:42 +0100564 # read next lines
565 line3 = resources[index]
566 index += 1
567 while len(line3) > 1 and index < num_lines:
568 ready_value = line3[1]
beierlmf52cb7c2020-04-21 16:36:35 -0400569 parts = ready_value.split(sep="/")
quilesj26c78a42019-10-28 18:10:42 +0100570 current = int(parts[0])
571 total = int(parts[1])
572 if current < total:
beierlmf52cb7c2020-04-21 16:36:35 -0400573 self.log.debug("NOT READY:\n {}".format(line3))
quilesj26c78a42019-10-28 18:10:42 +0100574 ready = False
575 line3 = resources[index]
576 index += 1
577
beierlmf52cb7c2020-04-21 16:36:35 -0400578 except Exception:
quilesj26c78a42019-10-28 18:10:42 +0100579 pass
580
581 return ready
582
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000583 def _get_install_command(
David Garcia05bccf72022-02-02 11:35:20 +0100584 self,
585 kdu_model,
586 kdu_instance,
587 namespace,
588 params_str,
589 version,
590 atomic,
591 timeout,
592 kubeconfig,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000593 ) -> str:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000594 timeout_str = ""
595 if timeout:
596 timeout_str = "--timeout {}".format(timeout)
lloretgallegd99f3f22020-06-29 14:18:30 +0000597
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000598 # atomic
599 atomic_str = ""
600 if atomic:
601 atomic_str = "--atomic"
602 # namespace
603 namespace_str = ""
604 if namespace:
605 namespace_str = "--namespace {}".format(namespace)
lloretgallegd99f3f22020-06-29 14:18:30 +0000606
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000607 # version
608 version_str = ""
609 if version:
610 version_str = version_str = "--version {}".format(version)
lloretgallegd99f3f22020-06-29 14:18:30 +0000611
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000612 command = (
David Garcia05bccf72022-02-02 11:35:20 +0100613 "env KUBECONFIG={kubeconfig} {helm} install {atomic} --output yaml "
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000614 "{params} {timeout} --name={name} {ns} {model} {ver}".format(
David Garcia05bccf72022-02-02 11:35:20 +0100615 kubeconfig=kubeconfig,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000616 helm=self._helm_command,
617 atomic=atomic_str,
618 params=params_str,
619 timeout=timeout_str,
620 name=kdu_instance,
621 ns=namespace_str,
622 model=kdu_model,
623 ver=version_str,
beierlmf52cb7c2020-04-21 16:36:35 -0400624 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000625 )
626 return command
quilesj26c78a42019-10-28 18:10:42 +0100627
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000628 def _get_upgrade_command(
David Garcia05bccf72022-02-02 11:35:20 +0100629 self,
630 kdu_model,
631 kdu_instance,
632 namespace,
633 params_str,
634 version,
635 atomic,
636 timeout,
637 kubeconfig,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000638 ) -> str:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000639 timeout_str = ""
640 if timeout:
641 timeout_str = "--timeout {}".format(timeout)
quilesj26c78a42019-10-28 18:10:42 +0100642
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000643 # atomic
644 atomic_str = ""
645 if atomic:
646 atomic_str = "--atomic"
quilesj26c78a42019-10-28 18:10:42 +0100647
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000648 # version
649 version_str = ""
650 if version:
651 version_str = "--version {}".format(version)
quilesj26c78a42019-10-28 18:10:42 +0100652
David Garcia05bccf72022-02-02 11:35:20 +0100653 command = (
654 "env KUBECONFIG={kubeconfig} {helm} upgrade {atomic} --output yaml {params} {timeout} {name} {model} {ver}"
655 ).format(
656 kubeconfig=kubeconfig,
garciadeblas82b591c2021-03-24 09:22:13 +0100657 helm=self._helm_command,
658 atomic=atomic_str,
659 params=params_str,
660 timeout=timeout_str,
661 name=kdu_instance,
662 model=kdu_model,
663 ver=version_str,
664 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000665 return command
quilesj26c78a42019-10-28 18:10:42 +0100666
David Garcia05bccf72022-02-02 11:35:20 +0100667 def _get_rollback_command(
668 self, kdu_instance, namespace, revision, kubeconfig
669 ) -> str:
670 return "env KUBECONFIG={} {} rollback {} {} --wait".format(
671 kubeconfig, self._helm_command, kdu_instance, revision
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000672 )
quilesj26c78a42019-10-28 18:10:42 +0100673
David Garcia05bccf72022-02-02 11:35:20 +0100674 def _get_uninstall_command(
675 self, kdu_instance: str, namespace: str, kubeconfig: str
676 ) -> str:
677 return "env KUBECONFIG={} {} delete --purge {}".format(
678 kubeconfig, self._helm_command, kdu_instance
679 )