blob: 8b93edcefbd4b45fa053313231fd5369c50e1c59 [file] [log] [blame]
quilesj26c78a42019-10-28 18:10:42 +01001##
2# Copyright 2019 Telefonica Investigacion y Desarrollo, S.A.U.
3# This file is part of OSM
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
15# implied.
16# See the License for the specific language governing permissions and
17# limitations under the License.
18#
19# For those usages not covered by the Apache License, Version 2.0 please
20# contact with: nfvlabs@tid.es
21##
22
quilesj26c78a42019-10-28 18:10:42 +010023import asyncio
beierlmf52cb7c2020-04-21 16:36:35 -040024import os
quilesj26c78a42019-10-28 18:10:42 +010025import random
beierlmf52cb7c2020-04-21 16:36:35 -040026import shutil
27import subprocess
28import time
29from uuid import uuid4
30
quilesja6748412019-12-04 07:51:26 +000031from n2vc.exceptions import K8sException
beierlmf52cb7c2020-04-21 16:36:35 -040032from n2vc.k8s_conn import K8sConnector
33import yaml
quilesj26c78a42019-10-28 18:10:42 +010034
35
36class K8sHelmConnector(K8sConnector):
37
38 """
beierlmf52cb7c2020-04-21 16:36:35 -040039 ####################################################################################
40 ################################### P U B L I C ####################################
41 ####################################################################################
quilesj26c78a42019-10-28 18:10:42 +010042 """
43
44 def __init__(
beierlmf52cb7c2020-04-21 16:36:35 -040045 self,
46 fs: object,
47 db: object,
48 kubectl_command: str = "/usr/bin/kubectl",
49 helm_command: str = "/usr/bin/helm",
50 log: object = None,
51 on_update_db=None,
quilesj26c78a42019-10-28 18:10:42 +010052 ):
53 """
54
55 :param fs: file system for kubernetes and helm configuration
56 :param db: database object to write current operation status
57 :param kubectl_command: path to kubectl executable
58 :param helm_command: path to helm executable
59 :param log: logger
60 :param on_update_db: callback called when k8s connector updates database
61 """
62
63 # parent class
beierlmf52cb7c2020-04-21 16:36:35 -040064 K8sConnector.__init__(self, db=db, log=log, on_update_db=on_update_db)
quilesj26c78a42019-10-28 18:10:42 +010065
beierlmf52cb7c2020-04-21 16:36:35 -040066 self.log.info("Initializing K8S Helm connector")
quilesj26c78a42019-10-28 18:10:42 +010067
68 # random numbers for release name generation
69 random.seed(time.time())
70
71 # the file system
72 self.fs = fs
73
74 # exception if kubectl is not installed
75 self.kubectl_command = kubectl_command
76 self._check_file_exists(filename=kubectl_command, exception_if_not_exists=True)
77
78 # exception if helm is not installed
79 self._helm_command = helm_command
80 self._check_file_exists(filename=helm_command, exception_if_not_exists=True)
81
quilesj1be06302019-11-29 11:17:11 +000082 # initialize helm client-only
beierlmf52cb7c2020-04-21 16:36:35 -040083 self.log.debug("Initializing helm client-only...")
84 command = "{} init --client-only".format(self._helm_command)
quilesj1be06302019-11-29 11:17:11 +000085 try:
beierlmf52cb7c2020-04-21 16:36:35 -040086 asyncio.ensure_future(
87 self._local_async_exec(command=command, raise_exception_on_error=False)
88 )
quilesj1be06302019-11-29 11:17:11 +000089 # loop = asyncio.get_event_loop()
beierlmf52cb7c2020-04-21 16:36:35 -040090 # loop.run_until_complete(self._local_async_exec(command=command,
91 # raise_exception_on_error=False))
quilesj1be06302019-11-29 11:17:11 +000092 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -040093 self.warning(
94 msg="helm init failed (it was already initialized): {}".format(e)
95 )
quilesj1be06302019-11-29 11:17:11 +000096
beierlmf52cb7c2020-04-21 16:36:35 -040097 self.log.info("K8S Helm connector initialized")
quilesj26c78a42019-10-28 18:10:42 +010098
99 async def init_env(
beierlmf52cb7c2020-04-21 16:36:35 -0400100 self, k8s_creds: str, namespace: str = "kube-system", reuse_cluster_uuid=None
quilesj26c78a42019-10-28 18:10:42 +0100101 ) -> (str, bool):
garciadeblas54771fa2019-12-13 13:39:03 +0100102 """
103 It prepares a given K8s cluster environment to run Charts on both sides:
104 client (OSM)
105 server (Tiller)
106
beierlmf52cb7c2020-04-21 16:36:35 -0400107 :param k8s_creds: credentials to access a given K8s cluster, i.e. a valid
108 '.kube/config'
109 :param namespace: optional namespace to be used for helm. By default,
110 'kube-system' will be used
garciadeblas54771fa2019-12-13 13:39:03 +0100111 :param reuse_cluster_uuid: existing cluster uuid for reuse
beierlmf52cb7c2020-04-21 16:36:35 -0400112 :return: uuid of the K8s cluster and True if connector has installed some
113 software in the cluster
garciadeblas54771fa2019-12-13 13:39:03 +0100114 (on error, an exception will be raised)
115 """
quilesj26c78a42019-10-28 18:10:42 +0100116
117 cluster_uuid = reuse_cluster_uuid
118 if not cluster_uuid:
119 cluster_uuid = str(uuid4())
120
beierlmf52cb7c2020-04-21 16:36:35 -0400121 self.log.debug("Initializing K8S environment. namespace: {}".format(namespace))
quilesj26c78a42019-10-28 18:10:42 +0100122
123 # create config filename
beierlmf52cb7c2020-04-21 16:36:35 -0400124 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
125 cluster_name=cluster_uuid, create_if_not_exist=True
126 )
tierno119f7232020-04-21 13:22:26 +0000127 with open(config_filename, "w") as f:
128 f.write(k8s_creds)
quilesj26c78a42019-10-28 18:10:42 +0100129
130 # check if tiller pod is up in cluster
beierlmf52cb7c2020-04-21 16:36:35 -0400131 command = "{} --kubeconfig={} --namespace={} get deployments".format(
132 self.kubectl_command, config_filename, namespace
133 )
134 output, _rc = await self._local_async_exec(
135 command=command, raise_exception_on_error=True
136 )
quilesj26c78a42019-10-28 18:10:42 +0100137
tierno119f7232020-04-21 13:22:26 +0000138 output_table = self._output_to_table(output=output)
quilesj26c78a42019-10-28 18:10:42 +0100139
140 # find 'tiller' pod in all pods
141 already_initialized = False
142 try:
143 for row in output_table:
beierlmf52cb7c2020-04-21 16:36:35 -0400144 if row[0].startswith("tiller-deploy"):
quilesj26c78a42019-10-28 18:10:42 +0100145 already_initialized = True
146 break
beierlmf52cb7c2020-04-21 16:36:35 -0400147 except Exception:
quilesj26c78a42019-10-28 18:10:42 +0100148 pass
149
150 # helm init
151 n2vc_installed_sw = False
152 if not already_initialized:
beierlmf52cb7c2020-04-21 16:36:35 -0400153 self.log.info(
154 "Initializing helm in client and server: {}".format(cluster_uuid)
155 )
156 command = "{} --kubeconfig={} --tiller-namespace={} --home={} init".format(
157 self._helm_command, config_filename, namespace, helm_dir
158 )
159 output, _rc = await self._local_async_exec(
160 command=command, raise_exception_on_error=True
161 )
quilesj26c78a42019-10-28 18:10:42 +0100162 n2vc_installed_sw = True
163 else:
164 # check client helm installation
beierlmf52cb7c2020-04-21 16:36:35 -0400165 check_file = helm_dir + "/repository/repositories.yaml"
166 if not self._check_file_exists(
167 filename=check_file, exception_if_not_exists=False
168 ):
169 self.log.info("Initializing helm in client: {}".format(cluster_uuid))
170 command = (
171 "{} --kubeconfig={} --tiller-namespace={} "
172 "--home={} init --client-only"
173 ).format(self._helm_command, config_filename, namespace, helm_dir)
174 output, _rc = await self._local_async_exec(
175 command=command, raise_exception_on_error=True
176 )
quilesj26c78a42019-10-28 18:10:42 +0100177 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400178 self.log.info("Helm client already initialized")
quilesj26c78a42019-10-28 18:10:42 +0100179
beierlmf52cb7c2020-04-21 16:36:35 -0400180 self.log.info("Cluster initialized {}".format(cluster_uuid))
quilesj26c78a42019-10-28 18:10:42 +0100181
182 return cluster_uuid, n2vc_installed_sw
183
184 async def repo_add(
beierlmf52cb7c2020-04-21 16:36:35 -0400185 self, cluster_uuid: str, name: str, url: str, repo_type: str = "chart"
quilesj26c78a42019-10-28 18:10:42 +0100186 ):
187
beierlmf52cb7c2020-04-21 16:36:35 -0400188 self.log.debug("adding {} repository {}. URL: {}".format(repo_type, name, url))
quilesj26c78a42019-10-28 18:10:42 +0100189
190 # config filename
beierlmf52cb7c2020-04-21 16:36:35 -0400191 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
192 cluster_name=cluster_uuid, create_if_not_exist=True
193 )
quilesj26c78a42019-10-28 18:10:42 +0100194
195 # helm repo update
beierlmf52cb7c2020-04-21 16:36:35 -0400196 command = "{} --kubeconfig={} --home={} repo update".format(
197 self._helm_command, config_filename, helm_dir
198 )
199 self.log.debug("updating repo: {}".format(command))
quilesj26c78a42019-10-28 18:10:42 +0100200 await self._local_async_exec(command=command, raise_exception_on_error=False)
201
202 # helm repo add name url
beierlmf52cb7c2020-04-21 16:36:35 -0400203 command = "{} --kubeconfig={} --home={} repo add {} {}".format(
204 self._helm_command, config_filename, helm_dir, name, url
205 )
206 self.log.debug("adding repo: {}".format(command))
quilesj26c78a42019-10-28 18:10:42 +0100207 await self._local_async_exec(command=command, raise_exception_on_error=True)
208
beierlmf52cb7c2020-04-21 16:36:35 -0400209 async def repo_list(self, cluster_uuid: str) -> list:
quilesj26c78a42019-10-28 18:10:42 +0100210 """
211 Get the list of registered repositories
212
213 :return: list of registered repositories: [ (name, url) .... ]
214 """
215
beierlmf52cb7c2020-04-21 16:36:35 -0400216 self.log.debug("list repositories for cluster {}".format(cluster_uuid))
quilesj26c78a42019-10-28 18:10:42 +0100217
218 # config filename
beierlmf52cb7c2020-04-21 16:36:35 -0400219 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
220 cluster_name=cluster_uuid, create_if_not_exist=True
221 )
quilesj26c78a42019-10-28 18:10:42 +0100222
beierlmf52cb7c2020-04-21 16:36:35 -0400223 command = "{} --kubeconfig={} --home={} repo list --output yaml".format(
224 self._helm_command, config_filename, helm_dir
225 )
quilesj26c78a42019-10-28 18:10:42 +0100226
beierlmf52cb7c2020-04-21 16:36:35 -0400227 output, _rc = await self._local_async_exec(
228 command=command, raise_exception_on_error=True
229 )
quilesj26c78a42019-10-28 18:10:42 +0100230 if output and len(output) > 0:
231 return yaml.load(output, Loader=yaml.SafeLoader)
232 else:
233 return []
234
beierlmf52cb7c2020-04-21 16:36:35 -0400235 async def repo_remove(self, cluster_uuid: str, name: str):
quilesj26c78a42019-10-28 18:10:42 +0100236 """
237 Remove a repository from OSM
238
239 :param cluster_uuid: the cluster
240 :param name: repo name in OSM
241 :return: True if successful
242 """
243
beierlmf52cb7c2020-04-21 16:36:35 -0400244 self.log.debug("list repositories for cluster {}".format(cluster_uuid))
quilesj26c78a42019-10-28 18:10:42 +0100245
246 # config filename
beierlmf52cb7c2020-04-21 16:36:35 -0400247 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
248 cluster_name=cluster_uuid, create_if_not_exist=True
249 )
quilesj26c78a42019-10-28 18:10:42 +0100250
beierlmf52cb7c2020-04-21 16:36:35 -0400251 command = "{} --kubeconfig={} --home={} repo remove {}".format(
252 self._helm_command, config_filename, helm_dir, name
253 )
quilesj26c78a42019-10-28 18:10:42 +0100254
255 await self._local_async_exec(command=command, raise_exception_on_error=True)
256
257 async def reset(
beierlmf52cb7c2020-04-21 16:36:35 -0400258 self, cluster_uuid: str, force: bool = False, uninstall_sw: bool = False
quilesj26c78a42019-10-28 18:10:42 +0100259 ) -> bool:
260
beierlmf52cb7c2020-04-21 16:36:35 -0400261 self.log.debug(
262 "Resetting K8s environment. cluster uuid: {}".format(cluster_uuid)
263 )
quilesj26c78a42019-10-28 18:10:42 +0100264
265 # get kube and helm directories
beierlmf52cb7c2020-04-21 16:36:35 -0400266 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
267 cluster_name=cluster_uuid, create_if_not_exist=False
268 )
quilesj26c78a42019-10-28 18:10:42 +0100269
270 # uninstall releases if needed
271 releases = await self.instances_list(cluster_uuid=cluster_uuid)
272 if len(releases) > 0:
273 if force:
274 for r in releases:
275 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400276 kdu_instance = r.get("Name")
277 chart = r.get("Chart")
278 self.log.debug(
279 "Uninstalling {} -> {}".format(chart, kdu_instance)
280 )
281 await self.uninstall(
282 cluster_uuid=cluster_uuid, kdu_instance=kdu_instance
283 )
quilesj26c78a42019-10-28 18:10:42 +0100284 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400285 self.log.error(
286 "Error uninstalling release {}: {}".format(kdu_instance, e)
287 )
quilesj26c78a42019-10-28 18:10:42 +0100288 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400289 msg = (
290 "Cluster has releases and not force. Cannot reset K8s "
291 "environment. Cluster uuid: {}"
292 ).format(cluster_uuid)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100293 self.log.error(msg)
quilesja6748412019-12-04 07:51:26 +0000294 raise K8sException(msg)
quilesj26c78a42019-10-28 18:10:42 +0100295
296 if uninstall_sw:
297
beierlmf52cb7c2020-04-21 16:36:35 -0400298 self.log.debug("Uninstalling tiller from cluster {}".format(cluster_uuid))
quilesj26c78a42019-10-28 18:10:42 +0100299
300 # find namespace for tiller pod
beierlmf52cb7c2020-04-21 16:36:35 -0400301 command = "{} --kubeconfig={} get deployments --all-namespaces".format(
302 self.kubectl_command, config_filename
303 )
304 output, _rc = await self._local_async_exec(
305 command=command, raise_exception_on_error=False
306 )
quilesj26c78a42019-10-28 18:10:42 +0100307 output_table = K8sHelmConnector._output_to_table(output=output)
308 namespace = None
309 for r in output_table:
310 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400311 if "tiller-deploy" in r[1]:
quilesj26c78a42019-10-28 18:10:42 +0100312 namespace = r[0]
313 break
beierlmf52cb7c2020-04-21 16:36:35 -0400314 except Exception:
quilesj26c78a42019-10-28 18:10:42 +0100315 pass
316 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400317 msg = "Tiller deployment not found in cluster {}".format(cluster_uuid)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100318 self.log.error(msg)
quilesj26c78a42019-10-28 18:10:42 +0100319
beierlmf52cb7c2020-04-21 16:36:35 -0400320 self.log.debug("namespace for tiller: {}".format(namespace))
quilesj26c78a42019-10-28 18:10:42 +0100321
beierlmf52cb7c2020-04-21 16:36:35 -0400322 force_str = "--force"
quilesj26c78a42019-10-28 18:10:42 +0100323
324 if namespace:
325 # delete tiller deployment
beierlmf52cb7c2020-04-21 16:36:35 -0400326 self.log.debug(
327 "Deleting tiller deployment for cluster {}, namespace {}".format(
328 cluster_uuid, namespace
329 )
330 )
331 command = (
332 "{} --namespace {} --kubeconfig={} {} delete deployment "
333 "tiller-deploy"
334 ).format(self.kubectl_command, namespace, config_filename, force_str)
335 await self._local_async_exec(
336 command=command, raise_exception_on_error=False
337 )
quilesj26c78a42019-10-28 18:10:42 +0100338
339 # uninstall tiller from cluster
beierlmf52cb7c2020-04-21 16:36:35 -0400340 self.log.debug(
341 "Uninstalling tiller from cluster {}".format(cluster_uuid)
342 )
343 command = "{} --kubeconfig={} --home={} reset".format(
344 self._helm_command, config_filename, helm_dir
345 )
346 self.log.debug("resetting: {}".format(command))
347 output, _rc = await self._local_async_exec(
348 command=command, raise_exception_on_error=True
349 )
quilesj26c78a42019-10-28 18:10:42 +0100350 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400351 self.log.debug("namespace not found")
quilesj26c78a42019-10-28 18:10:42 +0100352
353 # delete cluster directory
beierlmf52cb7c2020-04-21 16:36:35 -0400354 direct = self.fs.path + "/" + cluster_uuid
355 self.log.debug("Removing directory {}".format(direct))
356 shutil.rmtree(direct, ignore_errors=True)
quilesj26c78a42019-10-28 18:10:42 +0100357
358 return True
359
360 async def install(
beierlmf52cb7c2020-04-21 16:36:35 -0400361 self,
362 cluster_uuid: str,
363 kdu_model: str,
364 atomic: bool = True,
365 timeout: float = 300,
366 params: dict = None,
367 db_dict: dict = None,
368 kdu_name: str = None,
369 namespace: str = None,
quilesj26c78a42019-10-28 18:10:42 +0100370 ):
371
beierlmf52cb7c2020-04-21 16:36:35 -0400372 self.log.debug("installing {} in cluster {}".format(kdu_model, cluster_uuid))
quilesj26c78a42019-10-28 18:10:42 +0100373
quilesj26c78a42019-10-28 18:10:42 +0100374 # config filename
beierlmf52cb7c2020-04-21 16:36:35 -0400375 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
376 cluster_name=cluster_uuid, create_if_not_exist=True
377 )
quilesj26c78a42019-10-28 18:10:42 +0100378
379 # params to str
quilesjcda5f412019-11-18 11:32:12 +0100380 # params_str = K8sHelmConnector._params_to_set_option(params)
beierlmf52cb7c2020-04-21 16:36:35 -0400381 params_str, file_to_delete = self._params_to_file_option(
382 cluster_uuid=cluster_uuid, params=params
383 )
quilesj26c78a42019-10-28 18:10:42 +0100384
beierlmf52cb7c2020-04-21 16:36:35 -0400385 timeout_str = ""
quilesj26c78a42019-10-28 18:10:42 +0100386 if timeout:
beierlmf52cb7c2020-04-21 16:36:35 -0400387 timeout_str = "--timeout {}".format(timeout)
quilesj26c78a42019-10-28 18:10:42 +0100388
389 # atomic
beierlmf52cb7c2020-04-21 16:36:35 -0400390 atomic_str = ""
quilesj26c78a42019-10-28 18:10:42 +0100391 if atomic:
beierlmf52cb7c2020-04-21 16:36:35 -0400392 atomic_str = "--atomic"
tierno53555f62020-04-07 11:08:16 +0000393 # namespace
beierlmf52cb7c2020-04-21 16:36:35 -0400394 namespace_str = ""
tierno53555f62020-04-07 11:08:16 +0000395 if namespace:
396 namespace_str = "--namespace {}".format(namespace)
quilesj26c78a42019-10-28 18:10:42 +0100397
398 # version
beierlmf52cb7c2020-04-21 16:36:35 -0400399 version_str = ""
400 if ":" in kdu_model:
401 parts = kdu_model.split(sep=":")
quilesj26c78a42019-10-28 18:10:42 +0100402 if len(parts) == 2:
beierlmf52cb7c2020-04-21 16:36:35 -0400403 version_str = "--version {}".format(parts[1])
quilesj26c78a42019-10-28 18:10:42 +0100404 kdu_model = parts[0]
405
quilesja6748412019-12-04 07:51:26 +0000406 # generate a name for the release. Then, check if already exists
quilesj26c78a42019-10-28 18:10:42 +0100407 kdu_instance = None
408 while kdu_instance is None:
409 kdu_instance = K8sHelmConnector._generate_release_name(kdu_model)
410 try:
411 result = await self._status_kdu(
412 cluster_uuid=cluster_uuid,
413 kdu_instance=kdu_instance,
beierlmf52cb7c2020-04-21 16:36:35 -0400414 show_error_log=False,
quilesj26c78a42019-10-28 18:10:42 +0100415 )
416 if result is not None:
417 # instance already exists: generate a new one
418 kdu_instance = None
tierno601697a2020-02-04 15:26:25 +0000419 except K8sException:
420 pass
quilesj26c78a42019-10-28 18:10:42 +0100421
422 # helm repo install
beierlmf52cb7c2020-04-21 16:36:35 -0400423 command = (
424 "{helm} install {atomic} --output yaml --kubeconfig={config} --home={dir} "
425 "{params} {timeout} --name={name} {ns} {model} {ver}".format(
426 helm=self._helm_command,
427 atomic=atomic_str,
428 config=config_filename,
429 dir=helm_dir,
430 params=params_str,
431 timeout=timeout_str,
432 name=kdu_instance,
433 ns=namespace_str,
434 model=kdu_model,
435 ver=version_str,
436 )
437 )
438 self.log.debug("installing: {}".format(command))
quilesj26c78a42019-10-28 18:10:42 +0100439
440 if atomic:
441 # exec helm in a task
442 exec_task = asyncio.ensure_future(
beierlmf52cb7c2020-04-21 16:36:35 -0400443 coro_or_future=self._local_async_exec(
444 command=command, raise_exception_on_error=False
445 )
quilesj26c78a42019-10-28 18:10:42 +0100446 )
lloretgalleg65ddf852020-02-20 12:01:17 +0100447
quilesj26c78a42019-10-28 18:10:42 +0100448 # write status in another task
449 status_task = asyncio.ensure_future(
450 coro_or_future=self._store_status(
451 cluster_uuid=cluster_uuid,
452 kdu_instance=kdu_instance,
453 db_dict=db_dict,
beierlmf52cb7c2020-04-21 16:36:35 -0400454 operation="install",
455 run_once=False,
quilesj26c78a42019-10-28 18:10:42 +0100456 )
457 )
458
459 # wait for execution task
460 await asyncio.wait([exec_task])
461
462 # cancel status task
463 status_task.cancel()
464
465 output, rc = exec_task.result()
466
467 else:
468
beierlmf52cb7c2020-04-21 16:36:35 -0400469 output, rc = await self._local_async_exec(
470 command=command, raise_exception_on_error=False
471 )
quilesj26c78a42019-10-28 18:10:42 +0100472
quilesjcda5f412019-11-18 11:32:12 +0100473 # remove temporal values yaml file
474 if file_to_delete:
475 os.remove(file_to_delete)
476
quilesj26c78a42019-10-28 18:10:42 +0100477 # write final status
478 await self._store_status(
479 cluster_uuid=cluster_uuid,
480 kdu_instance=kdu_instance,
481 db_dict=db_dict,
beierlmf52cb7c2020-04-21 16:36:35 -0400482 operation="install",
quilesj26c78a42019-10-28 18:10:42 +0100483 run_once=True,
beierlmf52cb7c2020-04-21 16:36:35 -0400484 check_every=0,
quilesj26c78a42019-10-28 18:10:42 +0100485 )
486
487 if rc != 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400488 msg = "Error executing command: {}\nOutput: {}".format(command, output)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100489 self.log.error(msg)
quilesja6748412019-12-04 07:51:26 +0000490 raise K8sException(msg)
quilesj26c78a42019-10-28 18:10:42 +0100491
beierlmf52cb7c2020-04-21 16:36:35 -0400492 self.log.debug("Returning kdu_instance {}".format(kdu_instance))
quilesj26c78a42019-10-28 18:10:42 +0100493 return kdu_instance
494
beierlmf52cb7c2020-04-21 16:36:35 -0400495 async def instances_list(self, cluster_uuid: str) -> list:
quilesj26c78a42019-10-28 18:10:42 +0100496 """
497 returns a list of deployed releases in a cluster
498
499 :param cluster_uuid: the cluster
500 :return:
501 """
502
beierlmf52cb7c2020-04-21 16:36:35 -0400503 self.log.debug("list releases for cluster {}".format(cluster_uuid))
quilesj26c78a42019-10-28 18:10:42 +0100504
505 # config filename
beierlmf52cb7c2020-04-21 16:36:35 -0400506 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
507 cluster_name=cluster_uuid, create_if_not_exist=True
508 )
quilesj26c78a42019-10-28 18:10:42 +0100509
beierlmf52cb7c2020-04-21 16:36:35 -0400510 command = "{} --kubeconfig={} --home={} list --output yaml".format(
511 self._helm_command, config_filename, helm_dir
512 )
quilesj26c78a42019-10-28 18:10:42 +0100513
beierlmf52cb7c2020-04-21 16:36:35 -0400514 output, _rc = await self._local_async_exec(
515 command=command, raise_exception_on_error=True
516 )
quilesj26c78a42019-10-28 18:10:42 +0100517
518 if output and len(output) > 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400519 return yaml.load(output, Loader=yaml.SafeLoader).get("Releases")
quilesj26c78a42019-10-28 18:10:42 +0100520 else:
521 return []
522
523 async def upgrade(
beierlmf52cb7c2020-04-21 16:36:35 -0400524 self,
525 cluster_uuid: str,
526 kdu_instance: str,
527 kdu_model: str = None,
528 atomic: bool = True,
529 timeout: float = 300,
530 params: dict = None,
531 db_dict: dict = None,
quilesj26c78a42019-10-28 18:10:42 +0100532 ):
533
beierlmf52cb7c2020-04-21 16:36:35 -0400534 self.log.debug("upgrading {} in cluster {}".format(kdu_model, cluster_uuid))
quilesj26c78a42019-10-28 18:10:42 +0100535
quilesj26c78a42019-10-28 18:10:42 +0100536 # config filename
beierlmf52cb7c2020-04-21 16:36:35 -0400537 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
538 cluster_name=cluster_uuid, create_if_not_exist=True
539 )
quilesj26c78a42019-10-28 18:10:42 +0100540
541 # params to str
quilesjcda5f412019-11-18 11:32:12 +0100542 # params_str = K8sHelmConnector._params_to_set_option(params)
beierlmf52cb7c2020-04-21 16:36:35 -0400543 params_str, file_to_delete = self._params_to_file_option(
544 cluster_uuid=cluster_uuid, params=params
545 )
quilesj26c78a42019-10-28 18:10:42 +0100546
beierlmf52cb7c2020-04-21 16:36:35 -0400547 timeout_str = ""
quilesj26c78a42019-10-28 18:10:42 +0100548 if timeout:
beierlmf52cb7c2020-04-21 16:36:35 -0400549 timeout_str = "--timeout {}".format(timeout)
quilesj26c78a42019-10-28 18:10:42 +0100550
551 # atomic
beierlmf52cb7c2020-04-21 16:36:35 -0400552 atomic_str = ""
quilesj26c78a42019-10-28 18:10:42 +0100553 if atomic:
beierlmf52cb7c2020-04-21 16:36:35 -0400554 atomic_str = "--atomic"
quilesj26c78a42019-10-28 18:10:42 +0100555
556 # version
beierlmf52cb7c2020-04-21 16:36:35 -0400557 version_str = ""
558 if kdu_model and ":" in kdu_model:
559 parts = kdu_model.split(sep=":")
quilesj26c78a42019-10-28 18:10:42 +0100560 if len(parts) == 2:
beierlmf52cb7c2020-04-21 16:36:35 -0400561 version_str = "--version {}".format(parts[1])
quilesj26c78a42019-10-28 18:10:42 +0100562 kdu_model = parts[0]
563
564 # helm repo upgrade
beierlmf52cb7c2020-04-21 16:36:35 -0400565 command = (
566 "{} upgrade {} --output yaml --kubeconfig={} " "--home={} {} {} {} {} {}"
567 ).format(
568 self._helm_command,
569 atomic_str,
570 config_filename,
571 helm_dir,
572 params_str,
573 timeout_str,
574 kdu_instance,
575 kdu_model,
576 version_str,
577 )
578 self.log.debug("upgrading: {}".format(command))
quilesj26c78a42019-10-28 18:10:42 +0100579
580 if atomic:
581
582 # exec helm in a task
583 exec_task = asyncio.ensure_future(
beierlmf52cb7c2020-04-21 16:36:35 -0400584 coro_or_future=self._local_async_exec(
585 command=command, raise_exception_on_error=False
586 )
quilesj26c78a42019-10-28 18:10:42 +0100587 )
588 # write status in another task
589 status_task = asyncio.ensure_future(
590 coro_or_future=self._store_status(
591 cluster_uuid=cluster_uuid,
592 kdu_instance=kdu_instance,
593 db_dict=db_dict,
beierlmf52cb7c2020-04-21 16:36:35 -0400594 operation="upgrade",
595 run_once=False,
quilesj26c78a42019-10-28 18:10:42 +0100596 )
597 )
598
599 # wait for execution task
quilesj1be06302019-11-29 11:17:11 +0000600 await asyncio.wait([exec_task])
quilesj26c78a42019-10-28 18:10:42 +0100601
602 # cancel status task
603 status_task.cancel()
604 output, rc = exec_task.result()
605
606 else:
607
beierlmf52cb7c2020-04-21 16:36:35 -0400608 output, rc = await self._local_async_exec(
609 command=command, raise_exception_on_error=False
610 )
quilesj26c78a42019-10-28 18:10:42 +0100611
quilesjcda5f412019-11-18 11:32:12 +0100612 # remove temporal values yaml file
613 if file_to_delete:
614 os.remove(file_to_delete)
615
quilesj26c78a42019-10-28 18:10:42 +0100616 # write final status
617 await self._store_status(
618 cluster_uuid=cluster_uuid,
619 kdu_instance=kdu_instance,
620 db_dict=db_dict,
beierlmf52cb7c2020-04-21 16:36:35 -0400621 operation="upgrade",
quilesj26c78a42019-10-28 18:10:42 +0100622 run_once=True,
beierlmf52cb7c2020-04-21 16:36:35 -0400623 check_every=0,
quilesj26c78a42019-10-28 18:10:42 +0100624 )
625
626 if rc != 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400627 msg = "Error executing command: {}\nOutput: {}".format(command, output)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100628 self.log.error(msg)
quilesja6748412019-12-04 07:51:26 +0000629 raise K8sException(msg)
quilesj26c78a42019-10-28 18:10:42 +0100630
631 # return new revision number
beierlmf52cb7c2020-04-21 16:36:35 -0400632 instance = await self.get_instance_info(
633 cluster_uuid=cluster_uuid, kdu_instance=kdu_instance
634 )
quilesj26c78a42019-10-28 18:10:42 +0100635 if instance:
beierlmf52cb7c2020-04-21 16:36:35 -0400636 revision = int(instance.get("Revision"))
637 self.log.debug("New revision: {}".format(revision))
quilesj26c78a42019-10-28 18:10:42 +0100638 return revision
639 else:
640 return 0
641
642 async def rollback(
beierlmf52cb7c2020-04-21 16:36:35 -0400643 self, cluster_uuid: str, kdu_instance: str, revision=0, db_dict: dict = None
quilesj26c78a42019-10-28 18:10:42 +0100644 ):
645
beierlmf52cb7c2020-04-21 16:36:35 -0400646 self.log.debug(
647 "rollback kdu_instance {} to revision {} from cluster {}".format(
648 kdu_instance, revision, cluster_uuid
649 )
650 )
quilesj26c78a42019-10-28 18:10:42 +0100651
652 # config filename
beierlmf52cb7c2020-04-21 16:36:35 -0400653 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
654 cluster_name=cluster_uuid, create_if_not_exist=True
655 )
quilesj26c78a42019-10-28 18:10:42 +0100656
beierlmf52cb7c2020-04-21 16:36:35 -0400657 command = "{} rollback --kubeconfig={} --home={} {} {} --wait".format(
658 self._helm_command, config_filename, helm_dir, kdu_instance, revision
659 )
quilesj26c78a42019-10-28 18:10:42 +0100660
661 # exec helm in a task
662 exec_task = asyncio.ensure_future(
beierlmf52cb7c2020-04-21 16:36:35 -0400663 coro_or_future=self._local_async_exec(
664 command=command, raise_exception_on_error=False
665 )
quilesj26c78a42019-10-28 18:10:42 +0100666 )
667 # write status in another task
668 status_task = asyncio.ensure_future(
669 coro_or_future=self._store_status(
670 cluster_uuid=cluster_uuid,
671 kdu_instance=kdu_instance,
672 db_dict=db_dict,
beierlmf52cb7c2020-04-21 16:36:35 -0400673 operation="rollback",
674 run_once=False,
quilesj26c78a42019-10-28 18:10:42 +0100675 )
676 )
677
678 # wait for execution task
679 await asyncio.wait([exec_task])
680
681 # cancel status task
682 status_task.cancel()
683
684 output, rc = exec_task.result()
685
686 # write final status
687 await self._store_status(
688 cluster_uuid=cluster_uuid,
689 kdu_instance=kdu_instance,
690 db_dict=db_dict,
beierlmf52cb7c2020-04-21 16:36:35 -0400691 operation="rollback",
quilesj26c78a42019-10-28 18:10:42 +0100692 run_once=True,
beierlmf52cb7c2020-04-21 16:36:35 -0400693 check_every=0,
quilesj26c78a42019-10-28 18:10:42 +0100694 )
695
696 if rc != 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400697 msg = "Error executing command: {}\nOutput: {}".format(command, output)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100698 self.log.error(msg)
quilesja6748412019-12-04 07:51:26 +0000699 raise K8sException(msg)
quilesj26c78a42019-10-28 18:10:42 +0100700
701 # return new revision number
beierlmf52cb7c2020-04-21 16:36:35 -0400702 instance = await self.get_instance_info(
703 cluster_uuid=cluster_uuid, kdu_instance=kdu_instance
704 )
quilesj26c78a42019-10-28 18:10:42 +0100705 if instance:
beierlmf52cb7c2020-04-21 16:36:35 -0400706 revision = int(instance.get("Revision"))
707 self.log.debug("New revision: {}".format(revision))
quilesj26c78a42019-10-28 18:10:42 +0100708 return revision
709 else:
710 return 0
711
beierlmf52cb7c2020-04-21 16:36:35 -0400712 async def uninstall(self, cluster_uuid: str, kdu_instance: str):
quilesj26c78a42019-10-28 18:10:42 +0100713 """
beierlmf52cb7c2020-04-21 16:36:35 -0400714 Removes an existing KDU instance. It would implicitly use the `delete` call
715 (this call would happen after all _terminate-config-primitive_ of the VNF
716 are invoked).
quilesj26c78a42019-10-28 18:10:42 +0100717
718 :param cluster_uuid: UUID of a K8s cluster known by OSM
719 :param kdu_instance: unique name for the KDU instance to be deleted
720 :return: True if successful
721 """
722
beierlmf52cb7c2020-04-21 16:36:35 -0400723 self.log.debug(
724 "uninstall kdu_instance {} from cluster {}".format(
725 kdu_instance, cluster_uuid
726 )
727 )
quilesj26c78a42019-10-28 18:10:42 +0100728
729 # config filename
beierlmf52cb7c2020-04-21 16:36:35 -0400730 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
731 cluster_name=cluster_uuid, create_if_not_exist=True
732 )
quilesj26c78a42019-10-28 18:10:42 +0100733
beierlmf52cb7c2020-04-21 16:36:35 -0400734 command = "{} --kubeconfig={} --home={} delete --purge {}".format(
735 self._helm_command, config_filename, helm_dir, kdu_instance
736 )
quilesj26c78a42019-10-28 18:10:42 +0100737
beierlmf52cb7c2020-04-21 16:36:35 -0400738 output, _rc = await self._local_async_exec(
739 command=command, raise_exception_on_error=True
740 )
quilesj26c78a42019-10-28 18:10:42 +0100741
742 return self._output_to_table(output)
743
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200744 async def exec_primitive(
745 self,
746 cluster_uuid: str = None,
747 kdu_instance: str = None,
748 primitive_name: str = None,
749 timeout: float = 300,
750 params: dict = None,
751 db_dict: dict = None,
752 ) -> str:
753 """Exec primitive (Juju action)
754
755 :param cluster_uuid str: The UUID of the cluster
756 :param kdu_instance str: The unique name of the KDU instance
757 :param primitive_name: Name of action that will be executed
758 :param timeout: Timeout for action execution
759 :param params: Dictionary of all the parameters needed for the action
760 :db_dict: Dictionary for any additional data
761
762 :return: Returns the output of the action
763 """
beierlmf52cb7c2020-04-21 16:36:35 -0400764 raise K8sException(
765 "KDUs deployed with Helm don't support actions "
766 "different from rollback, upgrade and status"
767 )
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200768
beierlmf52cb7c2020-04-21 16:36:35 -0400769 async def inspect_kdu(self, kdu_model: str, repo_url: str = None) -> str:
quilesj26c78a42019-10-28 18:10:42 +0100770
beierlmf52cb7c2020-04-21 16:36:35 -0400771 self.log.debug(
772 "inspect kdu_model {} from (optional) repo: {}".format(kdu_model, repo_url)
773 )
quilesj26c78a42019-10-28 18:10:42 +0100774
beierlmf52cb7c2020-04-21 16:36:35 -0400775 return await self._exec_inspect_comand(
776 inspect_command="", kdu_model=kdu_model, repo_url=repo_url
777 )
quilesj26c78a42019-10-28 18:10:42 +0100778
beierlmf52cb7c2020-04-21 16:36:35 -0400779 async def values_kdu(self, kdu_model: str, repo_url: str = None) -> str:
quilesj26c78a42019-10-28 18:10:42 +0100780
beierlmf52cb7c2020-04-21 16:36:35 -0400781 self.log.debug(
782 "inspect kdu_model values {} from (optional) repo: {}".format(
783 kdu_model, repo_url
784 )
785 )
quilesj1be06302019-11-29 11:17:11 +0000786
beierlmf52cb7c2020-04-21 16:36:35 -0400787 return await self._exec_inspect_comand(
788 inspect_command="values", kdu_model=kdu_model, repo_url=repo_url
789 )
quilesj26c78a42019-10-28 18:10:42 +0100790
beierlmf52cb7c2020-04-21 16:36:35 -0400791 async def help_kdu(self, kdu_model: str, repo_url: str = None) -> str:
quilesj26c78a42019-10-28 18:10:42 +0100792
beierlmf52cb7c2020-04-21 16:36:35 -0400793 self.log.debug(
794 "inspect kdu_model {} readme.md from repo: {}".format(kdu_model, repo_url)
795 )
quilesj26c78a42019-10-28 18:10:42 +0100796
beierlmf52cb7c2020-04-21 16:36:35 -0400797 return await self._exec_inspect_comand(
798 inspect_command="readme", kdu_model=kdu_model, repo_url=repo_url
799 )
quilesj26c78a42019-10-28 18:10:42 +0100800
beierlmf52cb7c2020-04-21 16:36:35 -0400801 async def status_kdu(self, cluster_uuid: str, kdu_instance: str) -> str:
quilesj26c78a42019-10-28 18:10:42 +0100802
quilesj1be06302019-11-29 11:17:11 +0000803 # call internal function
804 return await self._status_kdu(
805 cluster_uuid=cluster_uuid,
806 kdu_instance=kdu_instance,
807 show_error_log=True,
beierlmf52cb7c2020-04-21 16:36:35 -0400808 return_text=True,
quilesj1be06302019-11-29 11:17:11 +0000809 )
quilesj26c78a42019-10-28 18:10:42 +0100810
lloretgalleg65ddf852020-02-20 12:01:17 +0100811 async def synchronize_repos(self, cluster_uuid: str):
812
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100813 self.log.debug("syncronize repos for cluster helm-id: {}",)
lloretgalleg65ddf852020-02-20 12:01:17 +0100814 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400815 update_repos_timeout = (
816 300 # max timeout to sync a single repos, more than this is too much
817 )
818 db_k8scluster = self.db.get_one(
819 "k8sclusters", {"_admin.helm-chart.id": cluster_uuid}
820 )
lloretgalleg65ddf852020-02-20 12:01:17 +0100821 if db_k8scluster:
beierlmf52cb7c2020-04-21 16:36:35 -0400822 nbi_repo_list = (
823 db_k8scluster.get("_admin").get("helm_chart_repos") or []
824 )
825 cluster_repo_dict = (
826 db_k8scluster.get("_admin").get("helm_charts_added") or {}
827 )
lloretgalleg65ddf852020-02-20 12:01:17 +0100828 # elements that must be deleted
829 deleted_repo_list = []
830 added_repo_dict = {}
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100831 self.log.debug("helm_chart_repos: {}".format(nbi_repo_list))
832 self.log.debug("helm_charts_added: {}".format(cluster_repo_dict))
lloretgalleg65ddf852020-02-20 12:01:17 +0100833
834 # obtain repos to add: registered by nbi but not added
beierlmf52cb7c2020-04-21 16:36:35 -0400835 repos_to_add = [
836 repo for repo in nbi_repo_list if not cluster_repo_dict.get(repo)
837 ]
lloretgalleg65ddf852020-02-20 12:01:17 +0100838
839 # obtain repos to delete: added by cluster but not in nbi list
beierlmf52cb7c2020-04-21 16:36:35 -0400840 repos_to_delete = [
841 repo
842 for repo in cluster_repo_dict.keys()
843 if repo not in nbi_repo_list
844 ]
lloretgalleg65ddf852020-02-20 12:01:17 +0100845
beierlmf52cb7c2020-04-21 16:36:35 -0400846 # delete repos: must delete first then add because there may be
847 # different repos with same name but
lloretgalleg65ddf852020-02-20 12:01:17 +0100848 # different id and url
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100849 self.log.debug("repos to delete: {}".format(repos_to_delete))
lloretgalleg65ddf852020-02-20 12:01:17 +0100850 for repo_id in repos_to_delete:
851 # try to delete repos
852 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400853 repo_delete_task = asyncio.ensure_future(
854 self.repo_remove(
855 cluster_uuid=cluster_uuid,
856 name=cluster_repo_dict[repo_id],
857 )
858 )
lloretgalleg65ddf852020-02-20 12:01:17 +0100859 await asyncio.wait_for(repo_delete_task, update_repos_timeout)
860 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400861 self.warning(
862 "Error deleting repo, id: {}, name: {}, err_msg: {}".format(
863 repo_id, cluster_repo_dict[repo_id], str(e)
864 )
865 )
866 # always add to the list of to_delete if there is an error
867 # because if is not there
868 # deleting raises error
lloretgalleg65ddf852020-02-20 12:01:17 +0100869 deleted_repo_list.append(repo_id)
870
871 # add repos
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100872 self.log.debug("repos to add: {}".format(repos_to_add))
lloretgalleg65ddf852020-02-20 12:01:17 +0100873 for repo_id in repos_to_add:
874 # obtain the repo data from the db
beierlmf52cb7c2020-04-21 16:36:35 -0400875 # if there is an error getting the repo in the database we will
876 # ignore this repo and continue
877 # because there is a possible race condition where the repo has
878 # been deleted while processing
lloretgalleg65ddf852020-02-20 12:01:17 +0100879 db_repo = self.db.get_one("k8srepos", {"_id": repo_id})
beierlmf52cb7c2020-04-21 16:36:35 -0400880 self.log.debug(
881 "obtained repo: id, {}, name: {}, url: {}".format(
882 repo_id, db_repo["name"], db_repo["url"]
883 )
884 )
lloretgalleg65ddf852020-02-20 12:01:17 +0100885 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400886 repo_add_task = asyncio.ensure_future(
887 self.repo_add(
888 cluster_uuid=cluster_uuid,
889 name=db_repo["name"],
890 url=db_repo["url"],
891 repo_type="chart",
892 )
893 )
lloretgalleg65ddf852020-02-20 12:01:17 +0100894 await asyncio.wait_for(repo_add_task, update_repos_timeout)
895 added_repo_dict[repo_id] = db_repo["name"]
beierlmf52cb7c2020-04-21 16:36:35 -0400896 self.log.debug(
897 "added repo: id, {}, name: {}".format(
898 repo_id, db_repo["name"]
899 )
900 )
lloretgalleg65ddf852020-02-20 12:01:17 +0100901 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400902 # deal with error adding repo, adding a repo that already
903 # exists does not raise any error
904 # will not raise error because a wrong repos added by
905 # anyone could prevent instantiating any ns
906 self.log.error(
907 "Error adding repo id: {}, err_msg: {} ".format(
908 repo_id, repr(e)
909 )
910 )
lloretgalleg65ddf852020-02-20 12:01:17 +0100911
912 return deleted_repo_list, added_repo_dict
913
beierlmf52cb7c2020-04-21 16:36:35 -0400914 else: # else db_k8scluster does not exist
915 raise K8sException(
916 "k8cluster with helm-id : {} not found".format(cluster_uuid)
917 )
lloretgalleg65ddf852020-02-20 12:01:17 +0100918
919 except Exception as e:
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100920 self.log.error("Error synchronizing repos: {}".format(str(e)))
lloretgalleg65ddf852020-02-20 12:01:17 +0100921 raise K8sException("Error synchronizing repos")
922
quilesj26c78a42019-10-28 18:10:42 +0100923 """
beierlmf52cb7c2020-04-21 16:36:35 -0400924 ####################################################################################
925 ################################### P R I V A T E ##################################
926 ####################################################################################
quilesj26c78a42019-10-28 18:10:42 +0100927 """
928
quilesj1be06302019-11-29 11:17:11 +0000929 async def _exec_inspect_comand(
beierlmf52cb7c2020-04-21 16:36:35 -0400930 self, inspect_command: str, kdu_model: str, repo_url: str = None
quilesj1be06302019-11-29 11:17:11 +0000931 ):
932
beierlmf52cb7c2020-04-21 16:36:35 -0400933 repo_str = ""
quilesj1be06302019-11-29 11:17:11 +0000934 if repo_url:
beierlmf52cb7c2020-04-21 16:36:35 -0400935 repo_str = " --repo {}".format(repo_url)
936 idx = kdu_model.find("/")
quilesj1be06302019-11-29 11:17:11 +0000937 if idx >= 0:
938 idx += 1
939 kdu_model = kdu_model[idx:]
940
beierlmf52cb7c2020-04-21 16:36:35 -0400941 inspect_command = "{} inspect {} {}{}".format(
942 self._helm_command, inspect_command, kdu_model, repo_str
943 )
944 output, _rc = await self._local_async_exec(
945 command=inspect_command, encode_utf8=True
946 )
quilesj1be06302019-11-29 11:17:11 +0000947
948 return output
949
quilesj26c78a42019-10-28 18:10:42 +0100950 async def _status_kdu(
beierlmf52cb7c2020-04-21 16:36:35 -0400951 self,
952 cluster_uuid: str,
953 kdu_instance: str,
954 show_error_log: bool = False,
955 return_text: bool = False,
quilesj26c78a42019-10-28 18:10:42 +0100956 ):
957
beierlmf52cb7c2020-04-21 16:36:35 -0400958 self.log.debug("status of kdu_instance {}".format(kdu_instance))
quilesj26c78a42019-10-28 18:10:42 +0100959
960 # config filename
beierlmf52cb7c2020-04-21 16:36:35 -0400961 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
962 cluster_name=cluster_uuid, create_if_not_exist=True
963 )
quilesj26c78a42019-10-28 18:10:42 +0100964
beierlmf52cb7c2020-04-21 16:36:35 -0400965 command = "{} --kubeconfig={} --home={} status {} --output yaml".format(
966 self._helm_command, config_filename, helm_dir, kdu_instance
967 )
quilesj26c78a42019-10-28 18:10:42 +0100968
969 output, rc = await self._local_async_exec(
970 command=command,
971 raise_exception_on_error=True,
beierlmf52cb7c2020-04-21 16:36:35 -0400972 show_error_log=show_error_log,
quilesj26c78a42019-10-28 18:10:42 +0100973 )
974
quilesj1be06302019-11-29 11:17:11 +0000975 if return_text:
976 return str(output)
977
quilesj26c78a42019-10-28 18:10:42 +0100978 if rc != 0:
979 return None
980
981 data = yaml.load(output, Loader=yaml.SafeLoader)
982
983 # remove field 'notes'
984 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400985 del data.get("info").get("status")["notes"]
quilesj26c78a42019-10-28 18:10:42 +0100986 except KeyError:
987 pass
988
989 # parse field 'resources'
990 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400991 resources = str(data.get("info").get("status").get("resources"))
quilesj26c78a42019-10-28 18:10:42 +0100992 resource_table = self._output_to_table(resources)
beierlmf52cb7c2020-04-21 16:36:35 -0400993 data.get("info").get("status")["resources"] = resource_table
994 except Exception:
quilesj26c78a42019-10-28 18:10:42 +0100995 pass
996
997 return data
998
beierlmf52cb7c2020-04-21 16:36:35 -0400999 async def get_instance_info(self, cluster_uuid: str, kdu_instance: str):
quilesj26c78a42019-10-28 18:10:42 +01001000 instances = await self.instances_list(cluster_uuid=cluster_uuid)
1001 for instance in instances:
beierlmf52cb7c2020-04-21 16:36:35 -04001002 if instance.get("Name") == kdu_instance:
quilesj26c78a42019-10-28 18:10:42 +01001003 return instance
beierlmf52cb7c2020-04-21 16:36:35 -04001004 self.log.debug("Instance {} not found".format(kdu_instance))
quilesj26c78a42019-10-28 18:10:42 +01001005 return None
1006
1007 @staticmethod
beierlmf52cb7c2020-04-21 16:36:35 -04001008 def _generate_release_name(chart_name: str):
quilesjbc355a12020-01-23 09:28:26 +00001009 # check embeded chart (file or dir)
beierlmf52cb7c2020-04-21 16:36:35 -04001010 if chart_name.startswith("/"):
quilesjbc355a12020-01-23 09:28:26 +00001011 # extract file or directory name
beierlmf52cb7c2020-04-21 16:36:35 -04001012 chart_name = chart_name[chart_name.rfind("/") + 1 :]
quilesjbc355a12020-01-23 09:28:26 +00001013 # check URL
beierlmf52cb7c2020-04-21 16:36:35 -04001014 elif "://" in chart_name:
quilesjbc355a12020-01-23 09:28:26 +00001015 # extract last portion of URL
beierlmf52cb7c2020-04-21 16:36:35 -04001016 chart_name = chart_name[chart_name.rfind("/") + 1 :]
quilesjbc355a12020-01-23 09:28:26 +00001017
beierlmf52cb7c2020-04-21 16:36:35 -04001018 name = ""
quilesj26c78a42019-10-28 18:10:42 +01001019 for c in chart_name:
1020 if c.isalpha() or c.isnumeric():
1021 name += c
1022 else:
beierlmf52cb7c2020-04-21 16:36:35 -04001023 name += "-"
quilesj26c78a42019-10-28 18:10:42 +01001024 if len(name) > 35:
1025 name = name[0:35]
1026
1027 # if does not start with alpha character, prefix 'a'
1028 if not name[0].isalpha():
beierlmf52cb7c2020-04-21 16:36:35 -04001029 name = "a" + name
quilesj26c78a42019-10-28 18:10:42 +01001030
beierlmf52cb7c2020-04-21 16:36:35 -04001031 name += "-"
quilesj26c78a42019-10-28 18:10:42 +01001032
1033 def get_random_number():
1034 r = random.randrange(start=1, stop=99999999)
1035 s = str(r)
beierlmf52cb7c2020-04-21 16:36:35 -04001036 s = s.rjust(10, "0")
quilesj26c78a42019-10-28 18:10:42 +01001037 return s
1038
1039 name = name + get_random_number()
1040 return name.lower()
1041
1042 async def _store_status(
beierlmf52cb7c2020-04-21 16:36:35 -04001043 self,
1044 cluster_uuid: str,
1045 operation: str,
1046 kdu_instance: str,
1047 check_every: float = 10,
1048 db_dict: dict = None,
1049 run_once: bool = False,
quilesj26c78a42019-10-28 18:10:42 +01001050 ):
1051 while True:
1052 try:
1053 await asyncio.sleep(check_every)
tierno119f7232020-04-21 13:22:26 +00001054 detailed_status = await self._status_kdu(
1055 cluster_uuid=cluster_uuid, kdu_instance=kdu_instance,
1056 return_text=False
beierlmf52cb7c2020-04-21 16:36:35 -04001057 )
1058 status = detailed_status.get("info").get("Description")
tierno119f7232020-04-21 13:22:26 +00001059 self.log.debug('KDU {} STATUS: {}.'.format(kdu_instance, status))
quilesj26c78a42019-10-28 18:10:42 +01001060 # write status to db
1061 result = await self.write_app_status_to_db(
1062 db_dict=db_dict,
1063 status=str(status),
1064 detailed_status=str(detailed_status),
beierlmf52cb7c2020-04-21 16:36:35 -04001065 operation=operation,
1066 )
quilesj26c78a42019-10-28 18:10:42 +01001067 if not result:
beierlmf52cb7c2020-04-21 16:36:35 -04001068 self.log.info("Error writing in database. Task exiting...")
quilesj26c78a42019-10-28 18:10:42 +01001069 return
1070 except asyncio.CancelledError:
beierlmf52cb7c2020-04-21 16:36:35 -04001071 self.log.debug("Task cancelled")
quilesj26c78a42019-10-28 18:10:42 +01001072 return
1073 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -04001074 self.log.debug("_store_status exception: {}".format(str(e)))
quilesj26c78a42019-10-28 18:10:42 +01001075 pass
1076 finally:
1077 if run_once:
1078 return
1079
beierlmf52cb7c2020-04-21 16:36:35 -04001080 async def _is_install_completed(self, cluster_uuid: str, kdu_instance: str) -> bool:
quilesj26c78a42019-10-28 18:10:42 +01001081
beierlmf52cb7c2020-04-21 16:36:35 -04001082 status = await self._status_kdu(
1083 cluster_uuid=cluster_uuid, kdu_instance=kdu_instance, return_text=False
1084 )
quilesj26c78a42019-10-28 18:10:42 +01001085
1086 # extract info.status.resources-> str
1087 # format:
1088 # ==> v1/Deployment
1089 # NAME READY UP-TO-DATE AVAILABLE AGE
1090 # halting-horse-mongodb 0/1 1 0 0s
1091 # halting-petit-mongodb 1/1 1 0 0s
1092 # blank line
beierlmf52cb7c2020-04-21 16:36:35 -04001093 resources = K8sHelmConnector._get_deep(status, ("info", "status", "resources"))
quilesj26c78a42019-10-28 18:10:42 +01001094
1095 # convert to table
1096 resources = K8sHelmConnector._output_to_table(resources)
1097
1098 num_lines = len(resources)
1099 index = 0
1100 while index < num_lines:
1101 try:
1102 line1 = resources[index]
1103 index += 1
1104 # find '==>' in column 0
beierlmf52cb7c2020-04-21 16:36:35 -04001105 if line1[0] == "==>":
quilesj26c78a42019-10-28 18:10:42 +01001106 line2 = resources[index]
1107 index += 1
1108 # find READY in column 1
beierlmf52cb7c2020-04-21 16:36:35 -04001109 if line2[1] == "READY":
quilesj26c78a42019-10-28 18:10:42 +01001110 # read next lines
1111 line3 = resources[index]
1112 index += 1
1113 while len(line3) > 1 and index < num_lines:
1114 ready_value = line3[1]
beierlmf52cb7c2020-04-21 16:36:35 -04001115 parts = ready_value.split(sep="/")
quilesj26c78a42019-10-28 18:10:42 +01001116 current = int(parts[0])
1117 total = int(parts[1])
1118 if current < total:
beierlmf52cb7c2020-04-21 16:36:35 -04001119 self.log.debug("NOT READY:\n {}".format(line3))
quilesj26c78a42019-10-28 18:10:42 +01001120 ready = False
1121 line3 = resources[index]
1122 index += 1
1123
beierlmf52cb7c2020-04-21 16:36:35 -04001124 except Exception:
quilesj26c78a42019-10-28 18:10:42 +01001125 pass
1126
1127 return ready
1128
1129 @staticmethod
1130 def _get_deep(dictionary: dict, members: tuple):
1131 target = dictionary
1132 value = None
1133 try:
1134 for m in members:
1135 value = target.get(m)
1136 if not value:
1137 return None
1138 else:
1139 target = value
beierlmf52cb7c2020-04-21 16:36:35 -04001140 except Exception:
quilesj26c78a42019-10-28 18:10:42 +01001141 pass
1142 return value
1143
1144 # find key:value in several lines
1145 @staticmethod
1146 def _find_in_lines(p_lines: list, p_key: str) -> str:
1147 for line in p_lines:
1148 try:
beierlmf52cb7c2020-04-21 16:36:35 -04001149 if line.startswith(p_key + ":"):
1150 parts = line.split(":")
quilesj26c78a42019-10-28 18:10:42 +01001151 the_value = parts[1].strip()
1152 return the_value
beierlmf52cb7c2020-04-21 16:36:35 -04001153 except Exception:
quilesj26c78a42019-10-28 18:10:42 +01001154 # ignore it
1155 pass
1156 return None
1157
quilesjcda5f412019-11-18 11:32:12 +01001158 # params for use in -f file
1159 # returns values file option and filename (in order to delete it at the end)
1160 def _params_to_file_option(self, cluster_uuid: str, params: dict) -> (str, str):
quilesjcda5f412019-11-18 11:32:12 +01001161
1162 if params and len(params) > 0:
beierlmf52cb7c2020-04-21 16:36:35 -04001163 self._get_paths(cluster_name=cluster_uuid, create_if_not_exist=True)
quilesjcda5f412019-11-18 11:32:12 +01001164
1165 def get_random_number():
1166 r = random.randrange(start=1, stop=99999999)
1167 s = str(r)
1168 while len(s) < 10:
beierlmf52cb7c2020-04-21 16:36:35 -04001169 s = "0" + s
quilesjcda5f412019-11-18 11:32:12 +01001170 return s
1171
1172 params2 = dict()
1173 for key in params:
1174 value = params.get(key)
beierlmf52cb7c2020-04-21 16:36:35 -04001175 if "!!yaml" in str(value):
quilesj1be06302019-11-29 11:17:11 +00001176 value = yaml.load(value[7:])
quilesjcda5f412019-11-18 11:32:12 +01001177 params2[key] = value
1178
beierlmf52cb7c2020-04-21 16:36:35 -04001179 values_file = get_random_number() + ".yaml"
1180 with open(values_file, "w") as stream:
quilesjcda5f412019-11-18 11:32:12 +01001181 yaml.dump(params2, stream, indent=4, default_flow_style=False)
1182
beierlmf52cb7c2020-04-21 16:36:35 -04001183 return "-f {}".format(values_file), values_file
quilesjcda5f412019-11-18 11:32:12 +01001184
beierlmf52cb7c2020-04-21 16:36:35 -04001185 return "", None
quilesjcda5f412019-11-18 11:32:12 +01001186
quilesj26c78a42019-10-28 18:10:42 +01001187 # params for use in --set option
1188 @staticmethod
1189 def _params_to_set_option(params: dict) -> str:
beierlmf52cb7c2020-04-21 16:36:35 -04001190 params_str = ""
quilesj26c78a42019-10-28 18:10:42 +01001191 if params and len(params) > 0:
1192 start = True
1193 for key in params:
1194 value = params.get(key, None)
1195 if value is not None:
1196 if start:
beierlmf52cb7c2020-04-21 16:36:35 -04001197 params_str += "--set "
quilesj26c78a42019-10-28 18:10:42 +01001198 start = False
1199 else:
beierlmf52cb7c2020-04-21 16:36:35 -04001200 params_str += ","
1201 params_str += "{}={}".format(key, value)
quilesj26c78a42019-10-28 18:10:42 +01001202 return params_str
1203
1204 @staticmethod
1205 def _output_to_lines(output: str) -> list:
1206 output_lines = list()
1207 lines = output.splitlines(keepends=False)
1208 for line in lines:
1209 line = line.strip()
1210 if len(line) > 0:
1211 output_lines.append(line)
1212 return output_lines
1213
1214 @staticmethod
1215 def _output_to_table(output: str) -> list:
1216 output_table = list()
1217 lines = output.splitlines(keepends=False)
1218 for line in lines:
beierlmf52cb7c2020-04-21 16:36:35 -04001219 line = line.replace("\t", " ")
quilesj26c78a42019-10-28 18:10:42 +01001220 line_list = list()
1221 output_table.append(line_list)
beierlmf52cb7c2020-04-21 16:36:35 -04001222 cells = line.split(sep=" ")
quilesj26c78a42019-10-28 18:10:42 +01001223 for cell in cells:
1224 cell = cell.strip()
1225 if len(cell) > 0:
1226 line_list.append(cell)
1227 return output_table
1228
beierlmf52cb7c2020-04-21 16:36:35 -04001229 def _get_paths(
1230 self, cluster_name: str, create_if_not_exist: bool = False
1231 ) -> (str, str, str, str):
quilesj26c78a42019-10-28 18:10:42 +01001232 """
1233 Returns kube and helm directories
1234
1235 :param cluster_name:
1236 :param create_if_not_exist:
quilesjcda5f412019-11-18 11:32:12 +01001237 :return: kube, helm directories, config filename and cluster dir.
1238 Raises exception if not exist and cannot create
quilesj26c78a42019-10-28 18:10:42 +01001239 """
1240
1241 base = self.fs.path
1242 if base.endswith("/") or base.endswith("\\"):
1243 base = base[:-1]
1244
1245 # base dir for cluster
beierlmf52cb7c2020-04-21 16:36:35 -04001246 cluster_dir = base + "/" + cluster_name
quilesj26c78a42019-10-28 18:10:42 +01001247 if create_if_not_exist and not os.path.exists(cluster_dir):
beierlmf52cb7c2020-04-21 16:36:35 -04001248 self.log.debug("Creating dir {}".format(cluster_dir))
quilesj26c78a42019-10-28 18:10:42 +01001249 os.makedirs(cluster_dir)
1250 if not os.path.exists(cluster_dir):
beierlmf52cb7c2020-04-21 16:36:35 -04001251 msg = "Base cluster dir {} does not exist".format(cluster_dir)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +01001252 self.log.error(msg)
quilesja6748412019-12-04 07:51:26 +00001253 raise K8sException(msg)
quilesj26c78a42019-10-28 18:10:42 +01001254
1255 # kube dir
beierlmf52cb7c2020-04-21 16:36:35 -04001256 kube_dir = cluster_dir + "/" + ".kube"
quilesj26c78a42019-10-28 18:10:42 +01001257 if create_if_not_exist and not os.path.exists(kube_dir):
beierlmf52cb7c2020-04-21 16:36:35 -04001258 self.log.debug("Creating dir {}".format(kube_dir))
quilesj26c78a42019-10-28 18:10:42 +01001259 os.makedirs(kube_dir)
1260 if not os.path.exists(kube_dir):
beierlmf52cb7c2020-04-21 16:36:35 -04001261 msg = "Kube config dir {} does not exist".format(kube_dir)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +01001262 self.log.error(msg)
quilesja6748412019-12-04 07:51:26 +00001263 raise K8sException(msg)
quilesj26c78a42019-10-28 18:10:42 +01001264
1265 # helm home dir
beierlmf52cb7c2020-04-21 16:36:35 -04001266 helm_dir = cluster_dir + "/" + ".helm"
quilesj26c78a42019-10-28 18:10:42 +01001267 if create_if_not_exist and not os.path.exists(helm_dir):
beierlmf52cb7c2020-04-21 16:36:35 -04001268 self.log.debug("Creating dir {}".format(helm_dir))
quilesj26c78a42019-10-28 18:10:42 +01001269 os.makedirs(helm_dir)
1270 if not os.path.exists(helm_dir):
beierlmf52cb7c2020-04-21 16:36:35 -04001271 msg = "Helm config dir {} does not exist".format(helm_dir)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +01001272 self.log.error(msg)
quilesja6748412019-12-04 07:51:26 +00001273 raise K8sException(msg)
quilesj26c78a42019-10-28 18:10:42 +01001274
beierlmf52cb7c2020-04-21 16:36:35 -04001275 config_filename = kube_dir + "/config"
quilesjcda5f412019-11-18 11:32:12 +01001276 return kube_dir, helm_dir, config_filename, cluster_dir
quilesj26c78a42019-10-28 18:10:42 +01001277
1278 @staticmethod
beierlmf52cb7c2020-04-21 16:36:35 -04001279 def _remove_multiple_spaces(strobj):
1280 strobj = strobj.strip()
1281 while " " in strobj:
1282 strobj = strobj.replace(" ", " ")
1283 return strobj
quilesj26c78a42019-10-28 18:10:42 +01001284
beierlmf52cb7c2020-04-21 16:36:35 -04001285 def _local_exec(self, command: str) -> (str, int):
quilesj26c78a42019-10-28 18:10:42 +01001286 command = K8sHelmConnector._remove_multiple_spaces(command)
beierlmf52cb7c2020-04-21 16:36:35 -04001287 self.log.debug("Executing sync local command: {}".format(command))
quilesj26c78a42019-10-28 18:10:42 +01001288 # raise exception if fails
beierlmf52cb7c2020-04-21 16:36:35 -04001289 output = ""
quilesj26c78a42019-10-28 18:10:42 +01001290 try:
beierlmf52cb7c2020-04-21 16:36:35 -04001291 output = subprocess.check_output(
1292 command, shell=True, universal_newlines=True
1293 )
quilesj26c78a42019-10-28 18:10:42 +01001294 return_code = 0
Dominik Fleischmannf9bed352020-02-27 10:04:34 +01001295 self.log.debug(output)
beierlmf52cb7c2020-04-21 16:36:35 -04001296 except Exception:
quilesj26c78a42019-10-28 18:10:42 +01001297 return_code = 1
1298
1299 return output, return_code
1300
1301 async def _local_async_exec(
beierlmf52cb7c2020-04-21 16:36:35 -04001302 self,
1303 command: str,
1304 raise_exception_on_error: bool = False,
1305 show_error_log: bool = True,
1306 encode_utf8: bool = False,
quilesj26c78a42019-10-28 18:10:42 +01001307 ) -> (str, int):
1308
1309 command = K8sHelmConnector._remove_multiple_spaces(command)
beierlmf52cb7c2020-04-21 16:36:35 -04001310 self.log.debug("Executing async local command: {}".format(command))
quilesj26c78a42019-10-28 18:10:42 +01001311
1312 # split command
beierlmf52cb7c2020-04-21 16:36:35 -04001313 command = command.split(sep=" ")
quilesj26c78a42019-10-28 18:10:42 +01001314
1315 try:
1316 process = await asyncio.create_subprocess_exec(
beierlmf52cb7c2020-04-21 16:36:35 -04001317 *command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
quilesj26c78a42019-10-28 18:10:42 +01001318 )
1319
1320 # wait for command terminate
1321 stdout, stderr = await process.communicate()
1322
1323 return_code = process.returncode
1324
beierlmf52cb7c2020-04-21 16:36:35 -04001325 output = ""
quilesj26c78a42019-10-28 18:10:42 +01001326 if stdout:
beierlmf52cb7c2020-04-21 16:36:35 -04001327 output = stdout.decode("utf-8").strip()
quilesj1be06302019-11-29 11:17:11 +00001328 # output = stdout.decode()
quilesj26c78a42019-10-28 18:10:42 +01001329 if stderr:
beierlmf52cb7c2020-04-21 16:36:35 -04001330 output = stderr.decode("utf-8").strip()
quilesj1be06302019-11-29 11:17:11 +00001331 # output = stderr.decode()
quilesj26c78a42019-10-28 18:10:42 +01001332
1333 if return_code != 0 and show_error_log:
beierlmf52cb7c2020-04-21 16:36:35 -04001334 self.log.debug(
1335 "Return code (FAIL): {}\nOutput:\n{}".format(return_code, output)
1336 )
quilesj26c78a42019-10-28 18:10:42 +01001337 else:
beierlmf52cb7c2020-04-21 16:36:35 -04001338 self.log.debug("Return code: {}".format(return_code))
quilesj26c78a42019-10-28 18:10:42 +01001339
1340 if raise_exception_on_error and return_code != 0:
tierno601697a2020-02-04 15:26:25 +00001341 raise K8sException(output)
quilesj26c78a42019-10-28 18:10:42 +01001342
quilesj1be06302019-11-29 11:17:11 +00001343 if encode_utf8:
beierlmf52cb7c2020-04-21 16:36:35 -04001344 output = output.encode("utf-8").strip()
1345 output = str(output).replace("\\n", "\n")
quilesj1be06302019-11-29 11:17:11 +00001346
quilesj26c78a42019-10-28 18:10:42 +01001347 return output, return_code
1348
lloretgallegdd0cdee2020-02-26 10:00:16 +01001349 except asyncio.CancelledError:
1350 raise
tierno601697a2020-02-04 15:26:25 +00001351 except K8sException:
1352 raise
quilesj26c78a42019-10-28 18:10:42 +01001353 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -04001354 msg = "Exception executing command: {} -> {}".format(command, e)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +01001355 self.log.error(msg)
quilesj32dc3c62020-01-23 16:30:04 +00001356 if raise_exception_on_error:
tierno601697a2020-02-04 15:26:25 +00001357 raise K8sException(e) from e
quilesj32dc3c62020-01-23 16:30:04 +00001358 else:
beierlmf52cb7c2020-04-21 16:36:35 -04001359 return "", -1
quilesj26c78a42019-10-28 18:10:42 +01001360
quilesj26c78a42019-10-28 18:10:42 +01001361 def _check_file_exists(self, filename: str, exception_if_not_exists: bool = False):
tierno8ff11992020-03-26 09:51:11 +00001362 # self.log.debug('Checking if file {} exists...'.format(filename))
quilesj26c78a42019-10-28 18:10:42 +01001363 if os.path.exists(filename):
1364 return True
1365 else:
beierlmf52cb7c2020-04-21 16:36:35 -04001366 msg = "File {} does not exist".format(filename)
quilesj26c78a42019-10-28 18:10:42 +01001367 if exception_if_not_exists:
tierno8ff11992020-03-26 09:51:11 +00001368 # self.log.error(msg)
quilesja6748412019-12-04 07:51:26 +00001369 raise K8sException(msg)