blob: fdfc443468ee3ccd820c9fb103c690371adebe32 [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
beierlm32862bb2020-04-21 16:36:35 -040024import os
quilesj26c78a42019-10-28 18:10:42 +010025import random
beierlm32862bb2020-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
beierlm32862bb2020-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 """
beierlm32862bb2020-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__(
beierlm32862bb2020-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
beierlm32862bb2020-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
beierlm32862bb2020-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
beierlm32862bb2020-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:
beierlm32862bb2020-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()
beierlm32862bb2020-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:
beierlm32862bb2020-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
beierlm32862bb2020-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(
beierlm32862bb2020-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):
garciadeblas2ce889d2019-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
beierlm32862bb2020-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
garciadeblas2ce889d2019-12-13 13:39:03 +0100111 :param reuse_cluster_uuid: existing cluster uuid for reuse
beierlm32862bb2020-04-21 16:36:35 -0400112 :return: uuid of the K8s cluster and True if connector has installed some
113 software in the cluster
garciadeblas2ce889d2019-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
beierlm32862bb2020-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
beierlm32862bb2020-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 )
quilesj26c78a42019-10-28 18:10:42 +0100127 f = open(config_filename, "w")
128 f.write(k8s_creds)
129 f.close()
130
131 # check if tiller pod is up in cluster
beierlm32862bb2020-04-21 16:36:35 -0400132 command = "{} --kubeconfig={} --namespace={} get deployments".format(
133 self.kubectl_command, config_filename, namespace
134 )
135 output, _rc = await self._local_async_exec(
136 command=command, raise_exception_on_error=True
137 )
quilesj26c78a42019-10-28 18:10:42 +0100138
139 output_table = K8sHelmConnector._output_to_table(output=output)
140
141 # find 'tiller' pod in all pods
142 already_initialized = False
143 try:
144 for row in output_table:
beierlm32862bb2020-04-21 16:36:35 -0400145 if row[0].startswith("tiller-deploy"):
quilesj26c78a42019-10-28 18:10:42 +0100146 already_initialized = True
147 break
beierlm32862bb2020-04-21 16:36:35 -0400148 except Exception:
quilesj26c78a42019-10-28 18:10:42 +0100149 pass
150
151 # helm init
152 n2vc_installed_sw = False
153 if not already_initialized:
beierlm32862bb2020-04-21 16:36:35 -0400154 self.log.info(
155 "Initializing helm in client and server: {}".format(cluster_uuid)
156 )
157 command = "{} --kubeconfig={} --tiller-namespace={} --home={} init".format(
158 self._helm_command, config_filename, namespace, helm_dir
159 )
160 output, _rc = await self._local_async_exec(
161 command=command, raise_exception_on_error=True
162 )
quilesj26c78a42019-10-28 18:10:42 +0100163 n2vc_installed_sw = True
164 else:
165 # check client helm installation
beierlm32862bb2020-04-21 16:36:35 -0400166 check_file = helm_dir + "/repository/repositories.yaml"
167 if not self._check_file_exists(
168 filename=check_file, exception_if_not_exists=False
169 ):
170 self.log.info("Initializing helm in client: {}".format(cluster_uuid))
171 command = (
172 "{} --kubeconfig={} --tiller-namespace={} "
173 "--home={} init --client-only"
174 ).format(self._helm_command, config_filename, namespace, helm_dir)
175 output, _rc = await self._local_async_exec(
176 command=command, raise_exception_on_error=True
177 )
quilesj26c78a42019-10-28 18:10:42 +0100178 else:
beierlm32862bb2020-04-21 16:36:35 -0400179 self.log.info("Helm client already initialized")
quilesj26c78a42019-10-28 18:10:42 +0100180
beierlm32862bb2020-04-21 16:36:35 -0400181 self.log.info("Cluster initialized {}".format(cluster_uuid))
quilesj26c78a42019-10-28 18:10:42 +0100182
183 return cluster_uuid, n2vc_installed_sw
184
185 async def repo_add(
beierlm32862bb2020-04-21 16:36:35 -0400186 self, cluster_uuid: str, name: str, url: str, repo_type: str = "chart"
quilesj26c78a42019-10-28 18:10:42 +0100187 ):
188
beierlm32862bb2020-04-21 16:36:35 -0400189 self.log.debug("adding {} repository {}. URL: {}".format(repo_type, name, url))
quilesj26c78a42019-10-28 18:10:42 +0100190
191 # config filename
beierlm32862bb2020-04-21 16:36:35 -0400192 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
193 cluster_name=cluster_uuid, create_if_not_exist=True
194 )
quilesj26c78a42019-10-28 18:10:42 +0100195
196 # helm repo update
beierlm32862bb2020-04-21 16:36:35 -0400197 command = "{} --kubeconfig={} --home={} repo update".format(
198 self._helm_command, config_filename, helm_dir
199 )
200 self.log.debug("updating repo: {}".format(command))
quilesj26c78a42019-10-28 18:10:42 +0100201 await self._local_async_exec(command=command, raise_exception_on_error=False)
202
203 # helm repo add name url
beierlm32862bb2020-04-21 16:36:35 -0400204 command = "{} --kubeconfig={} --home={} repo add {} {}".format(
205 self._helm_command, config_filename, helm_dir, name, url
206 )
207 self.log.debug("adding repo: {}".format(command))
quilesj26c78a42019-10-28 18:10:42 +0100208 await self._local_async_exec(command=command, raise_exception_on_error=True)
209
beierlm32862bb2020-04-21 16:36:35 -0400210 async def repo_list(self, cluster_uuid: str) -> list:
quilesj26c78a42019-10-28 18:10:42 +0100211 """
212 Get the list of registered repositories
213
214 :return: list of registered repositories: [ (name, url) .... ]
215 """
216
beierlm32862bb2020-04-21 16:36:35 -0400217 self.log.debug("list repositories for cluster {}".format(cluster_uuid))
quilesj26c78a42019-10-28 18:10:42 +0100218
219 # config filename
beierlm32862bb2020-04-21 16:36:35 -0400220 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
221 cluster_name=cluster_uuid, create_if_not_exist=True
222 )
quilesj26c78a42019-10-28 18:10:42 +0100223
beierlm32862bb2020-04-21 16:36:35 -0400224 command = "{} --kubeconfig={} --home={} repo list --output yaml".format(
225 self._helm_command, config_filename, helm_dir
226 )
quilesj26c78a42019-10-28 18:10:42 +0100227
beierlm32862bb2020-04-21 16:36:35 -0400228 output, _rc = await self._local_async_exec(
229 command=command, raise_exception_on_error=True
230 )
quilesj26c78a42019-10-28 18:10:42 +0100231 if output and len(output) > 0:
232 return yaml.load(output, Loader=yaml.SafeLoader)
233 else:
234 return []
235
beierlm32862bb2020-04-21 16:36:35 -0400236 async def repo_remove(self, cluster_uuid: str, name: str):
quilesj26c78a42019-10-28 18:10:42 +0100237 """
238 Remove a repository from OSM
239
240 :param cluster_uuid: the cluster
241 :param name: repo name in OSM
242 :return: True if successful
243 """
244
beierlm32862bb2020-04-21 16:36:35 -0400245 self.log.debug("list repositories for cluster {}".format(cluster_uuid))
quilesj26c78a42019-10-28 18:10:42 +0100246
247 # config filename
beierlm32862bb2020-04-21 16:36:35 -0400248 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
249 cluster_name=cluster_uuid, create_if_not_exist=True
250 )
quilesj26c78a42019-10-28 18:10:42 +0100251
beierlm32862bb2020-04-21 16:36:35 -0400252 command = "{} --kubeconfig={} --home={} repo remove {}".format(
253 self._helm_command, config_filename, helm_dir, name
254 )
quilesj26c78a42019-10-28 18:10:42 +0100255
256 await self._local_async_exec(command=command, raise_exception_on_error=True)
257
258 async def reset(
beierlm32862bb2020-04-21 16:36:35 -0400259 self, cluster_uuid: str, force: bool = False, uninstall_sw: bool = False
quilesj26c78a42019-10-28 18:10:42 +0100260 ) -> bool:
261
beierlm32862bb2020-04-21 16:36:35 -0400262 self.log.debug(
263 "Resetting K8s environment. cluster uuid: {}".format(cluster_uuid)
264 )
quilesj26c78a42019-10-28 18:10:42 +0100265
266 # get kube and helm directories
beierlm32862bb2020-04-21 16:36:35 -0400267 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
268 cluster_name=cluster_uuid, create_if_not_exist=False
269 )
quilesj26c78a42019-10-28 18:10:42 +0100270
271 # uninstall releases if needed
272 releases = await self.instances_list(cluster_uuid=cluster_uuid)
273 if len(releases) > 0:
274 if force:
275 for r in releases:
276 try:
beierlm32862bb2020-04-21 16:36:35 -0400277 kdu_instance = r.get("Name")
278 chart = r.get("Chart")
279 self.log.debug(
280 "Uninstalling {} -> {}".format(chart, kdu_instance)
281 )
282 await self.uninstall(
283 cluster_uuid=cluster_uuid, kdu_instance=kdu_instance
284 )
quilesj26c78a42019-10-28 18:10:42 +0100285 except Exception as e:
beierlm32862bb2020-04-21 16:36:35 -0400286 self.log.error(
287 "Error uninstalling release {}: {}".format(kdu_instance, e)
288 )
quilesj26c78a42019-10-28 18:10:42 +0100289 else:
beierlm32862bb2020-04-21 16:36:35 -0400290 msg = (
291 "Cluster has releases and not force. Cannot reset K8s "
292 "environment. Cluster uuid: {}"
293 ).format(cluster_uuid)
Dominik Fleischmannbc269eb2020-02-27 10:04:34 +0100294 self.log.error(msg)
quilesja6748412019-12-04 07:51:26 +0000295 raise K8sException(msg)
quilesj26c78a42019-10-28 18:10:42 +0100296
297 if uninstall_sw:
298
beierlm32862bb2020-04-21 16:36:35 -0400299 self.log.debug("Uninstalling tiller from cluster {}".format(cluster_uuid))
quilesj26c78a42019-10-28 18:10:42 +0100300
301 # find namespace for tiller pod
beierlm32862bb2020-04-21 16:36:35 -0400302 command = "{} --kubeconfig={} get deployments --all-namespaces".format(
303 self.kubectl_command, config_filename
304 )
305 output, _rc = await self._local_async_exec(
306 command=command, raise_exception_on_error=False
307 )
quilesj26c78a42019-10-28 18:10:42 +0100308 output_table = K8sHelmConnector._output_to_table(output=output)
309 namespace = None
310 for r in output_table:
311 try:
beierlm32862bb2020-04-21 16:36:35 -0400312 if "tiller-deploy" in r[1]:
quilesj26c78a42019-10-28 18:10:42 +0100313 namespace = r[0]
314 break
beierlm32862bb2020-04-21 16:36:35 -0400315 except Exception:
quilesj26c78a42019-10-28 18:10:42 +0100316 pass
317 else:
beierlm32862bb2020-04-21 16:36:35 -0400318 msg = "Tiller deployment not found in cluster {}".format(cluster_uuid)
Dominik Fleischmannbc269eb2020-02-27 10:04:34 +0100319 self.log.error(msg)
quilesj26c78a42019-10-28 18:10:42 +0100320
beierlm32862bb2020-04-21 16:36:35 -0400321 self.log.debug("namespace for tiller: {}".format(namespace))
quilesj26c78a42019-10-28 18:10:42 +0100322
beierlm32862bb2020-04-21 16:36:35 -0400323 force_str = "--force"
quilesj26c78a42019-10-28 18:10:42 +0100324
325 if namespace:
326 # delete tiller deployment
beierlm32862bb2020-04-21 16:36:35 -0400327 self.log.debug(
328 "Deleting tiller deployment for cluster {}, namespace {}".format(
329 cluster_uuid, namespace
330 )
331 )
332 command = (
333 "{} --namespace {} --kubeconfig={} {} delete deployment "
334 "tiller-deploy"
335 ).format(self.kubectl_command, namespace, config_filename, force_str)
336 await self._local_async_exec(
337 command=command, raise_exception_on_error=False
338 )
quilesj26c78a42019-10-28 18:10:42 +0100339
340 # uninstall tiller from cluster
beierlm32862bb2020-04-21 16:36:35 -0400341 self.log.debug(
342 "Uninstalling tiller from cluster {}".format(cluster_uuid)
343 )
344 command = "{} --kubeconfig={} --home={} reset".format(
345 self._helm_command, config_filename, helm_dir
346 )
347 self.log.debug("resetting: {}".format(command))
348 output, _rc = await self._local_async_exec(
349 command=command, raise_exception_on_error=True
350 )
quilesj26c78a42019-10-28 18:10:42 +0100351 else:
beierlm32862bb2020-04-21 16:36:35 -0400352 self.log.debug("namespace not found")
quilesj26c78a42019-10-28 18:10:42 +0100353
354 # delete cluster directory
beierlm32862bb2020-04-21 16:36:35 -0400355 direct = self.fs.path + "/" + cluster_uuid
356 self.log.debug("Removing directory {}".format(direct))
357 shutil.rmtree(direct, ignore_errors=True)
quilesj26c78a42019-10-28 18:10:42 +0100358
359 return True
360
361 async def install(
beierlm32862bb2020-04-21 16:36:35 -0400362 self,
363 cluster_uuid: str,
364 kdu_model: str,
365 atomic: bool = True,
366 timeout: float = 300,
367 params: dict = None,
368 db_dict: dict = None,
369 kdu_name: str = None,
370 namespace: str = None,
quilesj26c78a42019-10-28 18:10:42 +0100371 ):
372
beierlm32862bb2020-04-21 16:36:35 -0400373 self.log.debug("installing {} in cluster {}".format(kdu_model, cluster_uuid))
quilesj26c78a42019-10-28 18:10:42 +0100374
quilesj26c78a42019-10-28 18:10:42 +0100375 # config filename
beierlm32862bb2020-04-21 16:36:35 -0400376 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
377 cluster_name=cluster_uuid, create_if_not_exist=True
378 )
quilesj26c78a42019-10-28 18:10:42 +0100379
380 # params to str
quilesjcda5f412019-11-18 11:32:12 +0100381 # params_str = K8sHelmConnector._params_to_set_option(params)
beierlm32862bb2020-04-21 16:36:35 -0400382 params_str, file_to_delete = self._params_to_file_option(
383 cluster_uuid=cluster_uuid, params=params
384 )
quilesj26c78a42019-10-28 18:10:42 +0100385
beierlm32862bb2020-04-21 16:36:35 -0400386 timeout_str = ""
quilesj26c78a42019-10-28 18:10:42 +0100387 if timeout:
beierlm32862bb2020-04-21 16:36:35 -0400388 timeout_str = "--timeout {}".format(timeout)
quilesj26c78a42019-10-28 18:10:42 +0100389
390 # atomic
beierlm32862bb2020-04-21 16:36:35 -0400391 atomic_str = ""
quilesj26c78a42019-10-28 18:10:42 +0100392 if atomic:
beierlm32862bb2020-04-21 16:36:35 -0400393 atomic_str = "--atomic"
tiernod5d83a42020-04-07 11:08:16 +0000394 # namespace
beierlm32862bb2020-04-21 16:36:35 -0400395 namespace_str = ""
tiernod5d83a42020-04-07 11:08:16 +0000396 if namespace:
397 namespace_str = "--namespace {}".format(namespace)
quilesj26c78a42019-10-28 18:10:42 +0100398
399 # version
beierlm32862bb2020-04-21 16:36:35 -0400400 version_str = ""
401 if ":" in kdu_model:
402 parts = kdu_model.split(sep=":")
quilesj26c78a42019-10-28 18:10:42 +0100403 if len(parts) == 2:
beierlm32862bb2020-04-21 16:36:35 -0400404 version_str = "--version {}".format(parts[1])
quilesj26c78a42019-10-28 18:10:42 +0100405 kdu_model = parts[0]
406
quilesja6748412019-12-04 07:51:26 +0000407 # generate a name for the release. Then, check if already exists
quilesj26c78a42019-10-28 18:10:42 +0100408 kdu_instance = None
409 while kdu_instance is None:
410 kdu_instance = K8sHelmConnector._generate_release_name(kdu_model)
411 try:
412 result = await self._status_kdu(
413 cluster_uuid=cluster_uuid,
414 kdu_instance=kdu_instance,
beierlm32862bb2020-04-21 16:36:35 -0400415 show_error_log=False,
quilesj26c78a42019-10-28 18:10:42 +0100416 )
417 if result is not None:
418 # instance already exists: generate a new one
419 kdu_instance = None
tierno41d639e2020-02-04 15:26:25 +0000420 except K8sException:
421 pass
quilesj26c78a42019-10-28 18:10:42 +0100422
423 # helm repo install
beierlm32862bb2020-04-21 16:36:35 -0400424 command = (
425 "{helm} install {atomic} --output yaml --kubeconfig={config} --home={dir} "
426 "{params} {timeout} --name={name} {ns} {model} {ver}".format(
427 helm=self._helm_command,
428 atomic=atomic_str,
429 config=config_filename,
430 dir=helm_dir,
431 params=params_str,
432 timeout=timeout_str,
433 name=kdu_instance,
434 ns=namespace_str,
435 model=kdu_model,
436 ver=version_str,
437 )
438 )
439 self.log.debug("installing: {}".format(command))
quilesj26c78a42019-10-28 18:10:42 +0100440
441 if atomic:
442 # exec helm in a task
443 exec_task = asyncio.ensure_future(
beierlm32862bb2020-04-21 16:36:35 -0400444 coro_or_future=self._local_async_exec(
445 command=command, raise_exception_on_error=False
446 )
quilesj26c78a42019-10-28 18:10:42 +0100447 )
lloretgallegf00dcae2020-02-20 12:01:17 +0100448
quilesj26c78a42019-10-28 18:10:42 +0100449 # write status in another task
450 status_task = asyncio.ensure_future(
451 coro_or_future=self._store_status(
452 cluster_uuid=cluster_uuid,
453 kdu_instance=kdu_instance,
454 db_dict=db_dict,
beierlm32862bb2020-04-21 16:36:35 -0400455 operation="install",
456 run_once=False,
quilesj26c78a42019-10-28 18:10:42 +0100457 )
458 )
459
460 # wait for execution task
461 await asyncio.wait([exec_task])
462
463 # cancel status task
464 status_task.cancel()
465
466 output, rc = exec_task.result()
467
468 else:
469
beierlm32862bb2020-04-21 16:36:35 -0400470 output, rc = await self._local_async_exec(
471 command=command, raise_exception_on_error=False
472 )
quilesj26c78a42019-10-28 18:10:42 +0100473
quilesjcda5f412019-11-18 11:32:12 +0100474 # remove temporal values yaml file
475 if file_to_delete:
476 os.remove(file_to_delete)
477
quilesj26c78a42019-10-28 18:10:42 +0100478 # write final status
479 await self._store_status(
480 cluster_uuid=cluster_uuid,
481 kdu_instance=kdu_instance,
482 db_dict=db_dict,
beierlm32862bb2020-04-21 16:36:35 -0400483 operation="install",
quilesj26c78a42019-10-28 18:10:42 +0100484 run_once=True,
beierlm32862bb2020-04-21 16:36:35 -0400485 check_every=0,
quilesj26c78a42019-10-28 18:10:42 +0100486 )
487
488 if rc != 0:
beierlm32862bb2020-04-21 16:36:35 -0400489 msg = "Error executing command: {}\nOutput: {}".format(command, output)
Dominik Fleischmannbc269eb2020-02-27 10:04:34 +0100490 self.log.error(msg)
quilesja6748412019-12-04 07:51:26 +0000491 raise K8sException(msg)
quilesj26c78a42019-10-28 18:10:42 +0100492
beierlm32862bb2020-04-21 16:36:35 -0400493 self.log.debug("Returning kdu_instance {}".format(kdu_instance))
quilesj26c78a42019-10-28 18:10:42 +0100494 return kdu_instance
495
beierlm32862bb2020-04-21 16:36:35 -0400496 async def instances_list(self, cluster_uuid: str) -> list:
quilesj26c78a42019-10-28 18:10:42 +0100497 """
498 returns a list of deployed releases in a cluster
499
500 :param cluster_uuid: the cluster
501 :return:
502 """
503
beierlm32862bb2020-04-21 16:36:35 -0400504 self.log.debug("list releases for cluster {}".format(cluster_uuid))
quilesj26c78a42019-10-28 18:10:42 +0100505
506 # config filename
beierlm32862bb2020-04-21 16:36:35 -0400507 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
508 cluster_name=cluster_uuid, create_if_not_exist=True
509 )
quilesj26c78a42019-10-28 18:10:42 +0100510
beierlm32862bb2020-04-21 16:36:35 -0400511 command = "{} --kubeconfig={} --home={} list --output yaml".format(
512 self._helm_command, config_filename, helm_dir
513 )
quilesj26c78a42019-10-28 18:10:42 +0100514
beierlm32862bb2020-04-21 16:36:35 -0400515 output, _rc = await self._local_async_exec(
516 command=command, raise_exception_on_error=True
517 )
quilesj26c78a42019-10-28 18:10:42 +0100518
519 if output and len(output) > 0:
beierlm32862bb2020-04-21 16:36:35 -0400520 return yaml.load(output, Loader=yaml.SafeLoader).get("Releases")
quilesj26c78a42019-10-28 18:10:42 +0100521 else:
522 return []
523
524 async def upgrade(
beierlm32862bb2020-04-21 16:36:35 -0400525 self,
526 cluster_uuid: str,
527 kdu_instance: str,
528 kdu_model: str = None,
529 atomic: bool = True,
530 timeout: float = 300,
531 params: dict = None,
532 db_dict: dict = None,
quilesj26c78a42019-10-28 18:10:42 +0100533 ):
534
beierlm32862bb2020-04-21 16:36:35 -0400535 self.log.debug("upgrading {} in cluster {}".format(kdu_model, cluster_uuid))
quilesj26c78a42019-10-28 18:10:42 +0100536
quilesj26c78a42019-10-28 18:10:42 +0100537 # config filename
beierlm32862bb2020-04-21 16:36:35 -0400538 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
539 cluster_name=cluster_uuid, create_if_not_exist=True
540 )
quilesj26c78a42019-10-28 18:10:42 +0100541
542 # params to str
quilesjcda5f412019-11-18 11:32:12 +0100543 # params_str = K8sHelmConnector._params_to_set_option(params)
beierlm32862bb2020-04-21 16:36:35 -0400544 params_str, file_to_delete = self._params_to_file_option(
545 cluster_uuid=cluster_uuid, params=params
546 )
quilesj26c78a42019-10-28 18:10:42 +0100547
beierlm32862bb2020-04-21 16:36:35 -0400548 timeout_str = ""
quilesj26c78a42019-10-28 18:10:42 +0100549 if timeout:
beierlm32862bb2020-04-21 16:36:35 -0400550 timeout_str = "--timeout {}".format(timeout)
quilesj26c78a42019-10-28 18:10:42 +0100551
552 # atomic
beierlm32862bb2020-04-21 16:36:35 -0400553 atomic_str = ""
quilesj26c78a42019-10-28 18:10:42 +0100554 if atomic:
beierlm32862bb2020-04-21 16:36:35 -0400555 atomic_str = "--atomic"
quilesj26c78a42019-10-28 18:10:42 +0100556
557 # version
beierlm32862bb2020-04-21 16:36:35 -0400558 version_str = ""
559 if kdu_model and ":" in kdu_model:
560 parts = kdu_model.split(sep=":")
quilesj26c78a42019-10-28 18:10:42 +0100561 if len(parts) == 2:
beierlm32862bb2020-04-21 16:36:35 -0400562 version_str = "--version {}".format(parts[1])
quilesj26c78a42019-10-28 18:10:42 +0100563 kdu_model = parts[0]
564
565 # helm repo upgrade
beierlm32862bb2020-04-21 16:36:35 -0400566 command = (
567 "{} upgrade {} --output yaml --kubeconfig={} " "--home={} {} {} {} {} {}"
568 ).format(
569 self._helm_command,
570 atomic_str,
571 config_filename,
572 helm_dir,
573 params_str,
574 timeout_str,
575 kdu_instance,
576 kdu_model,
577 version_str,
578 )
579 self.log.debug("upgrading: {}".format(command))
quilesj26c78a42019-10-28 18:10:42 +0100580
581 if atomic:
582
583 # exec helm in a task
584 exec_task = asyncio.ensure_future(
beierlm32862bb2020-04-21 16:36:35 -0400585 coro_or_future=self._local_async_exec(
586 command=command, raise_exception_on_error=False
587 )
quilesj26c78a42019-10-28 18:10:42 +0100588 )
589 # write status in another task
590 status_task = asyncio.ensure_future(
591 coro_or_future=self._store_status(
592 cluster_uuid=cluster_uuid,
593 kdu_instance=kdu_instance,
594 db_dict=db_dict,
beierlm32862bb2020-04-21 16:36:35 -0400595 operation="upgrade",
596 run_once=False,
quilesj26c78a42019-10-28 18:10:42 +0100597 )
598 )
599
600 # wait for execution task
quilesj1be06302019-11-29 11:17:11 +0000601 await asyncio.wait([exec_task])
quilesj26c78a42019-10-28 18:10:42 +0100602
603 # cancel status task
604 status_task.cancel()
605 output, rc = exec_task.result()
606
607 else:
608
beierlm32862bb2020-04-21 16:36:35 -0400609 output, rc = await self._local_async_exec(
610 command=command, raise_exception_on_error=False
611 )
quilesj26c78a42019-10-28 18:10:42 +0100612
quilesjcda5f412019-11-18 11:32:12 +0100613 # remove temporal values yaml file
614 if file_to_delete:
615 os.remove(file_to_delete)
616
quilesj26c78a42019-10-28 18:10:42 +0100617 # write final status
618 await self._store_status(
619 cluster_uuid=cluster_uuid,
620 kdu_instance=kdu_instance,
621 db_dict=db_dict,
beierlm32862bb2020-04-21 16:36:35 -0400622 operation="upgrade",
quilesj26c78a42019-10-28 18:10:42 +0100623 run_once=True,
beierlm32862bb2020-04-21 16:36:35 -0400624 check_every=0,
quilesj26c78a42019-10-28 18:10:42 +0100625 )
626
627 if rc != 0:
beierlm32862bb2020-04-21 16:36:35 -0400628 msg = "Error executing command: {}\nOutput: {}".format(command, output)
Dominik Fleischmannbc269eb2020-02-27 10:04:34 +0100629 self.log.error(msg)
quilesja6748412019-12-04 07:51:26 +0000630 raise K8sException(msg)
quilesj26c78a42019-10-28 18:10:42 +0100631
632 # return new revision number
beierlm32862bb2020-04-21 16:36:35 -0400633 instance = await self.get_instance_info(
634 cluster_uuid=cluster_uuid, kdu_instance=kdu_instance
635 )
quilesj26c78a42019-10-28 18:10:42 +0100636 if instance:
beierlm32862bb2020-04-21 16:36:35 -0400637 revision = int(instance.get("Revision"))
638 self.log.debug("New revision: {}".format(revision))
quilesj26c78a42019-10-28 18:10:42 +0100639 return revision
640 else:
641 return 0
642
643 async def rollback(
beierlm32862bb2020-04-21 16:36:35 -0400644 self, cluster_uuid: str, kdu_instance: str, revision=0, db_dict: dict = None
quilesj26c78a42019-10-28 18:10:42 +0100645 ):
646
beierlm32862bb2020-04-21 16:36:35 -0400647 self.log.debug(
648 "rollback kdu_instance {} to revision {} from cluster {}".format(
649 kdu_instance, revision, cluster_uuid
650 )
651 )
quilesj26c78a42019-10-28 18:10:42 +0100652
653 # config filename
beierlm32862bb2020-04-21 16:36:35 -0400654 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
655 cluster_name=cluster_uuid, create_if_not_exist=True
656 )
quilesj26c78a42019-10-28 18:10:42 +0100657
beierlm32862bb2020-04-21 16:36:35 -0400658 command = "{} rollback --kubeconfig={} --home={} {} {} --wait".format(
659 self._helm_command, config_filename, helm_dir, kdu_instance, revision
660 )
quilesj26c78a42019-10-28 18:10:42 +0100661
662 # exec helm in a task
663 exec_task = asyncio.ensure_future(
beierlm32862bb2020-04-21 16:36:35 -0400664 coro_or_future=self._local_async_exec(
665 command=command, raise_exception_on_error=False
666 )
quilesj26c78a42019-10-28 18:10:42 +0100667 )
668 # write status in another task
669 status_task = asyncio.ensure_future(
670 coro_or_future=self._store_status(
671 cluster_uuid=cluster_uuid,
672 kdu_instance=kdu_instance,
673 db_dict=db_dict,
beierlm32862bb2020-04-21 16:36:35 -0400674 operation="rollback",
675 run_once=False,
quilesj26c78a42019-10-28 18:10:42 +0100676 )
677 )
678
679 # wait for execution task
680 await asyncio.wait([exec_task])
681
682 # cancel status task
683 status_task.cancel()
684
685 output, rc = exec_task.result()
686
687 # write final status
688 await self._store_status(
689 cluster_uuid=cluster_uuid,
690 kdu_instance=kdu_instance,
691 db_dict=db_dict,
beierlm32862bb2020-04-21 16:36:35 -0400692 operation="rollback",
quilesj26c78a42019-10-28 18:10:42 +0100693 run_once=True,
beierlm32862bb2020-04-21 16:36:35 -0400694 check_every=0,
quilesj26c78a42019-10-28 18:10:42 +0100695 )
696
697 if rc != 0:
beierlm32862bb2020-04-21 16:36:35 -0400698 msg = "Error executing command: {}\nOutput: {}".format(command, output)
Dominik Fleischmannbc269eb2020-02-27 10:04:34 +0100699 self.log.error(msg)
quilesja6748412019-12-04 07:51:26 +0000700 raise K8sException(msg)
quilesj26c78a42019-10-28 18:10:42 +0100701
702 # return new revision number
beierlm32862bb2020-04-21 16:36:35 -0400703 instance = await self.get_instance_info(
704 cluster_uuid=cluster_uuid, kdu_instance=kdu_instance
705 )
quilesj26c78a42019-10-28 18:10:42 +0100706 if instance:
beierlm32862bb2020-04-21 16:36:35 -0400707 revision = int(instance.get("Revision"))
708 self.log.debug("New revision: {}".format(revision))
quilesj26c78a42019-10-28 18:10:42 +0100709 return revision
710 else:
711 return 0
712
beierlm32862bb2020-04-21 16:36:35 -0400713 async def uninstall(self, cluster_uuid: str, kdu_instance: str):
quilesj26c78a42019-10-28 18:10:42 +0100714 """
beierlm32862bb2020-04-21 16:36:35 -0400715 Removes an existing KDU instance. It would implicitly use the `delete` call
716 (this call would happen after all _terminate-config-primitive_ of the VNF
717 are invoked).
quilesj26c78a42019-10-28 18:10:42 +0100718
719 :param cluster_uuid: UUID of a K8s cluster known by OSM
720 :param kdu_instance: unique name for the KDU instance to be deleted
721 :return: True if successful
722 """
723
beierlm32862bb2020-04-21 16:36:35 -0400724 self.log.debug(
725 "uninstall kdu_instance {} from cluster {}".format(
726 kdu_instance, cluster_uuid
727 )
728 )
quilesj26c78a42019-10-28 18:10:42 +0100729
730 # config filename
beierlm32862bb2020-04-21 16:36:35 -0400731 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
732 cluster_name=cluster_uuid, create_if_not_exist=True
733 )
quilesj26c78a42019-10-28 18:10:42 +0100734
beierlm32862bb2020-04-21 16:36:35 -0400735 command = "{} --kubeconfig={} --home={} delete --purge {}".format(
736 self._helm_command, config_filename, helm_dir, kdu_instance
737 )
quilesj26c78a42019-10-28 18:10:42 +0100738
beierlm32862bb2020-04-21 16:36:35 -0400739 output, _rc = await self._local_async_exec(
740 command=command, raise_exception_on_error=True
741 )
quilesj26c78a42019-10-28 18:10:42 +0100742
743 return self._output_to_table(output)
744
Dominik Fleischmannd14e4212020-04-06 14:51:00 +0200745 async def exec_primitive(
746 self,
747 cluster_uuid: str = None,
748 kdu_instance: str = None,
749 primitive_name: str = None,
750 timeout: float = 300,
751 params: dict = None,
752 db_dict: dict = None,
753 ) -> str:
754 """Exec primitive (Juju action)
755
756 :param cluster_uuid str: The UUID of the cluster
757 :param kdu_instance str: The unique name of the KDU instance
758 :param primitive_name: Name of action that will be executed
759 :param timeout: Timeout for action execution
760 :param params: Dictionary of all the parameters needed for the action
761 :db_dict: Dictionary for any additional data
762
763 :return: Returns the output of the action
764 """
beierlm32862bb2020-04-21 16:36:35 -0400765 raise K8sException(
766 "KDUs deployed with Helm don't support actions "
767 "different from rollback, upgrade and status"
768 )
Dominik Fleischmannd14e4212020-04-06 14:51:00 +0200769
beierlm32862bb2020-04-21 16:36:35 -0400770 async def inspect_kdu(self, kdu_model: str, repo_url: str = None) -> str:
quilesj26c78a42019-10-28 18:10:42 +0100771
beierlm32862bb2020-04-21 16:36:35 -0400772 self.log.debug(
773 "inspect kdu_model {} from (optional) repo: {}".format(kdu_model, repo_url)
774 )
quilesj26c78a42019-10-28 18:10:42 +0100775
beierlm32862bb2020-04-21 16:36:35 -0400776 return await self._exec_inspect_comand(
777 inspect_command="", kdu_model=kdu_model, repo_url=repo_url
778 )
quilesj26c78a42019-10-28 18:10:42 +0100779
beierlm32862bb2020-04-21 16:36:35 -0400780 async def values_kdu(self, kdu_model: str, repo_url: str = None) -> str:
quilesj26c78a42019-10-28 18:10:42 +0100781
beierlm32862bb2020-04-21 16:36:35 -0400782 self.log.debug(
783 "inspect kdu_model values {} from (optional) repo: {}".format(
784 kdu_model, repo_url
785 )
786 )
quilesj1be06302019-11-29 11:17:11 +0000787
beierlm32862bb2020-04-21 16:36:35 -0400788 return await self._exec_inspect_comand(
789 inspect_command="values", kdu_model=kdu_model, repo_url=repo_url
790 )
quilesj26c78a42019-10-28 18:10:42 +0100791
beierlm32862bb2020-04-21 16:36:35 -0400792 async def help_kdu(self, kdu_model: str, repo_url: str = None) -> str:
quilesj26c78a42019-10-28 18:10:42 +0100793
beierlm32862bb2020-04-21 16:36:35 -0400794 self.log.debug(
795 "inspect kdu_model {} readme.md from repo: {}".format(kdu_model, repo_url)
796 )
quilesj26c78a42019-10-28 18:10:42 +0100797
beierlm32862bb2020-04-21 16:36:35 -0400798 return await self._exec_inspect_comand(
799 inspect_command="readme", kdu_model=kdu_model, repo_url=repo_url
800 )
quilesj26c78a42019-10-28 18:10:42 +0100801
beierlm32862bb2020-04-21 16:36:35 -0400802 async def status_kdu(self, cluster_uuid: str, kdu_instance: str) -> str:
quilesj26c78a42019-10-28 18:10:42 +0100803
quilesj1be06302019-11-29 11:17:11 +0000804 # call internal function
805 return await self._status_kdu(
806 cluster_uuid=cluster_uuid,
807 kdu_instance=kdu_instance,
808 show_error_log=True,
beierlm32862bb2020-04-21 16:36:35 -0400809 return_text=True,
quilesj1be06302019-11-29 11:17:11 +0000810 )
quilesj26c78a42019-10-28 18:10:42 +0100811
lloretgallegf00dcae2020-02-20 12:01:17 +0100812 async def synchronize_repos(self, cluster_uuid: str):
813
Dominik Fleischmannbc269eb2020-02-27 10:04:34 +0100814 self.log.debug("syncronize repos for cluster helm-id: {}",)
lloretgallegf00dcae2020-02-20 12:01:17 +0100815 try:
beierlm32862bb2020-04-21 16:36:35 -0400816 update_repos_timeout = (
817 300 # max timeout to sync a single repos, more than this is too much
818 )
819 db_k8scluster = self.db.get_one(
820 "k8sclusters", {"_admin.helm-chart.id": cluster_uuid}
821 )
lloretgallegf00dcae2020-02-20 12:01:17 +0100822 if db_k8scluster:
beierlm32862bb2020-04-21 16:36:35 -0400823 nbi_repo_list = (
824 db_k8scluster.get("_admin").get("helm_chart_repos") or []
825 )
826 cluster_repo_dict = (
827 db_k8scluster.get("_admin").get("helm_charts_added") or {}
828 )
lloretgallegf00dcae2020-02-20 12:01:17 +0100829 # elements that must be deleted
830 deleted_repo_list = []
831 added_repo_dict = {}
Dominik Fleischmannbc269eb2020-02-27 10:04:34 +0100832 self.log.debug("helm_chart_repos: {}".format(nbi_repo_list))
833 self.log.debug("helm_charts_added: {}".format(cluster_repo_dict))
lloretgallegf00dcae2020-02-20 12:01:17 +0100834
835 # obtain repos to add: registered by nbi but not added
beierlm32862bb2020-04-21 16:36:35 -0400836 repos_to_add = [
837 repo for repo in nbi_repo_list if not cluster_repo_dict.get(repo)
838 ]
lloretgallegf00dcae2020-02-20 12:01:17 +0100839
840 # obtain repos to delete: added by cluster but not in nbi list
beierlm32862bb2020-04-21 16:36:35 -0400841 repos_to_delete = [
842 repo
843 for repo in cluster_repo_dict.keys()
844 if repo not in nbi_repo_list
845 ]
lloretgallegf00dcae2020-02-20 12:01:17 +0100846
beierlm32862bb2020-04-21 16:36:35 -0400847 # delete repos: must delete first then add because there may be
848 # different repos with same name but
lloretgallegf00dcae2020-02-20 12:01:17 +0100849 # different id and url
Dominik Fleischmannbc269eb2020-02-27 10:04:34 +0100850 self.log.debug("repos to delete: {}".format(repos_to_delete))
lloretgallegf00dcae2020-02-20 12:01:17 +0100851 for repo_id in repos_to_delete:
852 # try to delete repos
853 try:
beierlm32862bb2020-04-21 16:36:35 -0400854 repo_delete_task = asyncio.ensure_future(
855 self.repo_remove(
856 cluster_uuid=cluster_uuid,
857 name=cluster_repo_dict[repo_id],
858 )
859 )
lloretgallegf00dcae2020-02-20 12:01:17 +0100860 await asyncio.wait_for(repo_delete_task, update_repos_timeout)
861 except Exception as e:
beierlm32862bb2020-04-21 16:36:35 -0400862 self.warning(
863 "Error deleting repo, id: {}, name: {}, err_msg: {}".format(
864 repo_id, cluster_repo_dict[repo_id], str(e)
865 )
866 )
867 # always add to the list of to_delete if there is an error
868 # because if is not there
869 # deleting raises error
lloretgallegf00dcae2020-02-20 12:01:17 +0100870 deleted_repo_list.append(repo_id)
871
872 # add repos
Dominik Fleischmannbc269eb2020-02-27 10:04:34 +0100873 self.log.debug("repos to add: {}".format(repos_to_add))
lloretgallegf00dcae2020-02-20 12:01:17 +0100874 for repo_id in repos_to_add:
875 # obtain the repo data from the db
beierlm32862bb2020-04-21 16:36:35 -0400876 # if there is an error getting the repo in the database we will
877 # ignore this repo and continue
878 # because there is a possible race condition where the repo has
879 # been deleted while processing
lloretgallegf00dcae2020-02-20 12:01:17 +0100880 db_repo = self.db.get_one("k8srepos", {"_id": repo_id})
beierlm32862bb2020-04-21 16:36:35 -0400881 self.log.debug(
882 "obtained repo: id, {}, name: {}, url: {}".format(
883 repo_id, db_repo["name"], db_repo["url"]
884 )
885 )
lloretgallegf00dcae2020-02-20 12:01:17 +0100886 try:
beierlm32862bb2020-04-21 16:36:35 -0400887 repo_add_task = asyncio.ensure_future(
888 self.repo_add(
889 cluster_uuid=cluster_uuid,
890 name=db_repo["name"],
891 url=db_repo["url"],
892 repo_type="chart",
893 )
894 )
lloretgallegf00dcae2020-02-20 12:01:17 +0100895 await asyncio.wait_for(repo_add_task, update_repos_timeout)
896 added_repo_dict[repo_id] = db_repo["name"]
beierlm32862bb2020-04-21 16:36:35 -0400897 self.log.debug(
898 "added repo: id, {}, name: {}".format(
899 repo_id, db_repo["name"]
900 )
901 )
lloretgallegf00dcae2020-02-20 12:01:17 +0100902 except Exception as e:
beierlm32862bb2020-04-21 16:36:35 -0400903 # deal with error adding repo, adding a repo that already
904 # exists does not raise any error
905 # will not raise error because a wrong repos added by
906 # anyone could prevent instantiating any ns
907 self.log.error(
908 "Error adding repo id: {}, err_msg: {} ".format(
909 repo_id, repr(e)
910 )
911 )
lloretgallegf00dcae2020-02-20 12:01:17 +0100912
913 return deleted_repo_list, added_repo_dict
914
beierlm32862bb2020-04-21 16:36:35 -0400915 else: # else db_k8scluster does not exist
916 raise K8sException(
917 "k8cluster with helm-id : {} not found".format(cluster_uuid)
918 )
lloretgallegf00dcae2020-02-20 12:01:17 +0100919
920 except Exception as e:
Dominik Fleischmannbc269eb2020-02-27 10:04:34 +0100921 self.log.error("Error synchronizing repos: {}".format(str(e)))
lloretgallegf00dcae2020-02-20 12:01:17 +0100922 raise K8sException("Error synchronizing repos")
923
quilesj26c78a42019-10-28 18:10:42 +0100924 """
beierlm32862bb2020-04-21 16:36:35 -0400925 ####################################################################################
926 ################################### P R I V A T E ##################################
927 ####################################################################################
quilesj26c78a42019-10-28 18:10:42 +0100928 """
929
quilesj1be06302019-11-29 11:17:11 +0000930 async def _exec_inspect_comand(
beierlm32862bb2020-04-21 16:36:35 -0400931 self, inspect_command: str, kdu_model: str, repo_url: str = None
quilesj1be06302019-11-29 11:17:11 +0000932 ):
933
beierlm32862bb2020-04-21 16:36:35 -0400934 repo_str = ""
quilesj1be06302019-11-29 11:17:11 +0000935 if repo_url:
beierlm32862bb2020-04-21 16:36:35 -0400936 repo_str = " --repo {}".format(repo_url)
937 idx = kdu_model.find("/")
quilesj1be06302019-11-29 11:17:11 +0000938 if idx >= 0:
939 idx += 1
940 kdu_model = kdu_model[idx:]
941
beierlm32862bb2020-04-21 16:36:35 -0400942 inspect_command = "{} inspect {} {}{}".format(
943 self._helm_command, inspect_command, kdu_model, repo_str
944 )
945 output, _rc = await self._local_async_exec(
946 command=inspect_command, encode_utf8=True
947 )
quilesj1be06302019-11-29 11:17:11 +0000948
949 return output
950
quilesj26c78a42019-10-28 18:10:42 +0100951 async def _status_kdu(
beierlm32862bb2020-04-21 16:36:35 -0400952 self,
953 cluster_uuid: str,
954 kdu_instance: str,
955 show_error_log: bool = False,
956 return_text: bool = False,
quilesj26c78a42019-10-28 18:10:42 +0100957 ):
958
beierlm32862bb2020-04-21 16:36:35 -0400959 self.log.debug("status of kdu_instance {}".format(kdu_instance))
quilesj26c78a42019-10-28 18:10:42 +0100960
961 # config filename
beierlm32862bb2020-04-21 16:36:35 -0400962 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
963 cluster_name=cluster_uuid, create_if_not_exist=True
964 )
quilesj26c78a42019-10-28 18:10:42 +0100965
beierlm32862bb2020-04-21 16:36:35 -0400966 command = "{} --kubeconfig={} --home={} status {} --output yaml".format(
967 self._helm_command, config_filename, helm_dir, kdu_instance
968 )
quilesj26c78a42019-10-28 18:10:42 +0100969
970 output, rc = await self._local_async_exec(
971 command=command,
972 raise_exception_on_error=True,
beierlm32862bb2020-04-21 16:36:35 -0400973 show_error_log=show_error_log,
quilesj26c78a42019-10-28 18:10:42 +0100974 )
975
quilesj1be06302019-11-29 11:17:11 +0000976 if return_text:
977 return str(output)
978
quilesj26c78a42019-10-28 18:10:42 +0100979 if rc != 0:
980 return None
981
982 data = yaml.load(output, Loader=yaml.SafeLoader)
983
984 # remove field 'notes'
985 try:
beierlm32862bb2020-04-21 16:36:35 -0400986 del data.get("info").get("status")["notes"]
quilesj26c78a42019-10-28 18:10:42 +0100987 except KeyError:
988 pass
989
990 # parse field 'resources'
991 try:
beierlm32862bb2020-04-21 16:36:35 -0400992 resources = str(data.get("info").get("status").get("resources"))
quilesj26c78a42019-10-28 18:10:42 +0100993 resource_table = self._output_to_table(resources)
beierlm32862bb2020-04-21 16:36:35 -0400994 data.get("info").get("status")["resources"] = resource_table
995 except Exception:
quilesj26c78a42019-10-28 18:10:42 +0100996 pass
997
998 return data
999
beierlm32862bb2020-04-21 16:36:35 -04001000 async def get_instance_info(self, cluster_uuid: str, kdu_instance: str):
quilesj26c78a42019-10-28 18:10:42 +01001001 instances = await self.instances_list(cluster_uuid=cluster_uuid)
1002 for instance in instances:
beierlm32862bb2020-04-21 16:36:35 -04001003 if instance.get("Name") == kdu_instance:
quilesj26c78a42019-10-28 18:10:42 +01001004 return instance
beierlm32862bb2020-04-21 16:36:35 -04001005 self.log.debug("Instance {} not found".format(kdu_instance))
quilesj26c78a42019-10-28 18:10:42 +01001006 return None
1007
1008 @staticmethod
beierlm32862bb2020-04-21 16:36:35 -04001009 def _generate_release_name(chart_name: str):
quilesj8d780a92020-01-23 09:28:26 +00001010 # check embeded chart (file or dir)
beierlm32862bb2020-04-21 16:36:35 -04001011 if chart_name.startswith("/"):
quilesj8d780a92020-01-23 09:28:26 +00001012 # extract file or directory name
beierlm32862bb2020-04-21 16:36:35 -04001013 chart_name = chart_name[chart_name.rfind("/") + 1 :]
quilesj8d780a92020-01-23 09:28:26 +00001014 # check URL
beierlm32862bb2020-04-21 16:36:35 -04001015 elif "://" in chart_name:
quilesj8d780a92020-01-23 09:28:26 +00001016 # extract last portion of URL
beierlm32862bb2020-04-21 16:36:35 -04001017 chart_name = chart_name[chart_name.rfind("/") + 1 :]
quilesj8d780a92020-01-23 09:28:26 +00001018
beierlm32862bb2020-04-21 16:36:35 -04001019 name = ""
quilesj26c78a42019-10-28 18:10:42 +01001020 for c in chart_name:
1021 if c.isalpha() or c.isnumeric():
1022 name += c
1023 else:
beierlm32862bb2020-04-21 16:36:35 -04001024 name += "-"
quilesj26c78a42019-10-28 18:10:42 +01001025 if len(name) > 35:
1026 name = name[0:35]
1027
1028 # if does not start with alpha character, prefix 'a'
1029 if not name[0].isalpha():
beierlm32862bb2020-04-21 16:36:35 -04001030 name = "a" + name
quilesj26c78a42019-10-28 18:10:42 +01001031
beierlm32862bb2020-04-21 16:36:35 -04001032 name += "-"
quilesj26c78a42019-10-28 18:10:42 +01001033
1034 def get_random_number():
1035 r = random.randrange(start=1, stop=99999999)
1036 s = str(r)
beierlm32862bb2020-04-21 16:36:35 -04001037 s = s.rjust(10, "0")
quilesj26c78a42019-10-28 18:10:42 +01001038 return s
1039
1040 name = name + get_random_number()
1041 return name.lower()
1042
1043 async def _store_status(
beierlm32862bb2020-04-21 16:36:35 -04001044 self,
1045 cluster_uuid: str,
1046 operation: str,
1047 kdu_instance: str,
1048 check_every: float = 10,
1049 db_dict: dict = None,
1050 run_once: bool = False,
quilesj26c78a42019-10-28 18:10:42 +01001051 ):
1052 while True:
1053 try:
1054 await asyncio.sleep(check_every)
beierlm32862bb2020-04-21 16:36:35 -04001055 detailed_status = await self.status_kdu(
1056 cluster_uuid=cluster_uuid, kdu_instance=kdu_instance
1057 )
1058 status = detailed_status.get("info").get("Description")
1059 self.log.debug("STATUS:\n{}".format(status))
1060 self.log.debug("DETAILED STATUS:\n{}".format(detailed_status))
quilesj26c78a42019-10-28 18:10:42 +01001061 # write status to db
1062 result = await self.write_app_status_to_db(
1063 db_dict=db_dict,
1064 status=str(status),
1065 detailed_status=str(detailed_status),
beierlm32862bb2020-04-21 16:36:35 -04001066 operation=operation,
1067 )
quilesj26c78a42019-10-28 18:10:42 +01001068 if not result:
beierlm32862bb2020-04-21 16:36:35 -04001069 self.log.info("Error writing in database. Task exiting...")
quilesj26c78a42019-10-28 18:10:42 +01001070 return
1071 except asyncio.CancelledError:
beierlm32862bb2020-04-21 16:36:35 -04001072 self.log.debug("Task cancelled")
quilesj26c78a42019-10-28 18:10:42 +01001073 return
1074 except Exception as e:
beierlm32862bb2020-04-21 16:36:35 -04001075 self.log.debug("_store_status exception: {}".format(str(e)))
quilesj26c78a42019-10-28 18:10:42 +01001076 pass
1077 finally:
1078 if run_once:
1079 return
1080
beierlm32862bb2020-04-21 16:36:35 -04001081 async def _is_install_completed(self, cluster_uuid: str, kdu_instance: str) -> bool:
quilesj26c78a42019-10-28 18:10:42 +01001082
beierlm32862bb2020-04-21 16:36:35 -04001083 status = await self._status_kdu(
1084 cluster_uuid=cluster_uuid, kdu_instance=kdu_instance, return_text=False
1085 )
quilesj26c78a42019-10-28 18:10:42 +01001086
1087 # extract info.status.resources-> str
1088 # format:
1089 # ==> v1/Deployment
1090 # NAME READY UP-TO-DATE AVAILABLE AGE
1091 # halting-horse-mongodb 0/1 1 0 0s
1092 # halting-petit-mongodb 1/1 1 0 0s
1093 # blank line
beierlm32862bb2020-04-21 16:36:35 -04001094 resources = K8sHelmConnector._get_deep(status, ("info", "status", "resources"))
quilesj26c78a42019-10-28 18:10:42 +01001095
1096 # convert to table
1097 resources = K8sHelmConnector._output_to_table(resources)
1098
1099 num_lines = len(resources)
1100 index = 0
1101 while index < num_lines:
1102 try:
1103 line1 = resources[index]
1104 index += 1
1105 # find '==>' in column 0
beierlm32862bb2020-04-21 16:36:35 -04001106 if line1[0] == "==>":
quilesj26c78a42019-10-28 18:10:42 +01001107 line2 = resources[index]
1108 index += 1
1109 # find READY in column 1
beierlm32862bb2020-04-21 16:36:35 -04001110 if line2[1] == "READY":
quilesj26c78a42019-10-28 18:10:42 +01001111 # read next lines
1112 line3 = resources[index]
1113 index += 1
1114 while len(line3) > 1 and index < num_lines:
1115 ready_value = line3[1]
beierlm32862bb2020-04-21 16:36:35 -04001116 parts = ready_value.split(sep="/")
quilesj26c78a42019-10-28 18:10:42 +01001117 current = int(parts[0])
1118 total = int(parts[1])
1119 if current < total:
beierlm32862bb2020-04-21 16:36:35 -04001120 self.log.debug("NOT READY:\n {}".format(line3))
quilesj26c78a42019-10-28 18:10:42 +01001121 ready = False
1122 line3 = resources[index]
1123 index += 1
1124
beierlm32862bb2020-04-21 16:36:35 -04001125 except Exception:
quilesj26c78a42019-10-28 18:10:42 +01001126 pass
1127
1128 return ready
1129
1130 @staticmethod
1131 def _get_deep(dictionary: dict, members: tuple):
1132 target = dictionary
1133 value = None
1134 try:
1135 for m in members:
1136 value = target.get(m)
1137 if not value:
1138 return None
1139 else:
1140 target = value
beierlm32862bb2020-04-21 16:36:35 -04001141 except Exception:
quilesj26c78a42019-10-28 18:10:42 +01001142 pass
1143 return value
1144
1145 # find key:value in several lines
1146 @staticmethod
1147 def _find_in_lines(p_lines: list, p_key: str) -> str:
1148 for line in p_lines:
1149 try:
beierlm32862bb2020-04-21 16:36:35 -04001150 if line.startswith(p_key + ":"):
1151 parts = line.split(":")
quilesj26c78a42019-10-28 18:10:42 +01001152 the_value = parts[1].strip()
1153 return the_value
beierlm32862bb2020-04-21 16:36:35 -04001154 except Exception:
quilesj26c78a42019-10-28 18:10:42 +01001155 # ignore it
1156 pass
1157 return None
1158
quilesjcda5f412019-11-18 11:32:12 +01001159 # params for use in -f file
1160 # returns values file option and filename (in order to delete it at the end)
1161 def _params_to_file_option(self, cluster_uuid: str, params: dict) -> (str, str):
quilesjcda5f412019-11-18 11:32:12 +01001162
1163 if params and len(params) > 0:
beierlm32862bb2020-04-21 16:36:35 -04001164 self._get_paths(cluster_name=cluster_uuid, create_if_not_exist=True)
quilesjcda5f412019-11-18 11:32:12 +01001165
1166 def get_random_number():
1167 r = random.randrange(start=1, stop=99999999)
1168 s = str(r)
1169 while len(s) < 10:
beierlm32862bb2020-04-21 16:36:35 -04001170 s = "0" + s
quilesjcda5f412019-11-18 11:32:12 +01001171 return s
1172
1173 params2 = dict()
1174 for key in params:
1175 value = params.get(key)
beierlm32862bb2020-04-21 16:36:35 -04001176 if "!!yaml" in str(value):
quilesj1be06302019-11-29 11:17:11 +00001177 value = yaml.load(value[7:])
quilesjcda5f412019-11-18 11:32:12 +01001178 params2[key] = value
1179
beierlm32862bb2020-04-21 16:36:35 -04001180 values_file = get_random_number() + ".yaml"
1181 with open(values_file, "w") as stream:
quilesjcda5f412019-11-18 11:32:12 +01001182 yaml.dump(params2, stream, indent=4, default_flow_style=False)
1183
beierlm32862bb2020-04-21 16:36:35 -04001184 return "-f {}".format(values_file), values_file
quilesjcda5f412019-11-18 11:32:12 +01001185
beierlm32862bb2020-04-21 16:36:35 -04001186 return "", None
quilesjcda5f412019-11-18 11:32:12 +01001187
quilesj26c78a42019-10-28 18:10:42 +01001188 # params for use in --set option
1189 @staticmethod
1190 def _params_to_set_option(params: dict) -> str:
beierlm32862bb2020-04-21 16:36:35 -04001191 params_str = ""
quilesj26c78a42019-10-28 18:10:42 +01001192 if params and len(params) > 0:
1193 start = True
1194 for key in params:
1195 value = params.get(key, None)
1196 if value is not None:
1197 if start:
beierlm32862bb2020-04-21 16:36:35 -04001198 params_str += "--set "
quilesj26c78a42019-10-28 18:10:42 +01001199 start = False
1200 else:
beierlm32862bb2020-04-21 16:36:35 -04001201 params_str += ","
1202 params_str += "{}={}".format(key, value)
quilesj26c78a42019-10-28 18:10:42 +01001203 return params_str
1204
1205 @staticmethod
1206 def _output_to_lines(output: str) -> list:
1207 output_lines = list()
1208 lines = output.splitlines(keepends=False)
1209 for line in lines:
1210 line = line.strip()
1211 if len(line) > 0:
1212 output_lines.append(line)
1213 return output_lines
1214
1215 @staticmethod
1216 def _output_to_table(output: str) -> list:
1217 output_table = list()
1218 lines = output.splitlines(keepends=False)
1219 for line in lines:
beierlm32862bb2020-04-21 16:36:35 -04001220 line = line.replace("\t", " ")
quilesj26c78a42019-10-28 18:10:42 +01001221 line_list = list()
1222 output_table.append(line_list)
beierlm32862bb2020-04-21 16:36:35 -04001223 cells = line.split(sep=" ")
quilesj26c78a42019-10-28 18:10:42 +01001224 for cell in cells:
1225 cell = cell.strip()
1226 if len(cell) > 0:
1227 line_list.append(cell)
1228 return output_table
1229
beierlm32862bb2020-04-21 16:36:35 -04001230 def _get_paths(
1231 self, cluster_name: str, create_if_not_exist: bool = False
1232 ) -> (str, str, str, str):
quilesj26c78a42019-10-28 18:10:42 +01001233 """
1234 Returns kube and helm directories
1235
1236 :param cluster_name:
1237 :param create_if_not_exist:
quilesjcda5f412019-11-18 11:32:12 +01001238 :return: kube, helm directories, config filename and cluster dir.
1239 Raises exception if not exist and cannot create
quilesj26c78a42019-10-28 18:10:42 +01001240 """
1241
1242 base = self.fs.path
1243 if base.endswith("/") or base.endswith("\\"):
1244 base = base[:-1]
1245
1246 # base dir for cluster
beierlm32862bb2020-04-21 16:36:35 -04001247 cluster_dir = base + "/" + cluster_name
quilesj26c78a42019-10-28 18:10:42 +01001248 if create_if_not_exist and not os.path.exists(cluster_dir):
beierlm32862bb2020-04-21 16:36:35 -04001249 self.log.debug("Creating dir {}".format(cluster_dir))
quilesj26c78a42019-10-28 18:10:42 +01001250 os.makedirs(cluster_dir)
1251 if not os.path.exists(cluster_dir):
beierlm32862bb2020-04-21 16:36:35 -04001252 msg = "Base cluster dir {} does not exist".format(cluster_dir)
Dominik Fleischmannbc269eb2020-02-27 10:04:34 +01001253 self.log.error(msg)
quilesja6748412019-12-04 07:51:26 +00001254 raise K8sException(msg)
quilesj26c78a42019-10-28 18:10:42 +01001255
1256 # kube dir
beierlm32862bb2020-04-21 16:36:35 -04001257 kube_dir = cluster_dir + "/" + ".kube"
quilesj26c78a42019-10-28 18:10:42 +01001258 if create_if_not_exist and not os.path.exists(kube_dir):
beierlm32862bb2020-04-21 16:36:35 -04001259 self.log.debug("Creating dir {}".format(kube_dir))
quilesj26c78a42019-10-28 18:10:42 +01001260 os.makedirs(kube_dir)
1261 if not os.path.exists(kube_dir):
beierlm32862bb2020-04-21 16:36:35 -04001262 msg = "Kube config dir {} does not exist".format(kube_dir)
Dominik Fleischmannbc269eb2020-02-27 10:04:34 +01001263 self.log.error(msg)
quilesja6748412019-12-04 07:51:26 +00001264 raise K8sException(msg)
quilesj26c78a42019-10-28 18:10:42 +01001265
1266 # helm home dir
beierlm32862bb2020-04-21 16:36:35 -04001267 helm_dir = cluster_dir + "/" + ".helm"
quilesj26c78a42019-10-28 18:10:42 +01001268 if create_if_not_exist and not os.path.exists(helm_dir):
beierlm32862bb2020-04-21 16:36:35 -04001269 self.log.debug("Creating dir {}".format(helm_dir))
quilesj26c78a42019-10-28 18:10:42 +01001270 os.makedirs(helm_dir)
1271 if not os.path.exists(helm_dir):
beierlm32862bb2020-04-21 16:36:35 -04001272 msg = "Helm config dir {} does not exist".format(helm_dir)
Dominik Fleischmannbc269eb2020-02-27 10:04:34 +01001273 self.log.error(msg)
quilesja6748412019-12-04 07:51:26 +00001274 raise K8sException(msg)
quilesj26c78a42019-10-28 18:10:42 +01001275
beierlm32862bb2020-04-21 16:36:35 -04001276 config_filename = kube_dir + "/config"
quilesjcda5f412019-11-18 11:32:12 +01001277 return kube_dir, helm_dir, config_filename, cluster_dir
quilesj26c78a42019-10-28 18:10:42 +01001278
1279 @staticmethod
beierlm32862bb2020-04-21 16:36:35 -04001280 def _remove_multiple_spaces(strobj):
1281 strobj = strobj.strip()
1282 while " " in strobj:
1283 strobj = strobj.replace(" ", " ")
1284 return strobj
quilesj26c78a42019-10-28 18:10:42 +01001285
beierlm32862bb2020-04-21 16:36:35 -04001286 def _local_exec(self, command: str) -> (str, int):
quilesj26c78a42019-10-28 18:10:42 +01001287 command = K8sHelmConnector._remove_multiple_spaces(command)
beierlm32862bb2020-04-21 16:36:35 -04001288 self.log.debug("Executing sync local command: {}".format(command))
quilesj26c78a42019-10-28 18:10:42 +01001289 # raise exception if fails
beierlm32862bb2020-04-21 16:36:35 -04001290 output = ""
quilesj26c78a42019-10-28 18:10:42 +01001291 try:
beierlm32862bb2020-04-21 16:36:35 -04001292 output = subprocess.check_output(
1293 command, shell=True, universal_newlines=True
1294 )
quilesj26c78a42019-10-28 18:10:42 +01001295 return_code = 0
Dominik Fleischmannbc269eb2020-02-27 10:04:34 +01001296 self.log.debug(output)
beierlm32862bb2020-04-21 16:36:35 -04001297 except Exception:
quilesj26c78a42019-10-28 18:10:42 +01001298 return_code = 1
1299
1300 return output, return_code
1301
1302 async def _local_async_exec(
beierlm32862bb2020-04-21 16:36:35 -04001303 self,
1304 command: str,
1305 raise_exception_on_error: bool = False,
1306 show_error_log: bool = True,
1307 encode_utf8: bool = False,
quilesj26c78a42019-10-28 18:10:42 +01001308 ) -> (str, int):
1309
1310 command = K8sHelmConnector._remove_multiple_spaces(command)
beierlm32862bb2020-04-21 16:36:35 -04001311 self.log.debug("Executing async local command: {}".format(command))
quilesj26c78a42019-10-28 18:10:42 +01001312
1313 # split command
beierlm32862bb2020-04-21 16:36:35 -04001314 command = command.split(sep=" ")
quilesj26c78a42019-10-28 18:10:42 +01001315
1316 try:
1317 process = await asyncio.create_subprocess_exec(
beierlm32862bb2020-04-21 16:36:35 -04001318 *command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
quilesj26c78a42019-10-28 18:10:42 +01001319 )
1320
1321 # wait for command terminate
1322 stdout, stderr = await process.communicate()
1323
1324 return_code = process.returncode
1325
beierlm32862bb2020-04-21 16:36:35 -04001326 output = ""
quilesj26c78a42019-10-28 18:10:42 +01001327 if stdout:
beierlm32862bb2020-04-21 16:36:35 -04001328 output = stdout.decode("utf-8").strip()
quilesj1be06302019-11-29 11:17:11 +00001329 # output = stdout.decode()
quilesj26c78a42019-10-28 18:10:42 +01001330 if stderr:
beierlm32862bb2020-04-21 16:36:35 -04001331 output = stderr.decode("utf-8").strip()
quilesj1be06302019-11-29 11:17:11 +00001332 # output = stderr.decode()
quilesj26c78a42019-10-28 18:10:42 +01001333
1334 if return_code != 0 and show_error_log:
beierlm32862bb2020-04-21 16:36:35 -04001335 self.log.debug(
1336 "Return code (FAIL): {}\nOutput:\n{}".format(return_code, output)
1337 )
quilesj26c78a42019-10-28 18:10:42 +01001338 else:
beierlm32862bb2020-04-21 16:36:35 -04001339 self.log.debug("Return code: {}".format(return_code))
quilesj26c78a42019-10-28 18:10:42 +01001340
1341 if raise_exception_on_error and return_code != 0:
tierno41d639e2020-02-04 15:26:25 +00001342 raise K8sException(output)
quilesj26c78a42019-10-28 18:10:42 +01001343
quilesj1be06302019-11-29 11:17:11 +00001344 if encode_utf8:
beierlm32862bb2020-04-21 16:36:35 -04001345 output = output.encode("utf-8").strip()
1346 output = str(output).replace("\\n", "\n")
quilesj1be06302019-11-29 11:17:11 +00001347
quilesj26c78a42019-10-28 18:10:42 +01001348 return output, return_code
1349
lloretgallegb4b317c2020-02-26 10:00:16 +01001350 except asyncio.CancelledError:
1351 raise
tierno41d639e2020-02-04 15:26:25 +00001352 except K8sException:
1353 raise
quilesj26c78a42019-10-28 18:10:42 +01001354 except Exception as e:
beierlm32862bb2020-04-21 16:36:35 -04001355 msg = "Exception executing command: {} -> {}".format(command, e)
Dominik Fleischmannbc269eb2020-02-27 10:04:34 +01001356 self.log.error(msg)
quilesj23451b82020-01-23 16:30:04 +00001357 if raise_exception_on_error:
tierno41d639e2020-02-04 15:26:25 +00001358 raise K8sException(e) from e
quilesj23451b82020-01-23 16:30:04 +00001359 else:
beierlm32862bb2020-04-21 16:36:35 -04001360 return "", -1
quilesj26c78a42019-10-28 18:10:42 +01001361
quilesj26c78a42019-10-28 18:10:42 +01001362 def _check_file_exists(self, filename: str, exception_if_not_exists: bool = False):
tiernoe2bd3da2020-03-26 09:51:11 +00001363 # self.log.debug('Checking if file {} exists...'.format(filename))
quilesj26c78a42019-10-28 18:10:42 +01001364 if os.path.exists(filename):
1365 return True
1366 else:
beierlm32862bb2020-04-21 16:36:35 -04001367 msg = "File {} does not exist".format(filename)
quilesj26c78a42019-10-28 18:10:42 +01001368 if exception_if_not_exists:
tiernoe2bd3da2020-03-26 09:51:11 +00001369 # self.log.error(msg)
quilesja6748412019-12-04 07:51:26 +00001370 raise K8sException(msg)