blob: 383ce7d78897f74c1c9de4124720f140e01fa871 [file] [log] [blame]
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001##
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##
22import abc
23import asyncio
Pedro Escaleiraa8980cc2022-04-05 17:32:13 +010024from typing import Union
Daniel Arndtde6984b2023-06-27 16:42:41 -030025from shlex import quote
lloretgalleg1c83f2e2020-10-22 09:12:35 +000026import random
27import time
28import shlex
29import shutil
30import stat
lloretgalleg1c83f2e2020-10-22 09:12:35 +000031import os
32import yaml
33from uuid import uuid4
34
David Garcia4395cfa2021-05-28 16:21:51 +020035from n2vc.config import EnvironConfig
lloretgalleg1c83f2e2020-10-22 09:12:35 +000036from n2vc.exceptions import K8sException
37from n2vc.k8s_conn import K8sConnector
Gabriel Cubafb03e902022-10-07 11:40:03 -050038from n2vc.kubectl import Kubectl
lloretgalleg1c83f2e2020-10-22 09:12:35 +000039
40
41class K8sHelmBaseConnector(K8sConnector):
42
43 """
44 ####################################################################################
45 ################################### P U B L I C ####################################
46 ####################################################################################
47 """
garciadeblas82b591c2021-03-24 09:22:13 +010048
lloretgalleg1c83f2e2020-10-22 09:12:35 +000049 service_account = "osm"
50
51 def __init__(
52 self,
53 fs: object,
54 db: object,
55 kubectl_command: str = "/usr/bin/kubectl",
56 helm_command: str = "/usr/bin/helm",
57 log: object = None,
58 on_update_db=None,
59 ):
60 """
61
62 :param fs: file system for kubernetes and helm configuration
63 :param db: database object to write current operation status
64 :param kubectl_command: path to kubectl executable
65 :param helm_command: path to helm executable
66 :param log: logger
67 :param on_update_db: callback called when k8s connector updates database
68 """
69
70 # parent class
71 K8sConnector.__init__(self, db=db, log=log, on_update_db=on_update_db)
72
73 self.log.info("Initializing K8S Helm connector")
74
David Garcia4395cfa2021-05-28 16:21:51 +020075 self.config = EnvironConfig()
lloretgalleg1c83f2e2020-10-22 09:12:35 +000076 # random numbers for release name generation
77 random.seed(time.time())
78
79 # the file system
80 self.fs = fs
81
82 # exception if kubectl is not installed
83 self.kubectl_command = kubectl_command
84 self._check_file_exists(filename=kubectl_command, exception_if_not_exists=True)
85
86 # exception if helm is not installed
87 self._helm_command = helm_command
88 self._check_file_exists(filename=helm_command, exception_if_not_exists=True)
89
lloretgalleg83e55892020-12-17 12:42:11 +000090 # obtain stable repo url from config or apply default
David Garcia4395cfa2021-05-28 16:21:51 +020091 self._stable_repo_url = self.config.get("stablerepourl")
92 if self._stable_repo_url == "None":
93 self._stable_repo_url = None
lloretgalleg83e55892020-12-17 12:42:11 +000094
Pedro Escaleira1f222a92022-06-20 15:40:43 +010095 # Lock to avoid concurrent execution of helm commands
96 self.cmd_lock = asyncio.Lock()
97
Pedro Escaleirab41de172022-04-02 00:44:08 +010098 def _get_namespace(self, cluster_uuid: str) -> str:
lloretgalleg1c83f2e2020-10-22 09:12:35 +000099 """
Pedro Escaleirab41de172022-04-02 00:44:08 +0100100 Obtains the namespace used by the cluster with the uuid passed by argument
101
102 param: cluster_uuid: cluster's uuid
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000103 """
Pedro Escaleirab41de172022-04-02 00:44:08 +0100104
105 # first, obtain the cluster corresponding to the uuid passed by argument
106 k8scluster = self.db.get_one(
107 "k8sclusters", q_filter={"_id": cluster_uuid}, fail_on_empty=False
108 )
109 return k8scluster.get("namespace")
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000110
111 async def init_env(
garciadeblas82b591c2021-03-24 09:22:13 +0100112 self,
113 k8s_creds: str,
114 namespace: str = "kube-system",
115 reuse_cluster_uuid=None,
116 **kwargs,
Daniel Arndtde6984b2023-06-27 16:42:41 -0300117 ) -> tuple[str, bool]:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000118 """
119 It prepares a given K8s cluster environment to run Charts
120
121 :param k8s_creds: credentials to access a given K8s cluster, i.e. a valid
122 '.kube/config'
123 :param namespace: optional namespace to be used for helm. By default,
124 'kube-system' will be used
125 :param reuse_cluster_uuid: existing cluster uuid for reuse
David Garciaeb8943a2021-04-12 12:07:37 +0200126 :param kwargs: Additional parameters (None yet)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000127 :return: uuid of the K8s cluster and True if connector has installed some
128 software in the cluster
129 (on error, an exception will be raised)
130 """
131
132 if reuse_cluster_uuid:
Pedro Escaleirab41de172022-04-02 00:44:08 +0100133 cluster_id = reuse_cluster_uuid
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000134 else:
135 cluster_id = str(uuid4())
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000136
garciadeblas82b591c2021-03-24 09:22:13 +0100137 self.log.debug(
138 "Initializing K8S Cluster {}. namespace: {}".format(cluster_id, namespace)
139 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000140
141 paths, env = self._init_paths_env(
142 cluster_name=cluster_id, create_if_not_exist=True
143 )
144 mode = stat.S_IRUSR | stat.S_IWUSR
145 with open(paths["kube_config"], "w", mode) as f:
146 f.write(k8s_creds)
147 os.chmod(paths["kube_config"], 0o600)
148
149 # Code with initialization specific of helm version
150 n2vc_installed_sw = await self._cluster_init(cluster_id, namespace, paths, env)
151
152 # sync fs with local data
153 self.fs.reverse_sync(from_path=cluster_id)
154
155 self.log.info("Cluster {} initialized".format(cluster_id))
156
Pedro Escaleirab41de172022-04-02 00:44:08 +0100157 return cluster_id, n2vc_installed_sw
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000158
159 async def repo_add(
bravof0ab522f2021-11-23 19:33:18 -0300160 self,
161 cluster_uuid: str,
162 name: str,
163 url: str,
164 repo_type: str = "chart",
165 cert: str = None,
166 user: str = None,
167 password: str = None,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000168 ):
garciadeblas82b591c2021-03-24 09:22:13 +0100169 self.log.debug(
170 "Cluster {}, adding {} repository {}. URL: {}".format(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100171 cluster_uuid, repo_type, name, url
garciadeblas82b591c2021-03-24 09:22:13 +0100172 )
173 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000174
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000175 # init_env
176 paths, env = self._init_paths_env(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100177 cluster_name=cluster_uuid, create_if_not_exist=True
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000178 )
179
bravof7bd5c6a2021-11-17 11:14:57 -0300180 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +0100181 self.fs.sync(from_path=cluster_uuid)
bravof7bd5c6a2021-11-17 11:14:57 -0300182
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000183 # helm repo add name url
bravof0ab522f2021-11-23 19:33:18 -0300184 command = ("env KUBECONFIG={} {} repo add {} {}").format(
Daniel Arndtde6984b2023-06-27 16:42:41 -0300185 paths["kube_config"], self._helm_command, quote(name), quote(url)
bravof7bd5c6a2021-11-17 11:14:57 -0300186 )
bravof0ab522f2021-11-23 19:33:18 -0300187
188 if cert:
189 temp_cert_file = os.path.join(
Pedro Escaleira1188b5d2022-04-22 18:51:00 +0100190 self.fs.path, "{}/helmcerts/".format(cluster_uuid), "temp.crt"
bravof0ab522f2021-11-23 19:33:18 -0300191 )
192 os.makedirs(os.path.dirname(temp_cert_file), exist_ok=True)
193 with open(temp_cert_file, "w") as the_cert:
194 the_cert.write(cert)
Daniel Arndtde6984b2023-06-27 16:42:41 -0300195 command += " --ca-file {}".format(quote(temp_cert_file))
bravof0ab522f2021-11-23 19:33:18 -0300196
197 if user:
Daniel Arndtde6984b2023-06-27 16:42:41 -0300198 command += " --username={}".format(quote(user))
bravof0ab522f2021-11-23 19:33:18 -0300199
200 if password:
Daniel Arndtde6984b2023-06-27 16:42:41 -0300201 command += " --password={}".format(quote(password))
bravof0ab522f2021-11-23 19:33:18 -0300202
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000203 self.log.debug("adding repo: {}".format(command))
garciadeblas82b591c2021-03-24 09:22:13 +0100204 await self._local_async_exec(
205 command=command, raise_exception_on_error=True, env=env
206 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000207
garciadeblasd4cee8c2022-05-04 10:57:36 +0200208 # helm repo update
garciadeblas069f0a32022-05-04 11:07:41 +0200209 command = "env KUBECONFIG={} {} repo update {}".format(
Daniel Arndtde6984b2023-06-27 16:42:41 -0300210 paths["kube_config"], self._helm_command, quote(name)
garciadeblasd4cee8c2022-05-04 10:57:36 +0200211 )
212 self.log.debug("updating repo: {}".format(command))
213 await self._local_async_exec(
214 command=command, raise_exception_on_error=False, env=env
215 )
216
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000217 # sync fs
Pedro Escaleirab41de172022-04-02 00:44:08 +0100218 self.fs.reverse_sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000219
garciadeblas7faf4ec2022-04-08 22:53:25 +0200220 async def repo_update(self, cluster_uuid: str, name: str, repo_type: str = "chart"):
221 self.log.debug(
222 "Cluster {}, updating {} repository {}".format(
223 cluster_uuid, repo_type, name
224 )
225 )
226
227 # init_env
228 paths, env = self._init_paths_env(
229 cluster_name=cluster_uuid, create_if_not_exist=True
230 )
231
232 # sync local dir
233 self.fs.sync(from_path=cluster_uuid)
234
235 # helm repo update
Daniel Arndtde6984b2023-06-27 16:42:41 -0300236 command = "{} repo update {}".format(self._helm_command, quote(name))
garciadeblas7faf4ec2022-04-08 22:53:25 +0200237 self.log.debug("updating repo: {}".format(command))
238 await self._local_async_exec(
239 command=command, raise_exception_on_error=False, env=env
240 )
241
242 # sync fs
243 self.fs.reverse_sync(from_path=cluster_uuid)
244
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000245 async def repo_list(self, cluster_uuid: str) -> list:
246 """
247 Get the list of registered repositories
248
249 :return: list of registered repositories: [ (name, url) .... ]
250 """
251
Pedro Escaleirab41de172022-04-02 00:44:08 +0100252 self.log.debug("list repositories for cluster {}".format(cluster_uuid))
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000253
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000254 # config filename
255 paths, env = self._init_paths_env(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100256 cluster_name=cluster_uuid, create_if_not_exist=True
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000257 )
258
bravof7bd5c6a2021-11-17 11:14:57 -0300259 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +0100260 self.fs.sync(from_path=cluster_uuid)
bravof7bd5c6a2021-11-17 11:14:57 -0300261
262 command = "env KUBECONFIG={} {} repo list --output yaml".format(
263 paths["kube_config"], self._helm_command
264 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000265
266 # Set exception to false because if there are no repos just want an empty list
267 output, _rc = await self._local_async_exec(
268 command=command, raise_exception_on_error=False, env=env
269 )
270
271 # sync fs
Pedro Escaleirab41de172022-04-02 00:44:08 +0100272 self.fs.reverse_sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000273
274 if _rc == 0:
275 if output and len(output) > 0:
276 repos = yaml.load(output, Loader=yaml.SafeLoader)
277 # unify format between helm2 and helm3 setting all keys lowercase
278 return self._lower_keys_list(repos)
279 else:
280 return []
281 else:
282 return []
283
284 async def repo_remove(self, cluster_uuid: str, name: str):
Pedro Escaleirab41de172022-04-02 00:44:08 +0100285 self.log.debug(
286 "remove {} repositories for cluster {}".format(name, cluster_uuid)
287 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000288
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000289 # init env, paths
290 paths, env = self._init_paths_env(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100291 cluster_name=cluster_uuid, create_if_not_exist=True
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000292 )
293
bravof7bd5c6a2021-11-17 11:14:57 -0300294 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +0100295 self.fs.sync(from_path=cluster_uuid)
bravof7bd5c6a2021-11-17 11:14:57 -0300296
297 command = "env KUBECONFIG={} {} repo remove {}".format(
Daniel Arndtde6984b2023-06-27 16:42:41 -0300298 paths["kube_config"], self._helm_command, quote(name)
bravof7bd5c6a2021-11-17 11:14:57 -0300299 )
garciadeblas82b591c2021-03-24 09:22:13 +0100300 await self._local_async_exec(
301 command=command, raise_exception_on_error=True, env=env
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000302 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000303
304 # sync fs
Pedro Escaleirab41de172022-04-02 00:44:08 +0100305 self.fs.reverse_sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000306
307 async def reset(
garciadeblas82b591c2021-03-24 09:22:13 +0100308 self,
309 cluster_uuid: str,
310 force: bool = False,
311 uninstall_sw: bool = False,
312 **kwargs,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000313 ) -> bool:
David Garciaeb8943a2021-04-12 12:07:37 +0200314 """Reset a cluster
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000315
David Garciaeb8943a2021-04-12 12:07:37 +0200316 Resets the Kubernetes cluster by removing the helm deployment that represents it.
317
318 :param cluster_uuid: The UUID of the cluster to reset
319 :param force: Boolean to force the reset
320 :param uninstall_sw: Boolean to force the reset
321 :param kwargs: Additional parameters (None yet)
322 :return: Returns True if successful or raises an exception.
323 """
Pedro Escaleirab41de172022-04-02 00:44:08 +0100324 namespace = self._get_namespace(cluster_uuid=cluster_uuid)
garciadeblas82b591c2021-03-24 09:22:13 +0100325 self.log.debug(
326 "Resetting K8s environment. cluster uuid: {} uninstall={}".format(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100327 cluster_uuid, uninstall_sw
garciadeblas82b591c2021-03-24 09:22:13 +0100328 )
329 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000330
331 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +0100332 self.fs.sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000333
334 # uninstall releases if needed.
335 if uninstall_sw:
336 releases = await self.instances_list(cluster_uuid=cluster_uuid)
337 if len(releases) > 0:
338 if force:
339 for r in releases:
340 try:
341 kdu_instance = r.get("name")
342 chart = r.get("chart")
343 self.log.debug(
344 "Uninstalling {} -> {}".format(chart, kdu_instance)
345 )
346 await self.uninstall(
347 cluster_uuid=cluster_uuid, kdu_instance=kdu_instance
348 )
349 except Exception as e:
350 # will not raise exception as it was found
351 # that in some cases of previously installed helm releases it
352 # raised an error
353 self.log.warn(
garciadeblas82b591c2021-03-24 09:22:13 +0100354 "Error uninstalling release {}: {}".format(
355 kdu_instance, e
356 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000357 )
358 else:
359 msg = (
360 "Cluster uuid: {} has releases and not force. Leaving K8s helm environment"
Pedro Escaleirab41de172022-04-02 00:44:08 +0100361 ).format(cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000362 self.log.warn(msg)
garciadeblas82b591c2021-03-24 09:22:13 +0100363 uninstall_sw = (
364 False # Allow to remove k8s cluster without removing Tiller
365 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000366
367 if uninstall_sw:
Pedro Escaleirab41de172022-04-02 00:44:08 +0100368 await self._uninstall_sw(cluster_id=cluster_uuid, namespace=namespace)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000369
370 # delete cluster directory
Pedro Escaleirab41de172022-04-02 00:44:08 +0100371 self.log.debug("Removing directory {}".format(cluster_uuid))
372 self.fs.file_delete(cluster_uuid, ignore_non_exist=True)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000373 # Remove also local directorio if still exist
Pedro Escaleirab41de172022-04-02 00:44:08 +0100374 direct = self.fs.path + "/" + cluster_uuid
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000375 shutil.rmtree(direct, ignore_errors=True)
376
377 return True
378
garciadeblas04393192022-06-08 15:39:24 +0200379 def _is_helm_chart_a_file(self, chart_name: str):
380 return chart_name.count("/") > 1
381
lloretgalleg095392b2020-11-20 11:28:08 +0000382 async def _install_impl(
garciadeblas82b591c2021-03-24 09:22:13 +0100383 self,
384 cluster_id: str,
385 kdu_model: str,
386 paths: dict,
387 env: dict,
388 kdu_instance: str,
389 atomic: bool = True,
390 timeout: float = 300,
391 params: dict = None,
392 db_dict: dict = None,
393 kdu_name: str = None,
394 namespace: str = None,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000395 ):
bravof7bd5c6a2021-11-17 11:14:57 -0300396 # init env, paths
397 paths, env = self._init_paths_env(
398 cluster_name=cluster_id, create_if_not_exist=True
399 )
400
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000401 # params to str
402 params_str, file_to_delete = self._params_to_file_option(
403 cluster_id=cluster_id, params=params
404 )
405
406 # version
aktas867418c2021-10-19 18:26:13 +0300407 kdu_model, version = self._split_version(kdu_model)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000408
Pedro Escaleira0fcb6fe2022-06-04 19:14:11 +0100409 _, repo = self._split_repo(kdu_model)
garciadeblas7faf4ec2022-04-08 22:53:25 +0200410 if repo:
limon3c443f52022-07-21 13:55:55 +0200411 await self.repo_update(cluster_id, repo)
garciadeblas7faf4ec2022-04-08 22:53:25 +0200412
garciadeblas82b591c2021-03-24 09:22:13 +0100413 command = self._get_install_command(
bravof7bd5c6a2021-11-17 11:14:57 -0300414 kdu_model,
415 kdu_instance,
416 namespace,
417 params_str,
418 version,
419 atomic,
420 timeout,
421 paths["kube_config"],
garciadeblas82b591c2021-03-24 09:22:13 +0100422 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000423
424 self.log.debug("installing: {}".format(command))
425
426 if atomic:
427 # exec helm in a task
428 exec_task = asyncio.ensure_future(
429 coro_or_future=self._local_async_exec(
430 command=command, raise_exception_on_error=False, env=env
431 )
432 )
433
434 # write status in another task
435 status_task = asyncio.ensure_future(
436 coro_or_future=self._store_status(
437 cluster_id=cluster_id,
438 kdu_instance=kdu_instance,
439 namespace=namespace,
440 db_dict=db_dict,
441 operation="install",
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000442 )
443 )
444
445 # wait for execution task
446 await asyncio.wait([exec_task])
447
448 # cancel status task
449 status_task.cancel()
450
451 output, rc = exec_task.result()
452
453 else:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000454 output, rc = await self._local_async_exec(
455 command=command, raise_exception_on_error=False, env=env
456 )
457
458 # remove temporal values yaml file
459 if file_to_delete:
460 os.remove(file_to_delete)
461
462 # write final status
463 await self._store_status(
464 cluster_id=cluster_id,
465 kdu_instance=kdu_instance,
466 namespace=namespace,
467 db_dict=db_dict,
468 operation="install",
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000469 )
470
471 if rc != 0:
472 msg = "Error executing command: {}\nOutput: {}".format(command, output)
473 self.log.error(msg)
474 raise K8sException(msg)
475
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000476 async def upgrade(
477 self,
478 cluster_uuid: str,
479 kdu_instance: str,
480 kdu_model: str = None,
481 atomic: bool = True,
482 timeout: float = 300,
483 params: dict = None,
484 db_dict: dict = None,
Gabriel Cuba085fa8d2022-10-10 12:13:55 -0500485 namespace: str = None,
486 force: bool = False,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000487 ):
Pedro Escaleirab41de172022-04-02 00:44:08 +0100488 self.log.debug("upgrading {} in cluster {}".format(kdu_model, cluster_uuid))
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000489
490 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +0100491 self.fs.sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000492
493 # look for instance to obtain namespace
Gabriel Cuba085fa8d2022-10-10 12:13:55 -0500494
495 # set namespace
496 if not namespace:
497 instance_info = await self.get_instance_info(cluster_uuid, kdu_instance)
498 if not instance_info:
499 raise K8sException("kdu_instance {} not found".format(kdu_instance))
500 namespace = instance_info["namespace"]
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000501
502 # init env, paths
503 paths, env = self._init_paths_env(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100504 cluster_name=cluster_uuid, create_if_not_exist=True
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000505 )
506
bravof7bd5c6a2021-11-17 11:14:57 -0300507 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +0100508 self.fs.sync(from_path=cluster_uuid)
bravof7bd5c6a2021-11-17 11:14:57 -0300509
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000510 # params to str
511 params_str, file_to_delete = self._params_to_file_option(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100512 cluster_id=cluster_uuid, params=params
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000513 )
514
515 # version
aktas867418c2021-10-19 18:26:13 +0300516 kdu_model, version = self._split_version(kdu_model)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000517
Pedro Escaleira0fcb6fe2022-06-04 19:14:11 +0100518 _, repo = self._split_repo(kdu_model)
garciadeblas7faf4ec2022-04-08 22:53:25 +0200519 if repo:
limon3c443f52022-07-21 13:55:55 +0200520 await self.repo_update(cluster_uuid, repo)
garciadeblas7faf4ec2022-04-08 22:53:25 +0200521
garciadeblas82b591c2021-03-24 09:22:13 +0100522 command = self._get_upgrade_command(
523 kdu_model,
524 kdu_instance,
Gabriel Cuba085fa8d2022-10-10 12:13:55 -0500525 namespace,
garciadeblas82b591c2021-03-24 09:22:13 +0100526 params_str,
527 version,
528 atomic,
529 timeout,
bravof7bd5c6a2021-11-17 11:14:57 -0300530 paths["kube_config"],
Gabriel Cuba085fa8d2022-10-10 12:13:55 -0500531 force,
garciadeblas82b591c2021-03-24 09:22:13 +0100532 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000533
534 self.log.debug("upgrading: {}".format(command))
535
536 if atomic:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000537 # exec helm in a task
538 exec_task = asyncio.ensure_future(
539 coro_or_future=self._local_async_exec(
540 command=command, raise_exception_on_error=False, env=env
541 )
542 )
543 # write status in another task
544 status_task = asyncio.ensure_future(
545 coro_or_future=self._store_status(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100546 cluster_id=cluster_uuid,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000547 kdu_instance=kdu_instance,
Gabriel Cuba085fa8d2022-10-10 12:13:55 -0500548 namespace=namespace,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000549 db_dict=db_dict,
550 operation="upgrade",
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000551 )
552 )
553
554 # wait for execution task
555 await asyncio.wait([exec_task])
556
557 # cancel status task
558 status_task.cancel()
559 output, rc = exec_task.result()
560
561 else:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000562 output, rc = await self._local_async_exec(
563 command=command, raise_exception_on_error=False, env=env
564 )
565
566 # remove temporal values yaml file
567 if file_to_delete:
568 os.remove(file_to_delete)
569
570 # write final status
571 await self._store_status(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100572 cluster_id=cluster_uuid,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000573 kdu_instance=kdu_instance,
Gabriel Cuba085fa8d2022-10-10 12:13:55 -0500574 namespace=namespace,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000575 db_dict=db_dict,
576 operation="upgrade",
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000577 )
578
579 if rc != 0:
580 msg = "Error executing command: {}\nOutput: {}".format(command, output)
581 self.log.error(msg)
582 raise K8sException(msg)
583
584 # sync fs
Pedro Escaleirab41de172022-04-02 00:44:08 +0100585 self.fs.reverse_sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000586
587 # return new revision number
588 instance = await self.get_instance_info(
589 cluster_uuid=cluster_uuid, kdu_instance=kdu_instance
590 )
591 if instance:
592 revision = int(instance.get("revision"))
593 self.log.debug("New revision: {}".format(revision))
594 return revision
595 else:
596 return 0
597
aktas2962f3e2021-03-15 11:05:35 +0300598 async def scale(
garciadeblas82b591c2021-03-24 09:22:13 +0100599 self,
600 kdu_instance: str,
601 scale: int,
602 resource_name: str,
603 total_timeout: float = 1800,
aktas867418c2021-10-19 18:26:13 +0300604 cluster_uuid: str = None,
605 kdu_model: str = None,
606 atomic: bool = True,
607 db_dict: dict = None,
garciadeblas82b591c2021-03-24 09:22:13 +0100608 **kwargs,
aktas2962f3e2021-03-15 11:05:35 +0300609 ):
aktas867418c2021-10-19 18:26:13 +0300610 """Scale a resource in a Helm Chart.
611
612 Args:
613 kdu_instance: KDU instance name
614 scale: Scale to which to set the resource
615 resource_name: Resource name
616 total_timeout: The time, in seconds, to wait
617 cluster_uuid: The UUID of the cluster
618 kdu_model: The chart reference
619 atomic: if set, upgrade process rolls back changes made in case of failed upgrade.
620 The --wait flag will be set automatically if --atomic is used
621 db_dict: Dictionary for any additional data
622 kwargs: Additional parameters
623
624 Returns:
625 True if successful, False otherwise
626 """
627
Pedro Escaleirab41de172022-04-02 00:44:08 +0100628 debug_mgs = "scaling {} in cluster {}".format(kdu_model, cluster_uuid)
aktas867418c2021-10-19 18:26:13 +0300629 if resource_name:
630 debug_mgs = "scaling resource {} in model {} (cluster {})".format(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100631 resource_name, kdu_model, cluster_uuid
aktas867418c2021-10-19 18:26:13 +0300632 )
633
634 self.log.debug(debug_mgs)
635
636 # look for instance to obtain namespace
637 # get_instance_info function calls the sync command
638 instance_info = await self.get_instance_info(cluster_uuid, kdu_instance)
639 if not instance_info:
640 raise K8sException("kdu_instance {} not found".format(kdu_instance))
641
642 # init env, paths
643 paths, env = self._init_paths_env(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100644 cluster_name=cluster_uuid, create_if_not_exist=True
aktas867418c2021-10-19 18:26:13 +0300645 )
646
647 # version
648 kdu_model, version = self._split_version(kdu_model)
649
650 repo_url = await self._find_repo(kdu_model, cluster_uuid)
aktas867418c2021-10-19 18:26:13 +0300651
652 _, replica_str = await self._get_replica_count_url(
653 kdu_model, repo_url, resource_name
654 )
655
656 command = self._get_upgrade_scale_command(
657 kdu_model,
658 kdu_instance,
659 instance_info["namespace"],
660 scale,
661 version,
662 atomic,
663 replica_str,
664 total_timeout,
665 resource_name,
666 paths["kube_config"],
667 )
668
669 self.log.debug("scaling: {}".format(command))
670
671 if atomic:
672 # exec helm in a task
673 exec_task = asyncio.ensure_future(
674 coro_or_future=self._local_async_exec(
675 command=command, raise_exception_on_error=False, env=env
676 )
677 )
678 # write status in another task
679 status_task = asyncio.ensure_future(
680 coro_or_future=self._store_status(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100681 cluster_id=cluster_uuid,
aktas867418c2021-10-19 18:26:13 +0300682 kdu_instance=kdu_instance,
683 namespace=instance_info["namespace"],
684 db_dict=db_dict,
685 operation="scale",
aktas867418c2021-10-19 18:26:13 +0300686 )
687 )
688
689 # wait for execution task
690 await asyncio.wait([exec_task])
691
692 # cancel status task
693 status_task.cancel()
694 output, rc = exec_task.result()
695
696 else:
697 output, rc = await self._local_async_exec(
698 command=command, raise_exception_on_error=False, env=env
699 )
700
701 # write final status
702 await self._store_status(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100703 cluster_id=cluster_uuid,
aktas867418c2021-10-19 18:26:13 +0300704 kdu_instance=kdu_instance,
705 namespace=instance_info["namespace"],
706 db_dict=db_dict,
707 operation="scale",
aktas867418c2021-10-19 18:26:13 +0300708 )
709
710 if rc != 0:
711 msg = "Error executing command: {}\nOutput: {}".format(command, output)
712 self.log.error(msg)
713 raise K8sException(msg)
714
715 # sync fs
Pedro Escaleirab41de172022-04-02 00:44:08 +0100716 self.fs.reverse_sync(from_path=cluster_uuid)
aktas867418c2021-10-19 18:26:13 +0300717
718 return True
aktas2962f3e2021-03-15 11:05:35 +0300719
720 async def get_scale_count(
garciadeblas82b591c2021-03-24 09:22:13 +0100721 self,
722 resource_name: str,
723 kdu_instance: str,
aktas867418c2021-10-19 18:26:13 +0300724 cluster_uuid: str,
725 kdu_model: str,
garciadeblas82b591c2021-03-24 09:22:13 +0100726 **kwargs,
aktas867418c2021-10-19 18:26:13 +0300727 ) -> int:
728 """Get a resource scale count.
729
730 Args:
731 cluster_uuid: The UUID of the cluster
732 resource_name: Resource name
733 kdu_instance: KDU instance name
Pedro Escaleira547f8232022-06-03 19:48:46 +0100734 kdu_model: The name or path of an Helm Chart
aktas867418c2021-10-19 18:26:13 +0300735 kwargs: Additional parameters
736
737 Returns:
738 Resource instance count
739 """
740
aktas867418c2021-10-19 18:26:13 +0300741 self.log.debug(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100742 "getting scale count for {} in cluster {}".format(kdu_model, cluster_uuid)
aktas867418c2021-10-19 18:26:13 +0300743 )
744
745 # look for instance to obtain namespace
746 instance_info = await self.get_instance_info(cluster_uuid, kdu_instance)
747 if not instance_info:
748 raise K8sException("kdu_instance {} not found".format(kdu_instance))
749
750 # init env, paths
Pedro Escaleira06313992022-06-04 22:21:57 +0100751 paths, _ = self._init_paths_env(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100752 cluster_name=cluster_uuid, create_if_not_exist=True
aktas867418c2021-10-19 18:26:13 +0300753 )
754
755 replicas = await self._get_replica_count_instance(
Pedro Escaleiraaa5deb72022-06-05 01:29:57 +0100756 kdu_instance=kdu_instance,
757 namespace=instance_info["namespace"],
758 kubeconfig=paths["kube_config"],
759 resource_name=resource_name,
aktas867418c2021-10-19 18:26:13 +0300760 )
761
Pedro Escaleira06313992022-06-04 22:21:57 +0100762 self.log.debug(
763 f"Number of replicas of the KDU instance {kdu_instance} and resource {resource_name} obtained: {replicas}"
764 )
765
aktas867418c2021-10-19 18:26:13 +0300766 # Get default value if scale count is not found from provided values
Pedro Escaleira06313992022-06-04 22:21:57 +0100767 # Important note: this piece of code shall only be executed in the first scaling operation,
768 # since it is expected that the _get_replica_count_instance is able to obtain the number of
769 # replicas when a scale operation was already conducted previously for this KDU/resource!
770 if replicas is None:
Pedro Escaleira547f8232022-06-03 19:48:46 +0100771 repo_url = await self._find_repo(
772 kdu_model=kdu_model, cluster_uuid=cluster_uuid
773 )
aktas867418c2021-10-19 18:26:13 +0300774 replicas, _ = await self._get_replica_count_url(
Pedro Escaleira547f8232022-06-03 19:48:46 +0100775 kdu_model=kdu_model, repo_url=repo_url, resource_name=resource_name
aktas867418c2021-10-19 18:26:13 +0300776 )
777
Pedro Escaleira06313992022-06-04 22:21:57 +0100778 self.log.debug(
779 f"Number of replicas of the Helm Chart package for KDU instance {kdu_instance} and resource "
780 f"{resource_name} obtained: {replicas}"
781 )
782
783 if replicas is None:
784 msg = "Replica count not found. Cannot be scaled"
785 self.log.error(msg)
786 raise K8sException(msg)
aktas867418c2021-10-19 18:26:13 +0300787
788 return int(replicas)
aktas2962f3e2021-03-15 11:05:35 +0300789
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000790 async def rollback(
791 self, cluster_uuid: str, kdu_instance: str, revision=0, db_dict: dict = None
792 ):
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000793 self.log.debug(
794 "rollback kdu_instance {} to revision {} from cluster {}".format(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100795 kdu_instance, revision, cluster_uuid
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000796 )
797 )
798
799 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +0100800 self.fs.sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000801
802 # look for instance to obtain namespace
803 instance_info = await self.get_instance_info(cluster_uuid, kdu_instance)
804 if not instance_info:
805 raise K8sException("kdu_instance {} not found".format(kdu_instance))
806
807 # init env, paths
808 paths, env = self._init_paths_env(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100809 cluster_name=cluster_uuid, create_if_not_exist=True
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000810 )
811
bravof7bd5c6a2021-11-17 11:14:57 -0300812 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +0100813 self.fs.sync(from_path=cluster_uuid)
bravof7bd5c6a2021-11-17 11:14:57 -0300814
garciadeblas82b591c2021-03-24 09:22:13 +0100815 command = self._get_rollback_command(
bravof7bd5c6a2021-11-17 11:14:57 -0300816 kdu_instance, instance_info["namespace"], revision, paths["kube_config"]
garciadeblas82b591c2021-03-24 09:22:13 +0100817 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000818
819 self.log.debug("rolling_back: {}".format(command))
820
821 # exec helm in a task
822 exec_task = asyncio.ensure_future(
823 coro_or_future=self._local_async_exec(
824 command=command, raise_exception_on_error=False, env=env
825 )
826 )
827 # write status in another task
828 status_task = asyncio.ensure_future(
829 coro_or_future=self._store_status(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100830 cluster_id=cluster_uuid,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000831 kdu_instance=kdu_instance,
832 namespace=instance_info["namespace"],
833 db_dict=db_dict,
834 operation="rollback",
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000835 )
836 )
837
838 # wait for execution task
839 await asyncio.wait([exec_task])
840
841 # cancel status task
842 status_task.cancel()
843
844 output, rc = exec_task.result()
845
846 # write final status
847 await self._store_status(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100848 cluster_id=cluster_uuid,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000849 kdu_instance=kdu_instance,
850 namespace=instance_info["namespace"],
851 db_dict=db_dict,
852 operation="rollback",
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000853 )
854
855 if rc != 0:
856 msg = "Error executing command: {}\nOutput: {}".format(command, output)
857 self.log.error(msg)
858 raise K8sException(msg)
859
860 # sync fs
Pedro Escaleirab41de172022-04-02 00:44:08 +0100861 self.fs.reverse_sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000862
863 # return new revision number
864 instance = await self.get_instance_info(
865 cluster_uuid=cluster_uuid, kdu_instance=kdu_instance
866 )
867 if instance:
868 revision = int(instance.get("revision"))
869 self.log.debug("New revision: {}".format(revision))
870 return revision
871 else:
872 return 0
873
David Garciaeb8943a2021-04-12 12:07:37 +0200874 async def uninstall(self, cluster_uuid: str, kdu_instance: str, **kwargs):
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000875 """
876 Removes an existing KDU instance. It would implicitly use the `delete` or 'uninstall' call
877 (this call should happen after all _terminate-config-primitive_ of the VNF
878 are invoked).
879
880 :param cluster_uuid: UUID of a K8s cluster known by OSM, or namespace:cluster_id
881 :param kdu_instance: unique name for the KDU instance to be deleted
David Garciaeb8943a2021-04-12 12:07:37 +0200882 :param kwargs: Additional parameters (None yet)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000883 :return: True if successful
884 """
885
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000886 self.log.debug(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100887 "uninstall kdu_instance {} from cluster {}".format(
888 kdu_instance, cluster_uuid
889 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000890 )
891
892 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +0100893 self.fs.sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000894
895 # look for instance to obtain namespace
896 instance_info = await self.get_instance_info(cluster_uuid, kdu_instance)
897 if not instance_info:
David Garcia7add1872021-08-18 14:52:52 +0200898 self.log.warning(("kdu_instance {} not found".format(kdu_instance)))
899 return True
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000900 # init env, paths
901 paths, env = self._init_paths_env(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100902 cluster_name=cluster_uuid, create_if_not_exist=True
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000903 )
904
bravof7bd5c6a2021-11-17 11:14:57 -0300905 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +0100906 self.fs.sync(from_path=cluster_uuid)
bravof7bd5c6a2021-11-17 11:14:57 -0300907
908 command = self._get_uninstall_command(
909 kdu_instance, instance_info["namespace"], paths["kube_config"]
910 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000911 output, _rc = await self._local_async_exec(
912 command=command, raise_exception_on_error=True, env=env
913 )
914
915 # sync fs
Pedro Escaleirab41de172022-04-02 00:44:08 +0100916 self.fs.reverse_sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000917
918 return self._output_to_table(output)
919
920 async def instances_list(self, cluster_uuid: str) -> list:
921 """
922 returns a list of deployed releases in a cluster
923
924 :param cluster_uuid: the 'cluster' or 'namespace:cluster'
925 :return:
926 """
927
Pedro Escaleirab41de172022-04-02 00:44:08 +0100928 self.log.debug("list releases for cluster {}".format(cluster_uuid))
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000929
930 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +0100931 self.fs.sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000932
933 # execute internal command
Pedro Escaleirab41de172022-04-02 00:44:08 +0100934 result = await self._instances_list(cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000935
936 # sync fs
Pedro Escaleirab41de172022-04-02 00:44:08 +0100937 self.fs.reverse_sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000938
939 return result
940
941 async def get_instance_info(self, cluster_uuid: str, kdu_instance: str):
942 instances = await self.instances_list(cluster_uuid=cluster_uuid)
943 for instance in instances:
944 if instance.get("name") == kdu_instance:
945 return instance
946 self.log.debug("Instance {} not found".format(kdu_instance))
947 return None
948
aticig8070c3c2022-04-18 00:31:42 +0300949 async def upgrade_charm(
950 self,
951 ee_id: str = None,
952 path: str = None,
953 charm_id: str = None,
954 charm_type: str = None,
955 timeout: float = None,
956 ) -> str:
957 """This method upgrade charms in VNFs
958
959 Args:
960 ee_id: Execution environment id
961 path: Local path to the charm
962 charm_id: charm-id
963 charm_type: Charm type can be lxc-proxy-charm, native-charm or k8s-proxy-charm
964 timeout: (Float) Timeout for the ns update operation
965
966 Returns:
967 The output of the update operation if status equals to "completed"
968 """
969 raise K8sException("KDUs deployed with Helm do not support charm upgrade")
970
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000971 async def exec_primitive(
972 self,
973 cluster_uuid: str = None,
974 kdu_instance: str = None,
975 primitive_name: str = None,
976 timeout: float = 300,
977 params: dict = None,
978 db_dict: dict = None,
David Garciaeb8943a2021-04-12 12:07:37 +0200979 **kwargs,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000980 ) -> str:
981 """Exec primitive (Juju action)
982
983 :param cluster_uuid: The UUID of the cluster or namespace:cluster
984 :param kdu_instance: The unique name of the KDU instance
985 :param primitive_name: Name of action that will be executed
986 :param timeout: Timeout for action execution
987 :param params: Dictionary of all the parameters needed for the action
988 :db_dict: Dictionary for any additional data
David Garciaeb8943a2021-04-12 12:07:37 +0200989 :param kwargs: Additional parameters (None yet)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000990
991 :return: Returns the output of the action
992 """
993 raise K8sException(
994 "KDUs deployed with Helm don't support actions "
995 "different from rollback, upgrade and status"
996 )
997
garciadeblas82b591c2021-03-24 09:22:13 +0100998 async def get_services(
999 self, cluster_uuid: str, kdu_instance: str, namespace: str
1000 ) -> list:
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001001 """
1002 Returns a list of services defined for the specified kdu instance.
1003
1004 :param cluster_uuid: UUID of a K8s cluster known by OSM
1005 :param kdu_instance: unique name for the KDU instance
1006 :param namespace: K8s namespace used by the KDU instance
1007 :return: If successful, it will return a list of services, Each service
1008 can have the following data:
1009 - `name` of the service
1010 - `type` type of service in the k8 cluster
1011 - `ports` List of ports offered by the service, for each port includes at least
1012 name, port, protocol
1013 - `cluster_ip` Internal ip to be used inside k8s cluster
1014 - `external_ip` List of external ips (in case they are available)
1015 """
1016
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001017 self.log.debug(
1018 "get_services: cluster_uuid: {}, kdu_instance: {}".format(
1019 cluster_uuid, kdu_instance
1020 )
1021 )
1022
bravof7bd5c6a2021-11-17 11:14:57 -03001023 # init env, paths
1024 paths, env = self._init_paths_env(
Pedro Escaleirab41de172022-04-02 00:44:08 +01001025 cluster_name=cluster_uuid, create_if_not_exist=True
bravof7bd5c6a2021-11-17 11:14:57 -03001026 )
1027
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001028 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +01001029 self.fs.sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001030
1031 # get list of services names for kdu
bravof7bd5c6a2021-11-17 11:14:57 -03001032 service_names = await self._get_services(
Pedro Escaleirab41de172022-04-02 00:44:08 +01001033 cluster_uuid, kdu_instance, namespace, paths["kube_config"]
bravof7bd5c6a2021-11-17 11:14:57 -03001034 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001035
1036 service_list = []
1037 for service in service_names:
Pedro Escaleirab41de172022-04-02 00:44:08 +01001038 service = await self._get_service(cluster_uuid, service, namespace)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001039 service_list.append(service)
1040
1041 # sync fs
Pedro Escaleirab41de172022-04-02 00:44:08 +01001042 self.fs.reverse_sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001043
1044 return service_list
1045
garciadeblas82b591c2021-03-24 09:22:13 +01001046 async def get_service(
1047 self, cluster_uuid: str, service_name: str, namespace: str
1048 ) -> object:
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001049 self.log.debug(
1050 "get service, service_name: {}, namespace: {}, cluster_uuid: {}".format(
garciadeblas82b591c2021-03-24 09:22:13 +01001051 service_name, namespace, cluster_uuid
1052 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001053 )
1054
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001055 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +01001056 self.fs.sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001057
Pedro Escaleirab41de172022-04-02 00:44:08 +01001058 service = await self._get_service(cluster_uuid, service_name, namespace)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001059
1060 # sync fs
Pedro Escaleirab41de172022-04-02 00:44:08 +01001061 self.fs.reverse_sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001062
1063 return service
1064
Pedro Escaleiraa8980cc2022-04-05 17:32:13 +01001065 async def status_kdu(
1066 self, cluster_uuid: str, kdu_instance: str, yaml_format: str = False, **kwargs
1067 ) -> Union[str, dict]:
David Garciaeb8943a2021-04-12 12:07:37 +02001068 """
1069 This call would retrieve tha current state of a given KDU instance. It would be
1070 would allow to retrieve the _composition_ (i.e. K8s objects) and _specific
1071 values_ of the configuration parameters applied to a given instance. This call
1072 would be based on the `status` call.
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001073
David Garciaeb8943a2021-04-12 12:07:37 +02001074 :param cluster_uuid: UUID of a K8s cluster known by OSM
1075 :param kdu_instance: unique name for the KDU instance
1076 :param kwargs: Additional parameters (None yet)
Pedro Escaleiraa8980cc2022-04-05 17:32:13 +01001077 :param yaml_format: if the return shall be returned as an YAML string or as a
1078 dictionary
David Garciaeb8943a2021-04-12 12:07:37 +02001079 :return: If successful, it will return the following vector of arguments:
1080 - K8s `namespace` in the cluster where the KDU lives
1081 - `state` of the KDU instance. It can be:
1082 - UNKNOWN
1083 - DEPLOYED
1084 - DELETED
1085 - SUPERSEDED
1086 - FAILED or
1087 - DELETING
1088 - List of `resources` (objects) that this release consists of, sorted by kind,
1089 and the status of those resources
1090 - Last `deployment_time`.
1091
1092 """
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001093 self.log.debug(
1094 "status_kdu: cluster_uuid: {}, kdu_instance: {}".format(
1095 cluster_uuid, kdu_instance
1096 )
1097 )
1098
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001099 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +01001100 self.fs.sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001101
1102 # get instance: needed to obtain namespace
Pedro Escaleirab41de172022-04-02 00:44:08 +01001103 instances = await self._instances_list(cluster_id=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001104 for instance in instances:
1105 if instance.get("name") == kdu_instance:
1106 break
1107 else:
1108 # instance does not exist
garciadeblas82b591c2021-03-24 09:22:13 +01001109 raise K8sException(
1110 "Instance name: {} not found in cluster: {}".format(
Pedro Escaleirab41de172022-04-02 00:44:08 +01001111 kdu_instance, cluster_uuid
garciadeblas82b591c2021-03-24 09:22:13 +01001112 )
1113 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001114
1115 status = await self._status_kdu(
Pedro Escaleirab41de172022-04-02 00:44:08 +01001116 cluster_id=cluster_uuid,
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001117 kdu_instance=kdu_instance,
1118 namespace=instance["namespace"],
Pedro Escaleiraa8980cc2022-04-05 17:32:13 +01001119 yaml_format=yaml_format,
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001120 show_error_log=True,
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001121 )
1122
1123 # sync fs
Pedro Escaleirab41de172022-04-02 00:44:08 +01001124 self.fs.reverse_sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001125
1126 return status
1127
aktas867418c2021-10-19 18:26:13 +03001128 async def get_values_kdu(
1129 self, kdu_instance: str, namespace: str, kubeconfig: str
1130 ) -> str:
aktas867418c2021-10-19 18:26:13 +03001131 self.log.debug("get kdu_instance values {}".format(kdu_instance))
1132
1133 return await self._exec_get_command(
1134 get_command="values",
1135 kdu_instance=kdu_instance,
1136 namespace=namespace,
1137 kubeconfig=kubeconfig,
1138 )
1139
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001140 async def values_kdu(self, kdu_model: str, repo_url: str = None) -> str:
Pedro Escaleira547f8232022-06-03 19:48:46 +01001141 """Method to obtain the Helm Chart package's values
1142
1143 Args:
1144 kdu_model: The name or path of an Helm Chart
1145 repo_url: Helm Chart repository url
1146
1147 Returns:
1148 str: the values of the Helm Chart package
1149 """
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001150
1151 self.log.debug(
1152 "inspect kdu_model values {} from (optional) repo: {}".format(
1153 kdu_model, repo_url
1154 )
1155 )
1156
aktas867418c2021-10-19 18:26:13 +03001157 return await self._exec_inspect_command(
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001158 inspect_command="values", kdu_model=kdu_model, repo_url=repo_url
1159 )
1160
1161 async def help_kdu(self, kdu_model: str, repo_url: str = None) -> str:
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001162 self.log.debug(
1163 "inspect kdu_model {} readme.md from repo: {}".format(kdu_model, repo_url)
1164 )
1165
aktas867418c2021-10-19 18:26:13 +03001166 return await self._exec_inspect_command(
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001167 inspect_command="readme", kdu_model=kdu_model, repo_url=repo_url
1168 )
1169
1170 async def synchronize_repos(self, cluster_uuid: str):
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001171 self.log.debug("synchronize repos for cluster helm-id: {}".format(cluster_uuid))
1172 try:
1173 db_repo_ids = self._get_helm_chart_repos_ids(cluster_uuid)
1174 db_repo_dict = self._get_db_repos_dict(db_repo_ids)
1175
1176 local_repo_list = await self.repo_list(cluster_uuid)
1177 local_repo_dict = {repo["name"]: repo["url"] for repo in local_repo_list}
1178
1179 deleted_repo_list = []
1180 added_repo_dict = {}
1181
1182 # iterate over the list of repos in the database that should be
1183 # added if not present
1184 for repo_name, db_repo in db_repo_dict.items():
1185 try:
1186 # check if it is already present
1187 curr_repo_url = local_repo_dict.get(db_repo["name"])
1188 repo_id = db_repo.get("_id")
1189 if curr_repo_url != db_repo["url"]:
1190 if curr_repo_url:
garciadeblas82b591c2021-03-24 09:22:13 +01001191 self.log.debug(
1192 "repo {} url changed, delete and and again".format(
1193 db_repo["url"]
1194 )
1195 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001196 await self.repo_remove(cluster_uuid, db_repo["name"])
1197 deleted_repo_list.append(repo_id)
1198
1199 # add repo
1200 self.log.debug("add repo {}".format(db_repo["name"]))
bravof0ab522f2021-11-23 19:33:18 -03001201 if "ca_cert" in db_repo:
1202 await self.repo_add(
1203 cluster_uuid,
1204 db_repo["name"],
1205 db_repo["url"],
1206 cert=db_repo["ca_cert"],
1207 )
1208 else:
1209 await self.repo_add(
1210 cluster_uuid,
1211 db_repo["name"],
1212 db_repo["url"],
1213 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001214 added_repo_dict[repo_id] = db_repo["name"]
1215 except Exception as e:
1216 raise K8sException(
1217 "Error adding repo id: {}, err_msg: {} ".format(
1218 repo_id, repr(e)
1219 )
1220 )
1221
1222 # Delete repos that are present but not in nbi_list
1223 for repo_name in local_repo_dict:
1224 if not db_repo_dict.get(repo_name) and repo_name != "stable":
1225 self.log.debug("delete repo {}".format(repo_name))
1226 try:
1227 await self.repo_remove(cluster_uuid, repo_name)
1228 deleted_repo_list.append(repo_name)
1229 except Exception as e:
1230 self.warning(
1231 "Error deleting repo, name: {}, err_msg: {}".format(
1232 repo_name, str(e)
1233 )
1234 )
1235
1236 return deleted_repo_list, added_repo_dict
1237
1238 except K8sException:
1239 raise
1240 except Exception as e:
1241 # Do not raise errors synchronizing repos
1242 self.log.error("Error synchronizing repos: {}".format(e))
1243 raise Exception("Error synchronizing repos: {}".format(e))
1244
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001245 def _get_db_repos_dict(self, repo_ids: list):
1246 db_repos_dict = {}
1247 for repo_id in repo_ids:
1248 db_repo = self.db.get_one("k8srepos", {"_id": repo_id})
1249 db_repos_dict[db_repo["name"]] = db_repo
1250 return db_repos_dict
1251
1252 """
1253 ####################################################################################
1254 ################################### TO BE IMPLEMENTED SUBCLASSES ###################
1255 ####################################################################################
1256 """
1257
1258 @abc.abstractmethod
1259 def _init_paths_env(self, cluster_name: str, create_if_not_exist: bool = True):
1260 """
1261 Creates and returns base cluster and kube dirs and returns them.
1262 Also created helm3 dirs according to new directory specification, paths are
1263 not returned but assigned to helm environment variables
1264
1265 :param cluster_name: cluster_name
1266 :return: Dictionary with config_paths and dictionary with helm environment variables
1267 """
1268
1269 @abc.abstractmethod
1270 async def _cluster_init(self, cluster_id, namespace, paths, env):
1271 """
1272 Implements the helm version dependent cluster initialization
1273 """
1274
1275 @abc.abstractmethod
1276 async def _instances_list(self, cluster_id):
1277 """
1278 Implements the helm version dependent helm instances list
1279 """
1280
1281 @abc.abstractmethod
bravof7bd5c6a2021-11-17 11:14:57 -03001282 async def _get_services(self, cluster_id, kdu_instance, namespace, kubeconfig):
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001283 """
1284 Implements the helm version dependent method to obtain services from a helm instance
1285 """
1286
1287 @abc.abstractmethod
garciadeblas82b591c2021-03-24 09:22:13 +01001288 async def _status_kdu(
1289 self,
1290 cluster_id: str,
1291 kdu_instance: str,
1292 namespace: str = None,
Pedro Escaleiraa8980cc2022-04-05 17:32:13 +01001293 yaml_format: bool = False,
garciadeblas82b591c2021-03-24 09:22:13 +01001294 show_error_log: bool = False,
Pedro Escaleiraa8980cc2022-04-05 17:32:13 +01001295 ) -> Union[str, dict]:
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001296 """
1297 Implements the helm version dependent method to obtain status of a helm instance
1298 """
1299
1300 @abc.abstractmethod
garciadeblas82b591c2021-03-24 09:22:13 +01001301 def _get_install_command(
bravof7bd5c6a2021-11-17 11:14:57 -03001302 self,
1303 kdu_model,
1304 kdu_instance,
1305 namespace,
1306 params_str,
1307 version,
1308 atomic,
1309 timeout,
1310 kubeconfig,
garciadeblas82b591c2021-03-24 09:22:13 +01001311 ) -> str:
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001312 """
1313 Obtain command to be executed to delete the indicated instance
1314 """
1315
1316 @abc.abstractmethod
aktas867418c2021-10-19 18:26:13 +03001317 def _get_upgrade_scale_command(
1318 self,
1319 kdu_model,
1320 kdu_instance,
1321 namespace,
1322 count,
1323 version,
1324 atomic,
1325 replicas,
1326 timeout,
1327 resource_name,
1328 kubeconfig,
1329 ) -> str:
Pedro Escaleira0a2060c2022-07-07 22:18:35 +01001330 """Generates the command to scale a Helm Chart release
1331
1332 Args:
1333 kdu_model (str): Kdu model name, corresponding to the Helm local location or repository
1334 kdu_instance (str): KDU instance, corresponding to the Helm Chart release in question
1335 namespace (str): Namespace where this KDU instance is deployed
1336 scale (int): Scale count
1337 version (str): Constraint with specific version of the Chart to use
1338 atomic (bool): If set, upgrade process rolls back changes made in case of failed upgrade.
1339 The --wait flag will be set automatically if --atomic is used
1340 replica_str (str): The key under resource_name key where the scale count is stored
1341 timeout (float): The time, in seconds, to wait
1342 resource_name (str): The KDU's resource to scale
1343 kubeconfig (str): Kubeconfig file path
1344
1345 Returns:
1346 str: command to scale a Helm Chart release
1347 """
aktas867418c2021-10-19 18:26:13 +03001348
1349 @abc.abstractmethod
garciadeblas82b591c2021-03-24 09:22:13 +01001350 def _get_upgrade_command(
bravof7bd5c6a2021-11-17 11:14:57 -03001351 self,
1352 kdu_model,
1353 kdu_instance,
1354 namespace,
1355 params_str,
1356 version,
1357 atomic,
1358 timeout,
1359 kubeconfig,
Gabriel Cuba085fa8d2022-10-10 12:13:55 -05001360 force,
garciadeblas82b591c2021-03-24 09:22:13 +01001361 ) -> str:
Pedro Escaleira0a2060c2022-07-07 22:18:35 +01001362 """Generates the command to upgrade a Helm Chart release
1363
1364 Args:
1365 kdu_model (str): Kdu model name, corresponding to the Helm local location or repository
1366 kdu_instance (str): KDU instance, corresponding to the Helm Chart release in question
1367 namespace (str): Namespace where this KDU instance is deployed
1368 params_str (str): Params used to upgrade the Helm Chart release
1369 version (str): Constraint with specific version of the Chart to use
1370 atomic (bool): If set, upgrade process rolls back changes made in case of failed upgrade.
1371 The --wait flag will be set automatically if --atomic is used
1372 timeout (float): The time, in seconds, to wait
1373 kubeconfig (str): Kubeconfig file path
Gabriel Cuba085fa8d2022-10-10 12:13:55 -05001374 force (bool): If set, helm forces resource updates through a replacement strategy. This may recreate pods.
Pedro Escaleira0a2060c2022-07-07 22:18:35 +01001375 Returns:
1376 str: command to upgrade a Helm Chart release
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001377 """
1378
1379 @abc.abstractmethod
bravof7bd5c6a2021-11-17 11:14:57 -03001380 def _get_rollback_command(
1381 self, kdu_instance, namespace, revision, kubeconfig
1382 ) -> str:
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001383 """
1384 Obtain command to be executed to rollback the indicated instance
1385 """
1386
1387 @abc.abstractmethod
bravof7bd5c6a2021-11-17 11:14:57 -03001388 def _get_uninstall_command(
1389 self, kdu_instance: str, namespace: str, kubeconfig: str
1390 ) -> str:
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001391 """
1392 Obtain command to be executed to delete the indicated instance
1393 """
1394
1395 @abc.abstractmethod
garciadeblas82b591c2021-03-24 09:22:13 +01001396 def _get_inspect_command(
1397 self, show_command: str, kdu_model: str, repo_str: str, version: str
1398 ):
Pedro Escaleira547f8232022-06-03 19:48:46 +01001399 """Generates the command to obtain the information about an Helm Chart package
1400 (´helm show ...´ command)
1401
1402 Args:
1403 show_command: the second part of the command (`helm show <show_command>`)
1404 kdu_model: The name or path of an Helm Chart
1405 repo_url: Helm Chart repository url
1406 version: constraint with specific version of the Chart to use
1407
1408 Returns:
1409 str: the generated Helm Chart command
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001410 """
1411
1412 @abc.abstractmethod
aktas867418c2021-10-19 18:26:13 +03001413 def _get_get_command(
1414 self, get_command: str, kdu_instance: str, namespace: str, kubeconfig: str
1415 ):
1416 """Obtain command to be executed to get information about the kdu instance."""
1417
1418 @abc.abstractmethod
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001419 async def _uninstall_sw(self, cluster_id: str, namespace: str):
1420 """
1421 Method call to uninstall cluster software for helm. This method is dependent
1422 of helm version
1423 For Helm v2 it will be called when Tiller must be uninstalled
1424 For Helm v3 it does nothing and does not need to be callled
1425 """
1426
lloretgalleg095392b2020-11-20 11:28:08 +00001427 @abc.abstractmethod
1428 def _get_helm_chart_repos_ids(self, cluster_uuid) -> list:
1429 """
1430 Obtains the cluster repos identifiers
1431 """
1432
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001433 """
1434 ####################################################################################
1435 ################################### P R I V A T E ##################################
1436 ####################################################################################
1437 """
1438
1439 @staticmethod
1440 def _check_file_exists(filename: str, exception_if_not_exists: bool = False):
1441 if os.path.exists(filename):
1442 return True
1443 else:
1444 msg = "File {} does not exist".format(filename)
1445 if exception_if_not_exists:
1446 raise K8sException(msg)
1447
1448 @staticmethod
1449 def _remove_multiple_spaces(strobj):
1450 strobj = strobj.strip()
1451 while " " in strobj:
1452 strobj = strobj.replace(" ", " ")
1453 return strobj
1454
1455 @staticmethod
1456 def _output_to_lines(output: str) -> list:
1457 output_lines = list()
1458 lines = output.splitlines(keepends=False)
1459 for line in lines:
1460 line = line.strip()
1461 if len(line) > 0:
1462 output_lines.append(line)
1463 return output_lines
1464
1465 @staticmethod
1466 def _output_to_table(output: str) -> list:
1467 output_table = list()
1468 lines = output.splitlines(keepends=False)
1469 for line in lines:
1470 line = line.replace("\t", " ")
1471 line_list = list()
1472 output_table.append(line_list)
1473 cells = line.split(sep=" ")
1474 for cell in cells:
1475 cell = cell.strip()
1476 if len(cell) > 0:
1477 line_list.append(cell)
1478 return output_table
1479
1480 @staticmethod
1481 def _parse_services(output: str) -> list:
1482 lines = output.splitlines(keepends=False)
1483 services = []
1484 for line in lines:
1485 line = line.replace("\t", " ")
1486 cells = line.split(sep=" ")
1487 if len(cells) > 0 and cells[0].startswith("service/"):
1488 elems = cells[0].split(sep="/")
1489 if len(elems) > 1:
1490 services.append(elems[1])
1491 return services
1492
1493 @staticmethod
1494 def _get_deep(dictionary: dict, members: tuple):
1495 target = dictionary
1496 value = None
1497 try:
1498 for m in members:
1499 value = target.get(m)
1500 if not value:
1501 return None
1502 else:
1503 target = value
1504 except Exception:
1505 pass
1506 return value
1507
1508 # find key:value in several lines
1509 @staticmethod
1510 def _find_in_lines(p_lines: list, p_key: str) -> str:
1511 for line in p_lines:
1512 try:
1513 if line.startswith(p_key + ":"):
1514 parts = line.split(":")
1515 the_value = parts[1].strip()
1516 return the_value
1517 except Exception:
1518 # ignore it
1519 pass
1520 return None
1521
1522 @staticmethod
1523 def _lower_keys_list(input_list: list):
1524 """
1525 Transform the keys in a list of dictionaries to lower case and returns a new list
1526 of dictionaries
1527 """
1528 new_list = []
David Garcia4395cfa2021-05-28 16:21:51 +02001529 if input_list:
1530 for dictionary in input_list:
1531 new_dict = dict((k.lower(), v) for k, v in dictionary.items())
1532 new_list.append(new_dict)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001533 return new_list
1534
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001535 async def _local_async_exec(
1536 self,
1537 command: str,
1538 raise_exception_on_error: bool = False,
1539 show_error_log: bool = True,
1540 encode_utf8: bool = False,
garciadeblas82b591c2021-03-24 09:22:13 +01001541 env: dict = None,
Daniel Arndtde6984b2023-06-27 16:42:41 -03001542 ) -> tuple[str, int]:
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001543 command = K8sHelmBaseConnector._remove_multiple_spaces(command)
garciadeblas82b591c2021-03-24 09:22:13 +01001544 self.log.debug(
1545 "Executing async local command: {}, env: {}".format(command, env)
1546 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001547
1548 # split command
1549 command = shlex.split(command)
1550
1551 environ = os.environ.copy()
1552 if env:
1553 environ.update(env)
1554
1555 try:
Pedro Escaleira1f222a92022-06-20 15:40:43 +01001556 async with self.cmd_lock:
1557 process = await asyncio.create_subprocess_exec(
1558 *command,
1559 stdout=asyncio.subprocess.PIPE,
1560 stderr=asyncio.subprocess.PIPE,
1561 env=environ,
1562 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001563
Pedro Escaleira1f222a92022-06-20 15:40:43 +01001564 # wait for command terminate
1565 stdout, stderr = await process.communicate()
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001566
Pedro Escaleira1f222a92022-06-20 15:40:43 +01001567 return_code = process.returncode
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001568
1569 output = ""
1570 if stdout:
1571 output = stdout.decode("utf-8").strip()
1572 # output = stdout.decode()
1573 if stderr:
1574 output = stderr.decode("utf-8").strip()
1575 # output = stderr.decode()
1576
1577 if return_code != 0 and show_error_log:
1578 self.log.debug(
1579 "Return code (FAIL): {}\nOutput:\n{}".format(return_code, output)
1580 )
1581 else:
1582 self.log.debug("Return code: {}".format(return_code))
1583
1584 if raise_exception_on_error and return_code != 0:
1585 raise K8sException(output)
1586
1587 if encode_utf8:
1588 output = output.encode("utf-8").strip()
1589 output = str(output).replace("\\n", "\n")
1590
1591 return output, return_code
1592
1593 except asyncio.CancelledError:
Pedro Escaleirad3817992022-07-23 23:34:42 +01001594 # first, kill the process if it is still running
1595 if process.returncode is None:
1596 process.kill()
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001597 raise
1598 except K8sException:
1599 raise
1600 except Exception as e:
1601 msg = "Exception executing command: {} -> {}".format(command, e)
1602 self.log.error(msg)
1603 if raise_exception_on_error:
1604 raise K8sException(e) from e
1605 else:
1606 return "", -1
1607
garciadeblas82b591c2021-03-24 09:22:13 +01001608 async def _local_async_exec_pipe(
1609 self,
1610 command1: str,
1611 command2: str,
1612 raise_exception_on_error: bool = True,
1613 show_error_log: bool = True,
1614 encode_utf8: bool = False,
1615 env: dict = None,
1616 ):
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001617 command1 = K8sHelmBaseConnector._remove_multiple_spaces(command1)
1618 command2 = K8sHelmBaseConnector._remove_multiple_spaces(command2)
1619 command = "{} | {}".format(command1, command2)
garciadeblas82b591c2021-03-24 09:22:13 +01001620 self.log.debug(
1621 "Executing async local command: {}, env: {}".format(command, env)
1622 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001623
1624 # split command
1625 command1 = shlex.split(command1)
1626 command2 = shlex.split(command2)
1627
1628 environ = os.environ.copy()
1629 if env:
1630 environ.update(env)
1631
1632 try:
Pedro Escaleira1f222a92022-06-20 15:40:43 +01001633 async with self.cmd_lock:
1634 read, write = os.pipe()
Pedro Escaleirad3817992022-07-23 23:34:42 +01001635 process_1 = await asyncio.create_subprocess_exec(
Pedro Escaleira1f222a92022-06-20 15:40:43 +01001636 *command1, stdout=write, env=environ
1637 )
1638 os.close(write)
1639 process_2 = await asyncio.create_subprocess_exec(
1640 *command2, stdin=read, stdout=asyncio.subprocess.PIPE, env=environ
1641 )
1642 os.close(read)
1643 stdout, stderr = await process_2.communicate()
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001644
Pedro Escaleira1f222a92022-06-20 15:40:43 +01001645 return_code = process_2.returncode
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001646
1647 output = ""
1648 if stdout:
1649 output = stdout.decode("utf-8").strip()
1650 # output = stdout.decode()
1651 if stderr:
1652 output = stderr.decode("utf-8").strip()
1653 # output = stderr.decode()
1654
1655 if return_code != 0 and show_error_log:
1656 self.log.debug(
1657 "Return code (FAIL): {}\nOutput:\n{}".format(return_code, output)
1658 )
1659 else:
1660 self.log.debug("Return code: {}".format(return_code))
1661
1662 if raise_exception_on_error and return_code != 0:
1663 raise K8sException(output)
1664
1665 if encode_utf8:
1666 output = output.encode("utf-8").strip()
1667 output = str(output).replace("\\n", "\n")
1668
1669 return output, return_code
1670 except asyncio.CancelledError:
Pedro Escaleirad3817992022-07-23 23:34:42 +01001671 # first, kill the processes if they are still running
1672 for process in (process_1, process_2):
1673 if process.returncode is None:
1674 process.kill()
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001675 raise
1676 except K8sException:
1677 raise
1678 except Exception as e:
1679 msg = "Exception executing command: {} -> {}".format(command, e)
1680 self.log.error(msg)
1681 if raise_exception_on_error:
1682 raise K8sException(e) from e
1683 else:
1684 return "", -1
1685
1686 async def _get_service(self, cluster_id, service_name, namespace):
1687 """
1688 Obtains the data of the specified service in the k8cluster.
1689
1690 :param cluster_id: id of a K8s cluster known by OSM
1691 :param service_name: name of the K8s service in the specified namespace
1692 :param namespace: K8s namespace used by the KDU instance
1693 :return: If successful, it will return a service with the following data:
1694 - `name` of the service
1695 - `type` type of service in the k8 cluster
1696 - `ports` List of ports offered by the service, for each port includes at least
1697 name, port, protocol
1698 - `cluster_ip` Internal ip to be used inside k8s cluster
1699 - `external_ip` List of external ips (in case they are available)
1700 """
1701
1702 # init config, env
1703 paths, env = self._init_paths_env(
1704 cluster_name=cluster_id, create_if_not_exist=True
1705 )
1706
1707 command = "{} --kubeconfig={} --namespace={} get service {} -o=yaml".format(
Daniel Arndtde6984b2023-06-27 16:42:41 -03001708 self.kubectl_command,
1709 paths["kube_config"],
1710 quote(namespace),
1711 quote(service_name),
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001712 )
1713
1714 output, _rc = await self._local_async_exec(
1715 command=command, raise_exception_on_error=True, env=env
1716 )
1717
1718 data = yaml.load(output, Loader=yaml.SafeLoader)
1719
1720 service = {
1721 "name": service_name,
1722 "type": self._get_deep(data, ("spec", "type")),
1723 "ports": self._get_deep(data, ("spec", "ports")),
garciadeblas82b591c2021-03-24 09:22:13 +01001724 "cluster_ip": self._get_deep(data, ("spec", "clusterIP")),
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001725 }
1726 if service["type"] == "LoadBalancer":
1727 ip_map_list = self._get_deep(data, ("status", "loadBalancer", "ingress"))
1728 ip_list = [elem["ip"] for elem in ip_map_list]
1729 service["external_ip"] = ip_list
1730
1731 return service
1732
aktas867418c2021-10-19 18:26:13 +03001733 async def _exec_get_command(
1734 self, get_command: str, kdu_instance: str, namespace: str, kubeconfig: str
1735 ):
1736 """Obtains information about the kdu instance."""
1737
1738 full_command = self._get_get_command(
1739 get_command, kdu_instance, namespace, kubeconfig
1740 )
1741
1742 output, _rc = await self._local_async_exec(command=full_command)
1743
1744 return output
1745
1746 async def _exec_inspect_command(
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001747 self, inspect_command: str, kdu_model: str, repo_url: str = None
1748 ):
Pedro Escaleira547f8232022-06-03 19:48:46 +01001749 """Obtains information about an Helm Chart package (´helm show´ command)
1750
1751 Args:
1752 inspect_command: the Helm sub command (`helm show <inspect_command> ...`)
1753 kdu_model: The name or path of an Helm Chart
1754 repo_url: Helm Chart repository url
1755
1756 Returns:
1757 str: the requested info about the Helm Chart package
1758 """
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001759
1760 repo_str = ""
1761 if repo_url:
Daniel Arndtde6984b2023-06-27 16:42:41 -03001762 repo_str = " --repo {}".format(quote(repo_url))
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001763
Pedro Escaleira0fcb6fe2022-06-04 19:14:11 +01001764 # Obtain the Chart's name and store it in the var kdu_model
1765 kdu_model, _ = self._split_repo(kdu_model=kdu_model)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001766
aktas867418c2021-10-19 18:26:13 +03001767 kdu_model, version = self._split_version(kdu_model)
1768 if version:
Daniel Arndtde6984b2023-06-27 16:42:41 -03001769 version_str = "--version {}".format(quote(version))
aktas867418c2021-10-19 18:26:13 +03001770 else:
1771 version_str = ""
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001772
garciadeblas82b591c2021-03-24 09:22:13 +01001773 full_command = self._get_inspect_command(
Pedro Escaleira0fcb6fe2022-06-04 19:14:11 +01001774 show_command=inspect_command,
Daniel Arndtde6984b2023-06-27 16:42:41 -03001775 kdu_model=quote(kdu_model),
Pedro Escaleira0fcb6fe2022-06-04 19:14:11 +01001776 repo_str=repo_str,
1777 version=version_str,
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001778 )
1779
Pedro Escaleira0fcb6fe2022-06-04 19:14:11 +01001780 output, _ = await self._local_async_exec(command=full_command)
aktas867418c2021-10-19 18:26:13 +03001781
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001782 return output
1783
aktas867418c2021-10-19 18:26:13 +03001784 async def _get_replica_count_url(
1785 self,
1786 kdu_model: str,
Pedro Escaleira547f8232022-06-03 19:48:46 +01001787 repo_url: str = None,
aktas867418c2021-10-19 18:26:13 +03001788 resource_name: str = None,
Daniel Arndtde6984b2023-06-27 16:42:41 -03001789 ) -> tuple[int, str]:
aktas867418c2021-10-19 18:26:13 +03001790 """Get the replica count value in the Helm Chart Values.
1791
1792 Args:
Pedro Escaleira547f8232022-06-03 19:48:46 +01001793 kdu_model: The name or path of an Helm Chart
aktas867418c2021-10-19 18:26:13 +03001794 repo_url: Helm Chart repository url
1795 resource_name: Resource name
1796
1797 Returns:
Pedro Escaleira06313992022-06-04 22:21:57 +01001798 A tuple with:
1799 - The number of replicas of the specific instance; if not found, returns None; and
1800 - The string corresponding to the replica count key in the Helm values
aktas867418c2021-10-19 18:26:13 +03001801 """
1802
1803 kdu_values = yaml.load(
Pedro Escaleira547f8232022-06-03 19:48:46 +01001804 await self.values_kdu(kdu_model=kdu_model, repo_url=repo_url),
1805 Loader=yaml.SafeLoader,
aktas867418c2021-10-19 18:26:13 +03001806 )
1807
Pedro Escaleira06313992022-06-04 22:21:57 +01001808 self.log.debug(f"Obtained the Helm package values for the KDU: {kdu_values}")
1809
aktas867418c2021-10-19 18:26:13 +03001810 if not kdu_values:
1811 raise K8sException(
1812 "kdu_values not found for kdu_model {}".format(kdu_model)
1813 )
1814
1815 if resource_name:
1816 kdu_values = kdu_values.get(resource_name, None)
1817
1818 if not kdu_values:
1819 msg = "resource {} not found in the values in model {}".format(
1820 resource_name, kdu_model
1821 )
1822 self.log.error(msg)
1823 raise K8sException(msg)
1824
1825 duplicate_check = False
1826
1827 replica_str = ""
1828 replicas = None
1829
Pedro Escaleira06313992022-06-04 22:21:57 +01001830 if kdu_values.get("replicaCount") is not None:
aktas867418c2021-10-19 18:26:13 +03001831 replicas = kdu_values["replicaCount"]
1832 replica_str = "replicaCount"
Pedro Escaleira06313992022-06-04 22:21:57 +01001833 elif kdu_values.get("replicas") is not None:
aktas867418c2021-10-19 18:26:13 +03001834 duplicate_check = True
1835 replicas = kdu_values["replicas"]
1836 replica_str = "replicas"
1837 else:
1838 if resource_name:
1839 msg = (
1840 "replicaCount or replicas not found in the resource"
1841 "{} values in model {}. Cannot be scaled".format(
1842 resource_name, kdu_model
1843 )
1844 )
1845 else:
1846 msg = (
1847 "replicaCount or replicas not found in the values"
1848 "in model {}. Cannot be scaled".format(kdu_model)
1849 )
1850 self.log.error(msg)
1851 raise K8sException(msg)
1852
1853 # Control if replicas and replicaCount exists at the same time
1854 msg = "replicaCount and replicas are exists at the same time"
1855 if duplicate_check:
1856 if "replicaCount" in kdu_values:
1857 self.log.error(msg)
1858 raise K8sException(msg)
1859 else:
1860 if "replicas" in kdu_values:
1861 self.log.error(msg)
1862 raise K8sException(msg)
1863
1864 return replicas, replica_str
1865
1866 async def _get_replica_count_instance(
1867 self,
1868 kdu_instance: str,
1869 namespace: str,
1870 kubeconfig: str,
1871 resource_name: str = None,
Pedro Escaleira06313992022-06-04 22:21:57 +01001872 ) -> int:
aktas867418c2021-10-19 18:26:13 +03001873 """Get the replica count value in the instance.
1874
1875 Args:
1876 kdu_instance: The name of the KDU instance
1877 namespace: KDU instance namespace
1878 kubeconfig:
1879 resource_name: Resource name
1880
1881 Returns:
Pedro Escaleira06313992022-06-04 22:21:57 +01001882 The number of replicas of the specific instance; if not found, returns None
aktas867418c2021-10-19 18:26:13 +03001883 """
1884
1885 kdu_values = yaml.load(
1886 await self.get_values_kdu(kdu_instance, namespace, kubeconfig),
1887 Loader=yaml.SafeLoader,
1888 )
1889
Pedro Escaleira06313992022-06-04 22:21:57 +01001890 self.log.debug(f"Obtained the Helm values for the KDU instance: {kdu_values}")
1891
aktas867418c2021-10-19 18:26:13 +03001892 replicas = None
1893
1894 if kdu_values:
1895 resource_values = (
1896 kdu_values.get(resource_name, None) if resource_name else None
1897 )
Pedro Escaleira06313992022-06-04 22:21:57 +01001898
1899 for replica_str in ("replicaCount", "replicas"):
1900 if resource_values:
1901 replicas = resource_values.get(replica_str)
1902 else:
1903 replicas = kdu_values.get(replica_str)
1904
1905 if replicas is not None:
1906 break
aktas867418c2021-10-19 18:26:13 +03001907
1908 return replicas
1909
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001910 async def _store_status(
1911 self,
1912 cluster_id: str,
1913 operation: str,
1914 kdu_instance: str,
1915 namespace: str = None,
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001916 db_dict: dict = None,
Pedro Escaleirab46f88d2022-04-23 19:55:45 +01001917 ) -> None:
1918 """
1919 Obtains the status of the KDU instance based on Helm Charts, and stores it in the database.
1920
1921 :param cluster_id (str): the cluster where the KDU instance is deployed
1922 :param operation (str): The operation related to the status to be updated (for instance, "install" or "upgrade")
1923 :param kdu_instance (str): The KDU instance in relation to which the status is obtained
1924 :param namespace (str): The Kubernetes namespace where the KDU instance was deployed. Defaults to None
1925 :param db_dict (dict): A dictionary with the database necessary information. It shall contain the
1926 values for the keys:
1927 - "collection": The Mongo DB collection to write to
1928 - "filter": The query filter to use in the update process
1929 - "path": The dot separated keys which targets the object to be updated
1930 Defaults to None.
1931 """
1932
1933 try:
1934 detailed_status = await self._status_kdu(
1935 cluster_id=cluster_id,
1936 kdu_instance=kdu_instance,
1937 yaml_format=False,
1938 namespace=namespace,
1939 )
1940
1941 status = detailed_status.get("info").get("description")
1942 self.log.debug(f"Status for KDU {kdu_instance} obtained: {status}.")
1943
1944 # write status to db
1945 result = await self.write_app_status_to_db(
1946 db_dict=db_dict,
1947 status=str(status),
1948 detailed_status=str(detailed_status),
1949 operation=operation,
1950 )
1951
1952 if not result:
1953 self.log.info("Error writing in database. Task exiting...")
1954
1955 except asyncio.CancelledError as e:
1956 self.log.warning(
1957 f"Exception in method {self._store_status.__name__} (task cancelled): {e}"
1958 )
1959 except Exception as e:
1960 self.log.warning(f"Exception in method {self._store_status.__name__}: {e}")
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001961
1962 # params for use in -f file
1963 # returns values file option and filename (in order to delete it at the end)
Daniel Arndtde6984b2023-06-27 16:42:41 -03001964 def _params_to_file_option(self, cluster_id: str, params: dict) -> tuple[str, str]:
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001965 if params and len(params) > 0:
garciadeblas82b591c2021-03-24 09:22:13 +01001966 self._init_paths_env(cluster_name=cluster_id, create_if_not_exist=True)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001967
1968 def get_random_number():
selvi.j21852a02023-04-27 06:53:45 +00001969 r = random.SystemRandom().randint(1, 99999999)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001970 s = str(r)
1971 while len(s) < 10:
1972 s = "0" + s
1973 return s
1974
1975 params2 = dict()
1976 for key in params:
1977 value = params.get(key)
1978 if "!!yaml" in str(value):
David Garcia513cb2d2022-05-31 11:01:09 +02001979 value = yaml.safe_load(value[7:])
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001980 params2[key] = value
1981
1982 values_file = get_random_number() + ".yaml"
1983 with open(values_file, "w") as stream:
1984 yaml.dump(params2, stream, indent=4, default_flow_style=False)
1985
1986 return "-f {}".format(values_file), values_file
1987
1988 return "", None
1989
1990 # params for use in --set option
1991 @staticmethod
1992 def _params_to_set_option(params: dict) -> str:
Daniel Arndtde6984b2023-06-27 16:42:41 -03001993 pairs = [
1994 f"{quote(str(key))}={quote(str(value))}"
1995 for key, value in params.items()
1996 if value is not None
1997 ]
1998 if not pairs:
1999 return ""
2000 return "--set " + ",".join(pairs)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00002001
2002 @staticmethod
David Garciac4da25c2021-02-23 11:47:29 +01002003 def generate_kdu_instance_name(**kwargs):
2004 chart_name = kwargs["kdu_model"]
lloretgalleg1c83f2e2020-10-22 09:12:35 +00002005 # check embeded chart (file or dir)
2006 if chart_name.startswith("/"):
2007 # extract file or directory name
David Garcia4ae527e2021-07-26 16:04:59 +02002008 chart_name = chart_name[chart_name.rfind("/") + 1 :]
lloretgalleg1c83f2e2020-10-22 09:12:35 +00002009 # check URL
2010 elif "://" in chart_name:
2011 # extract last portion of URL
David Garcia4ae527e2021-07-26 16:04:59 +02002012 chart_name = chart_name[chart_name.rfind("/") + 1 :]
lloretgalleg1c83f2e2020-10-22 09:12:35 +00002013
2014 name = ""
2015 for c in chart_name:
2016 if c.isalpha() or c.isnumeric():
2017 name += c
2018 else:
2019 name += "-"
2020 if len(name) > 35:
2021 name = name[0:35]
2022
2023 # if does not start with alpha character, prefix 'a'
2024 if not name[0].isalpha():
2025 name = "a" + name
2026
2027 name += "-"
2028
2029 def get_random_number():
selvi.j21852a02023-04-27 06:53:45 +00002030 r = random.SystemRandom().randint(1, 99999999)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00002031 s = str(r)
2032 s = s.rjust(10, "0")
2033 return s
2034
2035 name = name + get_random_number()
2036 return name.lower()
aktas867418c2021-10-19 18:26:13 +03002037
Daniel Arndtde6984b2023-06-27 16:42:41 -03002038 def _split_version(self, kdu_model: str) -> tuple[str, str]:
aktas867418c2021-10-19 18:26:13 +03002039 version = None
garciadeblas04393192022-06-08 15:39:24 +02002040 if not self._is_helm_chart_a_file(kdu_model) and ":" in kdu_model:
aktas867418c2021-10-19 18:26:13 +03002041 parts = kdu_model.split(sep=":")
2042 if len(parts) == 2:
2043 version = str(parts[1])
2044 kdu_model = parts[0]
2045 return kdu_model, version
2046
Daniel Arndtde6984b2023-06-27 16:42:41 -03002047 def _split_repo(self, kdu_model: str) -> tuple[str, str]:
Pedro Escaleira0fcb6fe2022-06-04 19:14:11 +01002048 """Obtain the Helm Chart's repository and Chart's names from the KDU model
2049
2050 Args:
2051 kdu_model (str): Associated KDU model
2052
2053 Returns:
2054 (str, str): Tuple with the Chart name in index 0, and the repo name
2055 in index 2; if there was a problem finding them, return None
2056 for both
2057 """
2058
2059 chart_name = None
garciadeblas7faf4ec2022-04-08 22:53:25 +02002060 repo_name = None
Pedro Escaleira0fcb6fe2022-06-04 19:14:11 +01002061
garciadeblas7faf4ec2022-04-08 22:53:25 +02002062 idx = kdu_model.find("/")
2063 if idx >= 0:
Pedro Escaleira0fcb6fe2022-06-04 19:14:11 +01002064 chart_name = kdu_model[idx + 1 :]
garciadeblas7faf4ec2022-04-08 22:53:25 +02002065 repo_name = kdu_model[:idx]
Pedro Escaleira0fcb6fe2022-06-04 19:14:11 +01002066
2067 return chart_name, repo_name
garciadeblas7faf4ec2022-04-08 22:53:25 +02002068
aktas867418c2021-10-19 18:26:13 +03002069 async def _find_repo(self, kdu_model: str, cluster_uuid: str) -> str:
Pedro Escaleira547f8232022-06-03 19:48:46 +01002070 """Obtain the Helm repository for an Helm Chart
2071
2072 Args:
2073 kdu_model (str): the KDU model associated with the Helm Chart instantiation
2074 cluster_uuid (str): The cluster UUID associated with the Helm Chart instantiation
2075
2076 Returns:
2077 str: the repository URL; if Helm Chart is a local one, the function returns None
2078 """
2079
Pedro Escaleira0fcb6fe2022-06-04 19:14:11 +01002080 _, repo_name = self._split_repo(kdu_model=kdu_model)
2081
aktas867418c2021-10-19 18:26:13 +03002082 repo_url = None
Pedro Escaleira0fcb6fe2022-06-04 19:14:11 +01002083 if repo_name:
aktas867418c2021-10-19 18:26:13 +03002084 # Find repository link
2085 local_repo_list = await self.repo_list(cluster_uuid)
2086 for repo in local_repo_list:
Pedro Escaleira0fcb6fe2022-06-04 19:14:11 +01002087 if repo["name"] == repo_name:
2088 repo_url = repo["url"]
2089 break # it is not necessary to continue the loop if the repo link was found...
2090
aktas867418c2021-10-19 18:26:13 +03002091 return repo_url
Gabriel Cubafb03e902022-10-07 11:40:03 -05002092
2093 async def create_certificate(
2094 self, cluster_uuid, namespace, dns_prefix, name, secret_name, usage
2095 ):
2096 paths, env = self._init_paths_env(
2097 cluster_name=cluster_uuid, create_if_not_exist=True
2098 )
2099 kubectl = Kubectl(config_file=paths["kube_config"])
2100 await kubectl.create_certificate(
2101 namespace=namespace,
2102 name=name,
2103 dns_prefix=dns_prefix,
2104 secret_name=secret_name,
2105 usages=[usage],
2106 issuer_name="ca-issuer",
2107 )
2108
2109 async def delete_certificate(self, cluster_uuid, namespace, certificate_name):
2110 paths, env = self._init_paths_env(
2111 cluster_name=cluster_uuid, create_if_not_exist=True
2112 )
2113 kubectl = Kubectl(config_file=paths["kube_config"])
2114 await kubectl.delete_certificate(namespace, certificate_name)
Gabriel Cuba5f069332023-04-25 19:26:19 -05002115
2116 async def create_namespace(
2117 self,
2118 namespace,
2119 cluster_uuid,
Gabriel Cubad21509c2023-05-17 01:30:15 -05002120 labels,
Gabriel Cuba5f069332023-04-25 19:26:19 -05002121 ):
2122 """
2123 Create a namespace in a specific cluster
2124
Gabriel Cubad21509c2023-05-17 01:30:15 -05002125 :param namespace: Namespace to be created
Gabriel Cuba5f069332023-04-25 19:26:19 -05002126 :param cluster_uuid: K8s cluster uuid used to retrieve kubeconfig
Gabriel Cubad21509c2023-05-17 01:30:15 -05002127 :param labels: Dictionary with labels for the new namespace
Gabriel Cuba5f069332023-04-25 19:26:19 -05002128 :returns: None
2129 """
2130 paths, env = self._init_paths_env(
2131 cluster_name=cluster_uuid, create_if_not_exist=True
2132 )
2133 kubectl = Kubectl(config_file=paths["kube_config"])
2134 await kubectl.create_namespace(
2135 name=namespace,
Gabriel Cubad21509c2023-05-17 01:30:15 -05002136 labels=labels,
Gabriel Cuba5f069332023-04-25 19:26:19 -05002137 )
2138
2139 async def delete_namespace(
2140 self,
2141 namespace,
2142 cluster_uuid,
2143 ):
2144 """
2145 Delete a namespace in a specific cluster
2146
2147 :param namespace: namespace to be deleted
2148 :param cluster_uuid: K8s cluster uuid used to retrieve kubeconfig
2149 :returns: None
2150 """
2151 paths, env = self._init_paths_env(
2152 cluster_name=cluster_uuid, create_if_not_exist=True
2153 )
2154 kubectl = Kubectl(config_file=paths["kube_config"])
2155 await kubectl.delete_namespace(
2156 name=namespace,
2157 )
2158
2159 async def copy_secret_data(
2160 self,
2161 src_secret: str,
2162 dst_secret: str,
2163 cluster_uuid: str,
2164 data_key: str,
2165 src_namespace: str = "osm",
2166 dst_namespace: str = "osm",
2167 ):
2168 """
2169 Copy a single key and value from an existing secret to a new one
2170
2171 :param src_secret: name of the existing secret
2172 :param dst_secret: name of the new secret
2173 :param cluster_uuid: K8s cluster uuid used to retrieve kubeconfig
2174 :param data_key: key of the existing secret to be copied
2175 :param src_namespace: Namespace of the existing secret
2176 :param dst_namespace: Namespace of the new secret
2177 :returns: None
2178 """
2179 paths, env = self._init_paths_env(
2180 cluster_name=cluster_uuid, create_if_not_exist=True
2181 )
2182 kubectl = Kubectl(config_file=paths["kube_config"])
2183 secret_data = await kubectl.get_secret_content(
2184 name=src_secret,
2185 namespace=src_namespace,
2186 )
2187 # Only the corresponding data_key value needs to be copy
2188 data = {data_key: secret_data.get(data_key)}
2189 await kubectl.create_secret(
2190 name=dst_secret,
2191 data=data,
2192 namespace=dst_namespace,
2193 secret_type="Opaque",
2194 )
2195
2196 async def setup_default_rbac(
2197 self,
2198 name,
2199 namespace,
2200 cluster_uuid,
2201 api_groups,
2202 resources,
2203 verbs,
2204 service_account,
2205 ):
2206 """
2207 Create a basic RBAC for a new namespace.
2208
2209 :param name: name of both Role and Role Binding
2210 :param namespace: K8s namespace
2211 :param cluster_uuid: K8s cluster uuid used to retrieve kubeconfig
2212 :param api_groups: Api groups to be allowed in Policy Rule
2213 :param resources: Resources to be allowed in Policy Rule
2214 :param verbs: Verbs to be allowed in Policy Rule
2215 :param service_account: Service Account name used to bind the Role
2216 :returns: None
2217 """
2218 paths, env = self._init_paths_env(
2219 cluster_name=cluster_uuid, create_if_not_exist=True
2220 )
2221 kubectl = Kubectl(config_file=paths["kube_config"])
2222 await kubectl.create_role(
2223 name=name,
2224 labels={},
2225 namespace=namespace,
2226 api_groups=api_groups,
2227 resources=resources,
2228 verbs=verbs,
2229 )
2230 await kubectl.create_role_binding(
2231 name=name,
2232 labels={},
2233 namespace=namespace,
2234 role_name=name,
2235 sa_name=service_account,
2236 )