blob: 396d79be9a7f57acce7e710df96330f83ed31b53 [file] [log] [blame]
Adam Israeld4ec83b2019-11-07 09:46:59 -05001# Copyright 2019 Canonical Ltd.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Adam Israel3419aba2020-01-29 09:35:35 -050015import asyncio
Pedro Escaleiraa8980cc2022-04-05 17:32:13 +010016from typing import Union
Adam Israeld4ec83b2019-11-07 09:46:59 -050017import os
beierlmf52cb7c2020-04-21 16:36:35 -040018import uuid
beierlm55ca1c72020-05-05 14:55:19 -040019import yaml
David Garcia667696e2020-09-22 14:52:32 +020020import tempfile
David Garciaf6e9b002020-11-27 15:32:02 +010021import binascii
beierlmf52cb7c2020-04-21 16:36:35 -040022
David Garciaeb8943a2021-04-12 12:07:37 +020023from n2vc.config import EnvironConfig
David Garcia582b9232021-10-26 12:30:44 +020024from n2vc.definitions import RelationEndpoint
David Garciaeb8943a2021-04-12 12:07:37 +020025from n2vc.exceptions import K8sException
beierlmf52cb7c2020-04-21 16:36:35 -040026from n2vc.k8s_conn import K8sConnector
David Garciad8d4b6e2021-06-24 18:47:22 +020027from n2vc.kubectl import Kubectl
David Garcia667696e2020-09-22 14:52:32 +020028from .exceptions import MethodNotImplemented
David Garcia667696e2020-09-22 14:52:32 +020029from n2vc.libjuju import Libjuju
ksaikiranrb816d822021-03-17 12:50:20 +053030from n2vc.utils import obj_to_dict, obj_to_yaml
David Garciaeb8943a2021-04-12 12:07:37 +020031from n2vc.store import MotorStore
32from n2vc.vca.cloud import Cloud
33from n2vc.vca.connection import get_connection
David Garciaf6e9b002020-11-27 15:32:02 +010034
David Garciaf6e9b002020-11-27 15:32:02 +010035
David Garciaf6e9b002020-11-27 15:32:02 +010036RBAC_LABEL_KEY_NAME = "rbac-id"
David Garciaf6e9b002020-11-27 15:32:02 +010037RBAC_STACK_PREFIX = "juju-credential"
beierlmf52cb7c2020-04-21 16:36:35 -040038
David Garciaf6e9b002020-11-27 15:32:02 +010039
40def generate_rbac_id():
41 return binascii.hexlify(os.urandom(4)).decode()
42
43
Adam Israeld4ec83b2019-11-07 09:46:59 -050044class K8sJujuConnector(K8sConnector):
David Garciaeb8943a2021-04-12 12:07:37 +020045 libjuju = None
46
Adam Israeld4ec83b2019-11-07 09:46:59 -050047 def __init__(
beierlmf52cb7c2020-04-21 16:36:35 -040048 self,
49 fs: object,
50 db: object,
51 kubectl_command: str = "/usr/bin/kubectl",
52 juju_command: str = "/usr/bin/juju",
53 log: object = None,
David Garciaa0620742020-10-16 13:00:18 +020054 loop: object = None,
beierlmf52cb7c2020-04-21 16:36:35 -040055 on_update_db=None,
Adam Israeld4ec83b2019-11-07 09:46:59 -050056 ):
57 """
David Garcia667696e2020-09-22 14:52:32 +020058 :param fs: file system for kubernetes and helm configuration
59 :param db: Database object
Adam Israeld4ec83b2019-11-07 09:46:59 -050060 :param kubectl_command: path to kubectl executable
61 :param helm_command: path to helm executable
Adam Israeld4ec83b2019-11-07 09:46:59 -050062 :param log: logger
David Garcia667696e2020-09-22 14:52:32 +020063 :param: loop: Asyncio loop
Adam Israeld4ec83b2019-11-07 09:46:59 -050064 """
65
66 # parent class
67 K8sConnector.__init__(
David Garcia667696e2020-09-22 14:52:32 +020068 self,
69 db,
70 log=log,
71 on_update_db=on_update_db,
Adam Israeld4ec83b2019-11-07 09:46:59 -050072 )
73
Adam Israeleef68932019-11-28 16:27:46 -050074 self.fs = fs
David Garcia667696e2020-09-22 14:52:32 +020075 self.loop = loop or asyncio.get_event_loop()
beierlmf52cb7c2020-04-21 16:36:35 -040076 self.log.debug("Initializing K8S Juju connector")
Adam Israeld4ec83b2019-11-07 09:46:59 -050077
David Garciaeb8943a2021-04-12 12:07:37 +020078 db_uri = EnvironConfig(prefixes=["OSMLCM_", "OSMMON_"]).get("database_uri")
79 self._store = MotorStore(db_uri)
80 self.loading_libjuju = asyncio.Lock(loop=self.loop)
David Garciacd986062022-05-05 09:46:06 +020081 self.uninstall_locks = {}
Adam Israeleef68932019-11-28 16:27:46 -050082
beierlmf52cb7c2020-04-21 16:36:35 -040083 self.log.debug("K8S Juju connector initialized")
David Garcia4f74f592020-07-23 15:04:19 +020084 # TODO: Remove these commented lines:
85 # self.authenticated = False
86 # self.models = {}
87 # self.juju_secret = ""
Adam Israeld4ec83b2019-11-07 09:46:59 -050088
89 """Initialization"""
beierlmf52cb7c2020-04-21 16:36:35 -040090
Adam Israeld4ec83b2019-11-07 09:46:59 -050091 async def init_env(
92 self,
Adam Israeleef68932019-11-28 16:27:46 -050093 k8s_creds: str,
beierlmf52cb7c2020-04-21 16:36:35 -040094 namespace: str = "kube-system",
Adam Israeld4ec83b2019-11-07 09:46:59 -050095 reuse_cluster_uuid: str = None,
David Garciaeb8943a2021-04-12 12:07:37 +020096 **kwargs,
Adam Israeleef68932019-11-28 16:27:46 -050097 ) -> (str, bool):
garciadeblas54771fa2019-12-13 13:39:03 +010098 """
99 It prepares a given K8s cluster environment to run Juju bundles.
Adam Israeld4ec83b2019-11-07 09:46:59 -0500100
beierlmf52cb7c2020-04-21 16:36:35 -0400101 :param k8s_creds: credentials to access a given K8s cluster, i.e. a valid
102 '.kube/config'
103 :param namespace: optional namespace to be used for juju. By default,
104 'kube-system' will be used
garciadeblas54771fa2019-12-13 13:39:03 +0100105 :param reuse_cluster_uuid: existing cluster uuid for reuse
David Garciaeb8943a2021-04-12 12:07:37 +0200106 :param: kwargs: Additional parameters
107 vca_id (str): VCA ID
108
beierlmf52cb7c2020-04-21 16:36:35 -0400109 :return: uuid of the K8s cluster and True if connector has installed some
110 software in the cluster
111 (on error, an exception will be raised)
Adam Israeld4ec83b2019-11-07 09:46:59 -0500112 """
David Garciaeb8943a2021-04-12 12:07:37 +0200113 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
Adam Israeld4ec83b2019-11-07 09:46:59 -0500114
David Garcia37004982020-07-16 17:53:20 +0200115 cluster_uuid = reuse_cluster_uuid or str(uuid.uuid4())
David Garciad8d4b6e2021-06-24 18:47:22 +0200116 kubectl = self._get_kubectl(k8s_creds)
Adam Israeld4ec83b2019-11-07 09:46:59 -0500117
David Garciaf6e9b002020-11-27 15:32:02 +0100118 # CREATING RESOURCES IN K8S
119 rbac_id = generate_rbac_id()
120 metadata_name = "{}-{}".format(RBAC_STACK_PREFIX, rbac_id)
121 labels = {RBAC_STACK_PREFIX: rbac_id}
David Garcia4f74f592020-07-23 15:04:19 +0200122
David Garciaf6e9b002020-11-27 15:32:02 +0100123 # Create cleanup dictionary to clean up created resources
124 # if it fails in the middle of the process
125 cleanup_data = []
126 try:
garciadeblas47f65382021-05-31 15:49:15 +0200127 self.log.debug("Initializing K8s cluster for juju")
David Garciad8d4b6e2021-06-24 18:47:22 +0200128 kubectl.create_cluster_role(
David Garciaf6e9b002020-11-27 15:32:02 +0100129 name=metadata_name,
130 labels=labels,
131 )
garciadeblas47f65382021-05-31 15:49:15 +0200132 self.log.debug("Cluster role created")
David Garciaf6e9b002020-11-27 15:32:02 +0100133 cleanup_data.append(
134 {
David Garciad8d4b6e2021-06-24 18:47:22 +0200135 "delete": kubectl.delete_cluster_role,
Pedro Escaleira15667792022-03-17 23:59:29 +0000136 "args": (metadata_name,),
David Garciaf6e9b002020-11-27 15:32:02 +0100137 }
138 )
Adam Israeld4ec83b2019-11-07 09:46:59 -0500139
David Garciad8d4b6e2021-06-24 18:47:22 +0200140 kubectl.create_service_account(
David Garciaf6e9b002020-11-27 15:32:02 +0100141 name=metadata_name,
142 labels=labels,
143 )
garciadeblas47f65382021-05-31 15:49:15 +0200144 self.log.debug("Service account created")
David Garciaf6e9b002020-11-27 15:32:02 +0100145 cleanup_data.append(
146 {
David Garciad8d4b6e2021-06-24 18:47:22 +0200147 "delete": kubectl.delete_service_account,
Pedro Escaleira15667792022-03-17 23:59:29 +0000148 "args": (metadata_name,),
David Garciaf6e9b002020-11-27 15:32:02 +0100149 }
150 )
Adam Israeld4ec83b2019-11-07 09:46:59 -0500151
David Garciad8d4b6e2021-06-24 18:47:22 +0200152 kubectl.create_cluster_role_binding(
David Garciaf6e9b002020-11-27 15:32:02 +0100153 name=metadata_name,
154 labels=labels,
155 )
garciadeblas47f65382021-05-31 15:49:15 +0200156 self.log.debug("Role binding created")
David Garciaf6e9b002020-11-27 15:32:02 +0100157 cleanup_data.append(
158 {
David Garciad8d4b6e2021-06-24 18:47:22 +0200159 "delete": kubectl.delete_service_account,
Pedro Escaleira15667792022-03-17 23:59:29 +0000160 "args": (metadata_name,),
David Garciaf6e9b002020-11-27 15:32:02 +0100161 }
162 )
David Garciad8d4b6e2021-06-24 18:47:22 +0200163 token, client_cert_data = await kubectl.get_secret_data(
David Garciaf6e9b002020-11-27 15:32:02 +0100164 metadata_name,
165 )
Adam Israeld4ec83b2019-11-07 09:46:59 -0500166
David Garciaf6e9b002020-11-27 15:32:02 +0100167 default_storage_class = kubectl.get_default_storage_class()
garciadeblas47f65382021-05-31 15:49:15 +0200168 self.log.debug("Default storage class: {}".format(default_storage_class))
David Garciaeb8943a2021-04-12 12:07:37 +0200169 await libjuju.add_k8s(
David Garciaf6e9b002020-11-27 15:32:02 +0100170 name=cluster_uuid,
171 rbac_id=rbac_id,
172 token=token,
173 client_cert_data=client_cert_data,
174 configuration=kubectl.configuration,
175 storage_class=default_storage_class,
176 credential_name=self._get_credential_name(cluster_uuid),
177 )
garciadeblas47f65382021-05-31 15:49:15 +0200178 self.log.debug("K8s cluster added to juju controller")
David Garciaf6e9b002020-11-27 15:32:02 +0100179 return cluster_uuid, True
180 except Exception as e:
garciadeblas47f65382021-05-31 15:49:15 +0200181 self.log.error("Error initializing k8scluster: {}".format(e), exc_info=True)
David Garciaf6e9b002020-11-27 15:32:02 +0100182 if len(cleanup_data) > 0:
183 self.log.debug("Cleaning up created resources in k8s cluster...")
184 for item in cleanup_data:
185 delete_function = item["delete"]
186 delete_args = item["args"]
187 delete_function(*delete_args)
188 self.log.debug("Cleanup finished")
189 raise e
Adam Israeld4ec83b2019-11-07 09:46:59 -0500190
191 """Repo Management"""
beierlmf52cb7c2020-04-21 16:36:35 -0400192
Adam Israeld4ec83b2019-11-07 09:46:59 -0500193 async def repo_add(
David Garcia667696e2020-09-22 14:52:32 +0200194 self,
195 name: str,
196 url: str,
197 _type: str = "charm",
bravof0ab522f2021-11-23 19:33:18 -0300198 cert: str = None,
199 user: str = None,
200 password: str = None,
Adam Israeld4ec83b2019-11-07 09:46:59 -0500201 ):
beierlmf52cb7c2020-04-21 16:36:35 -0400202 raise MethodNotImplemented()
Adam Israeld4ec83b2019-11-07 09:46:59 -0500203
204 async def repo_list(self):
beierlmf52cb7c2020-04-21 16:36:35 -0400205 raise MethodNotImplemented()
Adam Israeld4ec83b2019-11-07 09:46:59 -0500206
207 async def repo_remove(
David Garcia667696e2020-09-22 14:52:32 +0200208 self,
209 name: str,
Adam Israeld4ec83b2019-11-07 09:46:59 -0500210 ):
beierlmf52cb7c2020-04-21 16:36:35 -0400211 raise MethodNotImplemented()
Adam Israeld4ec83b2019-11-07 09:46:59 -0500212
beierlmf52cb7c2020-04-21 16:36:35 -0400213 async def synchronize_repos(self, cluster_uuid: str, name: str):
lloretgalleg65ddf852020-02-20 12:01:17 +0100214 """
215 Returns None as currently add_repo is not implemented
216 """
217 return None
218
Adam Israeld4ec83b2019-11-07 09:46:59 -0500219 """Reset"""
beierlmf52cb7c2020-04-21 16:36:35 -0400220
Adam Israeld4ec83b2019-11-07 09:46:59 -0500221 async def reset(
David Garciaeb8943a2021-04-12 12:07:37 +0200222 self,
223 cluster_uuid: str,
224 force: bool = False,
225 uninstall_sw: bool = False,
226 **kwargs,
Adam Israeld4ec83b2019-11-07 09:46:59 -0500227 ) -> bool:
228 """Reset a cluster
229
230 Resets the Kubernetes cluster by removing the model that represents it.
231
232 :param cluster_uuid str: The UUID of the cluster to reset
David Garciaeb8943a2021-04-12 12:07:37 +0200233 :param force: Force reset
234 :param uninstall_sw: Boolean to uninstall sw
235 :param: kwargs: Additional parameters
236 vca_id (str): VCA ID
237
Adam Israeld4ec83b2019-11-07 09:46:59 -0500238 :return: Returns True if successful or raises an exception.
239 """
240
241 try:
David Garcia4f74f592020-07-23 15:04:19 +0200242 self.log.debug("[reset] Removing k8s cloud")
David Garciaeb8943a2021-04-12 12:07:37 +0200243 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
David Garciaf6e9b002020-11-27 15:32:02 +0100244
David Garciaeb8943a2021-04-12 12:07:37 +0200245 cloud = Cloud(cluster_uuid, self._get_credential_name(cluster_uuid))
David Garciaf6e9b002020-11-27 15:32:02 +0100246
David Garciaeb8943a2021-04-12 12:07:37 +0200247 cloud_creds = await libjuju.get_cloud_credentials(cloud)
248
249 await libjuju.remove_cloud(cluster_uuid)
Adam Israeld4ec83b2019-11-07 09:46:59 -0500250
David Garciad8d4b6e2021-06-24 18:47:22 +0200251 credentials = self.get_credentials(cluster_uuid=cluster_uuid)
David Garciaf6e9b002020-11-27 15:32:02 +0100252
David Garciad8d4b6e2021-06-24 18:47:22 +0200253 kubectl = self._get_kubectl(credentials)
David Garciaf6e9b002020-11-27 15:32:02 +0100254
255 delete_functions = [
David Garciad8d4b6e2021-06-24 18:47:22 +0200256 kubectl.delete_cluster_role_binding,
257 kubectl.delete_service_account,
258 kubectl.delete_cluster_role,
David Garciaf6e9b002020-11-27 15:32:02 +0100259 ]
260
261 credential_attrs = cloud_creds[0].result["attrs"]
262 if RBAC_LABEL_KEY_NAME in credential_attrs:
263 rbac_id = credential_attrs[RBAC_LABEL_KEY_NAME]
264 metadata_name = "{}-{}".format(RBAC_STACK_PREFIX, rbac_id)
David Garciaf6e9b002020-11-27 15:32:02 +0100265 for delete_func in delete_functions:
266 try:
David Garciad8d4b6e2021-06-24 18:47:22 +0200267 delete_func(metadata_name)
David Garciaf6e9b002020-11-27 15:32:02 +0100268 except Exception as e:
269 self.log.warning("Cannot remove resource in K8s {}".format(e))
270
David Garcia667696e2020-09-22 14:52:32 +0200271 except Exception as e:
272 self.log.debug("Caught exception during reset: {}".format(e))
273 raise e
Dominik Fleischmann1ac78b32020-02-26 19:58:25 +0100274 return True
275
Adam Israeld4ec83b2019-11-07 09:46:59 -0500276 """Deployment"""
Adam Israeleef68932019-11-28 16:27:46 -0500277
Adam Israeld4ec83b2019-11-07 09:46:59 -0500278 async def install(
279 self,
280 cluster_uuid: str,
281 kdu_model: str,
David Garciac4da25c2021-02-23 11:47:29 +0100282 kdu_instance: str,
Adam Israeld4ec83b2019-11-07 09:46:59 -0500283 atomic: bool = True,
David Garcia667696e2020-09-22 14:52:32 +0200284 timeout: float = 1800,
Adam Israeld4ec83b2019-11-07 09:46:59 -0500285 params: dict = None,
Dominik Fleischmann847f3c02020-02-04 15:32:42 +0100286 db_dict: dict = None,
tierno53555f62020-04-07 11:08:16 +0000287 kdu_name: str = None,
beierlmf52cb7c2020-04-21 16:36:35 -0400288 namespace: str = None,
David Garciaeb8943a2021-04-12 12:07:37 +0200289 **kwargs,
Adam Israeleef68932019-11-28 16:27:46 -0500290 ) -> bool:
Adam Israeld4ec83b2019-11-07 09:46:59 -0500291 """Install a bundle
292
293 :param cluster_uuid str: The UUID of the cluster to install to
294 :param kdu_model str: The name or path of a bundle to install
David Garciac4da25c2021-02-23 11:47:29 +0100295 :param kdu_instance: Kdu instance name
Adam Israeld4ec83b2019-11-07 09:46:59 -0500296 :param atomic bool: If set, waits until the model is active and resets
297 the cluster on failure.
298 :param timeout int: The time, in seconds, to wait for the install
299 to finish
300 :param params dict: Key-value pairs of instantiation parameters
Dominik Fleischmann847f3c02020-02-04 15:32:42 +0100301 :param kdu_name: Name of the KDU instance to be installed
tierno53555f62020-04-07 11:08:16 +0000302 :param namespace: K8s namespace to use for the KDU instance
David Garciaeb8943a2021-04-12 12:07:37 +0200303 :param kwargs: Additional parameters
304 vca_id (str): VCA ID
Adam Israeld4ec83b2019-11-07 09:46:59 -0500305
306 :return: If successful, returns ?
307 """
David Garciaeb8943a2021-04-12 12:07:37 +0200308 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
David Garcia667696e2020-09-22 14:52:32 +0200309 bundle = kdu_model
Adam Israeld4ec83b2019-11-07 09:46:59 -0500310
David Garcia667696e2020-09-22 14:52:32 +0200311 if not db_dict:
312 raise K8sException("db_dict must be set")
313 if not bundle:
314 raise K8sException("bundle must be set")
315
316 if bundle.startswith("cs:"):
Pedro Escaleira86a63142022-04-05 21:01:37 +0100317 # For Juju Bundles provided by the Charm Store
318 pass
319 elif bundle.startswith("ch:"):
320 # For Juju Bundles provided by the Charm Hub (this only works for juju version >= 2.9)
David Garcia667696e2020-09-22 14:52:32 +0200321 pass
322 elif bundle.startswith("http"):
323 # Download the file
324 pass
325 else:
326 new_workdir = kdu_model.strip(kdu_model.split("/")[-1])
327 os.chdir(new_workdir)
328 bundle = "local:{}".format(kdu_model)
329
Pedro Escaleira764d8662022-04-19 20:40:09 +0100330 # default namespace to kdu_instance
331 if not namespace:
332 namespace = kdu_instance
333
334 self.log.debug("Checking for model named {}".format(namespace))
Dominik Fleischmann1ac78b32020-02-26 19:58:25 +0100335
336 # Create the new model
Pedro Escaleira764d8662022-04-19 20:40:09 +0100337 self.log.debug("Adding model: {}".format(namespace))
David Garciaeb8943a2021-04-12 12:07:37 +0200338 cloud = Cloud(cluster_uuid, self._get_credential_name(cluster_uuid))
Pedro Escaleira764d8662022-04-19 20:40:09 +0100339 await libjuju.add_model(namespace, cloud)
Adam Israeld4ec83b2019-11-07 09:46:59 -0500340
David Garcia667696e2020-09-22 14:52:32 +0200341 # if model:
342 # TODO: Instantiation parameters
Adam Israeld4ec83b2019-11-07 09:46:59 -0500343
David Garcia667696e2020-09-22 14:52:32 +0200344 """
345 "Juju bundle that models the KDU, in any of the following ways:
346 - <juju-repo>/<juju-bundle>
347 - <juju-bundle folder under k8s_models folder in the package>
348 - <juju-bundle tgz file (w/ or w/o extension) under k8s_models folder
349 in the package>
350 - <URL_where_to_fetch_juju_bundle>
351 """
352 try:
353 previous_workdir = os.getcwd()
354 except FileNotFoundError:
355 previous_workdir = "/app/storage"
Dominik Fleischmann45d95772020-03-26 12:21:42 +0100356
David Garcia667696e2020-09-22 14:52:32 +0200357 self.log.debug("[install] deploying {}".format(bundle))
Pedro Escaleira764d8662022-04-19 20:40:09 +0100358 await libjuju.deploy(bundle, model_name=namespace, wait=atomic, timeout=timeout)
David Garcia667696e2020-09-22 14:52:32 +0200359 os.chdir(previous_workdir)
Pedro Escaleira764d8662022-04-19 20:40:09 +0100360
361 # update information in the database (first, the VCA status, and then, the namespace)
ksaikiranrb816d822021-03-17 12:50:20 +0530362 if self.on_update_db:
David Garciaeb8943a2021-04-12 12:07:37 +0200363 await self.on_update_db(
364 cluster_uuid,
365 kdu_instance,
366 filter=db_dict["filter"],
garciadeblas82b591c2021-03-24 09:22:13 +0100367 vca_id=kwargs.get("vca_id"),
David Garciaeb8943a2021-04-12 12:07:37 +0200368 )
Pedro Escaleira764d8662022-04-19 20:40:09 +0100369
370 self.db.set_one(
371 table="nsrs",
372 q_filter={"_admin.deployed.K8s.kdu-instance": kdu_instance},
373 update_dict={"_admin.deployed.K8s.$.namespace": namespace},
374 )
375
David Garciac4da25c2021-02-23 11:47:29 +0100376 return True
Adam Israeld4ec83b2019-11-07 09:46:59 -0500377
aktas2962f3e2021-03-15 11:05:35 +0300378 async def scale(
garciadeblas82b591c2021-03-24 09:22:13 +0100379 self,
380 kdu_instance: str,
381 scale: int,
382 resource_name: str,
383 total_timeout: float = 1800,
Pedro Escaleira764d8662022-04-19 20:40:09 +0100384 namespace: str = None,
garciadeblas82b591c2021-03-24 09:22:13 +0100385 **kwargs,
aktas2962f3e2021-03-15 11:05:35 +0300386 ) -> bool:
387 """Scale an application in a model
388
389 :param: kdu_instance str: KDU instance name
aktas867418c2021-10-19 18:26:13 +0300390 :param: scale int: Scale to which to set the application
391 :param: resource_name str: The application name in the Juju Bundle
aktas2962f3e2021-03-15 11:05:35 +0300392 :param: timeout float: The time, in seconds, to wait for the install
393 to finish
Pedro Escaleira764d8662022-04-19 20:40:09 +0100394 :param namespace str: The namespace (model) where the Bundle was deployed
aktas2962f3e2021-03-15 11:05:35 +0300395 :param kwargs: Additional parameters
396 vca_id (str): VCA ID
397
398 :return: If successful, returns True
399 """
400
Pedro Escaleira764d8662022-04-19 20:40:09 +0100401 model_name = self._obtain_namespace(
402 kdu_instance=kdu_instance, namespace=namespace
403 )
aktas2962f3e2021-03-15 11:05:35 +0300404 try:
405 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
406 await libjuju.scale_application(
Pedro Escaleira764d8662022-04-19 20:40:09 +0100407 model_name=model_name,
aktas2962f3e2021-03-15 11:05:35 +0300408 application_name=resource_name,
409 scale=scale,
garciadeblas82b591c2021-03-24 09:22:13 +0100410 total_timeout=total_timeout,
aktas2962f3e2021-03-15 11:05:35 +0300411 )
412 except Exception as e:
Pedro Escaleira764d8662022-04-19 20:40:09 +0100413 error_msg = "Error scaling application {} of the model {} of the kdu instance {}: {}".format(
414 resource_name, model_name, kdu_instance, e
garciadeblas82b591c2021-03-24 09:22:13 +0100415 )
aktas2962f3e2021-03-15 11:05:35 +0300416 self.log.error(error_msg)
417 raise K8sException(message=error_msg)
418 return True
419
420 async def get_scale_count(
garciadeblas82b591c2021-03-24 09:22:13 +0100421 self,
422 resource_name: str,
423 kdu_instance: str,
Pedro Escaleira764d8662022-04-19 20:40:09 +0100424 namespace: str = None,
garciadeblas82b591c2021-03-24 09:22:13 +0100425 **kwargs,
aktas2962f3e2021-03-15 11:05:35 +0300426 ) -> int:
427 """Get an application scale count
428
aktas867418c2021-10-19 18:26:13 +0300429 :param: resource_name str: The application name in the Juju Bundle
aktas2962f3e2021-03-15 11:05:35 +0300430 :param: kdu_instance str: KDU instance name
Pedro Escaleira764d8662022-04-19 20:40:09 +0100431 :param namespace str: The namespace (model) where the Bundle was deployed
aktas2962f3e2021-03-15 11:05:35 +0300432 :param kwargs: Additional parameters
433 vca_id (str): VCA ID
434 :return: Return application instance count
435 """
aktas867418c2021-10-19 18:26:13 +0300436
Pedro Escaleira764d8662022-04-19 20:40:09 +0100437 model_name = self._obtain_namespace(
438 kdu_instance=kdu_instance, namespace=namespace
439 )
aktas2962f3e2021-03-15 11:05:35 +0300440 try:
441 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
Pedro Escaleira764d8662022-04-19 20:40:09 +0100442 status = await libjuju.get_model_status(model_name=model_name)
aktas2962f3e2021-03-15 11:05:35 +0300443 return len(status.applications[resource_name].units)
444 except Exception as e:
Pedro Escaleira764d8662022-04-19 20:40:09 +0100445 error_msg = (
446 f"Error getting scale count from application {resource_name} of the model {model_name} of "
447 f"the kdu instance {kdu_instance}: {e}"
garciadeblas82b591c2021-03-24 09:22:13 +0100448 )
aktas2962f3e2021-03-15 11:05:35 +0300449 self.log.error(error_msg)
450 raise K8sException(message=error_msg)
451
beierlmf52cb7c2020-04-21 16:36:35 -0400452 async def instances_list(self, cluster_uuid: str) -> list:
Adam Israeld4ec83b2019-11-07 09:46:59 -0500453 """
454 returns a list of deployed releases in a cluster
455
456 :param cluster_uuid: the cluster
457 :return:
458 """
459 return []
460
461 async def upgrade(
462 self,
463 cluster_uuid: str,
464 kdu_instance: str,
465 kdu_model: str = None,
466 params: dict = None,
467 ) -> str:
468 """Upgrade a model
469
470 :param cluster_uuid str: The UUID of the cluster to upgrade
471 :param kdu_instance str: The unique name of the KDU instance
472 :param kdu_model str: The name or path of the bundle to upgrade to
473 :param params dict: Key-value pairs of instantiation parameters
474
475 :return: If successful, reference to the new revision number of the
476 KDU instance.
477 """
478
479 # TODO: Loop through the bundle and upgrade each charm individually
480
481 """
482 The API doesn't have a concept of bundle upgrades, because there are
483 many possible changes: charm revision, disk, number of units, etc.
484
485 As such, we are only supporting a limited subset of upgrades. We'll
486 upgrade the charm revision but leave storage and scale untouched.
487
488 Scale changes should happen through OSM constructs, and changes to
489 storage would require a redeployment of the service, at least in this
490 initial release.
491 """
beierlmf52cb7c2020-04-21 16:36:35 -0400492 raise MethodNotImplemented()
Adam Israeld4ec83b2019-11-07 09:46:59 -0500493
494 """Rollback"""
beierlmf52cb7c2020-04-21 16:36:35 -0400495
Adam Israeld4ec83b2019-11-07 09:46:59 -0500496 async def rollback(
David Garcia667696e2020-09-22 14:52:32 +0200497 self,
498 cluster_uuid: str,
499 kdu_instance: str,
500 revision: int = 0,
Adam Israeld4ec83b2019-11-07 09:46:59 -0500501 ) -> str:
502 """Rollback a model
503
504 :param cluster_uuid str: The UUID of the cluster to rollback
505 :param kdu_instance str: The unique name of the KDU instance
506 :param revision int: The revision to revert to. If omitted, rolls back
507 the previous upgrade.
508
509 :return: If successful, returns the revision of active KDU instance,
510 or raises an exception
511 """
beierlmf52cb7c2020-04-21 16:36:35 -0400512 raise MethodNotImplemented()
Adam Israeld4ec83b2019-11-07 09:46:59 -0500513
514 """Deletion"""
beierlmf52cb7c2020-04-21 16:36:35 -0400515
David Garciaeb8943a2021-04-12 12:07:37 +0200516 async def uninstall(
517 self,
518 cluster_uuid: str,
519 kdu_instance: str,
Pedro Escaleira764d8662022-04-19 20:40:09 +0100520 namespace: str = None,
David Garciaeb8943a2021-04-12 12:07:37 +0200521 **kwargs,
522 ) -> bool:
Adam Israeld4ec83b2019-11-07 09:46:59 -0500523 """Uninstall a KDU instance
524
Dominik Fleischmann847f3c02020-02-04 15:32:42 +0100525 :param cluster_uuid str: The UUID of the cluster
Adam Israeld4ec83b2019-11-07 09:46:59 -0500526 :param kdu_instance str: The unique name of the KDU instance
Pedro Escaleira764d8662022-04-19 20:40:09 +0100527 :param namespace str: The namespace (model) where the Bundle was deployed
David Garciaeb8943a2021-04-12 12:07:37 +0200528 :param kwargs: Additional parameters
529 vca_id (str): VCA ID
Adam Israeld4ec83b2019-11-07 09:46:59 -0500530
531 :return: Returns True if successful, or raises an exception
532 """
Pedro Escaleira764d8662022-04-19 20:40:09 +0100533 model_name = self._obtain_namespace(
534 kdu_instance=kdu_instance, namespace=namespace
535 )
David Garcia4f74f592020-07-23 15:04:19 +0200536
Pedro Escaleira764d8662022-04-19 20:40:09 +0100537 self.log.debug(f"[uninstall] Destroying model: {model_name}")
Dominik Fleischmann1ac78b32020-02-26 19:58:25 +0100538
David Garciacd986062022-05-05 09:46:06 +0200539 will_not_delete = False
Pedro Escaleira764d8662022-04-19 20:40:09 +0100540 if model_name not in self.uninstall_locks:
541 self.uninstall_locks[model_name] = asyncio.Lock(loop=self.loop)
542 delete_lock = self.uninstall_locks[model_name]
Adam Israeld4ec83b2019-11-07 09:46:59 -0500543
David Garciacd986062022-05-05 09:46:06 +0200544 while delete_lock.locked():
545 will_not_delete = True
546 await asyncio.sleep(0.1)
Dominik Fleischmann1ac78b32020-02-26 19:58:25 +0100547
David Garciacd986062022-05-05 09:46:06 +0200548 if will_not_delete:
Pedro Escaleira764d8662022-04-19 20:40:09 +0100549 self.log.info("Model {} deleted by another worker.".format(model_name))
David Garciacd986062022-05-05 09:46:06 +0200550 return True
551
552 try:
553 async with delete_lock:
554 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
555
Pedro Escaleira764d8662022-04-19 20:40:09 +0100556 await libjuju.destroy_model(model_name, total_timeout=3600)
David Garciacd986062022-05-05 09:46:06 +0200557 finally:
Pedro Escaleira764d8662022-04-19 20:40:09 +0100558 self.uninstall_locks.pop(model_name)
David Garciacd986062022-05-05 09:46:06 +0200559
Pedro Escaleira764d8662022-04-19 20:40:09 +0100560 self.log.debug(f"[uninstall] Model {model_name} destroyed")
Dominik Fleischmann847f3c02020-02-04 15:32:42 +0100561 return True
Adam Israeld4ec83b2019-11-07 09:46:59 -0500562
aticig8070c3c2022-04-18 00:31:42 +0300563 async def upgrade_charm(
564 self,
565 ee_id: str = None,
566 path: str = None,
567 charm_id: str = None,
568 charm_type: str = None,
569 timeout: float = None,
570 ) -> str:
571 """This method upgrade charms in VNFs
572
573 Args:
574 ee_id: Execution environment id
575 path: Local path to the charm
576 charm_id: charm-id
577 charm_type: Charm type can be lxc-proxy-charm, native-charm or k8s-proxy-charm
578 timeout: (Float) Timeout for the ns update operation
579
580 Returns:
581 The output of the update operation if status equals to "completed"
582 """
583 raise K8sException(
584 "KDUs deployed with Juju Bundle do not support charm upgrade"
585 )
586
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200587 async def exec_primitive(
588 self,
589 cluster_uuid: str = None,
590 kdu_instance: str = None,
591 primitive_name: str = None,
592 timeout: float = 300,
593 params: dict = None,
594 db_dict: dict = None,
Pedro Escaleira764d8662022-04-19 20:40:09 +0100595 namespace: str = None,
David Garciaeb8943a2021-04-12 12:07:37 +0200596 **kwargs,
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200597 ) -> str:
598 """Exec primitive (Juju action)
599
600 :param cluster_uuid str: The UUID of the cluster
601 :param kdu_instance str: The unique name of the KDU instance
602 :param primitive_name: Name of action that will be executed
603 :param timeout: Timeout for action execution
604 :param params: Dictionary of all the parameters needed for the action
David Garciaeb8943a2021-04-12 12:07:37 +0200605 :param db_dict: Dictionary for any additional data
Pedro Escaleira764d8662022-04-19 20:40:09 +0100606 :param namespace str: The namespace (model) where the Bundle was deployed
David Garciaeb8943a2021-04-12 12:07:37 +0200607 :param kwargs: Additional parameters
608 vca_id (str): VCA ID
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200609
610 :return: Returns the output of the action
611 """
David Garciaeb8943a2021-04-12 12:07:37 +0200612 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
David Garcia4f74f592020-07-23 15:04:19 +0200613
Pedro Escaleira764d8662022-04-19 20:40:09 +0100614 namespace = self._obtain_namespace(
615 kdu_instance=kdu_instance, namespace=namespace
616 )
617
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200618 if not params or "application-name" not in params:
beierlmf52cb7c2020-04-21 16:36:35 -0400619 raise K8sException(
620 "Missing application-name argument, \
621 argument needed for K8s actions"
622 )
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200623 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400624 self.log.debug(
625 "[exec_primitive] Getting model "
Pedro Escaleira764d8662022-04-19 20:40:09 +0100626 "{} for the kdu_instance: {}".format(namespace, kdu_instance)
beierlmf52cb7c2020-04-21 16:36:35 -0400627 )
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200628 application_name = params["application-name"]
Pedro Escaleira764d8662022-04-19 20:40:09 +0100629 actions = await libjuju.get_actions(
630 application_name=application_name, model_name=namespace
631 )
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200632 if primitive_name not in actions:
633 raise K8sException("Primitive {} not found".format(primitive_name))
David Garciaeb8943a2021-04-12 12:07:37 +0200634 output, status = await libjuju.execute_action(
Pedro Escaleira764d8662022-04-19 20:40:09 +0100635 application_name=application_name,
636 model_name=namespace,
637 action_name=primitive_name,
638 **params,
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200639 )
640
641 if status != "completed":
beierlmf52cb7c2020-04-21 16:36:35 -0400642 raise K8sException(
643 "status is not completed: {} output: {}".format(status, output)
644 )
ksaikiranrb816d822021-03-17 12:50:20 +0530645 if self.on_update_db:
garciadeblas82b591c2021-03-24 09:22:13 +0100646 await self.on_update_db(
Pedro Escaleira764d8662022-04-19 20:40:09 +0100647 cluster_uuid=cluster_uuid,
648 kdu_instance=kdu_instance,
649 filter=db_dict["filter"],
garciadeblas82b591c2021-03-24 09:22:13 +0100650 )
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200651
652 return output
653
654 except Exception as e:
655 error_msg = "Error executing primitive {}: {}".format(primitive_name, e)
656 self.log.error(error_msg)
657 raise K8sException(message=error_msg)
658
Adam Israeld4ec83b2019-11-07 09:46:59 -0500659 """Introspection"""
beierlmf52cb7c2020-04-21 16:36:35 -0400660
David Garcia667696e2020-09-22 14:52:32 +0200661 async def inspect_kdu(
662 self,
663 kdu_model: str,
664 ) -> dict:
Adam Israeld4ec83b2019-11-07 09:46:59 -0500665 """Inspect a KDU
666
667 Inspects a bundle and returns a dictionary of config parameters and
668 their default values.
669
670 :param kdu_model str: The name or path of the bundle to inspect.
671
672 :return: If successful, returns a dictionary of available parameters
673 and their default values.
674 """
675
676 kdu = {}
David Garcia667696e2020-09-22 14:52:32 +0200677 if not os.path.exists(kdu_model):
678 raise K8sException("file {} not found".format(kdu_model))
679
beierlmf52cb7c2020-04-21 16:36:35 -0400680 with open(kdu_model, "r") as f:
David Garcia667696e2020-09-22 14:52:32 +0200681 bundle = yaml.safe_load(f.read())
Adam Israeld4ec83b2019-11-07 09:46:59 -0500682
683 """
684 {
685 'description': 'Test bundle',
686 'bundle': 'kubernetes',
687 'applications': {
688 'mariadb-k8s': {
689 'charm': 'cs:~charmed-osm/mariadb-k8s-20',
690 'scale': 1,
691 'options': {
692 'password': 'manopw',
693 'root_password': 'osm4u',
694 'user': 'mano'
695 },
696 'series': 'kubernetes'
697 }
698 }
699 }
700 """
701 # TODO: This should be returned in an agreed-upon format
beierlmf52cb7c2020-04-21 16:36:35 -0400702 kdu = bundle["applications"]
Adam Israeld4ec83b2019-11-07 09:46:59 -0500703
704 return kdu
705
David Garcia667696e2020-09-22 14:52:32 +0200706 async def help_kdu(
707 self,
708 kdu_model: str,
709 ) -> str:
Adam Israeld4ec83b2019-11-07 09:46:59 -0500710 """View the README
711
Pedro Escaleira764d8662022-04-19 20:40:09 +0100712 If available, returns the README of the bundle.
Adam Israeld4ec83b2019-11-07 09:46:59 -0500713
Pedro Escaleira764d8662022-04-19 20:40:09 +0100714 :param kdu_model str: The name or path of a bundle
715 f
716 :return: If found, returns the contents of the README.
Adam Israeld4ec83b2019-11-07 09:46:59 -0500717 """
718 readme = None
719
beierlmf52cb7c2020-04-21 16:36:35 -0400720 files = ["README", "README.txt", "README.md"]
Adam Israeld4ec83b2019-11-07 09:46:59 -0500721 path = os.path.dirname(kdu_model)
722 for file in os.listdir(path):
723 if file in files:
beierlmf52cb7c2020-04-21 16:36:35 -0400724 with open(file, "r") as f:
Adam Israeld4ec83b2019-11-07 09:46:59 -0500725 readme = f.read()
726 break
727
728 return readme
729
David Garcia667696e2020-09-22 14:52:32 +0200730 async def status_kdu(
731 self,
732 cluster_uuid: str,
733 kdu_instance: str,
ksaikiranrb816d822021-03-17 12:50:20 +0530734 complete_status: bool = False,
David Garciaeb8943a2021-04-12 12:07:37 +0200735 yaml_format: bool = False,
Pedro Escaleira764d8662022-04-19 20:40:09 +0100736 namespace: str = None,
David Garciaeb8943a2021-04-12 12:07:37 +0200737 **kwargs,
Pedro Escaleiraa8980cc2022-04-05 17:32:13 +0100738 ) -> Union[str, dict]:
Adam Israeld4ec83b2019-11-07 09:46:59 -0500739 """Get the status of the KDU
740
741 Get the current status of the KDU instance.
742
743 :param cluster_uuid str: The UUID of the cluster
744 :param kdu_instance str: The unique id of the KDU instance
ksaikiranrb816d822021-03-17 12:50:20 +0530745 :param complete_status: To get the complete_status of the KDU
746 :param yaml_format: To get the status in proper format for NSR record
Pedro Escaleira764d8662022-04-19 20:40:09 +0100747 :param namespace str: The namespace (model) where the Bundle was deployed
David Garciaeb8943a2021-04-12 12:07:37 +0200748 :param: kwargs: Additional parameters
749 vca_id (str): VCA ID
Adam Israeld4ec83b2019-11-07 09:46:59 -0500750
751 :return: Returns a dictionary containing namespace, state, resources,
ksaikiranrb816d822021-03-17 12:50:20 +0530752 and deployment_time and returns complete_status if complete_status is True
Adam Israeld4ec83b2019-11-07 09:46:59 -0500753 """
David Garciaeb8943a2021-04-12 12:07:37 +0200754 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
Adam Israeld4ec83b2019-11-07 09:46:59 -0500755 status = {}
ksaikiranrb816d822021-03-17 12:50:20 +0530756
Pedro Escaleira764d8662022-04-19 20:40:09 +0100757 model_name = self._obtain_namespace(
758 kdu_instance=kdu_instance, namespace=namespace
759 )
760 model_status = await libjuju.get_model_status(model_name=model_name)
ksaikiranrb816d822021-03-17 12:50:20 +0530761
762 if not complete_status:
763 for name in model_status.applications:
764 application = model_status.applications[name]
765 status[name] = {"status": application["status"]["status"]}
766 else:
767 if yaml_format:
768 return obj_to_yaml(model_status)
769 else:
770 return obj_to_dict(model_status)
Adam Israeld4ec83b2019-11-07 09:46:59 -0500771
Adam Israeld4ec83b2019-11-07 09:46:59 -0500772 return status
773
David Garcia582b9232021-10-26 12:30:44 +0200774 async def add_relation(
775 self,
776 provider: RelationEndpoint,
777 requirer: RelationEndpoint,
778 ):
779 """
780 Add relation between two charmed endpoints
781
782 :param: provider: Provider relation endpoint
783 :param: requirer: Requirer relation endpoint
784 """
785 self.log.debug(f"adding new relation between {provider} and {requirer}")
786 cross_model_relation = (
787 provider.model_name != requirer.model_name
788 or requirer.vca_id != requirer.vca_id
789 )
790 try:
791 if cross_model_relation:
792 # Cross-model relation
793 provider_libjuju = await self._get_libjuju(provider.vca_id)
794 requirer_libjuju = await self._get_libjuju(requirer.vca_id)
795 offer = await provider_libjuju.offer(provider)
796 if offer:
797 saas_name = await requirer_libjuju.consume(
798 requirer.model_name, offer, provider_libjuju
799 )
800 await requirer_libjuju.add_relation(
801 requirer.model_name,
802 requirer.endpoint,
803 saas_name,
804 )
805 else:
806 # Standard relation
807 vca_id = provider.vca_id
808 model = provider.model_name
809 libjuju = await self._get_libjuju(vca_id)
810 # add juju relations between two applications
811 await libjuju.add_relation(
812 model_name=model,
813 endpoint_1=provider.endpoint,
814 endpoint_2=requirer.endpoint,
815 )
816 except Exception as e:
817 message = f"Error adding relation between {provider} and {requirer}: {e}"
818 self.log.error(message)
819 raise Exception(message=message)
820
Pedro Escaleira764d8662022-04-19 20:40:09 +0100821 async def update_vca_status(
822 self, vcastatus: dict, kdu_instance: str, namespace: str = None, **kwargs
823 ):
ksaikiranrb816d822021-03-17 12:50:20 +0530824 """
825 Add all configs, actions, executed actions of all applications in a model to vcastatus dict
826
827 :param vcastatus dict: dict containing vcastatus
828 :param kdu_instance str: The unique id of the KDU instance
Pedro Escaleira764d8662022-04-19 20:40:09 +0100829 :param namespace str: The namespace (model) where the Bundle was deployed
David Garciaeb8943a2021-04-12 12:07:37 +0200830 :param: kwargs: Additional parameters
831 vca_id (str): VCA ID
ksaikiranrb816d822021-03-17 12:50:20 +0530832
833 :return: None
834 """
Pedro Escaleira764d8662022-04-19 20:40:09 +0100835
836 model_name = self._obtain_namespace(
837 kdu_instance=kdu_instance, namespace=namespace
838 )
839
David Garciaeb8943a2021-04-12 12:07:37 +0200840 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
ksaikiranrb816d822021-03-17 12:50:20 +0530841 try:
Pedro Escaleira764d8662022-04-19 20:40:09 +0100842 for vca_model_name in vcastatus:
ksaikiranrb816d822021-03-17 12:50:20 +0530843 # Adding executed actions
Pedro Escaleira764d8662022-04-19 20:40:09 +0100844 vcastatus[vca_model_name][
garciadeblas82b591c2021-03-24 09:22:13 +0100845 "executedActions"
Pedro Escaleira764d8662022-04-19 20:40:09 +0100846 ] = await libjuju.get_executed_actions(model_name=model_name)
ksaikiranrb816d822021-03-17 12:50:20 +0530847
Pedro Escaleira764d8662022-04-19 20:40:09 +0100848 for application in vcastatus[vca_model_name]["applications"]:
ksaikiranrb816d822021-03-17 12:50:20 +0530849 # Adding application actions
Pedro Escaleira764d8662022-04-19 20:40:09 +0100850 vcastatus[vca_model_name]["applications"][application][
851 "actions"
852 ] = {}
ksaikiranrb816d822021-03-17 12:50:20 +0530853 # Adding application configs
Pedro Escaleira764d8662022-04-19 20:40:09 +0100854 vcastatus[vca_model_name]["applications"][application][
garciadeblas82b591c2021-03-24 09:22:13 +0100855 "configs"
Pedro Escaleira764d8662022-04-19 20:40:09 +0100856 ] = await libjuju.get_application_configs(
857 model_name=model_name, application_name=application
858 )
ksaikiranrb816d822021-03-17 12:50:20 +0530859
860 except Exception as e:
861 self.log.debug("Error in updating vca status: {}".format(str(e)))
862
David Garcia5d799392020-07-02 13:56:58 +0200863 async def get_services(
864 self, cluster_uuid: str, kdu_instance: str, namespace: str
865 ) -> list:
866 """Return a list of services of a kdu_instance"""
lloretgallegd99f3f22020-06-29 14:18:30 +0000867
Pedro Escaleira764d8662022-04-19 20:40:09 +0100868 namespace = self._obtain_namespace(
869 kdu_instance=kdu_instance, namespace=namespace
870 )
871
David Garcia2c791b32020-07-22 17:56:12 +0200872 credentials = self.get_credentials(cluster_uuid=cluster_uuid)
David Garciad8d4b6e2021-06-24 18:47:22 +0200873 kubectl = self._get_kubectl(credentials)
David Garcia5d799392020-07-02 13:56:58 +0200874 return kubectl.get_services(
Pedro Escaleira764d8662022-04-19 20:40:09 +0100875 field_selector="metadata.namespace={}".format(namespace)
David Garcia5d799392020-07-02 13:56:58 +0200876 )
877
878 async def get_service(
879 self, cluster_uuid: str, service_name: str, namespace: str
880 ) -> object:
881 """Return data for a specific service inside a namespace"""
882
David Garcia2c791b32020-07-22 17:56:12 +0200883 credentials = self.get_credentials(cluster_uuid=cluster_uuid)
David Garciad8d4b6e2021-06-24 18:47:22 +0200884 kubectl = self._get_kubectl(credentials)
David Garcia5d799392020-07-02 13:56:58 +0200885 return kubectl.get_services(
886 field_selector="metadata.name={},metadata.namespace={}".format(
887 service_name, namespace
888 )
889 )[0]
lloretgallegd99f3f22020-06-29 14:18:30 +0000890
David Garcia2c791b32020-07-22 17:56:12 +0200891 def get_credentials(self, cluster_uuid: str) -> str:
David Garcia5d799392020-07-02 13:56:58 +0200892 """
David Garcia2c791b32020-07-22 17:56:12 +0200893 Get Cluster Kubeconfig
David Garcia5d799392020-07-02 13:56:58 +0200894 """
David Garcia2c791b32020-07-22 17:56:12 +0200895 k8scluster = self.db.get_one(
896 "k8sclusters", q_filter={"_id": cluster_uuid}, fail_on_empty=False
897 )
898
899 self.db.encrypt_decrypt_fields(
900 k8scluster.get("credentials"),
901 "decrypt",
902 ["password", "secret"],
903 schema_version=k8scluster["schema_version"],
904 salt=k8scluster["_id"],
905 )
906
907 return yaml.safe_dump(k8scluster.get("credentials"))
David Garcia5d799392020-07-02 13:56:58 +0200908
David Garcia667696e2020-09-22 14:52:32 +0200909 def _get_credential_name(self, cluster_uuid: str) -> str:
Adam Israeld4ec83b2019-11-07 09:46:59 -0500910 """
David Garcia667696e2020-09-22 14:52:32 +0200911 Get credential name for a k8s cloud
David Garcia4f74f592020-07-23 15:04:19 +0200912
David Garcia667696e2020-09-22 14:52:32 +0200913 We cannot use the cluster_uuid for the credential name directly,
914 because it cannot start with a number, it must start with a letter.
915 Therefore, the k8s cloud credential name will be "cred-" followed
916 by the cluster uuid.
Adam Israeld4ec83b2019-11-07 09:46:59 -0500917
David Garcia667696e2020-09-22 14:52:32 +0200918 :param: cluster_uuid: Cluster UUID of the kubernetes cloud (=cloud_name)
Adam Israeld4ec83b2019-11-07 09:46:59 -0500919
David Garcia667696e2020-09-22 14:52:32 +0200920 :return: Name to use for the credential name.
Adam Israeld4ec83b2019-11-07 09:46:59 -0500921 """
David Garcia667696e2020-09-22 14:52:32 +0200922 return "cred-{}".format(cluster_uuid)
Adam Israeld4ec83b2019-11-07 09:46:59 -0500923
David Garcia667696e2020-09-22 14:52:32 +0200924 def get_namespace(
925 self,
926 cluster_uuid: str,
927 ) -> str:
Adam Israeld4ec83b2019-11-07 09:46:59 -0500928 """Get the namespace UUID
929 Gets the namespace's unique name
930
931 :param cluster_uuid str: The UUID of the cluster
932 :returns: The namespace UUID, or raises an exception
933 """
David Garcia667696e2020-09-22 14:52:32 +0200934 pass
Adam Israeld4ec83b2019-11-07 09:46:59 -0500935
David Garciac4da25c2021-02-23 11:47:29 +0100936 @staticmethod
937 def generate_kdu_instance_name(**kwargs):
938 db_dict = kwargs.get("db_dict")
939 kdu_name = kwargs.get("kdu_name", None)
940 if kdu_name:
941 kdu_instance = "{}-{}".format(kdu_name, db_dict["filter"]["_id"])
942 else:
943 kdu_instance = db_dict["filter"]["_id"]
944 return kdu_instance
David Garciaeb8943a2021-04-12 12:07:37 +0200945
946 async def _get_libjuju(self, vca_id: str = None) -> Libjuju:
947 """
948 Get libjuju object
949
950 :param: vca_id: VCA ID
951 If None, get a libjuju object with a Connection to the default VCA
952 Else, geta libjuju object with a Connection to the specified VCA
953 """
954 if not vca_id:
955 while self.loading_libjuju.locked():
956 await asyncio.sleep(0.1)
957 if not self.libjuju:
958 async with self.loading_libjuju:
959 vca_connection = await get_connection(self._store)
960 self.libjuju = Libjuju(vca_connection, loop=self.loop, log=self.log)
961 return self.libjuju
962 else:
963 vca_connection = await get_connection(self._store, vca_id)
964 return Libjuju(
965 vca_connection,
966 loop=self.loop,
967 log=self.log,
968 n2vc=self,
969 )
David Garciad8d4b6e2021-06-24 18:47:22 +0200970
971 def _get_kubectl(self, credentials: str) -> Kubectl:
972 """
973 Get Kubectl object
974
975 :param: kubeconfig_credentials: Kubeconfig credentials
976 """
977 kubecfg = tempfile.NamedTemporaryFile()
978 with open(kubecfg.name, "w") as kubecfg_file:
979 kubecfg_file.write(credentials)
980 return Kubectl(config_file=kubecfg.name)
Pedro Escaleira764d8662022-04-19 20:40:09 +0100981
982 def _obtain_namespace(self, kdu_instance: str, namespace: str = None) -> str:
983 """
984 Obtain the namespace/model name to use in the instantiation of a Juju Bundle in K8s. The default namespace is
985 the kdu_instance name. However, if the user passes the namespace where he wants to deploy the bundle,
986 that namespace will be used.
987
988 :param kdu_instance: the default KDU instance name
989 :param namespace: the namespace passed by the User
990 """
991
992 # deault the namespace/model name to the kdu_instance name TODO -> this should be the real return... But
993 # once the namespace is not passed in most methods, I had to do this in another way. But I think this should
994 # be the procedure in the future return namespace if namespace else kdu_instance
995
996 # TODO -> has referred above, this should be avoided in the future, this is temporary, in order to avoid
997 # compatibility issues
998 return (
999 namespace
1000 if namespace
1001 else self._obtain_namespace_from_db(kdu_instance=kdu_instance)
1002 )
1003
1004 def _obtain_namespace_from_db(self, kdu_instance: str) -> str:
1005 db_nsrs = self.db.get_one(
1006 table="nsrs", q_filter={"_admin.deployed.K8s.kdu-instance": kdu_instance}
1007 )
1008 for k8s in db_nsrs["_admin"]["deployed"]["K8s"]:
1009 if k8s.get("kdu-instance") == kdu_instance:
1010 return k8s.get("namespace")
1011 return ""