blob: 0748d59322648333a44dd196d2c11832c946781d [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
Gabriel Cuba1c1a2562023-11-20 01:08:39 -050034from urllib.parse import urlparse
lloretgalleg1c83f2e2020-10-22 09:12:35 +000035
David Garcia4395cfa2021-05-28 16:21:51 +020036from n2vc.config import EnvironConfig
lloretgalleg1c83f2e2020-10-22 09:12:35 +000037from n2vc.exceptions import K8sException
38from n2vc.k8s_conn import K8sConnector
Gabriel Cubafb03e902022-10-07 11:40:03 -050039from n2vc.kubectl import Kubectl
lloretgalleg1c83f2e2020-10-22 09:12:35 +000040
41
42class K8sHelmBaseConnector(K8sConnector):
43
44 """
45 ####################################################################################
46 ################################### P U B L I C ####################################
47 ####################################################################################
48 """
garciadeblas82b591c2021-03-24 09:22:13 +010049
lloretgalleg1c83f2e2020-10-22 09:12:35 +000050 service_account = "osm"
51
52 def __init__(
53 self,
54 fs: object,
55 db: object,
56 kubectl_command: str = "/usr/bin/kubectl",
57 helm_command: str = "/usr/bin/helm",
58 log: object = None,
59 on_update_db=None,
60 ):
61 """
62
63 :param fs: file system for kubernetes and helm configuration
64 :param db: database object to write current operation status
65 :param kubectl_command: path to kubectl executable
66 :param helm_command: path to helm executable
67 :param log: logger
68 :param on_update_db: callback called when k8s connector updates database
69 """
70
71 # parent class
72 K8sConnector.__init__(self, db=db, log=log, on_update_db=on_update_db)
73
74 self.log.info("Initializing K8S Helm connector")
75
David Garcia4395cfa2021-05-28 16:21:51 +020076 self.config = EnvironConfig()
lloretgalleg1c83f2e2020-10-22 09:12:35 +000077 # random numbers for release name generation
78 random.seed(time.time())
79
80 # the file system
81 self.fs = fs
82
83 # exception if kubectl is not installed
84 self.kubectl_command = kubectl_command
85 self._check_file_exists(filename=kubectl_command, exception_if_not_exists=True)
86
87 # exception if helm is not installed
88 self._helm_command = helm_command
89 self._check_file_exists(filename=helm_command, exception_if_not_exists=True)
90
Pedro Pereirabe693152024-03-12 14:58:13 +000091 # exception if main post renderer executable is not present
92 self.main_post_renderer_path = EnvironConfig(prefixes=["OSMLCM_"]).get(
93 "mainpostrendererpath"
94 )
95 if self.main_post_renderer_path:
96 self._check_file_exists(
97 filename=self.main_post_renderer_path, exception_if_not_exists=True
98 )
99
100 # exception if podLabels post renderer executable is not present
101 self.podLabels_post_renderer_path = EnvironConfig(prefixes=["OSMLCM_"]).get(
102 "podlabelspostrendererpath"
103 )
104 if self.podLabels_post_renderer_path:
105 self._check_file_exists(
106 filename=self.podLabels_post_renderer_path, exception_if_not_exists=True
107 )
108
lloretgalleg83e55892020-12-17 12:42:11 +0000109 # obtain stable repo url from config or apply default
David Garcia4395cfa2021-05-28 16:21:51 +0200110 self._stable_repo_url = self.config.get("stablerepourl")
111 if self._stable_repo_url == "None":
112 self._stable_repo_url = None
lloretgalleg83e55892020-12-17 12:42:11 +0000113
Pedro Escaleira1f222a92022-06-20 15:40:43 +0100114 # Lock to avoid concurrent execution of helm commands
115 self.cmd_lock = asyncio.Lock()
116
Pedro Escaleirab41de172022-04-02 00:44:08 +0100117 def _get_namespace(self, cluster_uuid: str) -> str:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000118 """
Pedro Escaleirab41de172022-04-02 00:44:08 +0100119 Obtains the namespace used by the cluster with the uuid passed by argument
120
121 param: cluster_uuid: cluster's uuid
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000122 """
Pedro Escaleirab41de172022-04-02 00:44:08 +0100123
124 # first, obtain the cluster corresponding to the uuid passed by argument
125 k8scluster = self.db.get_one(
126 "k8sclusters", q_filter={"_id": cluster_uuid}, fail_on_empty=False
127 )
128 return k8scluster.get("namespace")
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000129
130 async def init_env(
garciadeblas82b591c2021-03-24 09:22:13 +0100131 self,
132 k8s_creds: str,
133 namespace: str = "kube-system",
134 reuse_cluster_uuid=None,
135 **kwargs,
Daniel Arndtde6984b2023-06-27 16:42:41 -0300136 ) -> tuple[str, bool]:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000137 """
138 It prepares a given K8s cluster environment to run Charts
139
140 :param k8s_creds: credentials to access a given K8s cluster, i.e. a valid
141 '.kube/config'
142 :param namespace: optional namespace to be used for helm. By default,
143 'kube-system' will be used
144 :param reuse_cluster_uuid: existing cluster uuid for reuse
David Garciaeb8943a2021-04-12 12:07:37 +0200145 :param kwargs: Additional parameters (None yet)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000146 :return: uuid of the K8s cluster and True if connector has installed some
147 software in the cluster
148 (on error, an exception will be raised)
149 """
150
151 if reuse_cluster_uuid:
Pedro Escaleirab41de172022-04-02 00:44:08 +0100152 cluster_id = reuse_cluster_uuid
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000153 else:
154 cluster_id = str(uuid4())
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000155
garciadeblas82b591c2021-03-24 09:22:13 +0100156 self.log.debug(
157 "Initializing K8S Cluster {}. namespace: {}".format(cluster_id, namespace)
158 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000159
160 paths, env = self._init_paths_env(
161 cluster_name=cluster_id, create_if_not_exist=True
162 )
163 mode = stat.S_IRUSR | stat.S_IWUSR
164 with open(paths["kube_config"], "w", mode) as f:
165 f.write(k8s_creds)
166 os.chmod(paths["kube_config"], 0o600)
167
168 # Code with initialization specific of helm version
169 n2vc_installed_sw = await self._cluster_init(cluster_id, namespace, paths, env)
170
171 # sync fs with local data
172 self.fs.reverse_sync(from_path=cluster_id)
173
174 self.log.info("Cluster {} initialized".format(cluster_id))
175
Pedro Escaleirab41de172022-04-02 00:44:08 +0100176 return cluster_id, n2vc_installed_sw
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000177
178 async def repo_add(
bravof0ab522f2021-11-23 19:33:18 -0300179 self,
180 cluster_uuid: str,
181 name: str,
182 url: str,
183 repo_type: str = "chart",
184 cert: str = None,
185 user: str = None,
186 password: str = None,
Gabriel Cuba1c1a2562023-11-20 01:08:39 -0500187 oci: bool = False,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000188 ):
garciadeblas82b591c2021-03-24 09:22:13 +0100189 self.log.debug(
190 "Cluster {}, adding {} repository {}. URL: {}".format(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100191 cluster_uuid, repo_type, name, url
garciadeblas82b591c2021-03-24 09:22:13 +0100192 )
193 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000194
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000195 # init_env
196 paths, env = self._init_paths_env(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100197 cluster_name=cluster_uuid, create_if_not_exist=True
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000198 )
199
bravof7bd5c6a2021-11-17 11:14:57 -0300200 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +0100201 self.fs.sync(from_path=cluster_uuid)
bravof7bd5c6a2021-11-17 11:14:57 -0300202
Gabriel Cuba1c1a2562023-11-20 01:08:39 -0500203 if oci:
204 if user and password:
205 host_port = urlparse(url).netloc if url.startswith("oci://") else url
206 # helm registry login url
207 command = "env KUBECONFIG={} {} registry login {}".format(
208 paths["kube_config"], self._helm_command, quote(host_port)
209 )
210 else:
211 self.log.debug(
212 "OCI registry login is not needed for repo: {}".format(name)
213 )
214 return
215 else:
216 # helm repo add name url
217 command = "env KUBECONFIG={} {} repo add {} {}".format(
218 paths["kube_config"], self._helm_command, quote(name), quote(url)
219 )
bravof0ab522f2021-11-23 19:33:18 -0300220
221 if cert:
222 temp_cert_file = os.path.join(
Pedro Escaleira1188b5d2022-04-22 18:51:00 +0100223 self.fs.path, "{}/helmcerts/".format(cluster_uuid), "temp.crt"
bravof0ab522f2021-11-23 19:33:18 -0300224 )
225 os.makedirs(os.path.dirname(temp_cert_file), exist_ok=True)
226 with open(temp_cert_file, "w") as the_cert:
227 the_cert.write(cert)
Daniel Arndtde6984b2023-06-27 16:42:41 -0300228 command += " --ca-file {}".format(quote(temp_cert_file))
bravof0ab522f2021-11-23 19:33:18 -0300229
230 if user:
Daniel Arndtde6984b2023-06-27 16:42:41 -0300231 command += " --username={}".format(quote(user))
bravof0ab522f2021-11-23 19:33:18 -0300232
233 if password:
Daniel Arndtde6984b2023-06-27 16:42:41 -0300234 command += " --password={}".format(quote(password))
bravof0ab522f2021-11-23 19:33:18 -0300235
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000236 self.log.debug("adding repo: {}".format(command))
garciadeblas82b591c2021-03-24 09:22:13 +0100237 await self._local_async_exec(
238 command=command, raise_exception_on_error=True, env=env
239 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000240
Gabriel Cuba1c1a2562023-11-20 01:08:39 -0500241 if not oci:
242 # helm repo update
243 command = "env KUBECONFIG={} {} repo update {}".format(
244 paths["kube_config"], self._helm_command, quote(name)
245 )
246 self.log.debug("updating repo: {}".format(command))
247 await self._local_async_exec(
248 command=command, raise_exception_on_error=False, env=env
249 )
garciadeblasd4cee8c2022-05-04 10:57:36 +0200250
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000251 # sync fs
Pedro Escaleirab41de172022-04-02 00:44:08 +0100252 self.fs.reverse_sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000253
garciadeblas7faf4ec2022-04-08 22:53:25 +0200254 async def repo_update(self, cluster_uuid: str, name: str, repo_type: str = "chart"):
255 self.log.debug(
256 "Cluster {}, updating {} repository {}".format(
257 cluster_uuid, repo_type, name
258 )
259 )
260
261 # init_env
262 paths, env = self._init_paths_env(
263 cluster_name=cluster_uuid, create_if_not_exist=True
264 )
265
266 # sync local dir
267 self.fs.sync(from_path=cluster_uuid)
268
269 # helm repo update
Daniel Arndtde6984b2023-06-27 16:42:41 -0300270 command = "{} repo update {}".format(self._helm_command, quote(name))
garciadeblas7faf4ec2022-04-08 22:53:25 +0200271 self.log.debug("updating repo: {}".format(command))
272 await self._local_async_exec(
273 command=command, raise_exception_on_error=False, env=env
274 )
275
276 # sync fs
277 self.fs.reverse_sync(from_path=cluster_uuid)
278
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000279 async def repo_list(self, cluster_uuid: str) -> list:
280 """
281 Get the list of registered repositories
282
283 :return: list of registered repositories: [ (name, url) .... ]
284 """
285
Pedro Escaleirab41de172022-04-02 00:44:08 +0100286 self.log.debug("list repositories for cluster {}".format(cluster_uuid))
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000287
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000288 # config filename
289 paths, env = self._init_paths_env(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100290 cluster_name=cluster_uuid, create_if_not_exist=True
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000291 )
292
bravof7bd5c6a2021-11-17 11:14:57 -0300293 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +0100294 self.fs.sync(from_path=cluster_uuid)
bravof7bd5c6a2021-11-17 11:14:57 -0300295
296 command = "env KUBECONFIG={} {} repo list --output yaml".format(
297 paths["kube_config"], self._helm_command
298 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000299
300 # Set exception to false because if there are no repos just want an empty list
301 output, _rc = await self._local_async_exec(
302 command=command, raise_exception_on_error=False, env=env
303 )
304
305 # sync fs
Pedro Escaleirab41de172022-04-02 00:44:08 +0100306 self.fs.reverse_sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000307
308 if _rc == 0:
309 if output and len(output) > 0:
310 repos = yaml.load(output, Loader=yaml.SafeLoader)
311 # unify format between helm2 and helm3 setting all keys lowercase
312 return self._lower_keys_list(repos)
313 else:
314 return []
315 else:
316 return []
317
318 async def repo_remove(self, cluster_uuid: str, name: str):
Pedro Escaleirab41de172022-04-02 00:44:08 +0100319 self.log.debug(
320 "remove {} repositories for cluster {}".format(name, cluster_uuid)
321 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000322
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000323 # init env, paths
324 paths, env = self._init_paths_env(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100325 cluster_name=cluster_uuid, create_if_not_exist=True
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000326 )
327
bravof7bd5c6a2021-11-17 11:14:57 -0300328 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +0100329 self.fs.sync(from_path=cluster_uuid)
bravof7bd5c6a2021-11-17 11:14:57 -0300330
331 command = "env KUBECONFIG={} {} repo remove {}".format(
Daniel Arndtde6984b2023-06-27 16:42:41 -0300332 paths["kube_config"], self._helm_command, quote(name)
bravof7bd5c6a2021-11-17 11:14:57 -0300333 )
garciadeblas82b591c2021-03-24 09:22:13 +0100334 await self._local_async_exec(
335 command=command, raise_exception_on_error=True, env=env
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000336 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000337
338 # sync fs
Pedro Escaleirab41de172022-04-02 00:44:08 +0100339 self.fs.reverse_sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000340
341 async def reset(
garciadeblas82b591c2021-03-24 09:22:13 +0100342 self,
343 cluster_uuid: str,
344 force: bool = False,
345 uninstall_sw: bool = False,
346 **kwargs,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000347 ) -> bool:
David Garciaeb8943a2021-04-12 12:07:37 +0200348 """Reset a cluster
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000349
David Garciaeb8943a2021-04-12 12:07:37 +0200350 Resets the Kubernetes cluster by removing the helm deployment that represents it.
351
352 :param cluster_uuid: The UUID of the cluster to reset
353 :param force: Boolean to force the reset
354 :param uninstall_sw: Boolean to force the reset
355 :param kwargs: Additional parameters (None yet)
356 :return: Returns True if successful or raises an exception.
357 """
Pedro Escaleirab41de172022-04-02 00:44:08 +0100358 namespace = self._get_namespace(cluster_uuid=cluster_uuid)
garciadeblas82b591c2021-03-24 09:22:13 +0100359 self.log.debug(
360 "Resetting K8s environment. cluster uuid: {} uninstall={}".format(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100361 cluster_uuid, uninstall_sw
garciadeblas82b591c2021-03-24 09:22:13 +0100362 )
363 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000364
365 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +0100366 self.fs.sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000367
368 # uninstall releases if needed.
369 if uninstall_sw:
370 releases = await self.instances_list(cluster_uuid=cluster_uuid)
371 if len(releases) > 0:
372 if force:
373 for r in releases:
374 try:
375 kdu_instance = r.get("name")
376 chart = r.get("chart")
377 self.log.debug(
378 "Uninstalling {} -> {}".format(chart, kdu_instance)
379 )
380 await self.uninstall(
381 cluster_uuid=cluster_uuid, kdu_instance=kdu_instance
382 )
383 except Exception as e:
384 # will not raise exception as it was found
385 # that in some cases of previously installed helm releases it
386 # raised an error
387 self.log.warn(
garciadeblas82b591c2021-03-24 09:22:13 +0100388 "Error uninstalling release {}: {}".format(
389 kdu_instance, e
390 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000391 )
392 else:
393 msg = (
394 "Cluster uuid: {} has releases and not force. Leaving K8s helm environment"
Pedro Escaleirab41de172022-04-02 00:44:08 +0100395 ).format(cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000396 self.log.warn(msg)
garciadeblas82b591c2021-03-24 09:22:13 +0100397 uninstall_sw = (
398 False # Allow to remove k8s cluster without removing Tiller
399 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000400
401 if uninstall_sw:
Pedro Escaleirab41de172022-04-02 00:44:08 +0100402 await self._uninstall_sw(cluster_id=cluster_uuid, namespace=namespace)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000403
404 # delete cluster directory
Pedro Escaleirab41de172022-04-02 00:44:08 +0100405 self.log.debug("Removing directory {}".format(cluster_uuid))
406 self.fs.file_delete(cluster_uuid, ignore_non_exist=True)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000407 # Remove also local directorio if still exist
Pedro Escaleirab41de172022-04-02 00:44:08 +0100408 direct = self.fs.path + "/" + cluster_uuid
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000409 shutil.rmtree(direct, ignore_errors=True)
410
411 return True
412
garciadeblas04393192022-06-08 15:39:24 +0200413 def _is_helm_chart_a_file(self, chart_name: str):
414 return chart_name.count("/") > 1
415
Gabriel Cuba1c1a2562023-11-20 01:08:39 -0500416 @staticmethod
417 def _is_helm_chart_a_url(chart_name: str):
418 result = urlparse(chart_name)
419 return all([result.scheme, result.netloc])
420
lloretgalleg095392b2020-11-20 11:28:08 +0000421 async def _install_impl(
garciadeblas82b591c2021-03-24 09:22:13 +0100422 self,
423 cluster_id: str,
424 kdu_model: str,
425 paths: dict,
426 env: dict,
427 kdu_instance: str,
428 atomic: bool = True,
429 timeout: float = 300,
430 params: dict = None,
431 db_dict: dict = None,
Pedro Pereirabe693152024-03-12 14:58:13 +0000432 labels: dict = None,
garciadeblas82b591c2021-03-24 09:22:13 +0100433 kdu_name: str = None,
434 namespace: str = None,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000435 ):
bravof7bd5c6a2021-11-17 11:14:57 -0300436 # init env, paths
437 paths, env = self._init_paths_env(
438 cluster_name=cluster_id, create_if_not_exist=True
439 )
440
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000441 # params to str
442 params_str, file_to_delete = self._params_to_file_option(
443 cluster_id=cluster_id, params=params
444 )
445
Gabriel Cuba1c1a2562023-11-20 01:08:39 -0500446 kdu_model, version = await self._prepare_helm_chart(kdu_model, cluster_id)
garciadeblas7faf4ec2022-04-08 22:53:25 +0200447
garciadeblas82b591c2021-03-24 09:22:13 +0100448 command = self._get_install_command(
bravof7bd5c6a2021-11-17 11:14:57 -0300449 kdu_model,
450 kdu_instance,
451 namespace,
Pedro Pereirabe693152024-03-12 14:58:13 +0000452 labels,
bravof7bd5c6a2021-11-17 11:14:57 -0300453 params_str,
454 version,
455 atomic,
456 timeout,
457 paths["kube_config"],
garciadeblas82b591c2021-03-24 09:22:13 +0100458 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000459
460 self.log.debug("installing: {}".format(command))
461
462 if atomic:
463 # exec helm in a task
464 exec_task = asyncio.ensure_future(
465 coro_or_future=self._local_async_exec(
466 command=command, raise_exception_on_error=False, env=env
467 )
468 )
469
470 # write status in another task
471 status_task = asyncio.ensure_future(
472 coro_or_future=self._store_status(
473 cluster_id=cluster_id,
474 kdu_instance=kdu_instance,
475 namespace=namespace,
476 db_dict=db_dict,
477 operation="install",
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000478 )
479 )
480
481 # wait for execution task
482 await asyncio.wait([exec_task])
483
484 # cancel status task
485 status_task.cancel()
486
487 output, rc = exec_task.result()
488
489 else:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000490 output, rc = await self._local_async_exec(
491 command=command, raise_exception_on_error=False, env=env
492 )
493
494 # remove temporal values yaml file
495 if file_to_delete:
496 os.remove(file_to_delete)
497
498 # write final status
499 await self._store_status(
500 cluster_id=cluster_id,
501 kdu_instance=kdu_instance,
502 namespace=namespace,
503 db_dict=db_dict,
504 operation="install",
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000505 )
506
507 if rc != 0:
508 msg = "Error executing command: {}\nOutput: {}".format(command, output)
509 self.log.error(msg)
510 raise K8sException(msg)
511
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000512 async def upgrade(
513 self,
514 cluster_uuid: str,
515 kdu_instance: str,
516 kdu_model: str = None,
517 atomic: bool = True,
518 timeout: float = 300,
519 params: dict = None,
520 db_dict: dict = None,
Gabriel Cuba085fa8d2022-10-10 12:13:55 -0500521 namespace: str = None,
garciadeblas1db89e82024-02-08 13:05:27 +0100522 reset_values: bool = False,
523 reuse_values: bool = True,
524 reset_then_reuse_values: bool = False,
Gabriel Cuba085fa8d2022-10-10 12:13:55 -0500525 force: bool = False,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000526 ):
Pedro Escaleirab41de172022-04-02 00:44:08 +0100527 self.log.debug("upgrading {} in cluster {}".format(kdu_model, cluster_uuid))
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000528
529 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +0100530 self.fs.sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000531
532 # look for instance to obtain namespace
Gabriel Cuba085fa8d2022-10-10 12:13:55 -0500533
534 # set namespace
535 if not namespace:
536 instance_info = await self.get_instance_info(cluster_uuid, kdu_instance)
537 if not instance_info:
538 raise K8sException("kdu_instance {} not found".format(kdu_instance))
539 namespace = instance_info["namespace"]
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000540
541 # init env, paths
542 paths, env = self._init_paths_env(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100543 cluster_name=cluster_uuid, create_if_not_exist=True
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000544 )
545
bravof7bd5c6a2021-11-17 11:14:57 -0300546 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +0100547 self.fs.sync(from_path=cluster_uuid)
bravof7bd5c6a2021-11-17 11:14:57 -0300548
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000549 # params to str
550 params_str, file_to_delete = self._params_to_file_option(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100551 cluster_id=cluster_uuid, params=params
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000552 )
553
Gabriel Cuba1c1a2562023-11-20 01:08:39 -0500554 kdu_model, version = await self._prepare_helm_chart(kdu_model, cluster_uuid)
garciadeblas7faf4ec2022-04-08 22:53:25 +0200555
Pedro Pereirabe693152024-03-12 14:58:13 +0000556 labels_dict = None
557 if db_dict and await self._contains_labels(
558 kdu_instance, namespace, paths["kube_config"], env
559 ):
560 labels_dict = await self._labels_dict(db_dict, kdu_instance)
561
garciadeblas82b591c2021-03-24 09:22:13 +0100562 command = self._get_upgrade_command(
563 kdu_model,
564 kdu_instance,
Gabriel Cuba085fa8d2022-10-10 12:13:55 -0500565 namespace,
garciadeblas82b591c2021-03-24 09:22:13 +0100566 params_str,
Pedro Pereirabe693152024-03-12 14:58:13 +0000567 labels_dict,
garciadeblas82b591c2021-03-24 09:22:13 +0100568 version,
569 atomic,
570 timeout,
bravof7bd5c6a2021-11-17 11:14:57 -0300571 paths["kube_config"],
garciadeblas1db89e82024-02-08 13:05:27 +0100572 reset_values,
573 reuse_values,
574 reset_then_reuse_values,
Gabriel Cuba085fa8d2022-10-10 12:13:55 -0500575 force,
garciadeblas82b591c2021-03-24 09:22:13 +0100576 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000577
578 self.log.debug("upgrading: {}".format(command))
579
580 if atomic:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000581 # exec helm in a task
582 exec_task = asyncio.ensure_future(
583 coro_or_future=self._local_async_exec(
584 command=command, raise_exception_on_error=False, env=env
585 )
586 )
587 # write status in another task
588 status_task = asyncio.ensure_future(
589 coro_or_future=self._store_status(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100590 cluster_id=cluster_uuid,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000591 kdu_instance=kdu_instance,
Gabriel Cuba085fa8d2022-10-10 12:13:55 -0500592 namespace=namespace,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000593 db_dict=db_dict,
594 operation="upgrade",
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000595 )
596 )
597
598 # wait for execution task
599 await asyncio.wait([exec_task])
600
601 # cancel status task
602 status_task.cancel()
603 output, rc = exec_task.result()
604
605 else:
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000606 output, rc = await self._local_async_exec(
607 command=command, raise_exception_on_error=False, env=env
608 )
609
610 # remove temporal values yaml file
611 if file_to_delete:
612 os.remove(file_to_delete)
613
614 # write final status
615 await self._store_status(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100616 cluster_id=cluster_uuid,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000617 kdu_instance=kdu_instance,
Gabriel Cuba085fa8d2022-10-10 12:13:55 -0500618 namespace=namespace,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000619 db_dict=db_dict,
620 operation="upgrade",
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000621 )
622
623 if rc != 0:
624 msg = "Error executing command: {}\nOutput: {}".format(command, output)
625 self.log.error(msg)
626 raise K8sException(msg)
627
628 # sync fs
Pedro Escaleirab41de172022-04-02 00:44:08 +0100629 self.fs.reverse_sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000630
631 # return new revision number
632 instance = await self.get_instance_info(
633 cluster_uuid=cluster_uuid, kdu_instance=kdu_instance
634 )
635 if instance:
636 revision = int(instance.get("revision"))
637 self.log.debug("New revision: {}".format(revision))
638 return revision
639 else:
640 return 0
641
aktas2962f3e2021-03-15 11:05:35 +0300642 async def scale(
garciadeblas82b591c2021-03-24 09:22:13 +0100643 self,
644 kdu_instance: str,
645 scale: int,
646 resource_name: str,
647 total_timeout: float = 1800,
aktas867418c2021-10-19 18:26:13 +0300648 cluster_uuid: str = None,
649 kdu_model: str = None,
650 atomic: bool = True,
651 db_dict: dict = None,
garciadeblas82b591c2021-03-24 09:22:13 +0100652 **kwargs,
aktas2962f3e2021-03-15 11:05:35 +0300653 ):
aktas867418c2021-10-19 18:26:13 +0300654 """Scale a resource in a Helm Chart.
655
656 Args:
657 kdu_instance: KDU instance name
658 scale: Scale to which to set the resource
659 resource_name: Resource name
660 total_timeout: The time, in seconds, to wait
661 cluster_uuid: The UUID of the cluster
662 kdu_model: The chart reference
663 atomic: if set, upgrade process rolls back changes made in case of failed upgrade.
664 The --wait flag will be set automatically if --atomic is used
665 db_dict: Dictionary for any additional data
666 kwargs: Additional parameters
667
668 Returns:
669 True if successful, False otherwise
670 """
671
Pedro Escaleirab41de172022-04-02 00:44:08 +0100672 debug_mgs = "scaling {} in cluster {}".format(kdu_model, cluster_uuid)
aktas867418c2021-10-19 18:26:13 +0300673 if resource_name:
674 debug_mgs = "scaling resource {} in model {} (cluster {})".format(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100675 resource_name, kdu_model, cluster_uuid
aktas867418c2021-10-19 18:26:13 +0300676 )
677
678 self.log.debug(debug_mgs)
679
680 # look for instance to obtain namespace
681 # get_instance_info function calls the sync command
682 instance_info = await self.get_instance_info(cluster_uuid, kdu_instance)
683 if not instance_info:
684 raise K8sException("kdu_instance {} not found".format(kdu_instance))
685
686 # init env, paths
687 paths, env = self._init_paths_env(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100688 cluster_name=cluster_uuid, create_if_not_exist=True
aktas867418c2021-10-19 18:26:13 +0300689 )
690
691 # version
Gabriel Cuba1c1a2562023-11-20 01:08:39 -0500692 kdu_model, version = await self._prepare_helm_chart(kdu_model, cluster_uuid)
aktas867418c2021-10-19 18:26:13 +0300693
694 repo_url = await self._find_repo(kdu_model, cluster_uuid)
aktas867418c2021-10-19 18:26:13 +0300695
696 _, replica_str = await self._get_replica_count_url(
697 kdu_model, repo_url, resource_name
698 )
699
Pedro Pereirabe693152024-03-12 14:58:13 +0000700 labels_dict = None
701 if db_dict and await self._contains_labels(
702 kdu_instance, instance_info["namespace"], paths["kube_config"], env
703 ):
704 labels_dict = await self._labels_dict(db_dict, kdu_instance)
705
aktas867418c2021-10-19 18:26:13 +0300706 command = self._get_upgrade_scale_command(
707 kdu_model,
708 kdu_instance,
709 instance_info["namespace"],
710 scale,
Pedro Pereirabe693152024-03-12 14:58:13 +0000711 labels_dict,
aktas867418c2021-10-19 18:26:13 +0300712 version,
713 atomic,
714 replica_str,
715 total_timeout,
716 resource_name,
717 paths["kube_config"],
718 )
719
720 self.log.debug("scaling: {}".format(command))
721
722 if atomic:
723 # exec helm in a task
724 exec_task = asyncio.ensure_future(
725 coro_or_future=self._local_async_exec(
726 command=command, raise_exception_on_error=False, env=env
727 )
728 )
729 # write status in another task
730 status_task = asyncio.ensure_future(
731 coro_or_future=self._store_status(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100732 cluster_id=cluster_uuid,
aktas867418c2021-10-19 18:26:13 +0300733 kdu_instance=kdu_instance,
734 namespace=instance_info["namespace"],
735 db_dict=db_dict,
736 operation="scale",
aktas867418c2021-10-19 18:26:13 +0300737 )
738 )
739
740 # wait for execution task
741 await asyncio.wait([exec_task])
742
743 # cancel status task
744 status_task.cancel()
745 output, rc = exec_task.result()
746
747 else:
748 output, rc = await self._local_async_exec(
749 command=command, raise_exception_on_error=False, env=env
750 )
751
752 # write final status
753 await self._store_status(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100754 cluster_id=cluster_uuid,
aktas867418c2021-10-19 18:26:13 +0300755 kdu_instance=kdu_instance,
756 namespace=instance_info["namespace"],
757 db_dict=db_dict,
758 operation="scale",
aktas867418c2021-10-19 18:26:13 +0300759 )
760
761 if rc != 0:
762 msg = "Error executing command: {}\nOutput: {}".format(command, output)
763 self.log.error(msg)
764 raise K8sException(msg)
765
766 # sync fs
Pedro Escaleirab41de172022-04-02 00:44:08 +0100767 self.fs.reverse_sync(from_path=cluster_uuid)
aktas867418c2021-10-19 18:26:13 +0300768
769 return True
aktas2962f3e2021-03-15 11:05:35 +0300770
771 async def get_scale_count(
garciadeblas82b591c2021-03-24 09:22:13 +0100772 self,
773 resource_name: str,
774 kdu_instance: str,
aktas867418c2021-10-19 18:26:13 +0300775 cluster_uuid: str,
776 kdu_model: str,
garciadeblas82b591c2021-03-24 09:22:13 +0100777 **kwargs,
aktas867418c2021-10-19 18:26:13 +0300778 ) -> int:
779 """Get a resource scale count.
780
781 Args:
782 cluster_uuid: The UUID of the cluster
783 resource_name: Resource name
784 kdu_instance: KDU instance name
Pedro Escaleira547f8232022-06-03 19:48:46 +0100785 kdu_model: The name or path of an Helm Chart
aktas867418c2021-10-19 18:26:13 +0300786 kwargs: Additional parameters
787
788 Returns:
789 Resource instance count
790 """
791
aktas867418c2021-10-19 18:26:13 +0300792 self.log.debug(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100793 "getting scale count for {} in cluster {}".format(kdu_model, cluster_uuid)
aktas867418c2021-10-19 18:26:13 +0300794 )
795
796 # look for instance to obtain namespace
797 instance_info = await self.get_instance_info(cluster_uuid, kdu_instance)
798 if not instance_info:
799 raise K8sException("kdu_instance {} not found".format(kdu_instance))
800
801 # init env, paths
Pedro Escaleira06313992022-06-04 22:21:57 +0100802 paths, _ = self._init_paths_env(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100803 cluster_name=cluster_uuid, create_if_not_exist=True
aktas867418c2021-10-19 18:26:13 +0300804 )
805
806 replicas = await self._get_replica_count_instance(
Pedro Escaleiraaa5deb72022-06-05 01:29:57 +0100807 kdu_instance=kdu_instance,
808 namespace=instance_info["namespace"],
809 kubeconfig=paths["kube_config"],
810 resource_name=resource_name,
aktas867418c2021-10-19 18:26:13 +0300811 )
812
Pedro Escaleira06313992022-06-04 22:21:57 +0100813 self.log.debug(
814 f"Number of replicas of the KDU instance {kdu_instance} and resource {resource_name} obtained: {replicas}"
815 )
816
aktas867418c2021-10-19 18:26:13 +0300817 # Get default value if scale count is not found from provided values
Pedro Escaleira06313992022-06-04 22:21:57 +0100818 # Important note: this piece of code shall only be executed in the first scaling operation,
819 # since it is expected that the _get_replica_count_instance is able to obtain the number of
820 # replicas when a scale operation was already conducted previously for this KDU/resource!
821 if replicas is None:
Pedro Escaleira547f8232022-06-03 19:48:46 +0100822 repo_url = await self._find_repo(
823 kdu_model=kdu_model, cluster_uuid=cluster_uuid
824 )
aktas867418c2021-10-19 18:26:13 +0300825 replicas, _ = await self._get_replica_count_url(
Pedro Escaleira547f8232022-06-03 19:48:46 +0100826 kdu_model=kdu_model, repo_url=repo_url, resource_name=resource_name
aktas867418c2021-10-19 18:26:13 +0300827 )
828
Pedro Escaleira06313992022-06-04 22:21:57 +0100829 self.log.debug(
830 f"Number of replicas of the Helm Chart package for KDU instance {kdu_instance} and resource "
831 f"{resource_name} obtained: {replicas}"
832 )
833
834 if replicas is None:
835 msg = "Replica count not found. Cannot be scaled"
836 self.log.error(msg)
837 raise K8sException(msg)
aktas867418c2021-10-19 18:26:13 +0300838
839 return int(replicas)
aktas2962f3e2021-03-15 11:05:35 +0300840
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000841 async def rollback(
842 self, cluster_uuid: str, kdu_instance: str, revision=0, db_dict: dict = None
843 ):
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000844 self.log.debug(
845 "rollback kdu_instance {} to revision {} from cluster {}".format(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100846 kdu_instance, revision, cluster_uuid
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000847 )
848 )
849
850 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +0100851 self.fs.sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000852
853 # look for instance to obtain namespace
854 instance_info = await self.get_instance_info(cluster_uuid, kdu_instance)
855 if not instance_info:
856 raise K8sException("kdu_instance {} not found".format(kdu_instance))
857
858 # init env, paths
859 paths, env = self._init_paths_env(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100860 cluster_name=cluster_uuid, create_if_not_exist=True
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000861 )
862
bravof7bd5c6a2021-11-17 11:14:57 -0300863 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +0100864 self.fs.sync(from_path=cluster_uuid)
bravof7bd5c6a2021-11-17 11:14:57 -0300865
garciadeblas82b591c2021-03-24 09:22:13 +0100866 command = self._get_rollback_command(
bravof7bd5c6a2021-11-17 11:14:57 -0300867 kdu_instance, instance_info["namespace"], revision, paths["kube_config"]
garciadeblas82b591c2021-03-24 09:22:13 +0100868 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000869
870 self.log.debug("rolling_back: {}".format(command))
871
872 # exec helm in a task
873 exec_task = asyncio.ensure_future(
874 coro_or_future=self._local_async_exec(
875 command=command, raise_exception_on_error=False, env=env
876 )
877 )
878 # write status in another task
879 status_task = asyncio.ensure_future(
880 coro_or_future=self._store_status(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100881 cluster_id=cluster_uuid,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000882 kdu_instance=kdu_instance,
883 namespace=instance_info["namespace"],
884 db_dict=db_dict,
885 operation="rollback",
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000886 )
887 )
888
889 # wait for execution task
890 await asyncio.wait([exec_task])
891
892 # cancel status task
893 status_task.cancel()
894
895 output, rc = exec_task.result()
896
897 # write final status
898 await self._store_status(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100899 cluster_id=cluster_uuid,
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000900 kdu_instance=kdu_instance,
901 namespace=instance_info["namespace"],
902 db_dict=db_dict,
903 operation="rollback",
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000904 )
905
906 if rc != 0:
907 msg = "Error executing command: {}\nOutput: {}".format(command, output)
908 self.log.error(msg)
909 raise K8sException(msg)
910
911 # sync fs
Pedro Escaleirab41de172022-04-02 00:44:08 +0100912 self.fs.reverse_sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000913
914 # return new revision number
915 instance = await self.get_instance_info(
916 cluster_uuid=cluster_uuid, kdu_instance=kdu_instance
917 )
918 if instance:
919 revision = int(instance.get("revision"))
920 self.log.debug("New revision: {}".format(revision))
921 return revision
922 else:
923 return 0
924
David Garciaeb8943a2021-04-12 12:07:37 +0200925 async def uninstall(self, cluster_uuid: str, kdu_instance: str, **kwargs):
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000926 """
927 Removes an existing KDU instance. It would implicitly use the `delete` or 'uninstall' call
928 (this call should happen after all _terminate-config-primitive_ of the VNF
929 are invoked).
930
931 :param cluster_uuid: UUID of a K8s cluster known by OSM, or namespace:cluster_id
932 :param kdu_instance: unique name for the KDU instance to be deleted
David Garciaeb8943a2021-04-12 12:07:37 +0200933 :param kwargs: Additional parameters (None yet)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000934 :return: True if successful
935 """
936
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000937 self.log.debug(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100938 "uninstall kdu_instance {} from cluster {}".format(
939 kdu_instance, cluster_uuid
940 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000941 )
942
943 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +0100944 self.fs.sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000945
946 # look for instance to obtain namespace
947 instance_info = await self.get_instance_info(cluster_uuid, kdu_instance)
948 if not instance_info:
David Garcia7add1872021-08-18 14:52:52 +0200949 self.log.warning(("kdu_instance {} not found".format(kdu_instance)))
950 return True
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000951 # init env, paths
952 paths, env = self._init_paths_env(
Pedro Escaleirab41de172022-04-02 00:44:08 +0100953 cluster_name=cluster_uuid, create_if_not_exist=True
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000954 )
955
bravof7bd5c6a2021-11-17 11:14:57 -0300956 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +0100957 self.fs.sync(from_path=cluster_uuid)
bravof7bd5c6a2021-11-17 11:14:57 -0300958
959 command = self._get_uninstall_command(
960 kdu_instance, instance_info["namespace"], paths["kube_config"]
961 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000962 output, _rc = await self._local_async_exec(
963 command=command, raise_exception_on_error=True, env=env
964 )
965
966 # sync fs
Pedro Escaleirab41de172022-04-02 00:44:08 +0100967 self.fs.reverse_sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000968
969 return self._output_to_table(output)
970
971 async def instances_list(self, cluster_uuid: str) -> list:
972 """
973 returns a list of deployed releases in a cluster
974
975 :param cluster_uuid: the 'cluster' or 'namespace:cluster'
976 :return:
977 """
978
Pedro Escaleirab41de172022-04-02 00:44:08 +0100979 self.log.debug("list releases for cluster {}".format(cluster_uuid))
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000980
981 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +0100982 self.fs.sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000983
984 # execute internal command
Pedro Escaleirab41de172022-04-02 00:44:08 +0100985 result = await self._instances_list(cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000986
987 # sync fs
Pedro Escaleirab41de172022-04-02 00:44:08 +0100988 self.fs.reverse_sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +0000989
990 return result
991
992 async def get_instance_info(self, cluster_uuid: str, kdu_instance: str):
993 instances = await self.instances_list(cluster_uuid=cluster_uuid)
994 for instance in instances:
995 if instance.get("name") == kdu_instance:
996 return instance
997 self.log.debug("Instance {} not found".format(kdu_instance))
998 return None
999
aticig8070c3c2022-04-18 00:31:42 +03001000 async def upgrade_charm(
1001 self,
1002 ee_id: str = None,
1003 path: str = None,
1004 charm_id: str = None,
1005 charm_type: str = None,
1006 timeout: float = None,
1007 ) -> str:
1008 """This method upgrade charms in VNFs
1009
1010 Args:
1011 ee_id: Execution environment id
1012 path: Local path to the charm
1013 charm_id: charm-id
1014 charm_type: Charm type can be lxc-proxy-charm, native-charm or k8s-proxy-charm
1015 timeout: (Float) Timeout for the ns update operation
1016
1017 Returns:
1018 The output of the update operation if status equals to "completed"
1019 """
1020 raise K8sException("KDUs deployed with Helm do not support charm upgrade")
1021
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001022 async def exec_primitive(
1023 self,
1024 cluster_uuid: str = None,
1025 kdu_instance: str = None,
1026 primitive_name: str = None,
1027 timeout: float = 300,
1028 params: dict = None,
1029 db_dict: dict = None,
David Garciaeb8943a2021-04-12 12:07:37 +02001030 **kwargs,
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001031 ) -> str:
1032 """Exec primitive (Juju action)
1033
1034 :param cluster_uuid: The UUID of the cluster or namespace:cluster
1035 :param kdu_instance: The unique name of the KDU instance
1036 :param primitive_name: Name of action that will be executed
1037 :param timeout: Timeout for action execution
1038 :param params: Dictionary of all the parameters needed for the action
1039 :db_dict: Dictionary for any additional data
David Garciaeb8943a2021-04-12 12:07:37 +02001040 :param kwargs: Additional parameters (None yet)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001041
1042 :return: Returns the output of the action
1043 """
1044 raise K8sException(
1045 "KDUs deployed with Helm don't support actions "
1046 "different from rollback, upgrade and status"
1047 )
1048
garciadeblas82b591c2021-03-24 09:22:13 +01001049 async def get_services(
1050 self, cluster_uuid: str, kdu_instance: str, namespace: str
1051 ) -> list:
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001052 """
1053 Returns a list of services defined for the specified kdu instance.
1054
1055 :param cluster_uuid: UUID of a K8s cluster known by OSM
1056 :param kdu_instance: unique name for the KDU instance
1057 :param namespace: K8s namespace used by the KDU instance
1058 :return: If successful, it will return a list of services, Each service
1059 can have the following data:
1060 - `name` of the service
1061 - `type` type of service in the k8 cluster
1062 - `ports` List of ports offered by the service, for each port includes at least
1063 name, port, protocol
1064 - `cluster_ip` Internal ip to be used inside k8s cluster
1065 - `external_ip` List of external ips (in case they are available)
1066 """
1067
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001068 self.log.debug(
1069 "get_services: cluster_uuid: {}, kdu_instance: {}".format(
1070 cluster_uuid, kdu_instance
1071 )
1072 )
1073
bravof7bd5c6a2021-11-17 11:14:57 -03001074 # init env, paths
1075 paths, env = self._init_paths_env(
Pedro Escaleirab41de172022-04-02 00:44:08 +01001076 cluster_name=cluster_uuid, create_if_not_exist=True
bravof7bd5c6a2021-11-17 11:14:57 -03001077 )
1078
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001079 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +01001080 self.fs.sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001081
1082 # get list of services names for kdu
bravof7bd5c6a2021-11-17 11:14:57 -03001083 service_names = await self._get_services(
Pedro Escaleirab41de172022-04-02 00:44:08 +01001084 cluster_uuid, kdu_instance, namespace, paths["kube_config"]
bravof7bd5c6a2021-11-17 11:14:57 -03001085 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001086
1087 service_list = []
1088 for service in service_names:
Pedro Escaleirab41de172022-04-02 00:44:08 +01001089 service = await self._get_service(cluster_uuid, service, namespace)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001090 service_list.append(service)
1091
1092 # sync fs
Pedro Escaleirab41de172022-04-02 00:44:08 +01001093 self.fs.reverse_sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001094
1095 return service_list
1096
garciadeblas82b591c2021-03-24 09:22:13 +01001097 async def get_service(
1098 self, cluster_uuid: str, service_name: str, namespace: str
1099 ) -> object:
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001100 self.log.debug(
1101 "get service, service_name: {}, namespace: {}, cluster_uuid: {}".format(
garciadeblas82b591c2021-03-24 09:22:13 +01001102 service_name, namespace, cluster_uuid
1103 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001104 )
1105
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001106 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +01001107 self.fs.sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001108
Pedro Escaleirab41de172022-04-02 00:44:08 +01001109 service = await self._get_service(cluster_uuid, service_name, namespace)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001110
1111 # sync fs
Pedro Escaleirab41de172022-04-02 00:44:08 +01001112 self.fs.reverse_sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001113
1114 return service
1115
Pedro Escaleiraa8980cc2022-04-05 17:32:13 +01001116 async def status_kdu(
1117 self, cluster_uuid: str, kdu_instance: str, yaml_format: str = False, **kwargs
1118 ) -> Union[str, dict]:
David Garciaeb8943a2021-04-12 12:07:37 +02001119 """
1120 This call would retrieve tha current state of a given KDU instance. It would be
1121 would allow to retrieve the _composition_ (i.e. K8s objects) and _specific
1122 values_ of the configuration parameters applied to a given instance. This call
1123 would be based on the `status` call.
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001124
David Garciaeb8943a2021-04-12 12:07:37 +02001125 :param cluster_uuid: UUID of a K8s cluster known by OSM
1126 :param kdu_instance: unique name for the KDU instance
1127 :param kwargs: Additional parameters (None yet)
Pedro Escaleiraa8980cc2022-04-05 17:32:13 +01001128 :param yaml_format: if the return shall be returned as an YAML string or as a
1129 dictionary
David Garciaeb8943a2021-04-12 12:07:37 +02001130 :return: If successful, it will return the following vector of arguments:
1131 - K8s `namespace` in the cluster where the KDU lives
1132 - `state` of the KDU instance. It can be:
1133 - UNKNOWN
1134 - DEPLOYED
1135 - DELETED
1136 - SUPERSEDED
1137 - FAILED or
1138 - DELETING
1139 - List of `resources` (objects) that this release consists of, sorted by kind,
1140 and the status of those resources
1141 - Last `deployment_time`.
1142
1143 """
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001144 self.log.debug(
1145 "status_kdu: cluster_uuid: {}, kdu_instance: {}".format(
1146 cluster_uuid, kdu_instance
1147 )
1148 )
1149
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001150 # sync local dir
Pedro Escaleirab41de172022-04-02 00:44:08 +01001151 self.fs.sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001152
1153 # get instance: needed to obtain namespace
Pedro Escaleirab41de172022-04-02 00:44:08 +01001154 instances = await self._instances_list(cluster_id=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001155 for instance in instances:
1156 if instance.get("name") == kdu_instance:
1157 break
1158 else:
1159 # instance does not exist
garciadeblas82b591c2021-03-24 09:22:13 +01001160 raise K8sException(
1161 "Instance name: {} not found in cluster: {}".format(
Pedro Escaleirab41de172022-04-02 00:44:08 +01001162 kdu_instance, cluster_uuid
garciadeblas82b591c2021-03-24 09:22:13 +01001163 )
1164 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001165
1166 status = await self._status_kdu(
Pedro Escaleirab41de172022-04-02 00:44:08 +01001167 cluster_id=cluster_uuid,
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001168 kdu_instance=kdu_instance,
1169 namespace=instance["namespace"],
Pedro Escaleiraa8980cc2022-04-05 17:32:13 +01001170 yaml_format=yaml_format,
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001171 show_error_log=True,
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001172 )
1173
1174 # sync fs
Pedro Escaleirab41de172022-04-02 00:44:08 +01001175 self.fs.reverse_sync(from_path=cluster_uuid)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001176
1177 return status
1178
aktas867418c2021-10-19 18:26:13 +03001179 async def get_values_kdu(
1180 self, kdu_instance: str, namespace: str, kubeconfig: str
1181 ) -> str:
aktas867418c2021-10-19 18:26:13 +03001182 self.log.debug("get kdu_instance values {}".format(kdu_instance))
1183
1184 return await self._exec_get_command(
1185 get_command="values",
1186 kdu_instance=kdu_instance,
1187 namespace=namespace,
1188 kubeconfig=kubeconfig,
1189 )
1190
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001191 async def values_kdu(self, kdu_model: str, repo_url: str = None) -> str:
Pedro Escaleira547f8232022-06-03 19:48:46 +01001192 """Method to obtain the Helm Chart package's values
1193
1194 Args:
1195 kdu_model: The name or path of an Helm Chart
1196 repo_url: Helm Chart repository url
1197
1198 Returns:
1199 str: the values of the Helm Chart package
1200 """
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001201
1202 self.log.debug(
1203 "inspect kdu_model values {} from (optional) repo: {}".format(
1204 kdu_model, repo_url
1205 )
1206 )
1207
aktas867418c2021-10-19 18:26:13 +03001208 return await self._exec_inspect_command(
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001209 inspect_command="values", kdu_model=kdu_model, repo_url=repo_url
1210 )
1211
1212 async def help_kdu(self, kdu_model: str, repo_url: str = None) -> str:
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001213 self.log.debug(
1214 "inspect kdu_model {} readme.md from repo: {}".format(kdu_model, repo_url)
1215 )
1216
aktas867418c2021-10-19 18:26:13 +03001217 return await self._exec_inspect_command(
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001218 inspect_command="readme", kdu_model=kdu_model, repo_url=repo_url
1219 )
1220
1221 async def synchronize_repos(self, cluster_uuid: str):
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001222 self.log.debug("synchronize repos for cluster helm-id: {}".format(cluster_uuid))
1223 try:
1224 db_repo_ids = self._get_helm_chart_repos_ids(cluster_uuid)
1225 db_repo_dict = self._get_db_repos_dict(db_repo_ids)
1226
1227 local_repo_list = await self.repo_list(cluster_uuid)
1228 local_repo_dict = {repo["name"]: repo["url"] for repo in local_repo_list}
1229
1230 deleted_repo_list = []
1231 added_repo_dict = {}
1232
1233 # iterate over the list of repos in the database that should be
1234 # added if not present
1235 for repo_name, db_repo in db_repo_dict.items():
1236 try:
1237 # check if it is already present
1238 curr_repo_url = local_repo_dict.get(db_repo["name"])
1239 repo_id = db_repo.get("_id")
1240 if curr_repo_url != db_repo["url"]:
1241 if curr_repo_url:
garciadeblas82b591c2021-03-24 09:22:13 +01001242 self.log.debug(
1243 "repo {} url changed, delete and and again".format(
1244 db_repo["url"]
1245 )
1246 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001247 await self.repo_remove(cluster_uuid, db_repo["name"])
1248 deleted_repo_list.append(repo_id)
1249
1250 # add repo
1251 self.log.debug("add repo {}".format(db_repo["name"]))
Gabriel Cuba1c1a2562023-11-20 01:08:39 -05001252 await self.repo_add(
1253 cluster_uuid,
1254 db_repo["name"],
1255 db_repo["url"],
1256 cert=db_repo.get("ca_cert"),
1257 user=db_repo.get("user"),
1258 password=db_repo.get("password"),
1259 oci=db_repo.get("oci", False),
1260 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001261 added_repo_dict[repo_id] = db_repo["name"]
1262 except Exception as e:
1263 raise K8sException(
1264 "Error adding repo id: {}, err_msg: {} ".format(
1265 repo_id, repr(e)
1266 )
1267 )
1268
1269 # Delete repos that are present but not in nbi_list
1270 for repo_name in local_repo_dict:
1271 if not db_repo_dict.get(repo_name) and repo_name != "stable":
1272 self.log.debug("delete repo {}".format(repo_name))
1273 try:
1274 await self.repo_remove(cluster_uuid, repo_name)
1275 deleted_repo_list.append(repo_name)
1276 except Exception as e:
1277 self.warning(
1278 "Error deleting repo, name: {}, err_msg: {}".format(
1279 repo_name, str(e)
1280 )
1281 )
1282
1283 return deleted_repo_list, added_repo_dict
1284
1285 except K8sException:
1286 raise
1287 except Exception as e:
1288 # Do not raise errors synchronizing repos
1289 self.log.error("Error synchronizing repos: {}".format(e))
1290 raise Exception("Error synchronizing repos: {}".format(e))
1291
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001292 def _get_db_repos_dict(self, repo_ids: list):
1293 db_repos_dict = {}
1294 for repo_id in repo_ids:
1295 db_repo = self.db.get_one("k8srepos", {"_id": repo_id})
1296 db_repos_dict[db_repo["name"]] = db_repo
1297 return db_repos_dict
1298
1299 """
1300 ####################################################################################
1301 ################################### TO BE IMPLEMENTED SUBCLASSES ###################
1302 ####################################################################################
1303 """
1304
1305 @abc.abstractmethod
1306 def _init_paths_env(self, cluster_name: str, create_if_not_exist: bool = True):
1307 """
1308 Creates and returns base cluster and kube dirs and returns them.
1309 Also created helm3 dirs according to new directory specification, paths are
1310 not returned but assigned to helm environment variables
1311
1312 :param cluster_name: cluster_name
1313 :return: Dictionary with config_paths and dictionary with helm environment variables
1314 """
1315
1316 @abc.abstractmethod
1317 async def _cluster_init(self, cluster_id, namespace, paths, env):
1318 """
1319 Implements the helm version dependent cluster initialization
1320 """
1321
1322 @abc.abstractmethod
1323 async def _instances_list(self, cluster_id):
1324 """
1325 Implements the helm version dependent helm instances list
1326 """
1327
1328 @abc.abstractmethod
bravof7bd5c6a2021-11-17 11:14:57 -03001329 async def _get_services(self, cluster_id, kdu_instance, namespace, kubeconfig):
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001330 """
1331 Implements the helm version dependent method to obtain services from a helm instance
1332 """
1333
1334 @abc.abstractmethod
garciadeblas82b591c2021-03-24 09:22:13 +01001335 async def _status_kdu(
1336 self,
1337 cluster_id: str,
1338 kdu_instance: str,
1339 namespace: str = None,
Pedro Escaleiraa8980cc2022-04-05 17:32:13 +01001340 yaml_format: bool = False,
garciadeblas82b591c2021-03-24 09:22:13 +01001341 show_error_log: bool = False,
Pedro Escaleiraa8980cc2022-04-05 17:32:13 +01001342 ) -> Union[str, dict]:
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001343 """
1344 Implements the helm version dependent method to obtain status of a helm instance
1345 """
1346
1347 @abc.abstractmethod
garciadeblas82b591c2021-03-24 09:22:13 +01001348 def _get_install_command(
bravof7bd5c6a2021-11-17 11:14:57 -03001349 self,
1350 kdu_model,
1351 kdu_instance,
1352 namespace,
Pedro Pereirabe693152024-03-12 14:58:13 +00001353 labels,
bravof7bd5c6a2021-11-17 11:14:57 -03001354 params_str,
1355 version,
1356 atomic,
1357 timeout,
1358 kubeconfig,
garciadeblas82b591c2021-03-24 09:22:13 +01001359 ) -> str:
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001360 """
1361 Obtain command to be executed to delete the indicated instance
1362 """
1363
1364 @abc.abstractmethod
aktas867418c2021-10-19 18:26:13 +03001365 def _get_upgrade_scale_command(
1366 self,
1367 kdu_model,
1368 kdu_instance,
1369 namespace,
1370 count,
Pedro Pereirabe693152024-03-12 14:58:13 +00001371 labels,
aktas867418c2021-10-19 18:26:13 +03001372 version,
1373 atomic,
1374 replicas,
1375 timeout,
1376 resource_name,
1377 kubeconfig,
1378 ) -> str:
Pedro Escaleira0a2060c2022-07-07 22:18:35 +01001379 """Generates the command to scale a Helm Chart release
1380
1381 Args:
1382 kdu_model (str): Kdu model name, corresponding to the Helm local location or repository
1383 kdu_instance (str): KDU instance, corresponding to the Helm Chart release in question
1384 namespace (str): Namespace where this KDU instance is deployed
1385 scale (int): Scale count
1386 version (str): Constraint with specific version of the Chart to use
1387 atomic (bool): If set, upgrade process rolls back changes made in case of failed upgrade.
1388 The --wait flag will be set automatically if --atomic is used
1389 replica_str (str): The key under resource_name key where the scale count is stored
1390 timeout (float): The time, in seconds, to wait
1391 resource_name (str): The KDU's resource to scale
1392 kubeconfig (str): Kubeconfig file path
1393
1394 Returns:
1395 str: command to scale a Helm Chart release
1396 """
aktas867418c2021-10-19 18:26:13 +03001397
1398 @abc.abstractmethod
garciadeblas82b591c2021-03-24 09:22:13 +01001399 def _get_upgrade_command(
bravof7bd5c6a2021-11-17 11:14:57 -03001400 self,
1401 kdu_model,
1402 kdu_instance,
1403 namespace,
1404 params_str,
Pedro Pereirabe693152024-03-12 14:58:13 +00001405 labels,
bravof7bd5c6a2021-11-17 11:14:57 -03001406 version,
1407 atomic,
1408 timeout,
1409 kubeconfig,
garciadeblas1db89e82024-02-08 13:05:27 +01001410 reset_values,
1411 reuse_values,
1412 reset_then_reuse_values,
Gabriel Cuba085fa8d2022-10-10 12:13:55 -05001413 force,
garciadeblas82b591c2021-03-24 09:22:13 +01001414 ) -> str:
Pedro Escaleira0a2060c2022-07-07 22:18:35 +01001415 """Generates the command to upgrade a Helm Chart release
1416
1417 Args:
1418 kdu_model (str): Kdu model name, corresponding to the Helm local location or repository
1419 kdu_instance (str): KDU instance, corresponding to the Helm Chart release in question
1420 namespace (str): Namespace where this KDU instance is deployed
1421 params_str (str): Params used to upgrade the Helm Chart release
1422 version (str): Constraint with specific version of the Chart to use
1423 atomic (bool): If set, upgrade process rolls back changes made in case of failed upgrade.
1424 The --wait flag will be set automatically if --atomic is used
1425 timeout (float): The time, in seconds, to wait
1426 kubeconfig (str): Kubeconfig file path
garciadeblas1db89e82024-02-08 13:05:27 +01001427 reset_values(bool): If set, helm resets values instead of reusing previous values.
1428 reuse_values(bool): If set, helm reuses previous values.
1429 reset_then_reuse_values(bool): If set, helm resets values, then apply the last release's values
Gabriel Cuba085fa8d2022-10-10 12:13:55 -05001430 force (bool): If set, helm forces resource updates through a replacement strategy. This may recreate pods.
Pedro Escaleira0a2060c2022-07-07 22:18:35 +01001431 Returns:
1432 str: command to upgrade a Helm Chart release
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001433 """
1434
1435 @abc.abstractmethod
bravof7bd5c6a2021-11-17 11:14:57 -03001436 def _get_rollback_command(
1437 self, kdu_instance, namespace, revision, kubeconfig
1438 ) -> str:
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001439 """
1440 Obtain command to be executed to rollback the indicated instance
1441 """
1442
1443 @abc.abstractmethod
bravof7bd5c6a2021-11-17 11:14:57 -03001444 def _get_uninstall_command(
1445 self, kdu_instance: str, namespace: str, kubeconfig: str
1446 ) -> str:
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001447 """
1448 Obtain command to be executed to delete the indicated instance
1449 """
1450
1451 @abc.abstractmethod
garciadeblas82b591c2021-03-24 09:22:13 +01001452 def _get_inspect_command(
1453 self, show_command: str, kdu_model: str, repo_str: str, version: str
1454 ):
Pedro Escaleira547f8232022-06-03 19:48:46 +01001455 """Generates the command to obtain the information about an Helm Chart package
1456 (´helm show ...´ command)
1457
1458 Args:
1459 show_command: the second part of the command (`helm show <show_command>`)
1460 kdu_model: The name or path of an Helm Chart
1461 repo_url: Helm Chart repository url
1462 version: constraint with specific version of the Chart to use
1463
1464 Returns:
1465 str: the generated Helm Chart command
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001466 """
1467
1468 @abc.abstractmethod
aktas867418c2021-10-19 18:26:13 +03001469 def _get_get_command(
1470 self, get_command: str, kdu_instance: str, namespace: str, kubeconfig: str
1471 ):
1472 """Obtain command to be executed to get information about the kdu instance."""
1473
1474 @abc.abstractmethod
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001475 async def _uninstall_sw(self, cluster_id: str, namespace: str):
1476 """
1477 Method call to uninstall cluster software for helm. This method is dependent
1478 of helm version
1479 For Helm v2 it will be called when Tiller must be uninstalled
1480 For Helm v3 it does nothing and does not need to be callled
1481 """
1482
lloretgalleg095392b2020-11-20 11:28:08 +00001483 @abc.abstractmethod
1484 def _get_helm_chart_repos_ids(self, cluster_uuid) -> list:
1485 """
1486 Obtains the cluster repos identifiers
1487 """
1488
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001489 """
1490 ####################################################################################
1491 ################################### P R I V A T E ##################################
1492 ####################################################################################
1493 """
1494
1495 @staticmethod
1496 def _check_file_exists(filename: str, exception_if_not_exists: bool = False):
1497 if os.path.exists(filename):
1498 return True
1499 else:
1500 msg = "File {} does not exist".format(filename)
1501 if exception_if_not_exists:
1502 raise K8sException(msg)
1503
1504 @staticmethod
1505 def _remove_multiple_spaces(strobj):
1506 strobj = strobj.strip()
1507 while " " in strobj:
1508 strobj = strobj.replace(" ", " ")
1509 return strobj
1510
1511 @staticmethod
1512 def _output_to_lines(output: str) -> list:
1513 output_lines = list()
1514 lines = output.splitlines(keepends=False)
1515 for line in lines:
1516 line = line.strip()
1517 if len(line) > 0:
1518 output_lines.append(line)
1519 return output_lines
1520
1521 @staticmethod
1522 def _output_to_table(output: str) -> list:
1523 output_table = list()
1524 lines = output.splitlines(keepends=False)
1525 for line in lines:
1526 line = line.replace("\t", " ")
1527 line_list = list()
1528 output_table.append(line_list)
1529 cells = line.split(sep=" ")
1530 for cell in cells:
1531 cell = cell.strip()
1532 if len(cell) > 0:
1533 line_list.append(cell)
1534 return output_table
1535
1536 @staticmethod
1537 def _parse_services(output: str) -> list:
1538 lines = output.splitlines(keepends=False)
1539 services = []
1540 for line in lines:
1541 line = line.replace("\t", " ")
1542 cells = line.split(sep=" ")
1543 if len(cells) > 0 and cells[0].startswith("service/"):
1544 elems = cells[0].split(sep="/")
1545 if len(elems) > 1:
1546 services.append(elems[1])
1547 return services
1548
1549 @staticmethod
1550 def _get_deep(dictionary: dict, members: tuple):
1551 target = dictionary
1552 value = None
1553 try:
1554 for m in members:
1555 value = target.get(m)
1556 if not value:
1557 return None
1558 else:
1559 target = value
1560 except Exception:
1561 pass
1562 return value
1563
1564 # find key:value in several lines
1565 @staticmethod
1566 def _find_in_lines(p_lines: list, p_key: str) -> str:
1567 for line in p_lines:
1568 try:
1569 if line.startswith(p_key + ":"):
1570 parts = line.split(":")
1571 the_value = parts[1].strip()
1572 return the_value
1573 except Exception:
1574 # ignore it
1575 pass
1576 return None
1577
1578 @staticmethod
1579 def _lower_keys_list(input_list: list):
1580 """
1581 Transform the keys in a list of dictionaries to lower case and returns a new list
1582 of dictionaries
1583 """
1584 new_list = []
David Garcia4395cfa2021-05-28 16:21:51 +02001585 if input_list:
1586 for dictionary in input_list:
1587 new_dict = dict((k.lower(), v) for k, v in dictionary.items())
1588 new_list.append(new_dict)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001589 return new_list
1590
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001591 async def _local_async_exec(
1592 self,
1593 command: str,
1594 raise_exception_on_error: bool = False,
1595 show_error_log: bool = True,
1596 encode_utf8: bool = False,
garciadeblas82b591c2021-03-24 09:22:13 +01001597 env: dict = None,
Daniel Arndtde6984b2023-06-27 16:42:41 -03001598 ) -> tuple[str, int]:
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001599 command = K8sHelmBaseConnector._remove_multiple_spaces(command)
garciadeblas82b591c2021-03-24 09:22:13 +01001600 self.log.debug(
1601 "Executing async local command: {}, env: {}".format(command, env)
1602 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001603
1604 # split command
1605 command = shlex.split(command)
1606
1607 environ = os.environ.copy()
1608 if env:
1609 environ.update(env)
1610
1611 try:
Pedro Escaleira1f222a92022-06-20 15:40:43 +01001612 async with self.cmd_lock:
1613 process = await asyncio.create_subprocess_exec(
1614 *command,
1615 stdout=asyncio.subprocess.PIPE,
1616 stderr=asyncio.subprocess.PIPE,
1617 env=environ,
1618 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001619
Pedro Escaleira1f222a92022-06-20 15:40:43 +01001620 # wait for command terminate
1621 stdout, stderr = await process.communicate()
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001622
Pedro Escaleira1f222a92022-06-20 15:40:43 +01001623 return_code = process.returncode
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001624
1625 output = ""
1626 if stdout:
1627 output = stdout.decode("utf-8").strip()
1628 # output = stdout.decode()
1629 if stderr:
1630 output = stderr.decode("utf-8").strip()
1631 # output = stderr.decode()
1632
1633 if return_code != 0 and show_error_log:
1634 self.log.debug(
1635 "Return code (FAIL): {}\nOutput:\n{}".format(return_code, output)
1636 )
1637 else:
1638 self.log.debug("Return code: {}".format(return_code))
1639
1640 if raise_exception_on_error and return_code != 0:
1641 raise K8sException(output)
1642
1643 if encode_utf8:
1644 output = output.encode("utf-8").strip()
1645 output = str(output).replace("\\n", "\n")
1646
1647 return output, return_code
1648
1649 except asyncio.CancelledError:
Pedro Escaleirad3817992022-07-23 23:34:42 +01001650 # first, kill the process if it is still running
1651 if process.returncode is None:
1652 process.kill()
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001653 raise
1654 except K8sException:
1655 raise
1656 except Exception as e:
1657 msg = "Exception executing command: {} -> {}".format(command, e)
1658 self.log.error(msg)
1659 if raise_exception_on_error:
1660 raise K8sException(e) from e
1661 else:
1662 return "", -1
1663
garciadeblas82b591c2021-03-24 09:22:13 +01001664 async def _local_async_exec_pipe(
1665 self,
1666 command1: str,
1667 command2: str,
1668 raise_exception_on_error: bool = True,
1669 show_error_log: bool = True,
1670 encode_utf8: bool = False,
1671 env: dict = None,
1672 ):
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001673 command1 = K8sHelmBaseConnector._remove_multiple_spaces(command1)
1674 command2 = K8sHelmBaseConnector._remove_multiple_spaces(command2)
1675 command = "{} | {}".format(command1, command2)
garciadeblas82b591c2021-03-24 09:22:13 +01001676 self.log.debug(
1677 "Executing async local command: {}, env: {}".format(command, env)
1678 )
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001679
1680 # split command
1681 command1 = shlex.split(command1)
1682 command2 = shlex.split(command2)
1683
1684 environ = os.environ.copy()
1685 if env:
1686 environ.update(env)
1687
garciadeblas3e8e4dc2024-11-26 14:52:44 +01001688 process_1 = None
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001689 try:
Pedro Escaleira1f222a92022-06-20 15:40:43 +01001690 async with self.cmd_lock:
1691 read, write = os.pipe()
Pedro Escaleirad3817992022-07-23 23:34:42 +01001692 process_1 = await asyncio.create_subprocess_exec(
Pedro Escaleira1f222a92022-06-20 15:40:43 +01001693 *command1, stdout=write, env=environ
1694 )
1695 os.close(write)
1696 process_2 = await asyncio.create_subprocess_exec(
1697 *command2, stdin=read, stdout=asyncio.subprocess.PIPE, env=environ
1698 )
1699 os.close(read)
1700 stdout, stderr = await process_2.communicate()
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001701
Pedro Escaleira1f222a92022-06-20 15:40:43 +01001702 return_code = process_2.returncode
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001703
1704 output = ""
1705 if stdout:
1706 output = stdout.decode("utf-8").strip()
1707 # output = stdout.decode()
1708 if stderr:
1709 output = stderr.decode("utf-8").strip()
1710 # output = stderr.decode()
1711
1712 if return_code != 0 and show_error_log:
1713 self.log.debug(
1714 "Return code (FAIL): {}\nOutput:\n{}".format(return_code, output)
1715 )
1716 else:
1717 self.log.debug("Return code: {}".format(return_code))
1718
1719 if raise_exception_on_error and return_code != 0:
1720 raise K8sException(output)
1721
1722 if encode_utf8:
1723 output = output.encode("utf-8").strip()
1724 output = str(output).replace("\\n", "\n")
1725
1726 return output, return_code
1727 except asyncio.CancelledError:
Pedro Escaleirad3817992022-07-23 23:34:42 +01001728 # first, kill the processes if they are still running
1729 for process in (process_1, process_2):
1730 if process.returncode is None:
1731 process.kill()
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001732 raise
1733 except K8sException:
1734 raise
1735 except Exception as e:
1736 msg = "Exception executing command: {} -> {}".format(command, e)
1737 self.log.error(msg)
1738 if raise_exception_on_error:
1739 raise K8sException(e) from e
1740 else:
1741 return "", -1
1742
1743 async def _get_service(self, cluster_id, service_name, namespace):
1744 """
1745 Obtains the data of the specified service in the k8cluster.
1746
1747 :param cluster_id: id of a K8s cluster known by OSM
1748 :param service_name: name of the K8s service in the specified namespace
1749 :param namespace: K8s namespace used by the KDU instance
1750 :return: If successful, it will return a service with the following data:
1751 - `name` of the service
1752 - `type` type of service in the k8 cluster
1753 - `ports` List of ports offered by the service, for each port includes at least
1754 name, port, protocol
1755 - `cluster_ip` Internal ip to be used inside k8s cluster
1756 - `external_ip` List of external ips (in case they are available)
1757 """
1758
1759 # init config, env
1760 paths, env = self._init_paths_env(
1761 cluster_name=cluster_id, create_if_not_exist=True
1762 )
1763
1764 command = "{} --kubeconfig={} --namespace={} get service {} -o=yaml".format(
Daniel Arndtde6984b2023-06-27 16:42:41 -03001765 self.kubectl_command,
1766 paths["kube_config"],
1767 quote(namespace),
1768 quote(service_name),
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001769 )
1770
1771 output, _rc = await self._local_async_exec(
1772 command=command, raise_exception_on_error=True, env=env
1773 )
1774
1775 data = yaml.load(output, Loader=yaml.SafeLoader)
1776
1777 service = {
1778 "name": service_name,
1779 "type": self._get_deep(data, ("spec", "type")),
1780 "ports": self._get_deep(data, ("spec", "ports")),
garciadeblas82b591c2021-03-24 09:22:13 +01001781 "cluster_ip": self._get_deep(data, ("spec", "clusterIP")),
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001782 }
1783 if service["type"] == "LoadBalancer":
1784 ip_map_list = self._get_deep(data, ("status", "loadBalancer", "ingress"))
1785 ip_list = [elem["ip"] for elem in ip_map_list]
1786 service["external_ip"] = ip_list
1787
1788 return service
1789
aktas867418c2021-10-19 18:26:13 +03001790 async def _exec_get_command(
1791 self, get_command: str, kdu_instance: str, namespace: str, kubeconfig: str
1792 ):
1793 """Obtains information about the kdu instance."""
1794
1795 full_command = self._get_get_command(
1796 get_command, kdu_instance, namespace, kubeconfig
1797 )
1798
1799 output, _rc = await self._local_async_exec(command=full_command)
1800
1801 return output
1802
1803 async def _exec_inspect_command(
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001804 self, inspect_command: str, kdu_model: str, repo_url: str = None
1805 ):
Pedro Escaleira547f8232022-06-03 19:48:46 +01001806 """Obtains information about an Helm Chart package (´helm show´ command)
1807
1808 Args:
1809 inspect_command: the Helm sub command (`helm show <inspect_command> ...`)
1810 kdu_model: The name or path of an Helm Chart
1811 repo_url: Helm Chart repository url
1812
1813 Returns:
1814 str: the requested info about the Helm Chart package
1815 """
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001816
1817 repo_str = ""
1818 if repo_url:
Daniel Arndtde6984b2023-06-27 16:42:41 -03001819 repo_str = " --repo {}".format(quote(repo_url))
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001820
Pedro Escaleira0fcb6fe2022-06-04 19:14:11 +01001821 # Obtain the Chart's name and store it in the var kdu_model
1822 kdu_model, _ = self._split_repo(kdu_model=kdu_model)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001823
aktas867418c2021-10-19 18:26:13 +03001824 kdu_model, version = self._split_version(kdu_model)
1825 if version:
Daniel Arndtde6984b2023-06-27 16:42:41 -03001826 version_str = "--version {}".format(quote(version))
aktas867418c2021-10-19 18:26:13 +03001827 else:
1828 version_str = ""
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001829
garciadeblas82b591c2021-03-24 09:22:13 +01001830 full_command = self._get_inspect_command(
Pedro Escaleira0fcb6fe2022-06-04 19:14:11 +01001831 show_command=inspect_command,
Daniel Arndtde6984b2023-06-27 16:42:41 -03001832 kdu_model=quote(kdu_model),
Pedro Escaleira0fcb6fe2022-06-04 19:14:11 +01001833 repo_str=repo_str,
1834 version=version_str,
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001835 )
1836
Pedro Escaleira0fcb6fe2022-06-04 19:14:11 +01001837 output, _ = await self._local_async_exec(command=full_command)
aktas867418c2021-10-19 18:26:13 +03001838
lloretgalleg1c83f2e2020-10-22 09:12:35 +00001839 return output
1840
aktas867418c2021-10-19 18:26:13 +03001841 async def _get_replica_count_url(
1842 self,
1843 kdu_model: str,
Pedro Escaleira547f8232022-06-03 19:48:46 +01001844 repo_url: str = None,
aktas867418c2021-10-19 18:26:13 +03001845 resource_name: str = None,
Daniel Arndtde6984b2023-06-27 16:42:41 -03001846 ) -> tuple[int, str]:
aktas867418c2021-10-19 18:26:13 +03001847 """Get the replica count value in the Helm Chart Values.
1848
1849 Args:
Pedro Escaleira547f8232022-06-03 19:48:46 +01001850 kdu_model: The name or path of an Helm Chart
aktas867418c2021-10-19 18:26:13 +03001851 repo_url: Helm Chart repository url
1852 resource_name: Resource name
1853
1854 Returns:
Pedro Escaleira06313992022-06-04 22:21:57 +01001855 A tuple with:
1856 - The number of replicas of the specific instance; if not found, returns None; and
1857 - The string corresponding to the replica count key in the Helm values
aktas867418c2021-10-19 18:26:13 +03001858 """
1859
1860 kdu_values = yaml.load(
Pedro Escaleira547f8232022-06-03 19:48:46 +01001861 await self.values_kdu(kdu_model=kdu_model, repo_url=repo_url),
1862 Loader=yaml.SafeLoader,
aktas867418c2021-10-19 18:26:13 +03001863 )
1864
Pedro Escaleira06313992022-06-04 22:21:57 +01001865 self.log.debug(f"Obtained the Helm package values for the KDU: {kdu_values}")
1866
aktas867418c2021-10-19 18:26:13 +03001867 if not kdu_values:
1868 raise K8sException(
1869 "kdu_values not found for kdu_model {}".format(kdu_model)
1870 )
1871
1872 if resource_name:
1873 kdu_values = kdu_values.get(resource_name, None)
1874
1875 if not kdu_values:
1876 msg = "resource {} not found in the values in model {}".format(
1877 resource_name, kdu_model
1878 )
1879 self.log.error(msg)
1880 raise K8sException(msg)
1881
1882 duplicate_check = False
1883
1884 replica_str = ""
1885 replicas = None
1886
Pedro Escaleira06313992022-06-04 22:21:57 +01001887 if kdu_values.get("replicaCount") is not None:
aktas867418c2021-10-19 18:26:13 +03001888 replicas = kdu_values["replicaCount"]
1889 replica_str = "replicaCount"
Pedro Escaleira06313992022-06-04 22:21:57 +01001890 elif kdu_values.get("replicas") is not None:
aktas867418c2021-10-19 18:26:13 +03001891 duplicate_check = True
1892 replicas = kdu_values["replicas"]
1893 replica_str = "replicas"
1894 else:
1895 if resource_name:
1896 msg = (
1897 "replicaCount or replicas not found in the resource"
1898 "{} values in model {}. Cannot be scaled".format(
1899 resource_name, kdu_model
1900 )
1901 )
1902 else:
1903 msg = (
1904 "replicaCount or replicas not found in the values"
1905 "in model {}. Cannot be scaled".format(kdu_model)
1906 )
1907 self.log.error(msg)
1908 raise K8sException(msg)
1909
1910 # Control if replicas and replicaCount exists at the same time
1911 msg = "replicaCount and replicas are exists at the same time"
1912 if duplicate_check:
1913 if "replicaCount" in kdu_values:
1914 self.log.error(msg)
1915 raise K8sException(msg)
1916 else:
1917 if "replicas" in kdu_values:
1918 self.log.error(msg)
1919 raise K8sException(msg)
1920
1921 return replicas, replica_str
1922
1923 async def _get_replica_count_instance(
1924 self,
1925 kdu_instance: str,
1926 namespace: str,
1927 kubeconfig: str,
1928 resource_name: str = None,
Pedro Escaleira06313992022-06-04 22:21:57 +01001929 ) -> int:
aktas867418c2021-10-19 18:26:13 +03001930 """Get the replica count value in the instance.
1931
1932 Args:
1933 kdu_instance: The name of the KDU instance
1934 namespace: KDU instance namespace
1935 kubeconfig:
1936 resource_name: Resource name
1937
1938 Returns:
Pedro Escaleira06313992022-06-04 22:21:57 +01001939 The number of replicas of the specific instance; if not found, returns None
aktas867418c2021-10-19 18:26:13 +03001940 """
1941
1942 kdu_values = yaml.load(
1943 await self.get_values_kdu(kdu_instance, namespace, kubeconfig),
1944 Loader=yaml.SafeLoader,
1945 )
1946
Pedro Escaleira06313992022-06-04 22:21:57 +01001947 self.log.debug(f"Obtained the Helm values for the KDU instance: {kdu_values}")
1948
aktas867418c2021-10-19 18:26:13 +03001949 replicas = None
1950
1951 if kdu_values:
1952 resource_values = (
1953 kdu_values.get(resource_name, None) if resource_name else None
1954 )
Pedro Escaleira06313992022-06-04 22:21:57 +01001955
1956 for replica_str in ("replicaCount", "replicas"):
1957 if resource_values:
1958 replicas = resource_values.get(replica_str)
1959 else:
1960 replicas = kdu_values.get(replica_str)
1961
1962 if replicas is not None:
1963 break
aktas867418c2021-10-19 18:26:13 +03001964
1965 return replicas
1966
Pedro Pereirabe693152024-03-12 14:58:13 +00001967 async def _labels_dict(self, db_dict, kdu_instance):
1968 # get the network service registry
1969 ns_id = db_dict["filter"]["_id"]
1970 try:
1971 db_nsr = self.db.get_one("nsrs", {"_id": ns_id})
1972 except Exception as e:
1973 print("nsr {} not found: {}".format(ns_id, e))
1974 nsd_id = db_nsr["nsd"]["_id"]
1975
1976 # get the kdu registry
1977 for index, kdu in enumerate(db_nsr["_admin"]["deployed"]["K8s"]):
1978 if kdu["kdu-instance"] == kdu_instance:
1979 db_kdur = kdu
Pedro Pereirabe693152024-03-12 14:58:13 +00001980 break
Gabriel Cuba1f340832024-07-11 09:55:43 -05001981 else:
1982 # No kdur found, could be the case of an EE chart
1983 return {}
1984
1985 kdu_name = db_kdur["kdu-name"]
Pedro Pereirabe693152024-03-12 14:58:13 +00001986 member_vnf_index = db_kdur["member-vnf-index"]
1987 # get the vnf registry
1988 try:
1989 db_vnfr = self.db.get_one(
1990 "vnfrs",
1991 {"nsr-id-ref": ns_id, "member-vnf-index-ref": member_vnf_index},
1992 )
1993 except Exception as e:
1994 print("vnfr {} not found: {}".format(member_vnf_index, e))
1995
1996 vnf_id = db_vnfr["_id"]
1997 vnfd_id = db_vnfr["vnfd-id"]
1998
1999 return {
2000 "managed-by": "osm.etsi.org",
2001 "osm.etsi.org/ns-id": ns_id,
2002 "osm.etsi.org/nsd-id": nsd_id,
2003 "osm.etsi.org/vnf-id": vnf_id,
2004 "osm.etsi.org/vnfd-id": vnfd_id,
2005 "osm.etsi.org/kdu-id": kdu_instance,
2006 "osm.etsi.org/kdu-name": kdu_name,
2007 }
2008
2009 async def _contains_labels(self, kdu_instance, namespace, kube_config, env):
2010 command = "env KUBECONFIG={} {} get manifest {} --namespace={}".format(
2011 kube_config,
2012 self._helm_command,
2013 quote(kdu_instance),
2014 quote(namespace),
2015 )
2016 output, rc = await self._local_async_exec(
2017 command=command, raise_exception_on_error=False, env=env
2018 )
2019 manifests = yaml.safe_load_all(output)
2020 for manifest in manifests:
2021 # Check if the manifest has metadata and labels
2022 if (
2023 manifest is not None
2024 and "metadata" in manifest
2025 and "labels" in manifest["metadata"]
2026 ):
2027 labels = {
2028 "managed-by",
2029 "osm.etsi.org/kdu-id",
2030 "osm.etsi.org/kdu-name",
2031 "osm.etsi.org/ns-id",
2032 "osm.etsi.org/nsd-id",
2033 "osm.etsi.org/vnf-id",
2034 "osm.etsi.org/vnfd-id",
2035 }
2036 if labels.issubset(manifest["metadata"]["labels"].keys()):
2037 return True
2038 return False
2039
lloretgalleg1c83f2e2020-10-22 09:12:35 +00002040 async def _store_status(
2041 self,
2042 cluster_id: str,
2043 operation: str,
2044 kdu_instance: str,
2045 namespace: str = None,
lloretgalleg1c83f2e2020-10-22 09:12:35 +00002046 db_dict: dict = None,
Pedro Escaleirab46f88d2022-04-23 19:55:45 +01002047 ) -> None:
2048 """
2049 Obtains the status of the KDU instance based on Helm Charts, and stores it in the database.
2050
2051 :param cluster_id (str): the cluster where the KDU instance is deployed
2052 :param operation (str): The operation related to the status to be updated (for instance, "install" or "upgrade")
2053 :param kdu_instance (str): The KDU instance in relation to which the status is obtained
2054 :param namespace (str): The Kubernetes namespace where the KDU instance was deployed. Defaults to None
2055 :param db_dict (dict): A dictionary with the database necessary information. It shall contain the
2056 values for the keys:
2057 - "collection": The Mongo DB collection to write to
2058 - "filter": The query filter to use in the update process
2059 - "path": The dot separated keys which targets the object to be updated
2060 Defaults to None.
2061 """
2062
2063 try:
2064 detailed_status = await self._status_kdu(
2065 cluster_id=cluster_id,
2066 kdu_instance=kdu_instance,
2067 yaml_format=False,
2068 namespace=namespace,
2069 )
2070
2071 status = detailed_status.get("info").get("description")
2072 self.log.debug(f"Status for KDU {kdu_instance} obtained: {status}.")
2073
2074 # write status to db
2075 result = await self.write_app_status_to_db(
2076 db_dict=db_dict,
2077 status=str(status),
2078 detailed_status=str(detailed_status),
2079 operation=operation,
2080 )
2081
2082 if not result:
2083 self.log.info("Error writing in database. Task exiting...")
2084
2085 except asyncio.CancelledError as e:
2086 self.log.warning(
2087 f"Exception in method {self._store_status.__name__} (task cancelled): {e}"
2088 )
2089 except Exception as e:
2090 self.log.warning(f"Exception in method {self._store_status.__name__}: {e}")
lloretgalleg1c83f2e2020-10-22 09:12:35 +00002091
2092 # params for use in -f file
2093 # returns values file option and filename (in order to delete it at the end)
Daniel Arndtde6984b2023-06-27 16:42:41 -03002094 def _params_to_file_option(self, cluster_id: str, params: dict) -> tuple[str, str]:
lloretgalleg1c83f2e2020-10-22 09:12:35 +00002095 if params and len(params) > 0:
garciadeblas82b591c2021-03-24 09:22:13 +01002096 self._init_paths_env(cluster_name=cluster_id, create_if_not_exist=True)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00002097
2098 def get_random_number():
selvi.j21852a02023-04-27 06:53:45 +00002099 r = random.SystemRandom().randint(1, 99999999)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00002100 s = str(r)
2101 while len(s) < 10:
2102 s = "0" + s
2103 return s
2104
2105 params2 = dict()
2106 for key in params:
2107 value = params.get(key)
2108 if "!!yaml" in str(value):
David Garcia513cb2d2022-05-31 11:01:09 +02002109 value = yaml.safe_load(value[7:])
lloretgalleg1c83f2e2020-10-22 09:12:35 +00002110 params2[key] = value
2111
2112 values_file = get_random_number() + ".yaml"
2113 with open(values_file, "w") as stream:
2114 yaml.dump(params2, stream, indent=4, default_flow_style=False)
2115
2116 return "-f {}".format(values_file), values_file
2117
2118 return "", None
2119
2120 # params for use in --set option
2121 @staticmethod
2122 def _params_to_set_option(params: dict) -> str:
Daniel Arndtde6984b2023-06-27 16:42:41 -03002123 pairs = [
2124 f"{quote(str(key))}={quote(str(value))}"
2125 for key, value in params.items()
2126 if value is not None
2127 ]
2128 if not pairs:
2129 return ""
2130 return "--set " + ",".join(pairs)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00002131
2132 @staticmethod
David Garciac4da25c2021-02-23 11:47:29 +01002133 def generate_kdu_instance_name(**kwargs):
2134 chart_name = kwargs["kdu_model"]
lloretgalleg1c83f2e2020-10-22 09:12:35 +00002135 # check embeded chart (file or dir)
2136 if chart_name.startswith("/"):
2137 # extract file or directory name
David Garcia4ae527e2021-07-26 16:04:59 +02002138 chart_name = chart_name[chart_name.rfind("/") + 1 :]
lloretgalleg1c83f2e2020-10-22 09:12:35 +00002139 # check URL
2140 elif "://" in chart_name:
2141 # extract last portion of URL
David Garcia4ae527e2021-07-26 16:04:59 +02002142 chart_name = chart_name[chart_name.rfind("/") + 1 :]
lloretgalleg1c83f2e2020-10-22 09:12:35 +00002143
2144 name = ""
2145 for c in chart_name:
2146 if c.isalpha() or c.isnumeric():
2147 name += c
2148 else:
2149 name += "-"
2150 if len(name) > 35:
2151 name = name[0:35]
2152
2153 # if does not start with alpha character, prefix 'a'
2154 if not name[0].isalpha():
2155 name = "a" + name
2156
2157 name += "-"
2158
2159 def get_random_number():
selvi.j21852a02023-04-27 06:53:45 +00002160 r = random.SystemRandom().randint(1, 99999999)
lloretgalleg1c83f2e2020-10-22 09:12:35 +00002161 s = str(r)
2162 s = s.rjust(10, "0")
2163 return s
2164
2165 name = name + get_random_number()
2166 return name.lower()
aktas867418c2021-10-19 18:26:13 +03002167
Daniel Arndtde6984b2023-06-27 16:42:41 -03002168 def _split_version(self, kdu_model: str) -> tuple[str, str]:
aktas867418c2021-10-19 18:26:13 +03002169 version = None
Gabriel Cuba1c1a2562023-11-20 01:08:39 -05002170 if (
2171 not (
2172 self._is_helm_chart_a_file(kdu_model)
2173 or self._is_helm_chart_a_url(kdu_model)
2174 )
2175 and ":" in kdu_model
2176 ):
aktas867418c2021-10-19 18:26:13 +03002177 parts = kdu_model.split(sep=":")
2178 if len(parts) == 2:
2179 version = str(parts[1])
2180 kdu_model = parts[0]
2181 return kdu_model, version
2182
Daniel Arndtde6984b2023-06-27 16:42:41 -03002183 def _split_repo(self, kdu_model: str) -> tuple[str, str]:
Pedro Escaleira0fcb6fe2022-06-04 19:14:11 +01002184 """Obtain the Helm Chart's repository and Chart's names from the KDU model
2185
2186 Args:
2187 kdu_model (str): Associated KDU model
2188
2189 Returns:
2190 (str, str): Tuple with the Chart name in index 0, and the repo name
2191 in index 2; if there was a problem finding them, return None
2192 for both
2193 """
2194
2195 chart_name = None
garciadeblas7faf4ec2022-04-08 22:53:25 +02002196 repo_name = None
Pedro Escaleira0fcb6fe2022-06-04 19:14:11 +01002197
garciadeblas7faf4ec2022-04-08 22:53:25 +02002198 idx = kdu_model.find("/")
Gabriel Cuba1c1a2562023-11-20 01:08:39 -05002199 if not self._is_helm_chart_a_url(kdu_model) and idx >= 0:
Pedro Escaleira0fcb6fe2022-06-04 19:14:11 +01002200 chart_name = kdu_model[idx + 1 :]
garciadeblas7faf4ec2022-04-08 22:53:25 +02002201 repo_name = kdu_model[:idx]
Pedro Escaleira0fcb6fe2022-06-04 19:14:11 +01002202
2203 return chart_name, repo_name
garciadeblas7faf4ec2022-04-08 22:53:25 +02002204
aktas867418c2021-10-19 18:26:13 +03002205 async def _find_repo(self, kdu_model: str, cluster_uuid: str) -> str:
Pedro Escaleira547f8232022-06-03 19:48:46 +01002206 """Obtain the Helm repository for an Helm Chart
2207
2208 Args:
2209 kdu_model (str): the KDU model associated with the Helm Chart instantiation
2210 cluster_uuid (str): The cluster UUID associated with the Helm Chart instantiation
2211
2212 Returns:
2213 str: the repository URL; if Helm Chart is a local one, the function returns None
2214 """
2215
Pedro Escaleira0fcb6fe2022-06-04 19:14:11 +01002216 _, repo_name = self._split_repo(kdu_model=kdu_model)
2217
aktas867418c2021-10-19 18:26:13 +03002218 repo_url = None
Pedro Escaleira0fcb6fe2022-06-04 19:14:11 +01002219 if repo_name:
aktas867418c2021-10-19 18:26:13 +03002220 # Find repository link
2221 local_repo_list = await self.repo_list(cluster_uuid)
2222 for repo in local_repo_list:
Pedro Escaleira0fcb6fe2022-06-04 19:14:11 +01002223 if repo["name"] == repo_name:
2224 repo_url = repo["url"]
2225 break # it is not necessary to continue the loop if the repo link was found...
2226
aktas867418c2021-10-19 18:26:13 +03002227 return repo_url
Gabriel Cubafb03e902022-10-07 11:40:03 -05002228
Gabriel Cuba1c1a2562023-11-20 01:08:39 -05002229 def _repo_to_oci_url(self, repo):
2230 db_repo = self.db.get_one("k8srepos", {"name": repo}, fail_on_empty=False)
2231 if db_repo and "oci" in db_repo:
2232 return db_repo.get("url")
2233
2234 async def _prepare_helm_chart(self, kdu_model, cluster_id):
2235 # e.g.: "stable/openldap", "1.0"
2236 kdu_model, version = self._split_version(kdu_model)
2237 # e.g.: "openldap, stable"
2238 chart_name, repo = self._split_repo(kdu_model)
2239 if repo and chart_name: # repo/chart case
2240 oci_url = self._repo_to_oci_url(repo)
2241 if oci_url: # oci does not require helm repo update
2242 kdu_model = f"{oci_url.rstrip('/')}/{chart_name.lstrip('/')}" # urljoin doesn't work for oci schema
2243 else:
2244 await self.repo_update(cluster_id, repo)
2245 return kdu_model, version
2246
Gabriel Cubafb03e902022-10-07 11:40:03 -05002247 async def create_certificate(
2248 self, cluster_uuid, namespace, dns_prefix, name, secret_name, usage
2249 ):
2250 paths, env = self._init_paths_env(
2251 cluster_name=cluster_uuid, create_if_not_exist=True
2252 )
2253 kubectl = Kubectl(config_file=paths["kube_config"])
2254 await kubectl.create_certificate(
2255 namespace=namespace,
2256 name=name,
2257 dns_prefix=dns_prefix,
2258 secret_name=secret_name,
2259 usages=[usage],
2260 issuer_name="ca-issuer",
2261 )
2262
2263 async def delete_certificate(self, cluster_uuid, namespace, certificate_name):
2264 paths, env = self._init_paths_env(
2265 cluster_name=cluster_uuid, create_if_not_exist=True
2266 )
2267 kubectl = Kubectl(config_file=paths["kube_config"])
2268 await kubectl.delete_certificate(namespace, certificate_name)
Gabriel Cuba5f069332023-04-25 19:26:19 -05002269
2270 async def create_namespace(
2271 self,
2272 namespace,
2273 cluster_uuid,
Gabriel Cubad21509c2023-05-17 01:30:15 -05002274 labels,
Gabriel Cuba5f069332023-04-25 19:26:19 -05002275 ):
2276 """
2277 Create a namespace in a specific cluster
2278
Gabriel Cubad21509c2023-05-17 01:30:15 -05002279 :param namespace: Namespace to be created
Gabriel Cuba5f069332023-04-25 19:26:19 -05002280 :param cluster_uuid: K8s cluster uuid used to retrieve kubeconfig
Gabriel Cubad21509c2023-05-17 01:30:15 -05002281 :param labels: Dictionary with labels for the new namespace
Gabriel Cuba5f069332023-04-25 19:26:19 -05002282 :returns: None
2283 """
2284 paths, env = self._init_paths_env(
2285 cluster_name=cluster_uuid, create_if_not_exist=True
2286 )
2287 kubectl = Kubectl(config_file=paths["kube_config"])
2288 await kubectl.create_namespace(
2289 name=namespace,
Gabriel Cubad21509c2023-05-17 01:30:15 -05002290 labels=labels,
Gabriel Cuba5f069332023-04-25 19:26:19 -05002291 )
2292
2293 async def delete_namespace(
2294 self,
2295 namespace,
2296 cluster_uuid,
2297 ):
2298 """
2299 Delete a namespace in a specific cluster
2300
2301 :param namespace: namespace to be deleted
2302 :param cluster_uuid: K8s cluster uuid used to retrieve kubeconfig
2303 :returns: None
2304 """
2305 paths, env = self._init_paths_env(
2306 cluster_name=cluster_uuid, create_if_not_exist=True
2307 )
2308 kubectl = Kubectl(config_file=paths["kube_config"])
2309 await kubectl.delete_namespace(
2310 name=namespace,
2311 )
2312
2313 async def copy_secret_data(
2314 self,
2315 src_secret: str,
2316 dst_secret: str,
2317 cluster_uuid: str,
2318 data_key: str,
2319 src_namespace: str = "osm",
2320 dst_namespace: str = "osm",
2321 ):
2322 """
2323 Copy a single key and value from an existing secret to a new one
2324
2325 :param src_secret: name of the existing secret
2326 :param dst_secret: name of the new secret
2327 :param cluster_uuid: K8s cluster uuid used to retrieve kubeconfig
2328 :param data_key: key of the existing secret to be copied
2329 :param src_namespace: Namespace of the existing secret
2330 :param dst_namespace: Namespace of the new secret
2331 :returns: None
2332 """
2333 paths, env = self._init_paths_env(
2334 cluster_name=cluster_uuid, create_if_not_exist=True
2335 )
2336 kubectl = Kubectl(config_file=paths["kube_config"])
2337 secret_data = await kubectl.get_secret_content(
2338 name=src_secret,
2339 namespace=src_namespace,
2340 )
2341 # Only the corresponding data_key value needs to be copy
2342 data = {data_key: secret_data.get(data_key)}
2343 await kubectl.create_secret(
2344 name=dst_secret,
2345 data=data,
2346 namespace=dst_namespace,
2347 secret_type="Opaque",
2348 )
2349
2350 async def setup_default_rbac(
2351 self,
2352 name,
2353 namespace,
2354 cluster_uuid,
2355 api_groups,
2356 resources,
2357 verbs,
2358 service_account,
2359 ):
2360 """
2361 Create a basic RBAC for a new namespace.
2362
2363 :param name: name of both Role and Role Binding
2364 :param namespace: K8s namespace
2365 :param cluster_uuid: K8s cluster uuid used to retrieve kubeconfig
2366 :param api_groups: Api groups to be allowed in Policy Rule
2367 :param resources: Resources to be allowed in Policy Rule
2368 :param verbs: Verbs to be allowed in Policy Rule
2369 :param service_account: Service Account name used to bind the Role
2370 :returns: None
2371 """
2372 paths, env = self._init_paths_env(
2373 cluster_name=cluster_uuid, create_if_not_exist=True
2374 )
2375 kubectl = Kubectl(config_file=paths["kube_config"])
2376 await kubectl.create_role(
2377 name=name,
2378 labels={},
2379 namespace=namespace,
2380 api_groups=api_groups,
2381 resources=resources,
2382 verbs=verbs,
2383 )
2384 await kubectl.create_role_binding(
2385 name=name,
2386 labels={},
2387 namespace=namespace,
2388 role_name=name,
2389 sa_name=service_account,
2390 )