blob: a3644c807048779fa02f99b1ad4738fa03a598e7 [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:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000167 self.log.debug(
168 "inspect kdu_model {} from (optional) repo: {}".format(kdu_model, repo_url)
169 )
170
aktas867418c2021-10-19 18:26:13 +0300171 return await self._exec_inspect_command(
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000172 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
bravof7bd5c6a2021-11-17 11:14:57 -0300236 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
bravof7bd5c6a2021-11-17 11:14:57 -0300242 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 Cuba9a9a7862022-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 Garcia4395cfa2021-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 Cuba9a9a7862022-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 Garcia4395cfa2021-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
Pedro Escaleirab41de172022-04-02 00:44:08 +0100344 repo_list = await self.repo_list(cluster_id)
lloretgalleg83e55892020-12-17 12:42:11 +0000345 for repo in repo_list:
346 if repo["name"] == "stable" and repo["url"] != self._stable_repo_url:
347 self.log.debug("Add new stable repo url: {}")
Pedro Escaleirab41de172022-04-02 00:44:08 +0100348 await self.repo_remove(cluster_id, "stable")
David Garcia4395cfa2021-05-28 16:21:51 +0200349 if self._stable_repo_url:
Pedro Escaleirab41de172022-04-02 00:44:08 +0100350 await self.repo_add(cluster_id, "stable", self._stable_repo_url)
lloretgalleg83e55892020-12-17 12:42:11 +0000351 break
352
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000353 return n2vc_installed_sw
lloretgallege308c712020-09-02 09:40:38 +0000354
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000355 async def _uninstall_sw(self, cluster_id: str, namespace: str):
356 # uninstall Tiller if necessary
quilesj26c78a42019-10-28 18:10:42 +0100357
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000358 self.log.debug("Uninstalling tiller from cluster {}".format(cluster_id))
quilesj26c78a42019-10-28 18:10:42 +0100359
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000360 # init paths, env
361 paths, env = self._init_paths_env(
tiernoa5728bf2020-06-25 15:48:52 +0000362 cluster_name=cluster_id, create_if_not_exist=True
beierlmf52cb7c2020-04-21 16:36:35 -0400363 )
quilesj26c78a42019-10-28 18:10:42 +0100364
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000365 if not namespace:
366 # find namespace for tiller pod
367 command = "{} --kubeconfig={} get deployments --all-namespaces".format(
368 self.kubectl_command, paths["kube_config"]
369 )
370 output, _rc = await self._local_async_exec(
371 command=command, raise_exception_on_error=False, env=env
372 )
373 output_table = self._output_to_table(output=output)
374 namespace = None
375 for r in output_table:
376 try:
377 if "tiller-deploy" in r[1]:
378 namespace = r[0]
379 break
380 except Exception:
381 pass
quilesj26c78a42019-10-28 18:10:42 +0100382 else:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000383 msg = "Tiller deployment not found in cluster {}".format(cluster_id)
384 self.log.error(msg)
quilesj26c78a42019-10-28 18:10:42 +0100385
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000386 self.log.debug("namespace for tiller: {}".format(namespace))
quilesj26c78a42019-10-28 18:10:42 +0100387
tierno53555f62020-04-07 11:08:16 +0000388 if namespace:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000389 # uninstall tiller from cluster
390 self.log.debug("Uninstalling tiller from cluster {}".format(cluster_id))
391 command = "{} --kubeconfig={} --home={} reset".format(
392 self._helm_command, paths["kube_config"], paths["helm_dir"]
beierlmf52cb7c2020-04-21 16:36:35 -0400393 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000394 self.log.debug("resetting: {}".format(command))
395 output, _rc = await self._local_async_exec(
396 command=command, raise_exception_on_error=True, env=env
quilesj26c78a42019-10-28 18:10:42 +0100397 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000398 # Delete clusterrolebinding and serviceaccount.
399 # Ignore if errors for backward compatibility
400 command = (
401 "{} --kubeconfig={} delete clusterrolebinding.rbac.authorization.k8s."
402 "io/osm-tiller-cluster-rule"
403 ).format(self.kubectl_command, paths["kube_config"])
404 output, _rc = await self._local_async_exec(
405 command=command, raise_exception_on_error=False, env=env
quilesj26c78a42019-10-28 18:10:42 +0100406 )
Pedro Escaleirab41de172022-04-02 00:44:08 +0100407 command = (
408 "{} --kubeconfig={} --namespace {} delete serviceaccount/{}".format(
409 self.kubectl_command,
410 paths["kube_config"],
411 namespace,
412 self.service_account,
413 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000414 )
415 output, _rc = await self._local_async_exec(
416 command=command, raise_exception_on_error=False, env=env
417 )
quilesj26c78a42019-10-28 18:10:42 +0100418
419 else:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000420 self.log.debug("namespace not found")
quilesj26c78a42019-10-28 18:10:42 +0100421
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000422 async def _instances_list(self, cluster_id):
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000423 # init paths, env
424 paths, env = self._init_paths_env(
tiernoa5728bf2020-06-25 15:48:52 +0000425 cluster_name=cluster_id, create_if_not_exist=True
beierlmf52cb7c2020-04-21 16:36:35 -0400426 )
quilesj26c78a42019-10-28 18:10:42 +0100427
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000428 command = "{} list --output yaml".format(self._helm_command)
quilesj26c78a42019-10-28 18:10:42 +0100429
beierlmf52cb7c2020-04-21 16:36:35 -0400430 output, _rc = await self._local_async_exec(
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000431 command=command, raise_exception_on_error=True, env=env
beierlmf52cb7c2020-04-21 16:36:35 -0400432 )
quilesj26c78a42019-10-28 18:10:42 +0100433
434 if output and len(output) > 0:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000435 # parse yaml and update keys to lower case to unify with helm3
436 instances = yaml.load(output, Loader=yaml.SafeLoader).get("Releases")
437 new_instances = []
438 for instance in instances:
439 new_instance = dict((k.lower(), v) for k, v in instance.items())
440 new_instances.append(new_instance)
441 return new_instances
quilesj26c78a42019-10-28 18:10:42 +0100442 else:
443 return []
444
garciadeblas82b591c2021-03-24 09:22:13 +0100445 def _get_inspect_command(
446 self, show_command: str, kdu_model: str, repo_str: str, version: str
447 ):
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000448 inspect_command = "{} inspect {} {}{} {}".format(
449 self._helm_command, show_command, kdu_model, repo_str, version
beierlmf52cb7c2020-04-21 16:36:35 -0400450 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000451 return inspect_command
quilesj1be06302019-11-29 11:17:11 +0000452
aktas867418c2021-10-19 18:26:13 +0300453 def _get_get_command(
454 self, get_command: str, kdu_instance: str, namespace: str, kubeconfig: str
455 ):
456 get_command = "env KUBECONFIG={} {} get {} {} --output yaml".format(
457 kubeconfig, self._helm_command, get_command, kdu_instance
458 )
459 return get_command
460
quilesj26c78a42019-10-28 18:10:42 +0100461 async def _status_kdu(
beierlmf52cb7c2020-04-21 16:36:35 -0400462 self,
tiernoa5728bf2020-06-25 15:48:52 +0000463 cluster_id: str,
beierlmf52cb7c2020-04-21 16:36:35 -0400464 kdu_instance: str,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000465 namespace: str = None,
Pedro Escaleiraa8980cc2022-04-05 17:32:13 +0100466 yaml_format: bool = False,
beierlmf52cb7c2020-04-21 16:36:35 -0400467 show_error_log: bool = False,
Pedro Escaleiraa8980cc2022-04-05 17:32:13 +0100468 ) -> Union[str, dict]:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000469 self.log.debug(
470 "status of kdu_instance: {}, namespace: {} ".format(kdu_instance, namespace)
471 )
quilesj26c78a42019-10-28 18:10:42 +0100472
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000473 # init config, env
474 paths, env = self._init_paths_env(
tiernoa5728bf2020-06-25 15:48:52 +0000475 cluster_name=cluster_id, create_if_not_exist=True
beierlmf52cb7c2020-04-21 16:36:35 -0400476 )
bravof7bd5c6a2021-11-17 11:14:57 -0300477 command = ("env KUBECONFIG={} {} status {} --output yaml").format(
478 paths["kube_config"], self._helm_command, kdu_instance
479 )
quilesj26c78a42019-10-28 18:10:42 +0100480 output, rc = await self._local_async_exec(
481 command=command,
482 raise_exception_on_error=True,
beierlmf52cb7c2020-04-21 16:36:35 -0400483 show_error_log=show_error_log,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000484 env=env,
quilesj26c78a42019-10-28 18:10:42 +0100485 )
486
Pedro Escaleiraa8980cc2022-04-05 17:32:13 +0100487 if yaml_format:
quilesj1be06302019-11-29 11:17:11 +0000488 return str(output)
489
quilesj26c78a42019-10-28 18:10:42 +0100490 if rc != 0:
491 return None
492
493 data = yaml.load(output, Loader=yaml.SafeLoader)
494
495 # remove field 'notes'
496 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400497 del data.get("info").get("status")["notes"]
quilesj26c78a42019-10-28 18:10:42 +0100498 except KeyError:
499 pass
500
Pedro Escaleiraed0ff052022-04-03 13:51:46 +0100501 # parse the manifest to a list of dictionaries
502 if "manifest" in data:
503 manifest_str = data.get("manifest")
504 manifest_docs = yaml.load_all(manifest_str, Loader=yaml.SafeLoader)
505
506 data["manifest"] = []
507 for doc in manifest_docs:
508 data["manifest"].append(doc)
509
quilesj26c78a42019-10-28 18:10:42 +0100510 # parse field 'resources'
511 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400512 resources = str(data.get("info").get("status").get("resources"))
quilesj26c78a42019-10-28 18:10:42 +0100513 resource_table = self._output_to_table(resources)
beierlmf52cb7c2020-04-21 16:36:35 -0400514 data.get("info").get("status")["resources"] = resource_table
515 except Exception:
quilesj26c78a42019-10-28 18:10:42 +0100516 pass
517
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000518 # set description to lowercase (unify with helm3)
519 try:
520 data.get("info")["description"] = data.get("info").pop("Description")
521 except KeyError:
522 pass
523
quilesj26c78a42019-10-28 18:10:42 +0100524 return data
525
lloretgalleg095392b2020-11-20 11:28:08 +0000526 def _get_helm_chart_repos_ids(self, cluster_uuid) -> list:
527 repo_ids = []
528 cluster_filter = {"_admin.helm-chart.id": cluster_uuid}
529 cluster = self.db.get_one("k8sclusters", cluster_filter)
530 if cluster:
531 repo_ids = cluster.get("_admin").get("helm_chart_repos") or []
532 return repo_ids
533 else:
534 raise K8sException(
535 "k8cluster with helm-id : {} not found".format(cluster_uuid)
536 )
537
tiernoa5728bf2020-06-25 15:48:52 +0000538 async def _is_install_completed(self, cluster_id: str, kdu_instance: str) -> bool:
bravof7bd5c6a2021-11-17 11:14:57 -0300539 # init config, env
540 paths, env = self._init_paths_env(
541 cluster_name=cluster_id, create_if_not_exist=True
542 )
quilesj26c78a42019-10-28 18:10:42 +0100543
beierlmf52cb7c2020-04-21 16:36:35 -0400544 status = await self._status_kdu(
Pedro Escaleiraa8980cc2022-04-05 17:32:13 +0100545 cluster_id=cluster_id, kdu_instance=kdu_instance, yaml_format=False
beierlmf52cb7c2020-04-21 16:36:35 -0400546 )
quilesj26c78a42019-10-28 18:10:42 +0100547
548 # extract info.status.resources-> str
549 # format:
550 # ==> v1/Deployment
551 # NAME READY UP-TO-DATE AVAILABLE AGE
552 # halting-horse-mongodb 0/1 1 0 0s
553 # halting-petit-mongodb 1/1 1 0 0s
554 # blank line
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000555 resources = K8sHelmBaseConnector._get_deep(
556 status, ("info", "status", "resources")
557 )
quilesj26c78a42019-10-28 18:10:42 +0100558
559 # convert to table
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000560 resources = K8sHelmBaseConnector._output_to_table(resources)
quilesj26c78a42019-10-28 18:10:42 +0100561
562 num_lines = len(resources)
563 index = 0
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000564 ready = True
quilesj26c78a42019-10-28 18:10:42 +0100565 while index < num_lines:
566 try:
567 line1 = resources[index]
568 index += 1
569 # find '==>' in column 0
beierlmf52cb7c2020-04-21 16:36:35 -0400570 if line1[0] == "==>":
quilesj26c78a42019-10-28 18:10:42 +0100571 line2 = resources[index]
572 index += 1
573 # find READY in column 1
beierlmf52cb7c2020-04-21 16:36:35 -0400574 if line2[1] == "READY":
quilesj26c78a42019-10-28 18:10:42 +0100575 # read next lines
576 line3 = resources[index]
577 index += 1
578 while len(line3) > 1 and index < num_lines:
579 ready_value = line3[1]
beierlmf52cb7c2020-04-21 16:36:35 -0400580 parts = ready_value.split(sep="/")
quilesj26c78a42019-10-28 18:10:42 +0100581 current = int(parts[0])
582 total = int(parts[1])
583 if current < total:
beierlmf52cb7c2020-04-21 16:36:35 -0400584 self.log.debug("NOT READY:\n {}".format(line3))
quilesj26c78a42019-10-28 18:10:42 +0100585 ready = False
586 line3 = resources[index]
587 index += 1
588
beierlmf52cb7c2020-04-21 16:36:35 -0400589 except Exception:
quilesj26c78a42019-10-28 18:10:42 +0100590 pass
591
592 return ready
593
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000594 def _get_install_command(
bravof7bd5c6a2021-11-17 11:14:57 -0300595 self,
596 kdu_model,
597 kdu_instance,
598 namespace,
599 params_str,
600 version,
601 atomic,
602 timeout,
603 kubeconfig,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000604 ) -> str:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000605 timeout_str = ""
606 if timeout:
607 timeout_str = "--timeout {}".format(timeout)
lloretgallegd99f3f22020-06-29 14:18:30 +0000608
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000609 # atomic
610 atomic_str = ""
611 if atomic:
612 atomic_str = "--atomic"
613 # namespace
614 namespace_str = ""
615 if namespace:
616 namespace_str = "--namespace {}".format(namespace)
lloretgallegd99f3f22020-06-29 14:18:30 +0000617
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000618 # version
619 version_str = ""
620 if version:
aktas867418c2021-10-19 18:26:13 +0300621 version_str = "--version {}".format(version)
lloretgallegd99f3f22020-06-29 14:18:30 +0000622
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000623 command = (
bravof7bd5c6a2021-11-17 11:14:57 -0300624 "env KUBECONFIG={kubeconfig} {helm} install {atomic} --output yaml "
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000625 "{params} {timeout} --name={name} {ns} {model} {ver}".format(
bravof7bd5c6a2021-11-17 11:14:57 -0300626 kubeconfig=kubeconfig,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000627 helm=self._helm_command,
628 atomic=atomic_str,
629 params=params_str,
630 timeout=timeout_str,
631 name=kdu_instance,
632 ns=namespace_str,
633 model=kdu_model,
634 ver=version_str,
beierlmf52cb7c2020-04-21 16:36:35 -0400635 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000636 )
637 return command
quilesj26c78a42019-10-28 18:10:42 +0100638
aktas867418c2021-10-19 18:26:13 +0300639 def _get_upgrade_scale_command(
640 self,
641 kdu_model: str,
642 kdu_instance: str,
643 namespace: str,
644 scale: int,
645 version: str,
646 atomic: bool,
647 replica_str: str,
648 timeout: float,
649 resource_name: str,
650 kubeconfig: str,
651 ) -> str:
Pedro Escaleira44bd0682022-07-07 22:18:35 +0100652 """Generates the command to scale a Helm Chart release
aktas867418c2021-10-19 18:26:13 +0300653
Pedro Escaleira44bd0682022-07-07 22:18:35 +0100654 Args:
655 kdu_model (str): Kdu model name, corresponding to the Helm local location or repository
656 kdu_instance (str): KDU instance, corresponding to the Helm Chart release in question
657 namespace (str): Namespace where this KDU instance is deployed
658 scale (int): Scale count
659 version (str): Constraint with specific version of the Chart to use
660 atomic (bool): If set, upgrade process rolls back changes made in case of failed upgrade.
661 The --wait flag will be set automatically if --atomic is used
662 replica_str (str): The key under resource_name key where the scale count is stored
663 timeout (float): The time, in seconds, to wait
664 resource_name (str): The KDU's resource to scale
665 kubeconfig (str): Kubeconfig file path
aktas867418c2021-10-19 18:26:13 +0300666
Pedro Escaleira44bd0682022-07-07 22:18:35 +0100667 Returns:
668 str: command to scale a Helm Chart release
669 """
aktas867418c2021-10-19 18:26:13 +0300670
671 # scale
672 if resource_name:
673 scale_dict = {"{}.{}".format(resource_name, replica_str): scale}
674 else:
675 scale_dict = {replica_str: scale}
676
677 scale_str = self._params_to_set_option(scale_dict)
678
Pedro Escaleira44bd0682022-07-07 22:18:35 +0100679 return self._get_upgrade_command(
680 kdu_model=kdu_model,
681 kdu_instance=kdu_instance,
682 namespace=namespace,
683 params_str=scale_str,
684 version=version,
685 atomic=atomic,
686 timeout=timeout,
aktas867418c2021-10-19 18:26:13 +0300687 kubeconfig=kubeconfig,
688 )
aktas867418c2021-10-19 18:26:13 +0300689
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000690 def _get_upgrade_command(
bravof7bd5c6a2021-11-17 11:14:57 -0300691 self,
692 kdu_model,
693 kdu_instance,
694 namespace,
695 params_str,
696 version,
697 atomic,
698 timeout,
699 kubeconfig,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000700 ) -> str:
Pedro Escaleira44bd0682022-07-07 22:18:35 +0100701 """Generates the command to upgrade a Helm Chart release
702
703 Args:
704 kdu_model (str): Kdu model name, corresponding to the Helm local location or repository
705 kdu_instance (str): KDU instance, corresponding to the Helm Chart release in question
706 namespace (str): Namespace where this KDU instance is deployed
707 params_str (str): Params used to upgrade the Helm Chart release
708 version (str): Constraint with specific version of the Chart to use
709 atomic (bool): If set, upgrade process rolls back changes made in case of failed upgrade.
710 The --wait flag will be set automatically if --atomic is used
711 timeout (float): The time, in seconds, to wait
712 kubeconfig (str): Kubeconfig file path
713
714 Returns:
715 str: command to upgrade a Helm Chart release
716 """
quilesj26c78a42019-10-28 18:10:42 +0100717
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000718 timeout_str = ""
719 if timeout:
720 timeout_str = "--timeout {}".format(timeout)
quilesj26c78a42019-10-28 18:10:42 +0100721
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000722 # atomic
723 atomic_str = ""
724 if atomic:
725 atomic_str = "--atomic"
quilesj26c78a42019-10-28 18:10:42 +0100726
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000727 # version
728 version_str = ""
729 if version:
730 version_str = "--version {}".format(version)
quilesj26c78a42019-10-28 18:10:42 +0100731
bravof7bd5c6a2021-11-17 11:14:57 -0300732 command = (
Pedro Escaleira44bd0682022-07-07 22:18:35 +0100733 "env KUBECONFIG={kubeconfig} {helm} upgrade {atomic} --output yaml {params} {timeout} "
734 "--reuse-values {name} {model} {ver}"
bravof7bd5c6a2021-11-17 11:14:57 -0300735 ).format(
736 kubeconfig=kubeconfig,
garciadeblas82b591c2021-03-24 09:22:13 +0100737 helm=self._helm_command,
738 atomic=atomic_str,
739 params=params_str,
740 timeout=timeout_str,
741 name=kdu_instance,
742 model=kdu_model,
743 ver=version_str,
744 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000745 return command
quilesj26c78a42019-10-28 18:10:42 +0100746
bravof7bd5c6a2021-11-17 11:14:57 -0300747 def _get_rollback_command(
748 self, kdu_instance, namespace, revision, kubeconfig
749 ) -> str:
750 return "env KUBECONFIG={} {} rollback {} {} --wait".format(
751 kubeconfig, self._helm_command, kdu_instance, revision
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000752 )
quilesj26c78a42019-10-28 18:10:42 +0100753
bravof7bd5c6a2021-11-17 11:14:57 -0300754 def _get_uninstall_command(
755 self, kdu_instance: str, namespace: str, kubeconfig: str
756 ) -> str:
757 return "env KUBECONFIG={} {} delete --purge {}".format(
758 kubeconfig, self._helm_command, kdu_instance
759 )