blob: 9a0908f39341650f133f31006da179c2b6ad98a2 [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 """
tiernof9bdac22020-06-25 15:48:52 +000043 service_account = "osm"
lloretgalleg950f8452021-01-21 12:00:57 +000044 _STABLE_REPO_URL = "https://charts.helm.sh/stable"
quilesj26c78a42019-10-28 18:10:42 +010045
46 def __init__(
beierlmf52cb7c2020-04-21 16:36:35 -040047 self,
48 fs: object,
49 db: object,
50 kubectl_command: str = "/usr/bin/kubectl",
51 helm_command: str = "/usr/bin/helm",
52 log: object = None,
53 on_update_db=None,
lloretgalleg950f8452021-01-21 12:00:57 +000054 vca_config: dict = None,
quilesj26c78a42019-10-28 18:10:42 +010055 ):
56 """
57
58 :param fs: file system for kubernetes and helm configuration
59 :param db: database object to write current operation status
60 :param kubectl_command: path to kubectl executable
61 :param helm_command: path to helm executable
62 :param log: logger
63 :param on_update_db: callback called when k8s connector updates database
64 """
65
66 # parent class
beierlmf52cb7c2020-04-21 16:36:35 -040067 K8sConnector.__init__(self, db=db, log=log, on_update_db=on_update_db)
quilesj26c78a42019-10-28 18:10:42 +010068
beierlmf52cb7c2020-04-21 16:36:35 -040069 self.log.info("Initializing K8S Helm connector")
quilesj26c78a42019-10-28 18:10:42 +010070
71 # random numbers for release name generation
72 random.seed(time.time())
73
74 # the file system
75 self.fs = fs
76
77 # exception if kubectl is not installed
78 self.kubectl_command = kubectl_command
79 self._check_file_exists(filename=kubectl_command, exception_if_not_exists=True)
80
81 # exception if helm is not installed
82 self._helm_command = helm_command
83 self._check_file_exists(filename=helm_command, exception_if_not_exists=True)
84
lloretgalleg950f8452021-01-21 12:00:57 +000085 # obtain stable repo url from config or apply default
86 if not vca_config or not vca_config.get("stablerepourl"):
87 self._stable_repo_url = self._STABLE_REPO_URL
88 else:
89 self._stable_repo_url = vca_config.get("stablerepourl")
90
quilesj1be06302019-11-29 11:17:11 +000091 # initialize helm client-only
beierlmf52cb7c2020-04-21 16:36:35 -040092 self.log.debug("Initializing helm client-only...")
lloretgalleg950f8452021-01-21 12:00:57 +000093 command = "{} init --client-only --stable-repo-url {}".format(
94 self._helm_command, self._stable_repo_url)
quilesj1be06302019-11-29 11:17:11 +000095 try:
beierlmf52cb7c2020-04-21 16:36:35 -040096 asyncio.ensure_future(
97 self._local_async_exec(command=command, raise_exception_on_error=False)
98 )
quilesj1be06302019-11-29 11:17:11 +000099 # loop = asyncio.get_event_loop()
beierlmf52cb7c2020-04-21 16:36:35 -0400100 # loop.run_until_complete(self._local_async_exec(command=command,
101 # raise_exception_on_error=False))
quilesj1be06302019-11-29 11:17:11 +0000102 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400103 self.warning(
104 msg="helm init failed (it was already initialized): {}".format(e)
105 )
quilesj1be06302019-11-29 11:17:11 +0000106
beierlmf52cb7c2020-04-21 16:36:35 -0400107 self.log.info("K8S Helm connector initialized")
quilesj26c78a42019-10-28 18:10:42 +0100108
tiernof9bdac22020-06-25 15:48:52 +0000109 @staticmethod
110 def _get_namespace_cluster_id(cluster_uuid: str) -> (str, str):
111 """
112 Parses cluster_uuid stored at database that can be either 'namespace:cluster_id' or only
113 cluster_id for backward compatibility
114 """
115 namespace, _, cluster_id = cluster_uuid.rpartition(':')
116 return namespace, cluster_id
117
quilesj26c78a42019-10-28 18:10:42 +0100118 async def init_env(
beierlmf52cb7c2020-04-21 16:36:35 -0400119 self, k8s_creds: str, namespace: str = "kube-system", reuse_cluster_uuid=None
quilesj26c78a42019-10-28 18:10:42 +0100120 ) -> (str, bool):
garciadeblas54771fa2019-12-13 13:39:03 +0100121 """
122 It prepares a given K8s cluster environment to run Charts on both sides:
123 client (OSM)
124 server (Tiller)
125
beierlmf52cb7c2020-04-21 16:36:35 -0400126 :param k8s_creds: credentials to access a given K8s cluster, i.e. a valid
127 '.kube/config'
128 :param namespace: optional namespace to be used for helm. By default,
129 'kube-system' will be used
garciadeblas54771fa2019-12-13 13:39:03 +0100130 :param reuse_cluster_uuid: existing cluster uuid for reuse
beierlmf52cb7c2020-04-21 16:36:35 -0400131 :return: uuid of the K8s cluster and True if connector has installed some
132 software in the cluster
garciadeblas54771fa2019-12-13 13:39:03 +0100133 (on error, an exception will be raised)
134 """
quilesj26c78a42019-10-28 18:10:42 +0100135
tiernof9bdac22020-06-25 15:48:52 +0000136 if reuse_cluster_uuid:
137 namespace_, cluster_id = self._get_namespace_cluster_id(reuse_cluster_uuid)
138 namespace = namespace_ or namespace
139 else:
140 cluster_id = str(uuid4())
141 cluster_uuid = "{}:{}".format(namespace, cluster_id)
quilesj26c78a42019-10-28 18:10:42 +0100142
tiernof9bdac22020-06-25 15:48:52 +0000143 self.log.debug("Initializing K8S Cluster {}. namespace: {}".format(cluster_id, namespace))
quilesj26c78a42019-10-28 18:10:42 +0100144
145 # create config filename
beierlmf52cb7c2020-04-21 16:36:35 -0400146 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
tiernof9bdac22020-06-25 15:48:52 +0000147 cluster_name=cluster_id, create_if_not_exist=True
beierlmf52cb7c2020-04-21 16:36:35 -0400148 )
tierno119f7232020-04-21 13:22:26 +0000149 with open(config_filename, "w") as f:
150 f.write(k8s_creds)
quilesj26c78a42019-10-28 18:10:42 +0100151
152 # check if tiller pod is up in cluster
beierlmf52cb7c2020-04-21 16:36:35 -0400153 command = "{} --kubeconfig={} --namespace={} get deployments".format(
154 self.kubectl_command, config_filename, namespace
155 )
156 output, _rc = await self._local_async_exec(
157 command=command, raise_exception_on_error=True
158 )
quilesj26c78a42019-10-28 18:10:42 +0100159
tierno119f7232020-04-21 13:22:26 +0000160 output_table = self._output_to_table(output=output)
quilesj26c78a42019-10-28 18:10:42 +0100161
162 # find 'tiller' pod in all pods
163 already_initialized = False
164 try:
165 for row in output_table:
beierlmf52cb7c2020-04-21 16:36:35 -0400166 if row[0].startswith("tiller-deploy"):
quilesj26c78a42019-10-28 18:10:42 +0100167 already_initialized = True
168 break
beierlmf52cb7c2020-04-21 16:36:35 -0400169 except Exception:
quilesj26c78a42019-10-28 18:10:42 +0100170 pass
171
172 # helm init
173 n2vc_installed_sw = False
174 if not already_initialized:
beierlmf52cb7c2020-04-21 16:36:35 -0400175 self.log.info(
tiernof9bdac22020-06-25 15:48:52 +0000176 "Initializing helm in client and server: {}".format(cluster_id)
beierlmf52cb7c2020-04-21 16:36:35 -0400177 )
tiernof9bdac22020-06-25 15:48:52 +0000178 command = "{} --kubeconfig={} --namespace kube-system create serviceaccount {}".format(
179 self.kubectl_command, config_filename, self.service_account)
180 _, _rc = await self._local_async_exec(command=command, raise_exception_on_error=False)
181
182 command = ("{} --kubeconfig={} create clusterrolebinding osm-tiller-cluster-rule "
183 "--clusterrole=cluster-admin --serviceaccount=kube-system:{}"
184 ).format(self.kubectl_command, config_filename, self.service_account)
185 _, _rc = await self._local_async_exec(command=command, raise_exception_on_error=False)
186
187 command = ("{} --kubeconfig={} --tiller-namespace={} --home={} --service-account {} "
lloretgalleg950f8452021-01-21 12:00:57 +0000188 " --stable-repo-url {} init").format(self._helm_command,
189 config_filename,
190 namespace, helm_dir,
191 self.service_account,
192 self._stable_repo_url)
tiernof9bdac22020-06-25 15:48:52 +0000193 _, _rc = await self._local_async_exec(command=command, raise_exception_on_error=True)
quilesj26c78a42019-10-28 18:10:42 +0100194 n2vc_installed_sw = True
195 else:
196 # check client helm installation
beierlmf52cb7c2020-04-21 16:36:35 -0400197 check_file = helm_dir + "/repository/repositories.yaml"
tiernof9bdac22020-06-25 15:48:52 +0000198 if not self._check_file_exists(filename=check_file, exception_if_not_exists=False):
199 self.log.info("Initializing helm in client: {}".format(cluster_id))
beierlmf52cb7c2020-04-21 16:36:35 -0400200 command = (
201 "{} --kubeconfig={} --tiller-namespace={} "
lloretgalleg950f8452021-01-21 12:00:57 +0000202 "--home={} init --client-only --stable-repo-url {} "
203 ).format(self._helm_command, config_filename, namespace,
204 helm_dir, self._stable_repo_url)
beierlmf52cb7c2020-04-21 16:36:35 -0400205 output, _rc = await self._local_async_exec(
206 command=command, raise_exception_on_error=True
207 )
quilesj26c78a42019-10-28 18:10:42 +0100208 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400209 self.log.info("Helm client already initialized")
quilesj26c78a42019-10-28 18:10:42 +0100210
lloretgalleg950f8452021-01-21 12:00:57 +0000211 # remove old stable repo and add new one
212 cluster_uuid = "{}:{}".format(namespace, cluster_id)
213 repo_list = await self.repo_list(cluster_uuid)
214 for repo in repo_list:
215 if repo["Name"] == "stable" and repo["URL"] != self._stable_repo_url:
216 self.log.debug("Add new stable repo url: {}")
217 await self.repo_remove(cluster_uuid,
218 "stable")
219 await self.repo_add(cluster_uuid,
220 "stable",
221 self._stable_repo_url)
222 break
223
tiernof9bdac22020-06-25 15:48:52 +0000224 self.log.info("Cluster {} initialized".format(cluster_id))
quilesj26c78a42019-10-28 18:10:42 +0100225
226 return cluster_uuid, n2vc_installed_sw
227
228 async def repo_add(
beierlmf52cb7c2020-04-21 16:36:35 -0400229 self, cluster_uuid: str, name: str, url: str, repo_type: str = "chart"
quilesj26c78a42019-10-28 18:10:42 +0100230 ):
tiernof9bdac22020-06-25 15:48:52 +0000231 _, cluster_id = self._get_namespace_cluster_id(cluster_uuid)
232 self.log.debug("Cluster {}, adding {} repository {}. URL: {}".format(
233 cluster_id, repo_type, name, url))
quilesj26c78a42019-10-28 18:10:42 +0100234
235 # config filename
beierlmf52cb7c2020-04-21 16:36:35 -0400236 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
tiernof9bdac22020-06-25 15:48:52 +0000237 cluster_name=cluster_id, create_if_not_exist=True
beierlmf52cb7c2020-04-21 16:36:35 -0400238 )
quilesj26c78a42019-10-28 18:10:42 +0100239
240 # helm repo update
beierlmf52cb7c2020-04-21 16:36:35 -0400241 command = "{} --kubeconfig={} --home={} repo update".format(
242 self._helm_command, config_filename, helm_dir
243 )
244 self.log.debug("updating repo: {}".format(command))
quilesj26c78a42019-10-28 18:10:42 +0100245 await self._local_async_exec(command=command, raise_exception_on_error=False)
246
247 # helm repo add name url
beierlmf52cb7c2020-04-21 16:36:35 -0400248 command = "{} --kubeconfig={} --home={} repo add {} {}".format(
249 self._helm_command, config_filename, helm_dir, name, url
250 )
251 self.log.debug("adding repo: {}".format(command))
quilesj26c78a42019-10-28 18:10:42 +0100252 await self._local_async_exec(command=command, raise_exception_on_error=True)
253
beierlmf52cb7c2020-04-21 16:36:35 -0400254 async def repo_list(self, cluster_uuid: str) -> list:
quilesj26c78a42019-10-28 18:10:42 +0100255 """
256 Get the list of registered repositories
257
258 :return: list of registered repositories: [ (name, url) .... ]
259 """
260
tiernof9bdac22020-06-25 15:48:52 +0000261 _, cluster_id = self._get_namespace_cluster_id(cluster_uuid)
262 self.log.debug("list repositories for cluster {}".format(cluster_id))
quilesj26c78a42019-10-28 18:10:42 +0100263
264 # config filename
beierlmf52cb7c2020-04-21 16:36:35 -0400265 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
tiernof9bdac22020-06-25 15:48:52 +0000266 cluster_name=cluster_id, create_if_not_exist=True
beierlmf52cb7c2020-04-21 16:36:35 -0400267 )
quilesj26c78a42019-10-28 18:10:42 +0100268
beierlmf52cb7c2020-04-21 16:36:35 -0400269 command = "{} --kubeconfig={} --home={} repo list --output yaml".format(
270 self._helm_command, config_filename, helm_dir
271 )
quilesj26c78a42019-10-28 18:10:42 +0100272
beierlmf52cb7c2020-04-21 16:36:35 -0400273 output, _rc = await self._local_async_exec(
274 command=command, raise_exception_on_error=True
275 )
quilesj26c78a42019-10-28 18:10:42 +0100276 if output and len(output) > 0:
277 return yaml.load(output, Loader=yaml.SafeLoader)
278 else:
279 return []
280
beierlmf52cb7c2020-04-21 16:36:35 -0400281 async def repo_remove(self, cluster_uuid: str, name: str):
quilesj26c78a42019-10-28 18:10:42 +0100282 """
283 Remove a repository from OSM
284
tiernof9bdac22020-06-25 15:48:52 +0000285 :param cluster_uuid: the cluster or 'namespace:cluster'
quilesj26c78a42019-10-28 18:10:42 +0100286 :param name: repo name in OSM
287 :return: True if successful
288 """
289
tiernof9bdac22020-06-25 15:48:52 +0000290 _, cluster_id = self._get_namespace_cluster_id(cluster_uuid)
291 self.log.debug("list repositories for cluster {}".format(cluster_id))
quilesj26c78a42019-10-28 18:10:42 +0100292
293 # config filename
beierlmf52cb7c2020-04-21 16:36:35 -0400294 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
tiernof9bdac22020-06-25 15:48:52 +0000295 cluster_name=cluster_id, create_if_not_exist=True
beierlmf52cb7c2020-04-21 16:36:35 -0400296 )
quilesj26c78a42019-10-28 18:10:42 +0100297
beierlmf52cb7c2020-04-21 16:36:35 -0400298 command = "{} --kubeconfig={} --home={} repo remove {}".format(
299 self._helm_command, config_filename, helm_dir, name
300 )
quilesj26c78a42019-10-28 18:10:42 +0100301
302 await self._local_async_exec(command=command, raise_exception_on_error=True)
303
304 async def reset(
beierlmf52cb7c2020-04-21 16:36:35 -0400305 self, cluster_uuid: str, force: bool = False, uninstall_sw: bool = False
quilesj26c78a42019-10-28 18:10:42 +0100306 ) -> bool:
307
tiernof9bdac22020-06-25 15:48:52 +0000308 namespace, cluster_id = self._get_namespace_cluster_id(cluster_uuid)
tierno4d9facc2020-07-14 10:29:00 +0000309 self.log.debug("Resetting K8s environment. cluster uuid: {} uninstall={}"
310 .format(cluster_id, uninstall_sw))
quilesj26c78a42019-10-28 18:10:42 +0100311
312 # get kube and helm directories
beierlmf52cb7c2020-04-21 16:36:35 -0400313 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
tiernof9bdac22020-06-25 15:48:52 +0000314 cluster_name=cluster_id, create_if_not_exist=False
beierlmf52cb7c2020-04-21 16:36:35 -0400315 )
quilesj26c78a42019-10-28 18:10:42 +0100316
tierno4d9facc2020-07-14 10:29:00 +0000317 # uninstall releases if needed.
318 if uninstall_sw:
319 releases = await self.instances_list(cluster_uuid=cluster_uuid)
320 if len(releases) > 0:
321 if force:
322 for r in releases:
323 try:
324 kdu_instance = r.get("Name")
325 chart = r.get("Chart")
326 self.log.debug(
327 "Uninstalling {} -> {}".format(chart, kdu_instance)
328 )
329 await self.uninstall(
330 cluster_uuid=cluster_uuid, kdu_instance=kdu_instance
331 )
332 except Exception as e:
333 self.log.error(
334 "Error uninstalling release {}: {}".format(kdu_instance, e)
335 )
336 else:
337 msg = (
338 "Cluster uuid: {} has releases and not force. Leaving K8s helm environment"
339 ).format(cluster_id)
340 self.log.warn(msg)
341 uninstall_sw = False # Allow to remove k8s cluster without removing Tiller
quilesj26c78a42019-10-28 18:10:42 +0100342
343 if uninstall_sw:
344
tiernof9bdac22020-06-25 15:48:52 +0000345 self.log.debug("Uninstalling tiller from cluster {}".format(cluster_id))
quilesj26c78a42019-10-28 18:10:42 +0100346
tiernof9bdac22020-06-25 15:48:52 +0000347 if not namespace:
348 # find namespace for tiller pod
349 command = "{} --kubeconfig={} get deployments --all-namespaces".format(
350 self.kubectl_command, config_filename
beierlmf52cb7c2020-04-21 16:36:35 -0400351 )
tiernof9bdac22020-06-25 15:48:52 +0000352 output, _rc = await self._local_async_exec(
beierlmf52cb7c2020-04-21 16:36:35 -0400353 command=command, raise_exception_on_error=False
354 )
tiernof9bdac22020-06-25 15:48:52 +0000355 output_table = K8sHelmConnector._output_to_table(output=output)
356 namespace = None
357 for r in output_table:
358 try:
359 if "tiller-deploy" in r[1]:
360 namespace = r[0]
361 break
362 except Exception:
363 pass
364 else:
365 msg = "Tiller deployment not found in cluster {}".format(cluster_id)
366 self.log.error(msg)
quilesj26c78a42019-10-28 18:10:42 +0100367
tiernof9bdac22020-06-25 15:48:52 +0000368 self.log.debug("namespace for tiller: {}".format(namespace))
369
370 if namespace:
quilesj26c78a42019-10-28 18:10:42 +0100371 # uninstall tiller from cluster
beierlmf52cb7c2020-04-21 16:36:35 -0400372 self.log.debug(
tiernof9bdac22020-06-25 15:48:52 +0000373 "Uninstalling tiller from cluster {}".format(cluster_id)
beierlmf52cb7c2020-04-21 16:36:35 -0400374 )
375 command = "{} --kubeconfig={} --home={} reset".format(
376 self._helm_command, config_filename, helm_dir
377 )
378 self.log.debug("resetting: {}".format(command))
379 output, _rc = await self._local_async_exec(
380 command=command, raise_exception_on_error=True
381 )
tiernof9bdac22020-06-25 15:48:52 +0000382 # Delete clusterrolebinding and serviceaccount.
383 # Ignore if errors for backward compatibility
384 command = ("{} --kubeconfig={} delete clusterrolebinding.rbac.authorization.k8s."
385 "io/osm-tiller-cluster-rule").format(self.kubectl_command,
386 config_filename)
387 output, _rc = await self._local_async_exec(command=command,
388 raise_exception_on_error=False)
389 command = "{} --kubeconfig={} --namespace kube-system delete serviceaccount/{}".\
390 format(self.kubectl_command, config_filename, self.service_account)
391 output, _rc = await self._local_async_exec(command=command,
392 raise_exception_on_error=False)
393
quilesj26c78a42019-10-28 18:10:42 +0100394 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400395 self.log.debug("namespace not found")
quilesj26c78a42019-10-28 18:10:42 +0100396
397 # delete cluster directory
tiernof9bdac22020-06-25 15:48:52 +0000398 direct = self.fs.path + "/" + cluster_id
beierlmf52cb7c2020-04-21 16:36:35 -0400399 self.log.debug("Removing directory {}".format(direct))
400 shutil.rmtree(direct, ignore_errors=True)
quilesj26c78a42019-10-28 18:10:42 +0100401
402 return True
403
404 async def install(
beierlmf52cb7c2020-04-21 16:36:35 -0400405 self,
406 cluster_uuid: str,
407 kdu_model: str,
408 atomic: bool = True,
409 timeout: float = 300,
410 params: dict = None,
411 db_dict: dict = None,
412 kdu_name: str = None,
413 namespace: str = None,
quilesj26c78a42019-10-28 18:10:42 +0100414 ):
415
tiernof9bdac22020-06-25 15:48:52 +0000416 _, cluster_id = self._get_namespace_cluster_id(cluster_uuid)
417 self.log.debug("installing {} in cluster {}".format(kdu_model, cluster_id))
quilesj26c78a42019-10-28 18:10:42 +0100418
quilesj26c78a42019-10-28 18:10:42 +0100419 # config filename
beierlmf52cb7c2020-04-21 16:36:35 -0400420 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
tiernof9bdac22020-06-25 15:48:52 +0000421 cluster_name=cluster_id, create_if_not_exist=True
beierlmf52cb7c2020-04-21 16:36:35 -0400422 )
quilesj26c78a42019-10-28 18:10:42 +0100423
424 # params to str
quilesjcda5f412019-11-18 11:32:12 +0100425 # params_str = K8sHelmConnector._params_to_set_option(params)
beierlmf52cb7c2020-04-21 16:36:35 -0400426 params_str, file_to_delete = self._params_to_file_option(
tiernof9bdac22020-06-25 15:48:52 +0000427 cluster_id=cluster_id, params=params
beierlmf52cb7c2020-04-21 16:36:35 -0400428 )
quilesj26c78a42019-10-28 18:10:42 +0100429
beierlmf52cb7c2020-04-21 16:36:35 -0400430 timeout_str = ""
quilesj26c78a42019-10-28 18:10:42 +0100431 if timeout:
beierlmf52cb7c2020-04-21 16:36:35 -0400432 timeout_str = "--timeout {}".format(timeout)
quilesj26c78a42019-10-28 18:10:42 +0100433
434 # atomic
beierlmf52cb7c2020-04-21 16:36:35 -0400435 atomic_str = ""
quilesj26c78a42019-10-28 18:10:42 +0100436 if atomic:
beierlmf52cb7c2020-04-21 16:36:35 -0400437 atomic_str = "--atomic"
tierno53555f62020-04-07 11:08:16 +0000438 # namespace
beierlmf52cb7c2020-04-21 16:36:35 -0400439 namespace_str = ""
tierno53555f62020-04-07 11:08:16 +0000440 if namespace:
441 namespace_str = "--namespace {}".format(namespace)
quilesj26c78a42019-10-28 18:10:42 +0100442
443 # version
beierlmf52cb7c2020-04-21 16:36:35 -0400444 version_str = ""
445 if ":" in kdu_model:
446 parts = kdu_model.split(sep=":")
quilesj26c78a42019-10-28 18:10:42 +0100447 if len(parts) == 2:
beierlmf52cb7c2020-04-21 16:36:35 -0400448 version_str = "--version {}".format(parts[1])
quilesj26c78a42019-10-28 18:10:42 +0100449 kdu_model = parts[0]
450
quilesja6748412019-12-04 07:51:26 +0000451 # generate a name for the release. Then, check if already exists
quilesj26c78a42019-10-28 18:10:42 +0100452 kdu_instance = None
453 while kdu_instance is None:
454 kdu_instance = K8sHelmConnector._generate_release_name(kdu_model)
455 try:
456 result = await self._status_kdu(
tiernof9bdac22020-06-25 15:48:52 +0000457 cluster_id=cluster_id,
quilesj26c78a42019-10-28 18:10:42 +0100458 kdu_instance=kdu_instance,
beierlmf52cb7c2020-04-21 16:36:35 -0400459 show_error_log=False,
quilesj26c78a42019-10-28 18:10:42 +0100460 )
461 if result is not None:
462 # instance already exists: generate a new one
463 kdu_instance = None
tierno601697a2020-02-04 15:26:25 +0000464 except K8sException:
465 pass
quilesj26c78a42019-10-28 18:10:42 +0100466
467 # helm repo install
beierlmf52cb7c2020-04-21 16:36:35 -0400468 command = (
469 "{helm} install {atomic} --output yaml --kubeconfig={config} --home={dir} "
470 "{params} {timeout} --name={name} {ns} {model} {ver}".format(
471 helm=self._helm_command,
472 atomic=atomic_str,
473 config=config_filename,
474 dir=helm_dir,
475 params=params_str,
476 timeout=timeout_str,
477 name=kdu_instance,
478 ns=namespace_str,
479 model=kdu_model,
480 ver=version_str,
481 )
482 )
483 self.log.debug("installing: {}".format(command))
quilesj26c78a42019-10-28 18:10:42 +0100484
485 if atomic:
486 # exec helm in a task
487 exec_task = asyncio.ensure_future(
beierlmf52cb7c2020-04-21 16:36:35 -0400488 coro_or_future=self._local_async_exec(
489 command=command, raise_exception_on_error=False
490 )
quilesj26c78a42019-10-28 18:10:42 +0100491 )
lloretgalleg65ddf852020-02-20 12:01:17 +0100492
quilesj26c78a42019-10-28 18:10:42 +0100493 # write status in another task
494 status_task = asyncio.ensure_future(
495 coro_or_future=self._store_status(
tiernof9bdac22020-06-25 15:48:52 +0000496 cluster_id=cluster_id,
quilesj26c78a42019-10-28 18:10:42 +0100497 kdu_instance=kdu_instance,
498 db_dict=db_dict,
beierlmf52cb7c2020-04-21 16:36:35 -0400499 operation="install",
500 run_once=False,
quilesj26c78a42019-10-28 18:10:42 +0100501 )
502 )
503
504 # wait for execution task
505 await asyncio.wait([exec_task])
506
507 # cancel status task
508 status_task.cancel()
509
510 output, rc = exec_task.result()
511
512 else:
513
beierlmf52cb7c2020-04-21 16:36:35 -0400514 output, rc = await self._local_async_exec(
515 command=command, raise_exception_on_error=False
516 )
quilesj26c78a42019-10-28 18:10:42 +0100517
quilesjcda5f412019-11-18 11:32:12 +0100518 # remove temporal values yaml file
519 if file_to_delete:
520 os.remove(file_to_delete)
521
quilesj26c78a42019-10-28 18:10:42 +0100522 # write final status
523 await self._store_status(
tiernof9bdac22020-06-25 15:48:52 +0000524 cluster_id=cluster_id,
quilesj26c78a42019-10-28 18:10:42 +0100525 kdu_instance=kdu_instance,
526 db_dict=db_dict,
beierlmf52cb7c2020-04-21 16:36:35 -0400527 operation="install",
quilesj26c78a42019-10-28 18:10:42 +0100528 run_once=True,
beierlmf52cb7c2020-04-21 16:36:35 -0400529 check_every=0,
quilesj26c78a42019-10-28 18:10:42 +0100530 )
531
532 if rc != 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400533 msg = "Error executing command: {}\nOutput: {}".format(command, output)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100534 self.log.error(msg)
quilesja6748412019-12-04 07:51:26 +0000535 raise K8sException(msg)
quilesj26c78a42019-10-28 18:10:42 +0100536
beierlmf52cb7c2020-04-21 16:36:35 -0400537 self.log.debug("Returning kdu_instance {}".format(kdu_instance))
quilesj26c78a42019-10-28 18:10:42 +0100538 return kdu_instance
539
beierlmf52cb7c2020-04-21 16:36:35 -0400540 async def instances_list(self, cluster_uuid: str) -> list:
quilesj26c78a42019-10-28 18:10:42 +0100541 """
542 returns a list of deployed releases in a cluster
543
tiernof9bdac22020-06-25 15:48:52 +0000544 :param cluster_uuid: the 'cluster' or 'namespace:cluster'
quilesj26c78a42019-10-28 18:10:42 +0100545 :return:
546 """
547
tiernof9bdac22020-06-25 15:48:52 +0000548 _, cluster_id = self._get_namespace_cluster_id(cluster_uuid)
549 self.log.debug("list releases for cluster {}".format(cluster_id))
quilesj26c78a42019-10-28 18:10:42 +0100550
551 # config filename
beierlmf52cb7c2020-04-21 16:36:35 -0400552 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
tiernof9bdac22020-06-25 15:48:52 +0000553 cluster_name=cluster_id, create_if_not_exist=True
beierlmf52cb7c2020-04-21 16:36:35 -0400554 )
quilesj26c78a42019-10-28 18:10:42 +0100555
beierlmf52cb7c2020-04-21 16:36:35 -0400556 command = "{} --kubeconfig={} --home={} list --output yaml".format(
557 self._helm_command, config_filename, helm_dir
558 )
quilesj26c78a42019-10-28 18:10:42 +0100559
beierlmf52cb7c2020-04-21 16:36:35 -0400560 output, _rc = await self._local_async_exec(
561 command=command, raise_exception_on_error=True
562 )
quilesj26c78a42019-10-28 18:10:42 +0100563
564 if output and len(output) > 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400565 return yaml.load(output, Loader=yaml.SafeLoader).get("Releases")
quilesj26c78a42019-10-28 18:10:42 +0100566 else:
567 return []
568
569 async def upgrade(
beierlmf52cb7c2020-04-21 16:36:35 -0400570 self,
571 cluster_uuid: str,
572 kdu_instance: str,
573 kdu_model: str = None,
574 atomic: bool = True,
575 timeout: float = 300,
576 params: dict = None,
577 db_dict: dict = None,
quilesj26c78a42019-10-28 18:10:42 +0100578 ):
579
tiernof9bdac22020-06-25 15:48:52 +0000580 _, cluster_id = self._get_namespace_cluster_id(cluster_uuid)
581 self.log.debug("upgrading {} in cluster {}".format(kdu_model, cluster_id))
quilesj26c78a42019-10-28 18:10:42 +0100582
quilesj26c78a42019-10-28 18:10:42 +0100583 # config filename
beierlmf52cb7c2020-04-21 16:36:35 -0400584 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
tiernof9bdac22020-06-25 15:48:52 +0000585 cluster_name=cluster_id, create_if_not_exist=True
beierlmf52cb7c2020-04-21 16:36:35 -0400586 )
quilesj26c78a42019-10-28 18:10:42 +0100587
588 # params to str
quilesjcda5f412019-11-18 11:32:12 +0100589 # params_str = K8sHelmConnector._params_to_set_option(params)
beierlmf52cb7c2020-04-21 16:36:35 -0400590 params_str, file_to_delete = self._params_to_file_option(
tiernof9bdac22020-06-25 15:48:52 +0000591 cluster_id=cluster_id, params=params
beierlmf52cb7c2020-04-21 16:36:35 -0400592 )
quilesj26c78a42019-10-28 18:10:42 +0100593
beierlmf52cb7c2020-04-21 16:36:35 -0400594 timeout_str = ""
quilesj26c78a42019-10-28 18:10:42 +0100595 if timeout:
beierlmf52cb7c2020-04-21 16:36:35 -0400596 timeout_str = "--timeout {}".format(timeout)
quilesj26c78a42019-10-28 18:10:42 +0100597
598 # atomic
beierlmf52cb7c2020-04-21 16:36:35 -0400599 atomic_str = ""
quilesj26c78a42019-10-28 18:10:42 +0100600 if atomic:
beierlmf52cb7c2020-04-21 16:36:35 -0400601 atomic_str = "--atomic"
quilesj26c78a42019-10-28 18:10:42 +0100602
603 # version
beierlmf52cb7c2020-04-21 16:36:35 -0400604 version_str = ""
605 if kdu_model and ":" in kdu_model:
606 parts = kdu_model.split(sep=":")
quilesj26c78a42019-10-28 18:10:42 +0100607 if len(parts) == 2:
beierlmf52cb7c2020-04-21 16:36:35 -0400608 version_str = "--version {}".format(parts[1])
quilesj26c78a42019-10-28 18:10:42 +0100609 kdu_model = parts[0]
610
611 # helm repo upgrade
beierlmf52cb7c2020-04-21 16:36:35 -0400612 command = (
613 "{} upgrade {} --output yaml --kubeconfig={} " "--home={} {} {} {} {} {}"
614 ).format(
615 self._helm_command,
616 atomic_str,
617 config_filename,
618 helm_dir,
619 params_str,
620 timeout_str,
621 kdu_instance,
622 kdu_model,
623 version_str,
624 )
625 self.log.debug("upgrading: {}".format(command))
quilesj26c78a42019-10-28 18:10:42 +0100626
627 if atomic:
628
629 # exec helm in a task
630 exec_task = asyncio.ensure_future(
beierlmf52cb7c2020-04-21 16:36:35 -0400631 coro_or_future=self._local_async_exec(
632 command=command, raise_exception_on_error=False
633 )
quilesj26c78a42019-10-28 18:10:42 +0100634 )
635 # write status in another task
636 status_task = asyncio.ensure_future(
637 coro_or_future=self._store_status(
tiernof9bdac22020-06-25 15:48:52 +0000638 cluster_id=cluster_id,
quilesj26c78a42019-10-28 18:10:42 +0100639 kdu_instance=kdu_instance,
640 db_dict=db_dict,
beierlmf52cb7c2020-04-21 16:36:35 -0400641 operation="upgrade",
642 run_once=False,
quilesj26c78a42019-10-28 18:10:42 +0100643 )
644 )
645
646 # wait for execution task
quilesj1be06302019-11-29 11:17:11 +0000647 await asyncio.wait([exec_task])
quilesj26c78a42019-10-28 18:10:42 +0100648
649 # cancel status task
650 status_task.cancel()
651 output, rc = exec_task.result()
652
653 else:
654
beierlmf52cb7c2020-04-21 16:36:35 -0400655 output, rc = await self._local_async_exec(
656 command=command, raise_exception_on_error=False
657 )
quilesj26c78a42019-10-28 18:10:42 +0100658
quilesjcda5f412019-11-18 11:32:12 +0100659 # remove temporal values yaml file
660 if file_to_delete:
661 os.remove(file_to_delete)
662
quilesj26c78a42019-10-28 18:10:42 +0100663 # write final status
664 await self._store_status(
tiernof9bdac22020-06-25 15:48:52 +0000665 cluster_id=cluster_id,
quilesj26c78a42019-10-28 18:10:42 +0100666 kdu_instance=kdu_instance,
667 db_dict=db_dict,
beierlmf52cb7c2020-04-21 16:36:35 -0400668 operation="upgrade",
quilesj26c78a42019-10-28 18:10:42 +0100669 run_once=True,
beierlmf52cb7c2020-04-21 16:36:35 -0400670 check_every=0,
quilesj26c78a42019-10-28 18:10:42 +0100671 )
672
673 if rc != 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400674 msg = "Error executing command: {}\nOutput: {}".format(command, output)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100675 self.log.error(msg)
quilesja6748412019-12-04 07:51:26 +0000676 raise K8sException(msg)
quilesj26c78a42019-10-28 18:10:42 +0100677
678 # return new revision number
beierlmf52cb7c2020-04-21 16:36:35 -0400679 instance = await self.get_instance_info(
680 cluster_uuid=cluster_uuid, kdu_instance=kdu_instance
681 )
quilesj26c78a42019-10-28 18:10:42 +0100682 if instance:
beierlmf52cb7c2020-04-21 16:36:35 -0400683 revision = int(instance.get("Revision"))
684 self.log.debug("New revision: {}".format(revision))
quilesj26c78a42019-10-28 18:10:42 +0100685 return revision
686 else:
687 return 0
688
689 async def rollback(
beierlmf52cb7c2020-04-21 16:36:35 -0400690 self, cluster_uuid: str, kdu_instance: str, revision=0, db_dict: dict = None
quilesj26c78a42019-10-28 18:10:42 +0100691 ):
692
tiernof9bdac22020-06-25 15:48:52 +0000693 _, cluster_id = self._get_namespace_cluster_id(cluster_uuid)
beierlmf52cb7c2020-04-21 16:36:35 -0400694 self.log.debug(
695 "rollback kdu_instance {} to revision {} from cluster {}".format(
tiernof9bdac22020-06-25 15:48:52 +0000696 kdu_instance, revision, cluster_id
beierlmf52cb7c2020-04-21 16:36:35 -0400697 )
698 )
quilesj26c78a42019-10-28 18:10:42 +0100699
700 # config filename
beierlmf52cb7c2020-04-21 16:36:35 -0400701 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
tiernof9bdac22020-06-25 15:48:52 +0000702 cluster_name=cluster_id, create_if_not_exist=True
beierlmf52cb7c2020-04-21 16:36:35 -0400703 )
quilesj26c78a42019-10-28 18:10:42 +0100704
beierlmf52cb7c2020-04-21 16:36:35 -0400705 command = "{} rollback --kubeconfig={} --home={} {} {} --wait".format(
706 self._helm_command, config_filename, helm_dir, kdu_instance, revision
707 )
quilesj26c78a42019-10-28 18:10:42 +0100708
709 # exec helm in a task
710 exec_task = asyncio.ensure_future(
beierlmf52cb7c2020-04-21 16:36:35 -0400711 coro_or_future=self._local_async_exec(
712 command=command, raise_exception_on_error=False
713 )
quilesj26c78a42019-10-28 18:10:42 +0100714 )
715 # write status in another task
716 status_task = asyncio.ensure_future(
717 coro_or_future=self._store_status(
tiernof9bdac22020-06-25 15:48:52 +0000718 cluster_id=cluster_id,
quilesj26c78a42019-10-28 18:10:42 +0100719 kdu_instance=kdu_instance,
720 db_dict=db_dict,
beierlmf52cb7c2020-04-21 16:36:35 -0400721 operation="rollback",
722 run_once=False,
quilesj26c78a42019-10-28 18:10:42 +0100723 )
724 )
725
726 # wait for execution task
727 await asyncio.wait([exec_task])
728
729 # cancel status task
730 status_task.cancel()
731
732 output, rc = exec_task.result()
733
734 # write final status
735 await self._store_status(
tiernof9bdac22020-06-25 15:48:52 +0000736 cluster_id=cluster_id,
quilesj26c78a42019-10-28 18:10:42 +0100737 kdu_instance=kdu_instance,
738 db_dict=db_dict,
beierlmf52cb7c2020-04-21 16:36:35 -0400739 operation="rollback",
quilesj26c78a42019-10-28 18:10:42 +0100740 run_once=True,
beierlmf52cb7c2020-04-21 16:36:35 -0400741 check_every=0,
quilesj26c78a42019-10-28 18:10:42 +0100742 )
743
744 if rc != 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400745 msg = "Error executing command: {}\nOutput: {}".format(command, output)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +0100746 self.log.error(msg)
quilesja6748412019-12-04 07:51:26 +0000747 raise K8sException(msg)
quilesj26c78a42019-10-28 18:10:42 +0100748
749 # return new revision number
beierlmf52cb7c2020-04-21 16:36:35 -0400750 instance = await self.get_instance_info(
751 cluster_uuid=cluster_uuid, kdu_instance=kdu_instance
752 )
quilesj26c78a42019-10-28 18:10:42 +0100753 if instance:
beierlmf52cb7c2020-04-21 16:36:35 -0400754 revision = int(instance.get("Revision"))
755 self.log.debug("New revision: {}".format(revision))
quilesj26c78a42019-10-28 18:10:42 +0100756 return revision
757 else:
758 return 0
759
beierlmf52cb7c2020-04-21 16:36:35 -0400760 async def uninstall(self, cluster_uuid: str, kdu_instance: str):
quilesj26c78a42019-10-28 18:10:42 +0100761 """
beierlmf52cb7c2020-04-21 16:36:35 -0400762 Removes an existing KDU instance. It would implicitly use the `delete` call
763 (this call would happen after all _terminate-config-primitive_ of the VNF
764 are invoked).
quilesj26c78a42019-10-28 18:10:42 +0100765
tiernof9bdac22020-06-25 15:48:52 +0000766 :param cluster_uuid: UUID of a K8s cluster known by OSM, or namespace:cluster_id
quilesj26c78a42019-10-28 18:10:42 +0100767 :param kdu_instance: unique name for the KDU instance to be deleted
768 :return: True if successful
769 """
770
tiernof9bdac22020-06-25 15:48:52 +0000771 _, cluster_id = self._get_namespace_cluster_id(cluster_uuid)
beierlmf52cb7c2020-04-21 16:36:35 -0400772 self.log.debug(
773 "uninstall kdu_instance {} from cluster {}".format(
tiernof9bdac22020-06-25 15:48:52 +0000774 kdu_instance, cluster_id
beierlmf52cb7c2020-04-21 16:36:35 -0400775 )
776 )
quilesj26c78a42019-10-28 18:10:42 +0100777
778 # config filename
beierlmf52cb7c2020-04-21 16:36:35 -0400779 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
tiernof9bdac22020-06-25 15:48:52 +0000780 cluster_name=cluster_id, create_if_not_exist=True
beierlmf52cb7c2020-04-21 16:36:35 -0400781 )
quilesj26c78a42019-10-28 18:10:42 +0100782
beierlmf52cb7c2020-04-21 16:36:35 -0400783 command = "{} --kubeconfig={} --home={} delete --purge {}".format(
784 self._helm_command, config_filename, helm_dir, kdu_instance
785 )
quilesj26c78a42019-10-28 18:10:42 +0100786
beierlmf52cb7c2020-04-21 16:36:35 -0400787 output, _rc = await self._local_async_exec(
788 command=command, raise_exception_on_error=True
789 )
quilesj26c78a42019-10-28 18:10:42 +0100790
791 return self._output_to_table(output)
792
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200793 async def exec_primitive(
794 self,
795 cluster_uuid: str = None,
796 kdu_instance: str = None,
797 primitive_name: str = None,
798 timeout: float = 300,
799 params: dict = None,
800 db_dict: dict = None,
801 ) -> str:
802 """Exec primitive (Juju action)
803
tiernof9bdac22020-06-25 15:48:52 +0000804 :param cluster_uuid str: The UUID of the cluster or namespace:cluster
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200805 :param kdu_instance str: The unique name of the KDU instance
806 :param primitive_name: Name of action that will be executed
807 :param timeout: Timeout for action execution
808 :param params: Dictionary of all the parameters needed for the action
809 :db_dict: Dictionary for any additional data
810
811 :return: Returns the output of the action
812 """
beierlmf52cb7c2020-04-21 16:36:35 -0400813 raise K8sException(
814 "KDUs deployed with Helm don't support actions "
815 "different from rollback, upgrade and status"
816 )
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200817
beierlmf52cb7c2020-04-21 16:36:35 -0400818 async def inspect_kdu(self, kdu_model: str, repo_url: str = None) -> str:
quilesj26c78a42019-10-28 18:10:42 +0100819
beierlmf52cb7c2020-04-21 16:36:35 -0400820 self.log.debug(
821 "inspect kdu_model {} from (optional) repo: {}".format(kdu_model, repo_url)
822 )
quilesj26c78a42019-10-28 18:10:42 +0100823
beierlmf52cb7c2020-04-21 16:36:35 -0400824 return await self._exec_inspect_comand(
825 inspect_command="", kdu_model=kdu_model, repo_url=repo_url
826 )
quilesj26c78a42019-10-28 18:10:42 +0100827
beierlmf52cb7c2020-04-21 16:36:35 -0400828 async def values_kdu(self, kdu_model: str, repo_url: str = None) -> str:
quilesj26c78a42019-10-28 18:10:42 +0100829
beierlmf52cb7c2020-04-21 16:36:35 -0400830 self.log.debug(
831 "inspect kdu_model values {} from (optional) repo: {}".format(
832 kdu_model, repo_url
833 )
834 )
quilesj1be06302019-11-29 11:17:11 +0000835
beierlmf52cb7c2020-04-21 16:36:35 -0400836 return await self._exec_inspect_comand(
837 inspect_command="values", kdu_model=kdu_model, repo_url=repo_url
838 )
quilesj26c78a42019-10-28 18:10:42 +0100839
beierlmf52cb7c2020-04-21 16:36:35 -0400840 async def help_kdu(self, kdu_model: str, repo_url: str = None) -> str:
quilesj26c78a42019-10-28 18:10:42 +0100841
beierlmf52cb7c2020-04-21 16:36:35 -0400842 self.log.debug(
843 "inspect kdu_model {} readme.md from repo: {}".format(kdu_model, repo_url)
844 )
quilesj26c78a42019-10-28 18:10:42 +0100845
beierlmf52cb7c2020-04-21 16:36:35 -0400846 return await self._exec_inspect_comand(
847 inspect_command="readme", kdu_model=kdu_model, repo_url=repo_url
848 )
quilesj26c78a42019-10-28 18:10:42 +0100849
beierlmf52cb7c2020-04-21 16:36:35 -0400850 async def status_kdu(self, cluster_uuid: str, kdu_instance: str) -> str:
quilesj26c78a42019-10-28 18:10:42 +0100851
quilesj1be06302019-11-29 11:17:11 +0000852 # call internal function
tiernof9bdac22020-06-25 15:48:52 +0000853 _, cluster_id = self._get_namespace_cluster_id(cluster_uuid)
quilesj1be06302019-11-29 11:17:11 +0000854 return await self._status_kdu(
tiernof9bdac22020-06-25 15:48:52 +0000855 cluster_id=cluster_id,
quilesj1be06302019-11-29 11:17:11 +0000856 kdu_instance=kdu_instance,
857 show_error_log=True,
beierlmf52cb7c2020-04-21 16:36:35 -0400858 return_text=True,
quilesj1be06302019-11-29 11:17:11 +0000859 )
quilesj26c78a42019-10-28 18:10:42 +0100860
lloretgallegb8ba1af2020-06-29 14:18:30 +0000861 async def get_services(self,
862 cluster_uuid: str,
863 kdu_instance: str,
864 namespace: str) -> list:
865
tierno31dd6752020-07-17 11:47:32 +0000866 _, cluster_id = self._get_namespace_cluster_id(cluster_uuid)
lloretgallegb8ba1af2020-06-29 14:18:30 +0000867 self.log.debug(
868 "get_services: cluster_uuid: {}, kdu_instance: {}".format(
869 cluster_uuid, kdu_instance
870 )
871 )
872
873 status = await self._status_kdu(
tierno31dd6752020-07-17 11:47:32 +0000874 cluster_id, kdu_instance, return_text=False
lloretgallegb8ba1af2020-06-29 14:18:30 +0000875 )
876
877 service_names = self._parse_helm_status_service_info(status)
878 service_list = []
879 for service in service_names:
880 service = await self.get_service(cluster_uuid, service, namespace)
881 service_list.append(service)
882
883 return service_list
884
885 async def get_service(self,
886 cluster_uuid: str,
887 service_name: str,
888 namespace: str) -> object:
889
890 self.log.debug(
891 "get service, service_name: {}, namespace: {}, cluster_uuid: {}".format(
892 service_name, namespace, cluster_uuid)
893 )
894
895 # get paths
tierno31dd6752020-07-17 11:47:32 +0000896 _, cluster_id = self._get_namespace_cluster_id(cluster_uuid)
lloretgallegb8ba1af2020-06-29 14:18:30 +0000897 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
tierno31dd6752020-07-17 11:47:32 +0000898 cluster_name=cluster_id, create_if_not_exist=True
lloretgallegb8ba1af2020-06-29 14:18:30 +0000899 )
900
901 command = "{} --kubeconfig={} --namespace={} get service {} -o=yaml".format(
902 self.kubectl_command, config_filename, namespace, service_name
903 )
904
905 output, _rc = await self._local_async_exec(
906 command=command, raise_exception_on_error=True
907 )
908
909 data = yaml.load(output, Loader=yaml.SafeLoader)
910
911 service = {
912 "name": service_name,
913 "type": self._get_deep(data, ("spec", "type")),
914 "ports": self._get_deep(data, ("spec", "ports")),
915 "cluster_ip": self._get_deep(data, ("spec", "clusterIP"))
916 }
917 if service["type"] == "LoadBalancer":
918 ip_map_list = self._get_deep(data, ("status", "loadBalancer", "ingress"))
919 ip_list = [elem["ip"] for elem in ip_map_list]
920 service["external_ip"] = ip_list
921
922 return service
923
lloretgalleg65ddf852020-02-20 12:01:17 +0100924 async def synchronize_repos(self, cluster_uuid: str):
925
tiernof9bdac22020-06-25 15:48:52 +0000926 _, cluster_id = self._get_namespace_cluster_id(cluster_uuid)
tierno0b68fcb2020-08-14 10:05:43 +0000927 self.log.debug("syncronize repos for cluster helm-id: {}".format(cluster_id))
lloretgalleg65ddf852020-02-20 12:01:17 +0100928 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400929 update_repos_timeout = (
930 300 # max timeout to sync a single repos, more than this is too much
931 )
932 db_k8scluster = self.db.get_one(
933 "k8sclusters", {"_admin.helm-chart.id": cluster_uuid}
934 )
lloretgalleg65ddf852020-02-20 12:01:17 +0100935 if db_k8scluster:
beierlmf52cb7c2020-04-21 16:36:35 -0400936 nbi_repo_list = (
937 db_k8scluster.get("_admin").get("helm_chart_repos") or []
938 )
939 cluster_repo_dict = (
940 db_k8scluster.get("_admin").get("helm_charts_added") or {}
941 )
lloretgalleg65ddf852020-02-20 12:01:17 +0100942 # elements that must be deleted
943 deleted_repo_list = []
944 added_repo_dict = {}
tierno0b68fcb2020-08-14 10:05:43 +0000945 # self.log.debug("helm_chart_repos: {}".format(nbi_repo_list))
946 # self.log.debug("helm_charts_added: {}".format(cluster_repo_dict))
lloretgalleg65ddf852020-02-20 12:01:17 +0100947
948 # obtain repos to add: registered by nbi but not added
beierlmf52cb7c2020-04-21 16:36:35 -0400949 repos_to_add = [
950 repo for repo in nbi_repo_list if not cluster_repo_dict.get(repo)
951 ]
lloretgalleg65ddf852020-02-20 12:01:17 +0100952
953 # obtain repos to delete: added by cluster but not in nbi list
beierlmf52cb7c2020-04-21 16:36:35 -0400954 repos_to_delete = [
955 repo
956 for repo in cluster_repo_dict.keys()
957 if repo not in nbi_repo_list
958 ]
lloretgalleg65ddf852020-02-20 12:01:17 +0100959
beierlmf52cb7c2020-04-21 16:36:35 -0400960 # delete repos: must delete first then add because there may be
961 # different repos with same name but
lloretgalleg65ddf852020-02-20 12:01:17 +0100962 # different id and url
tierno0b68fcb2020-08-14 10:05:43 +0000963 if repos_to_delete:
964 self.log.debug("repos to delete: {}".format(repos_to_delete))
lloretgalleg65ddf852020-02-20 12:01:17 +0100965 for repo_id in repos_to_delete:
966 # try to delete repos
967 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400968 repo_delete_task = asyncio.ensure_future(
969 self.repo_remove(
970 cluster_uuid=cluster_uuid,
971 name=cluster_repo_dict[repo_id],
972 )
973 )
lloretgalleg65ddf852020-02-20 12:01:17 +0100974 await asyncio.wait_for(repo_delete_task, update_repos_timeout)
975 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -0400976 self.warning(
977 "Error deleting repo, id: {}, name: {}, err_msg: {}".format(
978 repo_id, cluster_repo_dict[repo_id], str(e)
979 )
980 )
981 # always add to the list of to_delete if there is an error
982 # because if is not there
983 # deleting raises error
lloretgalleg65ddf852020-02-20 12:01:17 +0100984 deleted_repo_list.append(repo_id)
985
986 # add repos
tierno0b68fcb2020-08-14 10:05:43 +0000987 if repos_to_add:
988 self.log.debug("repos to add: {}".format(repos_to_add))
lloretgalleg65ddf852020-02-20 12:01:17 +0100989 for repo_id in repos_to_add:
990 # obtain the repo data from the db
beierlmf52cb7c2020-04-21 16:36:35 -0400991 # if there is an error getting the repo in the database we will
992 # ignore this repo and continue
993 # because there is a possible race condition where the repo has
994 # been deleted while processing
lloretgalleg65ddf852020-02-20 12:01:17 +0100995 db_repo = self.db.get_one("k8srepos", {"_id": repo_id})
beierlmf52cb7c2020-04-21 16:36:35 -0400996 self.log.debug(
997 "obtained repo: id, {}, name: {}, url: {}".format(
998 repo_id, db_repo["name"], db_repo["url"]
999 )
1000 )
lloretgalleg65ddf852020-02-20 12:01:17 +01001001 try:
beierlmf52cb7c2020-04-21 16:36:35 -04001002 repo_add_task = asyncio.ensure_future(
1003 self.repo_add(
1004 cluster_uuid=cluster_uuid,
1005 name=db_repo["name"],
1006 url=db_repo["url"],
1007 repo_type="chart",
1008 )
1009 )
lloretgalleg65ddf852020-02-20 12:01:17 +01001010 await asyncio.wait_for(repo_add_task, update_repos_timeout)
1011 added_repo_dict[repo_id] = db_repo["name"]
beierlmf52cb7c2020-04-21 16:36:35 -04001012 self.log.debug(
1013 "added repo: id, {}, name: {}".format(
1014 repo_id, db_repo["name"]
1015 )
1016 )
lloretgalleg65ddf852020-02-20 12:01:17 +01001017 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -04001018 # deal with error adding repo, adding a repo that already
1019 # exists does not raise any error
1020 # will not raise error because a wrong repos added by
1021 # anyone could prevent instantiating any ns
1022 self.log.error(
1023 "Error adding repo id: {}, err_msg: {} ".format(
1024 repo_id, repr(e)
1025 )
1026 )
lloretgalleg65ddf852020-02-20 12:01:17 +01001027
1028 return deleted_repo_list, added_repo_dict
1029
beierlmf52cb7c2020-04-21 16:36:35 -04001030 else: # else db_k8scluster does not exist
1031 raise K8sException(
1032 "k8cluster with helm-id : {} not found".format(cluster_uuid)
1033 )
lloretgalleg65ddf852020-02-20 12:01:17 +01001034
1035 except Exception as e:
Dominik Fleischmannf9bed352020-02-27 10:04:34 +01001036 self.log.error("Error synchronizing repos: {}".format(str(e)))
lloretgalleg65ddf852020-02-20 12:01:17 +01001037 raise K8sException("Error synchronizing repos")
1038
quilesj26c78a42019-10-28 18:10:42 +01001039 """
beierlmf52cb7c2020-04-21 16:36:35 -04001040 ####################################################################################
1041 ################################### P R I V A T E ##################################
1042 ####################################################################################
quilesj26c78a42019-10-28 18:10:42 +01001043 """
1044
quilesj1be06302019-11-29 11:17:11 +00001045 async def _exec_inspect_comand(
beierlmf52cb7c2020-04-21 16:36:35 -04001046 self, inspect_command: str, kdu_model: str, repo_url: str = None
quilesj1be06302019-11-29 11:17:11 +00001047 ):
1048
beierlmf52cb7c2020-04-21 16:36:35 -04001049 repo_str = ""
quilesj1be06302019-11-29 11:17:11 +00001050 if repo_url:
beierlmf52cb7c2020-04-21 16:36:35 -04001051 repo_str = " --repo {}".format(repo_url)
1052 idx = kdu_model.find("/")
quilesj1be06302019-11-29 11:17:11 +00001053 if idx >= 0:
1054 idx += 1
1055 kdu_model = kdu_model[idx:]
1056
beierlmf52cb7c2020-04-21 16:36:35 -04001057 inspect_command = "{} inspect {} {}{}".format(
1058 self._helm_command, inspect_command, kdu_model, repo_str
1059 )
1060 output, _rc = await self._local_async_exec(
1061 command=inspect_command, encode_utf8=True
1062 )
quilesj1be06302019-11-29 11:17:11 +00001063
1064 return output
1065
quilesj26c78a42019-10-28 18:10:42 +01001066 async def _status_kdu(
beierlmf52cb7c2020-04-21 16:36:35 -04001067 self,
tiernof9bdac22020-06-25 15:48:52 +00001068 cluster_id: str,
beierlmf52cb7c2020-04-21 16:36:35 -04001069 kdu_instance: str,
1070 show_error_log: bool = False,
1071 return_text: bool = False,
quilesj26c78a42019-10-28 18:10:42 +01001072 ):
1073
beierlmf52cb7c2020-04-21 16:36:35 -04001074 self.log.debug("status of kdu_instance {}".format(kdu_instance))
quilesj26c78a42019-10-28 18:10:42 +01001075
1076 # config filename
beierlmf52cb7c2020-04-21 16:36:35 -04001077 _kube_dir, helm_dir, config_filename, _cluster_dir = self._get_paths(
tiernof9bdac22020-06-25 15:48:52 +00001078 cluster_name=cluster_id, create_if_not_exist=True
beierlmf52cb7c2020-04-21 16:36:35 -04001079 )
quilesj26c78a42019-10-28 18:10:42 +01001080
beierlmf52cb7c2020-04-21 16:36:35 -04001081 command = "{} --kubeconfig={} --home={} status {} --output yaml".format(
1082 self._helm_command, config_filename, helm_dir, kdu_instance
1083 )
quilesj26c78a42019-10-28 18:10:42 +01001084
1085 output, rc = await self._local_async_exec(
1086 command=command,
1087 raise_exception_on_error=True,
beierlmf52cb7c2020-04-21 16:36:35 -04001088 show_error_log=show_error_log,
quilesj26c78a42019-10-28 18:10:42 +01001089 )
1090
quilesj1be06302019-11-29 11:17:11 +00001091 if return_text:
1092 return str(output)
1093
quilesj26c78a42019-10-28 18:10:42 +01001094 if rc != 0:
1095 return None
1096
1097 data = yaml.load(output, Loader=yaml.SafeLoader)
1098
1099 # remove field 'notes'
1100 try:
beierlmf52cb7c2020-04-21 16:36:35 -04001101 del data.get("info").get("status")["notes"]
quilesj26c78a42019-10-28 18:10:42 +01001102 except KeyError:
1103 pass
1104
1105 # parse field 'resources'
1106 try:
beierlmf52cb7c2020-04-21 16:36:35 -04001107 resources = str(data.get("info").get("status").get("resources"))
quilesj26c78a42019-10-28 18:10:42 +01001108 resource_table = self._output_to_table(resources)
beierlmf52cb7c2020-04-21 16:36:35 -04001109 data.get("info").get("status")["resources"] = resource_table
1110 except Exception:
quilesj26c78a42019-10-28 18:10:42 +01001111 pass
1112
1113 return data
1114
beierlmf52cb7c2020-04-21 16:36:35 -04001115 async def get_instance_info(self, cluster_uuid: str, kdu_instance: str):
quilesj26c78a42019-10-28 18:10:42 +01001116 instances = await self.instances_list(cluster_uuid=cluster_uuid)
1117 for instance in instances:
beierlmf52cb7c2020-04-21 16:36:35 -04001118 if instance.get("Name") == kdu_instance:
quilesj26c78a42019-10-28 18:10:42 +01001119 return instance
beierlmf52cb7c2020-04-21 16:36:35 -04001120 self.log.debug("Instance {} not found".format(kdu_instance))
quilesj26c78a42019-10-28 18:10:42 +01001121 return None
1122
1123 @staticmethod
beierlmf52cb7c2020-04-21 16:36:35 -04001124 def _generate_release_name(chart_name: str):
quilesjbc355a12020-01-23 09:28:26 +00001125 # check embeded chart (file or dir)
beierlmf52cb7c2020-04-21 16:36:35 -04001126 if chart_name.startswith("/"):
quilesjbc355a12020-01-23 09:28:26 +00001127 # extract file or directory name
beierlmf52cb7c2020-04-21 16:36:35 -04001128 chart_name = chart_name[chart_name.rfind("/") + 1 :]
quilesjbc355a12020-01-23 09:28:26 +00001129 # check URL
beierlmf52cb7c2020-04-21 16:36:35 -04001130 elif "://" in chart_name:
quilesjbc355a12020-01-23 09:28:26 +00001131 # extract last portion of URL
beierlmf52cb7c2020-04-21 16:36:35 -04001132 chart_name = chart_name[chart_name.rfind("/") + 1 :]
quilesjbc355a12020-01-23 09:28:26 +00001133
beierlmf52cb7c2020-04-21 16:36:35 -04001134 name = ""
quilesj26c78a42019-10-28 18:10:42 +01001135 for c in chart_name:
1136 if c.isalpha() or c.isnumeric():
1137 name += c
1138 else:
beierlmf52cb7c2020-04-21 16:36:35 -04001139 name += "-"
quilesj26c78a42019-10-28 18:10:42 +01001140 if len(name) > 35:
1141 name = name[0:35]
1142
1143 # if does not start with alpha character, prefix 'a'
1144 if not name[0].isalpha():
beierlmf52cb7c2020-04-21 16:36:35 -04001145 name = "a" + name
quilesj26c78a42019-10-28 18:10:42 +01001146
beierlmf52cb7c2020-04-21 16:36:35 -04001147 name += "-"
quilesj26c78a42019-10-28 18:10:42 +01001148
1149 def get_random_number():
1150 r = random.randrange(start=1, stop=99999999)
1151 s = str(r)
beierlmf52cb7c2020-04-21 16:36:35 -04001152 s = s.rjust(10, "0")
quilesj26c78a42019-10-28 18:10:42 +01001153 return s
1154
1155 name = name + get_random_number()
1156 return name.lower()
1157
1158 async def _store_status(
beierlmf52cb7c2020-04-21 16:36:35 -04001159 self,
tiernof9bdac22020-06-25 15:48:52 +00001160 cluster_id: str,
beierlmf52cb7c2020-04-21 16:36:35 -04001161 operation: str,
1162 kdu_instance: str,
1163 check_every: float = 10,
1164 db_dict: dict = None,
1165 run_once: bool = False,
quilesj26c78a42019-10-28 18:10:42 +01001166 ):
tierno0b68fcb2020-08-14 10:05:43 +00001167 previous_exception = None
quilesj26c78a42019-10-28 18:10:42 +01001168 while True:
1169 try:
1170 await asyncio.sleep(check_every)
tierno119f7232020-04-21 13:22:26 +00001171 detailed_status = await self._status_kdu(
tiernof9bdac22020-06-25 15:48:52 +00001172 cluster_id=cluster_id, kdu_instance=kdu_instance,
tierno119f7232020-04-21 13:22:26 +00001173 return_text=False
beierlmf52cb7c2020-04-21 16:36:35 -04001174 )
1175 status = detailed_status.get("info").get("Description")
tierno119f7232020-04-21 13:22:26 +00001176 self.log.debug('KDU {} STATUS: {}.'.format(kdu_instance, status))
quilesj26c78a42019-10-28 18:10:42 +01001177 # write status to db
1178 result = await self.write_app_status_to_db(
1179 db_dict=db_dict,
1180 status=str(status),
1181 detailed_status=str(detailed_status),
beierlmf52cb7c2020-04-21 16:36:35 -04001182 operation=operation,
1183 )
quilesj26c78a42019-10-28 18:10:42 +01001184 if not result:
beierlmf52cb7c2020-04-21 16:36:35 -04001185 self.log.info("Error writing in database. Task exiting...")
quilesj26c78a42019-10-28 18:10:42 +01001186 return
1187 except asyncio.CancelledError:
beierlmf52cb7c2020-04-21 16:36:35 -04001188 self.log.debug("Task cancelled")
quilesj26c78a42019-10-28 18:10:42 +01001189 return
1190 except Exception as e:
tierno0b68fcb2020-08-14 10:05:43 +00001191 # log only once in the while loop
1192 if str(previous_exception) != str(e):
1193 self.log.debug("_store_status exception: {}".format(str(e)))
1194 previous_exception = e
quilesj26c78a42019-10-28 18:10:42 +01001195 finally:
1196 if run_once:
1197 return
1198
tiernof9bdac22020-06-25 15:48:52 +00001199 async def _is_install_completed(self, cluster_id: str, kdu_instance: str) -> bool:
quilesj26c78a42019-10-28 18:10:42 +01001200
beierlmf52cb7c2020-04-21 16:36:35 -04001201 status = await self._status_kdu(
tiernof9bdac22020-06-25 15:48:52 +00001202 cluster_id=cluster_id, kdu_instance=kdu_instance, return_text=False
beierlmf52cb7c2020-04-21 16:36:35 -04001203 )
quilesj26c78a42019-10-28 18:10:42 +01001204
1205 # extract info.status.resources-> str
1206 # format:
1207 # ==> v1/Deployment
1208 # NAME READY UP-TO-DATE AVAILABLE AGE
1209 # halting-horse-mongodb 0/1 1 0 0s
1210 # halting-petit-mongodb 1/1 1 0 0s
1211 # blank line
beierlmf52cb7c2020-04-21 16:36:35 -04001212 resources = K8sHelmConnector._get_deep(status, ("info", "status", "resources"))
quilesj26c78a42019-10-28 18:10:42 +01001213
1214 # convert to table
1215 resources = K8sHelmConnector._output_to_table(resources)
1216
1217 num_lines = len(resources)
1218 index = 0
1219 while index < num_lines:
1220 try:
1221 line1 = resources[index]
1222 index += 1
1223 # find '==>' in column 0
beierlmf52cb7c2020-04-21 16:36:35 -04001224 if line1[0] == "==>":
quilesj26c78a42019-10-28 18:10:42 +01001225 line2 = resources[index]
1226 index += 1
1227 # find READY in column 1
beierlmf52cb7c2020-04-21 16:36:35 -04001228 if line2[1] == "READY":
quilesj26c78a42019-10-28 18:10:42 +01001229 # read next lines
1230 line3 = resources[index]
1231 index += 1
1232 while len(line3) > 1 and index < num_lines:
1233 ready_value = line3[1]
beierlmf52cb7c2020-04-21 16:36:35 -04001234 parts = ready_value.split(sep="/")
quilesj26c78a42019-10-28 18:10:42 +01001235 current = int(parts[0])
1236 total = int(parts[1])
1237 if current < total:
beierlmf52cb7c2020-04-21 16:36:35 -04001238 self.log.debug("NOT READY:\n {}".format(line3))
quilesj26c78a42019-10-28 18:10:42 +01001239 ready = False
1240 line3 = resources[index]
1241 index += 1
1242
beierlmf52cb7c2020-04-21 16:36:35 -04001243 except Exception:
quilesj26c78a42019-10-28 18:10:42 +01001244 pass
1245
1246 return ready
1247
lloretgallegb8ba1af2020-06-29 14:18:30 +00001248 def _parse_helm_status_service_info(self, status):
1249
1250 # extract info.status.resources-> str
1251 # format:
1252 # ==> v1/Deployment
1253 # NAME READY UP-TO-DATE AVAILABLE AGE
1254 # halting-horse-mongodb 0/1 1 0 0s
1255 # halting-petit-mongodb 1/1 1 0 0s
1256 # blank line
1257 resources = K8sHelmConnector._get_deep(status, ("info", "status", "resources"))
1258
1259 service_list = []
1260 first_line_skipped = service_found = False
1261 for line in resources:
1262 if not service_found:
1263 if len(line) >= 2 and line[0] == "==>" and line[1] == "v1/Service":
1264 service_found = True
1265 continue
1266 else:
1267 if len(line) >= 2 and line[0] == "==>":
1268 service_found = first_line_skipped = False
1269 continue
1270 if not line:
1271 continue
1272 if not first_line_skipped:
1273 first_line_skipped = True
1274 continue
1275 service_list.append(line[0])
1276
1277 return service_list
1278
quilesj26c78a42019-10-28 18:10:42 +01001279 @staticmethod
1280 def _get_deep(dictionary: dict, members: tuple):
1281 target = dictionary
1282 value = None
1283 try:
1284 for m in members:
1285 value = target.get(m)
1286 if not value:
1287 return None
1288 else:
1289 target = value
beierlmf52cb7c2020-04-21 16:36:35 -04001290 except Exception:
quilesj26c78a42019-10-28 18:10:42 +01001291 pass
1292 return value
1293
1294 # find key:value in several lines
1295 @staticmethod
1296 def _find_in_lines(p_lines: list, p_key: str) -> str:
1297 for line in p_lines:
1298 try:
beierlmf52cb7c2020-04-21 16:36:35 -04001299 if line.startswith(p_key + ":"):
1300 parts = line.split(":")
quilesj26c78a42019-10-28 18:10:42 +01001301 the_value = parts[1].strip()
1302 return the_value
beierlmf52cb7c2020-04-21 16:36:35 -04001303 except Exception:
quilesj26c78a42019-10-28 18:10:42 +01001304 # ignore it
1305 pass
1306 return None
1307
quilesjcda5f412019-11-18 11:32:12 +01001308 # params for use in -f file
1309 # returns values file option and filename (in order to delete it at the end)
tiernof9bdac22020-06-25 15:48:52 +00001310 def _params_to_file_option(self, cluster_id: str, params: dict) -> (str, str):
quilesjcda5f412019-11-18 11:32:12 +01001311
1312 if params and len(params) > 0:
tiernof9bdac22020-06-25 15:48:52 +00001313 self._get_paths(cluster_name=cluster_id, create_if_not_exist=True)
quilesjcda5f412019-11-18 11:32:12 +01001314
1315 def get_random_number():
1316 r = random.randrange(start=1, stop=99999999)
1317 s = str(r)
1318 while len(s) < 10:
beierlmf52cb7c2020-04-21 16:36:35 -04001319 s = "0" + s
quilesjcda5f412019-11-18 11:32:12 +01001320 return s
1321
1322 params2 = dict()
1323 for key in params:
1324 value = params.get(key)
beierlmf52cb7c2020-04-21 16:36:35 -04001325 if "!!yaml" in str(value):
quilesj1be06302019-11-29 11:17:11 +00001326 value = yaml.load(value[7:])
quilesjcda5f412019-11-18 11:32:12 +01001327 params2[key] = value
1328
beierlmf52cb7c2020-04-21 16:36:35 -04001329 values_file = get_random_number() + ".yaml"
1330 with open(values_file, "w") as stream:
quilesjcda5f412019-11-18 11:32:12 +01001331 yaml.dump(params2, stream, indent=4, default_flow_style=False)
1332
beierlmf52cb7c2020-04-21 16:36:35 -04001333 return "-f {}".format(values_file), values_file
quilesjcda5f412019-11-18 11:32:12 +01001334
beierlmf52cb7c2020-04-21 16:36:35 -04001335 return "", None
quilesjcda5f412019-11-18 11:32:12 +01001336
quilesj26c78a42019-10-28 18:10:42 +01001337 # params for use in --set option
1338 @staticmethod
1339 def _params_to_set_option(params: dict) -> str:
beierlmf52cb7c2020-04-21 16:36:35 -04001340 params_str = ""
quilesj26c78a42019-10-28 18:10:42 +01001341 if params and len(params) > 0:
1342 start = True
1343 for key in params:
1344 value = params.get(key, None)
1345 if value is not None:
1346 if start:
beierlmf52cb7c2020-04-21 16:36:35 -04001347 params_str += "--set "
quilesj26c78a42019-10-28 18:10:42 +01001348 start = False
1349 else:
beierlmf52cb7c2020-04-21 16:36:35 -04001350 params_str += ","
1351 params_str += "{}={}".format(key, value)
quilesj26c78a42019-10-28 18:10:42 +01001352 return params_str
1353
1354 @staticmethod
1355 def _output_to_lines(output: str) -> list:
1356 output_lines = list()
1357 lines = output.splitlines(keepends=False)
1358 for line in lines:
1359 line = line.strip()
1360 if len(line) > 0:
1361 output_lines.append(line)
1362 return output_lines
1363
1364 @staticmethod
1365 def _output_to_table(output: str) -> list:
1366 output_table = list()
1367 lines = output.splitlines(keepends=False)
1368 for line in lines:
beierlmf52cb7c2020-04-21 16:36:35 -04001369 line = line.replace("\t", " ")
quilesj26c78a42019-10-28 18:10:42 +01001370 line_list = list()
1371 output_table.append(line_list)
beierlmf52cb7c2020-04-21 16:36:35 -04001372 cells = line.split(sep=" ")
quilesj26c78a42019-10-28 18:10:42 +01001373 for cell in cells:
1374 cell = cell.strip()
1375 if len(cell) > 0:
1376 line_list.append(cell)
1377 return output_table
1378
beierlmf52cb7c2020-04-21 16:36:35 -04001379 def _get_paths(
1380 self, cluster_name: str, create_if_not_exist: bool = False
1381 ) -> (str, str, str, str):
quilesj26c78a42019-10-28 18:10:42 +01001382 """
1383 Returns kube and helm directories
1384
1385 :param cluster_name:
1386 :param create_if_not_exist:
quilesjcda5f412019-11-18 11:32:12 +01001387 :return: kube, helm directories, config filename and cluster dir.
1388 Raises exception if not exist and cannot create
quilesj26c78a42019-10-28 18:10:42 +01001389 """
1390
1391 base = self.fs.path
1392 if base.endswith("/") or base.endswith("\\"):
1393 base = base[:-1]
1394
1395 # base dir for cluster
beierlmf52cb7c2020-04-21 16:36:35 -04001396 cluster_dir = base + "/" + cluster_name
quilesj26c78a42019-10-28 18:10:42 +01001397 if create_if_not_exist and not os.path.exists(cluster_dir):
beierlmf52cb7c2020-04-21 16:36:35 -04001398 self.log.debug("Creating dir {}".format(cluster_dir))
quilesj26c78a42019-10-28 18:10:42 +01001399 os.makedirs(cluster_dir)
1400 if not os.path.exists(cluster_dir):
beierlmf52cb7c2020-04-21 16:36:35 -04001401 msg = "Base cluster dir {} does not exist".format(cluster_dir)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +01001402 self.log.error(msg)
quilesja6748412019-12-04 07:51:26 +00001403 raise K8sException(msg)
quilesj26c78a42019-10-28 18:10:42 +01001404
1405 # kube dir
beierlmf52cb7c2020-04-21 16:36:35 -04001406 kube_dir = cluster_dir + "/" + ".kube"
quilesj26c78a42019-10-28 18:10:42 +01001407 if create_if_not_exist and not os.path.exists(kube_dir):
beierlmf52cb7c2020-04-21 16:36:35 -04001408 self.log.debug("Creating dir {}".format(kube_dir))
quilesj26c78a42019-10-28 18:10:42 +01001409 os.makedirs(kube_dir)
1410 if not os.path.exists(kube_dir):
beierlmf52cb7c2020-04-21 16:36:35 -04001411 msg = "Kube config dir {} does not exist".format(kube_dir)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +01001412 self.log.error(msg)
quilesja6748412019-12-04 07:51:26 +00001413 raise K8sException(msg)
quilesj26c78a42019-10-28 18:10:42 +01001414
1415 # helm home dir
beierlmf52cb7c2020-04-21 16:36:35 -04001416 helm_dir = cluster_dir + "/" + ".helm"
quilesj26c78a42019-10-28 18:10:42 +01001417 if create_if_not_exist and not os.path.exists(helm_dir):
beierlmf52cb7c2020-04-21 16:36:35 -04001418 self.log.debug("Creating dir {}".format(helm_dir))
quilesj26c78a42019-10-28 18:10:42 +01001419 os.makedirs(helm_dir)
1420 if not os.path.exists(helm_dir):
beierlmf52cb7c2020-04-21 16:36:35 -04001421 msg = "Helm config dir {} does not exist".format(helm_dir)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +01001422 self.log.error(msg)
quilesja6748412019-12-04 07:51:26 +00001423 raise K8sException(msg)
quilesj26c78a42019-10-28 18:10:42 +01001424
beierlmf52cb7c2020-04-21 16:36:35 -04001425 config_filename = kube_dir + "/config"
quilesjcda5f412019-11-18 11:32:12 +01001426 return kube_dir, helm_dir, config_filename, cluster_dir
quilesj26c78a42019-10-28 18:10:42 +01001427
1428 @staticmethod
beierlmf52cb7c2020-04-21 16:36:35 -04001429 def _remove_multiple_spaces(strobj):
1430 strobj = strobj.strip()
1431 while " " in strobj:
1432 strobj = strobj.replace(" ", " ")
1433 return strobj
quilesj26c78a42019-10-28 18:10:42 +01001434
beierlmf52cb7c2020-04-21 16:36:35 -04001435 def _local_exec(self, command: str) -> (str, int):
quilesj26c78a42019-10-28 18:10:42 +01001436 command = K8sHelmConnector._remove_multiple_spaces(command)
beierlmf52cb7c2020-04-21 16:36:35 -04001437 self.log.debug("Executing sync local command: {}".format(command))
quilesj26c78a42019-10-28 18:10:42 +01001438 # raise exception if fails
beierlmf52cb7c2020-04-21 16:36:35 -04001439 output = ""
quilesj26c78a42019-10-28 18:10:42 +01001440 try:
beierlmf52cb7c2020-04-21 16:36:35 -04001441 output = subprocess.check_output(
1442 command, shell=True, universal_newlines=True
1443 )
quilesj26c78a42019-10-28 18:10:42 +01001444 return_code = 0
Dominik Fleischmannf9bed352020-02-27 10:04:34 +01001445 self.log.debug(output)
beierlmf52cb7c2020-04-21 16:36:35 -04001446 except Exception:
quilesj26c78a42019-10-28 18:10:42 +01001447 return_code = 1
1448
1449 return output, return_code
1450
1451 async def _local_async_exec(
beierlmf52cb7c2020-04-21 16:36:35 -04001452 self,
1453 command: str,
1454 raise_exception_on_error: bool = False,
1455 show_error_log: bool = True,
1456 encode_utf8: bool = False,
quilesj26c78a42019-10-28 18:10:42 +01001457 ) -> (str, int):
1458
1459 command = K8sHelmConnector._remove_multiple_spaces(command)
beierlmf52cb7c2020-04-21 16:36:35 -04001460 self.log.debug("Executing async local command: {}".format(command))
quilesj26c78a42019-10-28 18:10:42 +01001461
1462 # split command
beierlmf52cb7c2020-04-21 16:36:35 -04001463 command = command.split(sep=" ")
quilesj26c78a42019-10-28 18:10:42 +01001464
1465 try:
1466 process = await asyncio.create_subprocess_exec(
beierlmf52cb7c2020-04-21 16:36:35 -04001467 *command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
quilesj26c78a42019-10-28 18:10:42 +01001468 )
1469
1470 # wait for command terminate
1471 stdout, stderr = await process.communicate()
1472
1473 return_code = process.returncode
1474
beierlmf52cb7c2020-04-21 16:36:35 -04001475 output = ""
quilesj26c78a42019-10-28 18:10:42 +01001476 if stdout:
beierlmf52cb7c2020-04-21 16:36:35 -04001477 output = stdout.decode("utf-8").strip()
quilesj1be06302019-11-29 11:17:11 +00001478 # output = stdout.decode()
quilesj26c78a42019-10-28 18:10:42 +01001479 if stderr:
beierlmf52cb7c2020-04-21 16:36:35 -04001480 output = stderr.decode("utf-8").strip()
quilesj1be06302019-11-29 11:17:11 +00001481 # output = stderr.decode()
quilesj26c78a42019-10-28 18:10:42 +01001482
1483 if return_code != 0 and show_error_log:
beierlmf52cb7c2020-04-21 16:36:35 -04001484 self.log.debug(
1485 "Return code (FAIL): {}\nOutput:\n{}".format(return_code, output)
1486 )
quilesj26c78a42019-10-28 18:10:42 +01001487 else:
beierlmf52cb7c2020-04-21 16:36:35 -04001488 self.log.debug("Return code: {}".format(return_code))
quilesj26c78a42019-10-28 18:10:42 +01001489
1490 if raise_exception_on_error and return_code != 0:
tierno601697a2020-02-04 15:26:25 +00001491 raise K8sException(output)
quilesj26c78a42019-10-28 18:10:42 +01001492
quilesj1be06302019-11-29 11:17:11 +00001493 if encode_utf8:
beierlmf52cb7c2020-04-21 16:36:35 -04001494 output = output.encode("utf-8").strip()
1495 output = str(output).replace("\\n", "\n")
quilesj1be06302019-11-29 11:17:11 +00001496
quilesj26c78a42019-10-28 18:10:42 +01001497 return output, return_code
1498
lloretgallegdd0cdee2020-02-26 10:00:16 +01001499 except asyncio.CancelledError:
1500 raise
tierno601697a2020-02-04 15:26:25 +00001501 except K8sException:
1502 raise
quilesj26c78a42019-10-28 18:10:42 +01001503 except Exception as e:
beierlmf52cb7c2020-04-21 16:36:35 -04001504 msg = "Exception executing command: {} -> {}".format(command, e)
Dominik Fleischmannf9bed352020-02-27 10:04:34 +01001505 self.log.error(msg)
quilesj32dc3c62020-01-23 16:30:04 +00001506 if raise_exception_on_error:
tierno601697a2020-02-04 15:26:25 +00001507 raise K8sException(e) from e
quilesj32dc3c62020-01-23 16:30:04 +00001508 else:
beierlmf52cb7c2020-04-21 16:36:35 -04001509 return "", -1
quilesj26c78a42019-10-28 18:10:42 +01001510
quilesj26c78a42019-10-28 18:10:42 +01001511 def _check_file_exists(self, filename: str, exception_if_not_exists: bool = False):
tierno8ff11992020-03-26 09:51:11 +00001512 # self.log.debug('Checking if file {} exists...'.format(filename))
quilesj26c78a42019-10-28 18:10:42 +01001513 if os.path.exists(filename):
1514 return True
1515 else:
beierlmf52cb7c2020-04-21 16:36:35 -04001516 msg = "File {} does not exist".format(filename)
quilesj26c78a42019-10-28 18:10:42 +01001517 if exception_if_not_exists:
tierno8ff11992020-03-26 09:51:11 +00001518 # self.log.error(msg)
quilesja6748412019-12-04 07:51:26 +00001519 raise K8sException(msg)