blob: 5fe624b6e93dbf4c9230ab50c2843058f6e182bb [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 Escaleiraa8980cc2022-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 Garcia4395cfa2021-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
garciadeblas04393192022-06-08 15:39:24 +0200113 :param kdu_model: chart/reference (string), which can be either
David Garciaeb8943a2021-04-12 12:07:37 +0200114 of these options:
115 - a name of chart available via the repos known by OSM
garciadeblas04393192022-06-08 15:39:24 +0200116 (e.g. stable/openldap, stable/openldap:1.2.4)
117 - a path to a packaged chart (e.g. mychart.tgz)
118 - a path to an unpacked chart directory or a URL (e.g. mychart)
David Garciaeb8943a2021-04-12 12:07:37 +0200119 :param kdu_instance: Kdu instance name
120 :param atomic: If set, installation process purges chart/bundle on fail, also
121 will wait until all the K8s objects are active
122 :param timeout: Time in seconds to wait for the install of the chart/bundle
123 (defaults to Helm default timeout: 300s)
124 :param params: dictionary of key-value pairs for instantiation parameters
125 (overriding default values)
126 :param dict db_dict: where to write into database when the status changes.
127 It contains a dict with {collection: <str>, filter: {},
128 path: <str>},
129 e.g. {collection: "nsrs", filter:
130 {_id: <nsd-id>, path: "_admin.deployed.K8S.3"}
131 :param kdu_name: Name of the KDU instance to be installed
132 :param namespace: K8s namespace to use for the KDU instance
133 :param kwargs: Additional parameters (None yet)
134 :return: True if successful
135 """
Pedro Escaleirab41de172022-04-02 00:44:08 +0100136 self.log.debug("installing {} in cluster {}".format(kdu_model, cluster_uuid))
lloretgalleg095392b2020-11-20 11:28:08 +0000137
138 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +0100139 self.fs.sync(from_path=cluster_uuid)
lloretgalleg095392b2020-11-20 11:28:08 +0000140
141 # init env, paths
142 paths, env = self._init_paths_env(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100143 cluster_name=cluster_uuid, 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(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100147 cluster_uuid,
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
Pedro Escaleirab41de172022-04-02 00:44:08 +0100161 self.fs.reverse_sync(from_path=cluster_uuid)
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:
167
168 self.log.debug(
169 "inspect kdu_model {} from (optional) repo: {}".format(kdu_model, repo_url)
170 )
171
aktas867418c2021-10-19 18:26:13 +0300172 return await self._exec_inspect_command(
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000173 inspect_command="", kdu_model=kdu_model, repo_url=repo_url
174 )
175
176 """
177 ####################################################################################
178 ################################### P R I V A T E ##################################
179 ####################################################################################
180 """
181
182 def _init_paths_env(self, cluster_name: str, create_if_not_exist: bool = True):
tiernoa5728bf2020-06-25 15:48:52 +0000183 """
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000184 Creates and returns base cluster and kube dirs and returns them.
185 Also created helm3 dirs according to new directory specification, paths are
186 returned and also environment variables that must be provided to execute commands
187
188 Helm 2 directory specification uses helm_home dir:
189
190 The variables assigned for this paths are:
191 - Helm hone: $HELM_HOME
192 - helm kubeconfig: $KUBECONFIG
193
194 :param cluster_name: cluster_name
195 :return: Dictionary with config_paths and dictionary with helm environment variables
tiernoa5728bf2020-06-25 15:48:52 +0000196 """
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000197 base = self.fs.path
198 if base.endswith("/") or base.endswith("\\"):
199 base = base[:-1]
tiernoa5728bf2020-06-25 15:48:52 +0000200
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000201 # base dir for cluster
202 cluster_dir = base + "/" + cluster_name
garciadeblas54771fa2019-12-13 13:39:03 +0100203
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000204 # kube dir
205 kube_dir = cluster_dir + "/" + ".kube"
206 if create_if_not_exist and not os.path.exists(kube_dir):
207 self.log.debug("Creating dir {}".format(kube_dir))
208 os.makedirs(kube_dir)
quilesj26c78a42019-10-28 18:10:42 +0100209
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000210 # helm home dir
211 helm_dir = cluster_dir + "/" + ".helm"
212 if create_if_not_exist and not os.path.exists(helm_dir):
213 self.log.debug("Creating dir {}".format(helm_dir))
214 os.makedirs(helm_dir)
quilesj26c78a42019-10-28 18:10:42 +0100215
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000216 config_filename = kube_dir + "/config"
quilesj26c78a42019-10-28 18:10:42 +0100217
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000218 # 2 - Prepare dictionary with paths
219 paths = {
220 "kube_dir": kube_dir,
221 "kube_config": config_filename,
222 "cluster_dir": cluster_dir,
223 "helm_dir": helm_dir,
224 }
225
226 for file_name, file in paths.items():
227 if "dir" in file_name and not os.path.exists(file):
228 err_msg = "{} dir does not exist".format(file)
229 self.log.error(err_msg)
230 raise K8sException(err_msg)
231
232 # 3 - Prepare environment variables
233 env = {"HELM_HOME": helm_dir, "KUBECONFIG": config_filename}
234
235 return paths, env
236
bravof7bd5c6a2021-11-17 11:14:57 -0300237 async def _get_services(self, cluster_id, kdu_instance, namespace, kubeconfig):
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000238
239 # init config, env
240 paths, env = self._init_paths_env(
tiernoa5728bf2020-06-25 15:48:52 +0000241 cluster_name=cluster_id, create_if_not_exist=True
beierlmf52cb7c2020-04-21 16:36:35 -0400242 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000243
bravof7bd5c6a2021-11-17 11:14:57 -0300244 command1 = "env KUBECONFIG={} {} get manifest {} ".format(
245 kubeconfig, self._helm_command, kdu_instance
246 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000247 command2 = "{} get --namespace={} -f -".format(self.kubectl_command, namespace)
248 output, _rc = await self._local_async_exec_pipe(
249 command1, command2, env=env, raise_exception_on_error=True
250 )
251 services = self._parse_services(output)
252
253 return services
254
garciadeblas82b591c2021-03-24 09:22:13 +0100255 async def _cluster_init(
256 self, cluster_id: str, namespace: str, paths: dict, env: dict
257 ):
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000258 """
259 Implements the helm version dependent cluster initialization:
260 For helm2 it initialized tiller environment if needed
261 """
quilesj26c78a42019-10-28 18:10:42 +0100262
263 # check if tiller pod is up in cluster
beierlmf52cb7c2020-04-21 16:36:35 -0400264 command = "{} --kubeconfig={} --namespace={} get deployments".format(
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000265 self.kubectl_command, paths["kube_config"], namespace
beierlmf52cb7c2020-04-21 16:36:35 -0400266 )
267 output, _rc = await self._local_async_exec(
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000268 command=command, raise_exception_on_error=True, env=env
beierlmf52cb7c2020-04-21 16:36:35 -0400269 )
quilesj26c78a42019-10-28 18:10:42 +0100270
tierno119f7232020-04-21 13:22:26 +0000271 output_table = self._output_to_table(output=output)
quilesj26c78a42019-10-28 18:10:42 +0100272
273 # find 'tiller' pod in all pods
274 already_initialized = False
275 try:
276 for row in output_table:
beierlmf52cb7c2020-04-21 16:36:35 -0400277 if row[0].startswith("tiller-deploy"):
quilesj26c78a42019-10-28 18:10:42 +0100278 already_initialized = True
279 break
beierlmf52cb7c2020-04-21 16:36:35 -0400280 except Exception:
quilesj26c78a42019-10-28 18:10:42 +0100281 pass
282
283 # helm init
284 n2vc_installed_sw = False
285 if not already_initialized:
beierlmf52cb7c2020-04-21 16:36:35 -0400286 self.log.info(
tiernoa5728bf2020-06-25 15:48:52 +0000287 "Initializing helm in client and server: {}".format(cluster_id)
beierlmf52cb7c2020-04-21 16:36:35 -0400288 )
tiernoa5728bf2020-06-25 15:48:52 +0000289 command = "{} --kubeconfig={} --namespace kube-system create serviceaccount {}".format(
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000290 self.kubectl_command, paths["kube_config"], self.service_account
291 )
292 _, _rc = await self._local_async_exec(
293 command=command, raise_exception_on_error=False, env=env
294 )
tiernoa5728bf2020-06-25 15:48:52 +0000295
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000296 command = (
297 "{} --kubeconfig={} create clusterrolebinding osm-tiller-cluster-rule "
298 "--clusterrole=cluster-admin --serviceaccount=kube-system:{}"
299 ).format(self.kubectl_command, paths["kube_config"], self.service_account)
300 _, _rc = await self._local_async_exec(
301 command=command, raise_exception_on_error=False, env=env
302 )
tiernoa5728bf2020-06-25 15:48:52 +0000303
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000304 command = (
305 "{} --kubeconfig={} --tiller-namespace={} --home={} --service-account {} "
David Garcia4395cfa2021-05-28 16:21:51 +0200306 " {} init"
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000307 ).format(
308 self._helm_command,
309 paths["kube_config"],
310 namespace,
311 paths["helm_dir"],
312 self.service_account,
David Garcia4395cfa2021-05-28 16:21:51 +0200313 "--stable-repo-url {}".format(self._stable_repo_url)
314 if self._stable_repo_url
315 else "--skip-repos",
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000316 )
317 _, _rc = await self._local_async_exec(
318 command=command, raise_exception_on_error=True, env=env
319 )
quilesj26c78a42019-10-28 18:10:42 +0100320 n2vc_installed_sw = True
321 else:
322 # check client helm installation
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000323 check_file = paths["helm_dir"] + "/repository/repositories.yaml"
324 if not self._check_file_exists(
325 filename=check_file, exception_if_not_exists=False
326 ):
tiernoa5728bf2020-06-25 15:48:52 +0000327 self.log.info("Initializing helm in client: {}".format(cluster_id))
beierlmf52cb7c2020-04-21 16:36:35 -0400328 command = (
329 "{} --kubeconfig={} --tiller-namespace={} "
David Garcia4395cfa2021-05-28 16:21:51 +0200330 "--home={} init --client-only {} "
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000331 ).format(
332 self._helm_command,
333 paths["kube_config"],
334 namespace,
335 paths["helm_dir"],
David Garcia4395cfa2021-05-28 16:21:51 +0200336 "--stable-repo-url {}".format(self._stable_repo_url)
337 if self._stable_repo_url
338 else "--skip-repos",
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000339 )
beierlmf52cb7c2020-04-21 16:36:35 -0400340 output, _rc = await self._local_async_exec(
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000341 command=command, raise_exception_on_error=True, env=env
beierlmf52cb7c2020-04-21 16:36:35 -0400342 )
quilesj26c78a42019-10-28 18:10:42 +0100343 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400344 self.log.info("Helm client already initialized")
quilesj26c78a42019-10-28 18:10:42 +0100345
Pedro Escaleirab41de172022-04-02 00:44:08 +0100346 repo_list = await self.repo_list(cluster_id)
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: {}")
Pedro Escaleirab41de172022-04-02 00:44:08 +0100350 await self.repo_remove(cluster_id, "stable")
David Garcia4395cfa2021-05-28 16:21:51 +0200351 if self._stable_repo_url:
Pedro Escaleirab41de172022-04-02 00:44:08 +0100352 await self.repo_add(cluster_id, "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 )
Pedro Escaleirab41de172022-04-02 00:44:08 +0100409 command = (
410 "{} --kubeconfig={} --namespace {} delete serviceaccount/{}".format(
411 self.kubectl_command,
412 paths["kube_config"],
413 namespace,
414 self.service_account,
415 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000416 )
417 output, _rc = await self._local_async_exec(
418 command=command, raise_exception_on_error=False, env=env
419 )
quilesj26c78a42019-10-28 18:10:42 +0100420
421 else:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000422 self.log.debug("namespace not found")
quilesj26c78a42019-10-28 18:10:42 +0100423
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000424 async def _instances_list(self, cluster_id):
quilesj26c78a42019-10-28 18:10:42 +0100425
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000426 # init paths, env
427 paths, env = self._init_paths_env(
tiernoa5728bf2020-06-25 15:48:52 +0000428 cluster_name=cluster_id, create_if_not_exist=True
beierlmf52cb7c2020-04-21 16:36:35 -0400429 )
quilesj26c78a42019-10-28 18:10:42 +0100430
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000431 command = "{} list --output yaml".format(self._helm_command)
quilesj26c78a42019-10-28 18:10:42 +0100432
beierlmf52cb7c2020-04-21 16:36:35 -0400433 output, _rc = await self._local_async_exec(
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000434 command=command, raise_exception_on_error=True, env=env
beierlmf52cb7c2020-04-21 16:36:35 -0400435 )
quilesj26c78a42019-10-28 18:10:42 +0100436
437 if output and len(output) > 0:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000438 # parse yaml and update keys to lower case to unify with helm3
439 instances = yaml.load(output, Loader=yaml.SafeLoader).get("Releases")
440 new_instances = []
441 for instance in instances:
442 new_instance = dict((k.lower(), v) for k, v in instance.items())
443 new_instances.append(new_instance)
444 return new_instances
quilesj26c78a42019-10-28 18:10:42 +0100445 else:
446 return []
447
garciadeblas82b591c2021-03-24 09:22:13 +0100448 def _get_inspect_command(
449 self, show_command: str, kdu_model: str, repo_str: str, version: str
450 ):
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000451 inspect_command = "{} inspect {} {}{} {}".format(
452 self._helm_command, show_command, kdu_model, repo_str, version
beierlmf52cb7c2020-04-21 16:36:35 -0400453 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000454 return inspect_command
quilesj1be06302019-11-29 11:17:11 +0000455
aktas867418c2021-10-19 18:26:13 +0300456 def _get_get_command(
457 self, get_command: str, kdu_instance: str, namespace: str, kubeconfig: str
458 ):
459 get_command = "env KUBECONFIG={} {} get {} {} --output yaml".format(
460 kubeconfig, self._helm_command, get_command, kdu_instance
461 )
462 return get_command
463
quilesj26c78a42019-10-28 18:10:42 +0100464 async def _status_kdu(
beierlmf52cb7c2020-04-21 16:36:35 -0400465 self,
tiernoa5728bf2020-06-25 15:48:52 +0000466 cluster_id: str,
beierlmf52cb7c2020-04-21 16:36:35 -0400467 kdu_instance: str,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000468 namespace: str = None,
Pedro Escaleiraa8980cc2022-04-05 17:32:13 +0100469 yaml_format: bool = False,
beierlmf52cb7c2020-04-21 16:36:35 -0400470 show_error_log: bool = False,
Pedro Escaleiraa8980cc2022-04-05 17:32:13 +0100471 ) -> Union[str, dict]:
quilesj26c78a42019-10-28 18:10:42 +0100472
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000473 self.log.debug(
474 "status of kdu_instance: {}, namespace: {} ".format(kdu_instance, namespace)
475 )
quilesj26c78a42019-10-28 18:10:42 +0100476
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000477 # init config, env
478 paths, env = self._init_paths_env(
tiernoa5728bf2020-06-25 15:48:52 +0000479 cluster_name=cluster_id, create_if_not_exist=True
beierlmf52cb7c2020-04-21 16:36:35 -0400480 )
bravof7bd5c6a2021-11-17 11:14:57 -0300481 command = ("env KUBECONFIG={} {} status {} --output yaml").format(
482 paths["kube_config"], self._helm_command, kdu_instance
483 )
quilesj26c78a42019-10-28 18:10:42 +0100484 output, rc = await self._local_async_exec(
485 command=command,
486 raise_exception_on_error=True,
beierlmf52cb7c2020-04-21 16:36:35 -0400487 show_error_log=show_error_log,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000488 env=env,
quilesj26c78a42019-10-28 18:10:42 +0100489 )
490
Pedro Escaleiraa8980cc2022-04-05 17:32:13 +0100491 if yaml_format:
quilesj1be06302019-11-29 11:17:11 +0000492 return str(output)
493
quilesj26c78a42019-10-28 18:10:42 +0100494 if rc != 0:
495 return None
496
497 data = yaml.load(output, Loader=yaml.SafeLoader)
498
499 # remove field 'notes'
500 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400501 del data.get("info").get("status")["notes"]
quilesj26c78a42019-10-28 18:10:42 +0100502 except KeyError:
503 pass
504
Pedro Escaleiraed0ff052022-04-03 13:51:46 +0100505 # parse the manifest to a list of dictionaries
506 if "manifest" in data:
507 manifest_str = data.get("manifest")
508 manifest_docs = yaml.load_all(manifest_str, Loader=yaml.SafeLoader)
509
510 data["manifest"] = []
511 for doc in manifest_docs:
512 data["manifest"].append(doc)
513
quilesj26c78a42019-10-28 18:10:42 +0100514 # parse field 'resources'
515 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400516 resources = str(data.get("info").get("status").get("resources"))
quilesj26c78a42019-10-28 18:10:42 +0100517 resource_table = self._output_to_table(resources)
beierlmf52cb7c2020-04-21 16:36:35 -0400518 data.get("info").get("status")["resources"] = resource_table
519 except Exception:
quilesj26c78a42019-10-28 18:10:42 +0100520 pass
521
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000522 # set description to lowercase (unify with helm3)
523 try:
524 data.get("info")["description"] = data.get("info").pop("Description")
525 except KeyError:
526 pass
527
quilesj26c78a42019-10-28 18:10:42 +0100528 return data
529
lloretgalleg095392b2020-11-20 11:28:08 +0000530 def _get_helm_chart_repos_ids(self, cluster_uuid) -> list:
531 repo_ids = []
532 cluster_filter = {"_admin.helm-chart.id": cluster_uuid}
533 cluster = self.db.get_one("k8sclusters", cluster_filter)
534 if cluster:
535 repo_ids = cluster.get("_admin").get("helm_chart_repos") or []
536 return repo_ids
537 else:
538 raise K8sException(
539 "k8cluster with helm-id : {} not found".format(cluster_uuid)
540 )
541
tiernoa5728bf2020-06-25 15:48:52 +0000542 async def _is_install_completed(self, cluster_id: str, kdu_instance: str) -> bool:
bravof7bd5c6a2021-11-17 11:14:57 -0300543 # init config, env
544 paths, env = self._init_paths_env(
545 cluster_name=cluster_id, create_if_not_exist=True
546 )
quilesj26c78a42019-10-28 18:10:42 +0100547
beierlmf52cb7c2020-04-21 16:36:35 -0400548 status = await self._status_kdu(
Pedro Escaleiraa8980cc2022-04-05 17:32:13 +0100549 cluster_id=cluster_id, kdu_instance=kdu_instance, yaml_format=False
beierlmf52cb7c2020-04-21 16:36:35 -0400550 )
quilesj26c78a42019-10-28 18:10:42 +0100551
552 # extract info.status.resources-> str
553 # format:
554 # ==> v1/Deployment
555 # NAME READY UP-TO-DATE AVAILABLE AGE
556 # halting-horse-mongodb 0/1 1 0 0s
557 # halting-petit-mongodb 1/1 1 0 0s
558 # blank line
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000559 resources = K8sHelmBaseConnector._get_deep(
560 status, ("info", "status", "resources")
561 )
quilesj26c78a42019-10-28 18:10:42 +0100562
563 # convert to table
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000564 resources = K8sHelmBaseConnector._output_to_table(resources)
quilesj26c78a42019-10-28 18:10:42 +0100565
566 num_lines = len(resources)
567 index = 0
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000568 ready = True
quilesj26c78a42019-10-28 18:10:42 +0100569 while index < num_lines:
570 try:
571 line1 = resources[index]
572 index += 1
573 # find '==>' in column 0
beierlmf52cb7c2020-04-21 16:36:35 -0400574 if line1[0] == "==>":
quilesj26c78a42019-10-28 18:10:42 +0100575 line2 = resources[index]
576 index += 1
577 # find READY in column 1
beierlmf52cb7c2020-04-21 16:36:35 -0400578 if line2[1] == "READY":
quilesj26c78a42019-10-28 18:10:42 +0100579 # read next lines
580 line3 = resources[index]
581 index += 1
582 while len(line3) > 1 and index < num_lines:
583 ready_value = line3[1]
beierlmf52cb7c2020-04-21 16:36:35 -0400584 parts = ready_value.split(sep="/")
quilesj26c78a42019-10-28 18:10:42 +0100585 current = int(parts[0])
586 total = int(parts[1])
587 if current < total:
beierlmf52cb7c2020-04-21 16:36:35 -0400588 self.log.debug("NOT READY:\n {}".format(line3))
quilesj26c78a42019-10-28 18:10:42 +0100589 ready = False
590 line3 = resources[index]
591 index += 1
592
beierlmf52cb7c2020-04-21 16:36:35 -0400593 except Exception:
quilesj26c78a42019-10-28 18:10:42 +0100594 pass
595
596 return ready
597
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000598 def _get_install_command(
bravof7bd5c6a2021-11-17 11:14:57 -0300599 self,
600 kdu_model,
601 kdu_instance,
602 namespace,
603 params_str,
604 version,
605 atomic,
606 timeout,
607 kubeconfig,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000608 ) -> str:
lloretgallegd99f3f22020-06-29 14:18:30 +0000609
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000610 timeout_str = ""
611 if timeout:
612 timeout_str = "--timeout {}".format(timeout)
lloretgallegd99f3f22020-06-29 14:18:30 +0000613
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000614 # atomic
615 atomic_str = ""
616 if atomic:
617 atomic_str = "--atomic"
618 # namespace
619 namespace_str = ""
620 if namespace:
621 namespace_str = "--namespace {}".format(namespace)
lloretgallegd99f3f22020-06-29 14:18:30 +0000622
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000623 # version
624 version_str = ""
625 if version:
aktas867418c2021-10-19 18:26:13 +0300626 version_str = "--version {}".format(version)
lloretgallegd99f3f22020-06-29 14:18:30 +0000627
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000628 command = (
bravof7bd5c6a2021-11-17 11:14:57 -0300629 "env KUBECONFIG={kubeconfig} {helm} install {atomic} --output yaml "
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000630 "{params} {timeout} --name={name} {ns} {model} {ver}".format(
bravof7bd5c6a2021-11-17 11:14:57 -0300631 kubeconfig=kubeconfig,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000632 helm=self._helm_command,
633 atomic=atomic_str,
634 params=params_str,
635 timeout=timeout_str,
636 name=kdu_instance,
637 ns=namespace_str,
638 model=kdu_model,
639 ver=version_str,
beierlmf52cb7c2020-04-21 16:36:35 -0400640 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000641 )
642 return command
quilesj26c78a42019-10-28 18:10:42 +0100643
aktas867418c2021-10-19 18:26:13 +0300644 def _get_upgrade_scale_command(
645 self,
646 kdu_model: str,
647 kdu_instance: str,
648 namespace: str,
649 scale: int,
650 version: str,
651 atomic: bool,
652 replica_str: str,
653 timeout: float,
654 resource_name: str,
655 kubeconfig: str,
656 ) -> str:
657
658 timeout_str = ""
659 if timeout:
660 timeout_str = "--timeout {}s".format(timeout)
661
662 # atomic
663 atomic_str = ""
664 if atomic:
665 atomic_str = "--atomic"
666
667 # version
668 version_str = ""
669 if version:
670 version_str = "--version {}".format(version)
671
672 # scale
673 if resource_name:
674 scale_dict = {"{}.{}".format(resource_name, replica_str): scale}
675 else:
676 scale_dict = {replica_str: scale}
677
678 scale_str = self._params_to_set_option(scale_dict)
679
680 command = (
681 "env KUBECONFIG={kubeconfig} {helm} upgrade {atomic} --output yaml {scale} {timeout} {name} {model} {ver}"
682 ).format(
683 helm=self._helm_command,
684 name=kdu_instance,
685 atomic=atomic_str,
686 scale=scale_str,
687 timeout=timeout_str,
688 model=kdu_model,
689 ver=version_str,
690 kubeconfig=kubeconfig,
691 )
692 return command
693
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000694 def _get_upgrade_command(
bravof7bd5c6a2021-11-17 11:14:57 -0300695 self,
696 kdu_model,
697 kdu_instance,
698 namespace,
699 params_str,
700 version,
701 atomic,
702 timeout,
703 kubeconfig,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000704 ) -> str:
quilesj26c78a42019-10-28 18:10:42 +0100705
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000706 timeout_str = ""
707 if timeout:
708 timeout_str = "--timeout {}".format(timeout)
quilesj26c78a42019-10-28 18:10:42 +0100709
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000710 # atomic
711 atomic_str = ""
712 if atomic:
713 atomic_str = "--atomic"
quilesj26c78a42019-10-28 18:10:42 +0100714
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000715 # version
716 version_str = ""
717 if version:
718 version_str = "--version {}".format(version)
quilesj26c78a42019-10-28 18:10:42 +0100719
bravof7bd5c6a2021-11-17 11:14:57 -0300720 command = (
721 "env KUBECONFIG={kubeconfig} {helm} upgrade {atomic} --output yaml {params} {timeout} {name} {model} {ver}"
722 ).format(
723 kubeconfig=kubeconfig,
garciadeblas82b591c2021-03-24 09:22:13 +0100724 helm=self._helm_command,
725 atomic=atomic_str,
726 params=params_str,
727 timeout=timeout_str,
728 name=kdu_instance,
729 model=kdu_model,
730 ver=version_str,
731 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000732 return command
quilesj26c78a42019-10-28 18:10:42 +0100733
bravof7bd5c6a2021-11-17 11:14:57 -0300734 def _get_rollback_command(
735 self, kdu_instance, namespace, revision, kubeconfig
736 ) -> str:
737 return "env KUBECONFIG={} {} rollback {} {} --wait".format(
738 kubeconfig, self._helm_command, kdu_instance, revision
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000739 )
quilesj26c78a42019-10-28 18:10:42 +0100740
bravof7bd5c6a2021-11-17 11:14:57 -0300741 def _get_uninstall_command(
742 self, kdu_instance: str, namespace: str, kubeconfig: str
743 ) -> str:
744 return "env KUBECONFIG={} {} delete --purge {}".format(
745 kubeconfig, self._helm_command, kdu_instance
746 )