blob: babe239752a616922dd9994f2b9ac7da3b2a5820 [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
Patricia Reinoso085942e2022-12-05 16:55:51 +000067 K8sConnector.__init__(self, db, log=log, on_update_db=on_update_db)
Adam Israeld4ec83b2019-11-07 09:46:59 -050068
Adam Israeleef68932019-11-28 16:27:46 -050069 self.fs = fs
David Garcia667696e2020-09-22 14:52:32 +020070 self.loop = loop or asyncio.get_event_loop()
beierlmf52cb7c2020-04-21 16:36:35 -040071 self.log.debug("Initializing K8S Juju connector")
Adam Israeld4ec83b2019-11-07 09:46:59 -050072
David Garciaeb8943a2021-04-12 12:07:37 +020073 db_uri = EnvironConfig(prefixes=["OSMLCM_", "OSMMON_"]).get("database_uri")
74 self._store = MotorStore(db_uri)
75 self.loading_libjuju = asyncio.Lock(loop=self.loop)
David Garciacd986062022-05-05 09:46:06 +020076 self.uninstall_locks = {}
Adam Israeleef68932019-11-28 16:27:46 -050077
beierlmf52cb7c2020-04-21 16:36:35 -040078 self.log.debug("K8S Juju connector initialized")
David Garcia4f74f592020-07-23 15:04:19 +020079 # TODO: Remove these commented lines:
80 # self.authenticated = False
81 # self.models = {}
82 # self.juju_secret = ""
Adam Israeld4ec83b2019-11-07 09:46:59 -050083
84 """Initialization"""
beierlmf52cb7c2020-04-21 16:36:35 -040085
Adam Israeld4ec83b2019-11-07 09:46:59 -050086 async def init_env(
87 self,
Adam Israeleef68932019-11-28 16:27:46 -050088 k8s_creds: str,
beierlmf52cb7c2020-04-21 16:36:35 -040089 namespace: str = "kube-system",
Adam Israeld4ec83b2019-11-07 09:46:59 -050090 reuse_cluster_uuid: str = None,
David Garciaeb8943a2021-04-12 12:07:37 +020091 **kwargs,
Adam Israeleef68932019-11-28 16:27:46 -050092 ) -> (str, bool):
garciadeblas54771fa2019-12-13 13:39:03 +010093 """
94 It prepares a given K8s cluster environment to run Juju bundles.
Adam Israeld4ec83b2019-11-07 09:46:59 -050095
beierlmf52cb7c2020-04-21 16:36:35 -040096 :param k8s_creds: credentials to access a given K8s cluster, i.e. a valid
97 '.kube/config'
98 :param namespace: optional namespace to be used for juju. By default,
99 'kube-system' will be used
garciadeblas54771fa2019-12-13 13:39:03 +0100100 :param reuse_cluster_uuid: existing cluster uuid for reuse
David Garciaeb8943a2021-04-12 12:07:37 +0200101 :param: kwargs: Additional parameters
102 vca_id (str): VCA ID
103
beierlmf52cb7c2020-04-21 16:36:35 -0400104 :return: uuid of the K8s cluster and True if connector has installed some
105 software in the cluster
106 (on error, an exception will be raised)
Adam Israeld4ec83b2019-11-07 09:46:59 -0500107 """
David Garciaeb8943a2021-04-12 12:07:37 +0200108 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
Adam Israeld4ec83b2019-11-07 09:46:59 -0500109
David Garcia37004982020-07-16 17:53:20 +0200110 cluster_uuid = reuse_cluster_uuid or str(uuid.uuid4())
David Garciad8d4b6e2021-06-24 18:47:22 +0200111 kubectl = self._get_kubectl(k8s_creds)
Adam Israeld4ec83b2019-11-07 09:46:59 -0500112
David Garciaf6e9b002020-11-27 15:32:02 +0100113 # CREATING RESOURCES IN K8S
114 rbac_id = generate_rbac_id()
115 metadata_name = "{}-{}".format(RBAC_STACK_PREFIX, rbac_id)
116 labels = {RBAC_STACK_PREFIX: rbac_id}
David Garcia4f74f592020-07-23 15:04:19 +0200117
David Garciaf6e9b002020-11-27 15:32:02 +0100118 # Create cleanup dictionary to clean up created resources
119 # if it fails in the middle of the process
120 cleanup_data = []
121 try:
garciadeblas47f65382021-05-31 15:49:15 +0200122 self.log.debug("Initializing K8s cluster for juju")
Patricia Reinoso085942e2022-12-05 16:55:51 +0000123 kubectl.create_cluster_role(name=metadata_name, labels=labels)
garciadeblas47f65382021-05-31 15:49:15 +0200124 self.log.debug("Cluster role created")
David Garciaf6e9b002020-11-27 15:32:02 +0100125 cleanup_data.append(
Patricia Reinoso085942e2022-12-05 16:55:51 +0000126 {"delete": kubectl.delete_cluster_role, "args": (metadata_name,)}
David Garciaf6e9b002020-11-27 15:32:02 +0100127 )
Adam Israeld4ec83b2019-11-07 09:46:59 -0500128
Patricia Reinoso085942e2022-12-05 16:55:51 +0000129 kubectl.create_service_account(name=metadata_name, labels=labels)
garciadeblas47f65382021-05-31 15:49:15 +0200130 self.log.debug("Service account created")
David Garciaf6e9b002020-11-27 15:32:02 +0100131 cleanup_data.append(
Patricia Reinoso085942e2022-12-05 16:55:51 +0000132 {"delete": kubectl.delete_service_account, "args": (metadata_name,)}
David Garciaf6e9b002020-11-27 15:32:02 +0100133 )
Adam Israeld4ec83b2019-11-07 09:46:59 -0500134
Patricia Reinoso085942e2022-12-05 16:55:51 +0000135 kubectl.create_cluster_role_binding(name=metadata_name, labels=labels)
garciadeblas47f65382021-05-31 15:49:15 +0200136 self.log.debug("Role binding created")
David Garciaf6e9b002020-11-27 15:32:02 +0100137 cleanup_data.append(
138 {
Patricia Reinoso4b68cb62022-08-30 16:08:48 +0000139 "delete": kubectl.delete_cluster_role_binding,
Pedro Escaleira15667792022-03-17 23:59:29 +0000140 "args": (metadata_name,),
David Garciaf6e9b002020-11-27 15:32:02 +0100141 }
142 )
Patricia Reinoso085942e2022-12-05 16:55:51 +0000143 token, client_cert_data = await kubectl.get_secret_data(metadata_name)
Adam Israeld4ec83b2019-11-07 09:46:59 -0500144
David Garciaf6e9b002020-11-27 15:32:02 +0100145 default_storage_class = kubectl.get_default_storage_class()
garciadeblas47f65382021-05-31 15:49:15 +0200146 self.log.debug("Default storage class: {}".format(default_storage_class))
David Garciaeb8943a2021-04-12 12:07:37 +0200147 await libjuju.add_k8s(
David Garciaf6e9b002020-11-27 15:32:02 +0100148 name=cluster_uuid,
149 rbac_id=rbac_id,
150 token=token,
151 client_cert_data=client_cert_data,
152 configuration=kubectl.configuration,
153 storage_class=default_storage_class,
154 credential_name=self._get_credential_name(cluster_uuid),
155 )
garciadeblas47f65382021-05-31 15:49:15 +0200156 self.log.debug("K8s cluster added to juju controller")
David Garciaf6e9b002020-11-27 15:32:02 +0100157 return cluster_uuid, True
158 except Exception as e:
garciadeblas47f65382021-05-31 15:49:15 +0200159 self.log.error("Error initializing k8scluster: {}".format(e), exc_info=True)
David Garciaf6e9b002020-11-27 15:32:02 +0100160 if len(cleanup_data) > 0:
161 self.log.debug("Cleaning up created resources in k8s cluster...")
162 for item in cleanup_data:
163 delete_function = item["delete"]
164 delete_args = item["args"]
165 delete_function(*delete_args)
166 self.log.debug("Cleanup finished")
167 raise e
Adam Israeld4ec83b2019-11-07 09:46:59 -0500168
169 """Repo Management"""
beierlmf52cb7c2020-04-21 16:36:35 -0400170
Adam Israeld4ec83b2019-11-07 09:46:59 -0500171 async def repo_add(
David Garcia667696e2020-09-22 14:52:32 +0200172 self,
173 name: str,
174 url: str,
175 _type: str = "charm",
bravof0ab522f2021-11-23 19:33:18 -0300176 cert: str = None,
177 user: str = None,
178 password: str = None,
Adam Israeld4ec83b2019-11-07 09:46:59 -0500179 ):
beierlmf52cb7c2020-04-21 16:36:35 -0400180 raise MethodNotImplemented()
Adam Israeld4ec83b2019-11-07 09:46:59 -0500181
182 async def repo_list(self):
beierlmf52cb7c2020-04-21 16:36:35 -0400183 raise MethodNotImplemented()
Adam Israeld4ec83b2019-11-07 09:46:59 -0500184
Patricia Reinoso085942e2022-12-05 16:55:51 +0000185 async def repo_remove(self, name: str):
beierlmf52cb7c2020-04-21 16:36:35 -0400186 raise MethodNotImplemented()
Adam Israeld4ec83b2019-11-07 09:46:59 -0500187
beierlmf52cb7c2020-04-21 16:36:35 -0400188 async def synchronize_repos(self, cluster_uuid: str, name: str):
lloretgalleg65ddf852020-02-20 12:01:17 +0100189 """
190 Returns None as currently add_repo is not implemented
191 """
192 return None
193
Adam Israeld4ec83b2019-11-07 09:46:59 -0500194 """Reset"""
beierlmf52cb7c2020-04-21 16:36:35 -0400195
Adam Israeld4ec83b2019-11-07 09:46:59 -0500196 async def reset(
David Garciaeb8943a2021-04-12 12:07:37 +0200197 self,
198 cluster_uuid: str,
199 force: bool = False,
200 uninstall_sw: bool = False,
201 **kwargs,
Adam Israeld4ec83b2019-11-07 09:46:59 -0500202 ) -> bool:
203 """Reset a cluster
204
205 Resets the Kubernetes cluster by removing the model that represents it.
206
207 :param cluster_uuid str: The UUID of the cluster to reset
David Garciaeb8943a2021-04-12 12:07:37 +0200208 :param force: Force reset
209 :param uninstall_sw: Boolean to uninstall sw
210 :param: kwargs: Additional parameters
211 vca_id (str): VCA ID
212
Adam Israeld4ec83b2019-11-07 09:46:59 -0500213 :return: Returns True if successful or raises an exception.
214 """
215
216 try:
David Garcia4f74f592020-07-23 15:04:19 +0200217 self.log.debug("[reset] Removing k8s cloud")
David Garciaeb8943a2021-04-12 12:07:37 +0200218 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
David Garciaf6e9b002020-11-27 15:32:02 +0100219
David Garciaeb8943a2021-04-12 12:07:37 +0200220 cloud = Cloud(cluster_uuid, self._get_credential_name(cluster_uuid))
David Garciaf6e9b002020-11-27 15:32:02 +0100221
David Garciaeb8943a2021-04-12 12:07:37 +0200222 cloud_creds = await libjuju.get_cloud_credentials(cloud)
223
224 await libjuju.remove_cloud(cluster_uuid)
Adam Israeld4ec83b2019-11-07 09:46:59 -0500225
David Garciad8d4b6e2021-06-24 18:47:22 +0200226 credentials = self.get_credentials(cluster_uuid=cluster_uuid)
David Garciaf6e9b002020-11-27 15:32:02 +0100227
David Garciad8d4b6e2021-06-24 18:47:22 +0200228 kubectl = self._get_kubectl(credentials)
David Garciaf6e9b002020-11-27 15:32:02 +0100229
230 delete_functions = [
David Garciad8d4b6e2021-06-24 18:47:22 +0200231 kubectl.delete_cluster_role_binding,
232 kubectl.delete_service_account,
233 kubectl.delete_cluster_role,
David Garciaf6e9b002020-11-27 15:32:02 +0100234 ]
235
236 credential_attrs = cloud_creds[0].result["attrs"]
237 if RBAC_LABEL_KEY_NAME in credential_attrs:
238 rbac_id = credential_attrs[RBAC_LABEL_KEY_NAME]
239 metadata_name = "{}-{}".format(RBAC_STACK_PREFIX, rbac_id)
David Garciaf6e9b002020-11-27 15:32:02 +0100240 for delete_func in delete_functions:
241 try:
David Garciad8d4b6e2021-06-24 18:47:22 +0200242 delete_func(metadata_name)
David Garciaf6e9b002020-11-27 15:32:02 +0100243 except Exception as e:
244 self.log.warning("Cannot remove resource in K8s {}".format(e))
245
David Garcia667696e2020-09-22 14:52:32 +0200246 except Exception as e:
247 self.log.debug("Caught exception during reset: {}".format(e))
248 raise e
Dominik Fleischmann1ac78b32020-02-26 19:58:25 +0100249 return True
250
Adam Israeld4ec83b2019-11-07 09:46:59 -0500251 """Deployment"""
Adam Israeleef68932019-11-28 16:27:46 -0500252
Adam Israeld4ec83b2019-11-07 09:46:59 -0500253 async def install(
254 self,
255 cluster_uuid: str,
256 kdu_model: str,
David Garciac4da25c2021-02-23 11:47:29 +0100257 kdu_instance: str,
Adam Israeld4ec83b2019-11-07 09:46:59 -0500258 atomic: bool = True,
David Garcia667696e2020-09-22 14:52:32 +0200259 timeout: float = 1800,
Adam Israeld4ec83b2019-11-07 09:46:59 -0500260 params: dict = None,
Dominik Fleischmann847f3c02020-02-04 15:32:42 +0100261 db_dict: dict = None,
tierno53555f62020-04-07 11:08:16 +0000262 kdu_name: str = None,
beierlmf52cb7c2020-04-21 16:36:35 -0400263 namespace: str = None,
David Garciaeb8943a2021-04-12 12:07:37 +0200264 **kwargs,
Adam Israeleef68932019-11-28 16:27:46 -0500265 ) -> bool:
Adam Israeld4ec83b2019-11-07 09:46:59 -0500266 """Install a bundle
267
268 :param cluster_uuid str: The UUID of the cluster to install to
269 :param kdu_model str: The name or path of a bundle to install
David Garciac4da25c2021-02-23 11:47:29 +0100270 :param kdu_instance: Kdu instance name
Adam Israeld4ec83b2019-11-07 09:46:59 -0500271 :param atomic bool: If set, waits until the model is active and resets
272 the cluster on failure.
273 :param timeout int: The time, in seconds, to wait for the install
274 to finish
275 :param params dict: Key-value pairs of instantiation parameters
Dominik Fleischmann847f3c02020-02-04 15:32:42 +0100276 :param kdu_name: Name of the KDU instance to be installed
tierno53555f62020-04-07 11:08:16 +0000277 :param namespace: K8s namespace to use for the KDU instance
David Garciaeb8943a2021-04-12 12:07:37 +0200278 :param kwargs: Additional parameters
279 vca_id (str): VCA ID
Adam Israeld4ec83b2019-11-07 09:46:59 -0500280
281 :return: If successful, returns ?
282 """
David Garciaeb8943a2021-04-12 12:07:37 +0200283 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
David Garcia667696e2020-09-22 14:52:32 +0200284 bundle = kdu_model
Adam Israeld4ec83b2019-11-07 09:46:59 -0500285
David Garcia667696e2020-09-22 14:52:32 +0200286 if not db_dict:
287 raise K8sException("db_dict must be set")
288 if not bundle:
289 raise K8sException("bundle must be set")
290
291 if bundle.startswith("cs:"):
Pedro Escaleira86a63142022-04-05 21:01:37 +0100292 # For Juju Bundles provided by the Charm Store
293 pass
294 elif bundle.startswith("ch:"):
295 # For Juju Bundles provided by the Charm Hub (this only works for juju version >= 2.9)
David Garcia667696e2020-09-22 14:52:32 +0200296 pass
297 elif bundle.startswith("http"):
298 # Download the file
299 pass
300 else:
301 new_workdir = kdu_model.strip(kdu_model.split("/")[-1])
302 os.chdir(new_workdir)
303 bundle = "local:{}".format(kdu_model)
304
Pedro Escaleira764d8662022-04-19 20:40:09 +0100305 # default namespace to kdu_instance
306 if not namespace:
307 namespace = kdu_instance
308
309 self.log.debug("Checking for model named {}".format(namespace))
Dominik Fleischmann1ac78b32020-02-26 19:58:25 +0100310
311 # Create the new model
Pedro Escaleira764d8662022-04-19 20:40:09 +0100312 self.log.debug("Adding model: {}".format(namespace))
David Garciaeb8943a2021-04-12 12:07:37 +0200313 cloud = Cloud(cluster_uuid, self._get_credential_name(cluster_uuid))
Pedro Escaleira764d8662022-04-19 20:40:09 +0100314 await libjuju.add_model(namespace, cloud)
Adam Israeld4ec83b2019-11-07 09:46:59 -0500315
David Garcia667696e2020-09-22 14:52:32 +0200316 # if model:
317 # TODO: Instantiation parameters
Adam Israeld4ec83b2019-11-07 09:46:59 -0500318
David Garcia667696e2020-09-22 14:52:32 +0200319 """
320 "Juju bundle that models the KDU, in any of the following ways:
321 - <juju-repo>/<juju-bundle>
322 - <juju-bundle folder under k8s_models folder in the package>
323 - <juju-bundle tgz file (w/ or w/o extension) under k8s_models folder
324 in the package>
325 - <URL_where_to_fetch_juju_bundle>
326 """
327 try:
328 previous_workdir = os.getcwd()
329 except FileNotFoundError:
330 previous_workdir = "/app/storage"
Dominik Fleischmann45d95772020-03-26 12:21:42 +0100331
David Garcia667696e2020-09-22 14:52:32 +0200332 self.log.debug("[install] deploying {}".format(bundle))
Patricia Reinosofedf9152023-01-17 08:39:44 +0000333 instantiation_params = params.get("overlay") if params else None
334 await libjuju.deploy(
335 bundle,
336 model_name=namespace,
337 wait=atomic,
338 timeout=timeout,
339 instantiation_params=instantiation_params,
340 )
David Garcia667696e2020-09-22 14:52:32 +0200341 os.chdir(previous_workdir)
Pedro Escaleira764d8662022-04-19 20:40:09 +0100342
343 # update information in the database (first, the VCA status, and then, the namespace)
ksaikiranrb816d822021-03-17 12:50:20 +0530344 if self.on_update_db:
David Garciaeb8943a2021-04-12 12:07:37 +0200345 await self.on_update_db(
346 cluster_uuid,
347 kdu_instance,
348 filter=db_dict["filter"],
garciadeblas82b591c2021-03-24 09:22:13 +0100349 vca_id=kwargs.get("vca_id"),
David Garciaeb8943a2021-04-12 12:07:37 +0200350 )
Pedro Escaleira764d8662022-04-19 20:40:09 +0100351
352 self.db.set_one(
353 table="nsrs",
354 q_filter={"_admin.deployed.K8s.kdu-instance": kdu_instance},
355 update_dict={"_admin.deployed.K8s.$.namespace": namespace},
356 )
357
David Garciac4da25c2021-02-23 11:47:29 +0100358 return True
Adam Israeld4ec83b2019-11-07 09:46:59 -0500359
aktas2962f3e2021-03-15 11:05:35 +0300360 async def scale(
garciadeblas82b591c2021-03-24 09:22:13 +0100361 self,
362 kdu_instance: str,
363 scale: int,
364 resource_name: str,
365 total_timeout: float = 1800,
Pedro Escaleira764d8662022-04-19 20:40:09 +0100366 namespace: str = None,
garciadeblas82b591c2021-03-24 09:22:13 +0100367 **kwargs,
aktas2962f3e2021-03-15 11:05:35 +0300368 ) -> bool:
369 """Scale an application in a model
370
371 :param: kdu_instance str: KDU instance name
aktas867418c2021-10-19 18:26:13 +0300372 :param: scale int: Scale to which to set the application
373 :param: resource_name str: The application name in the Juju Bundle
aktas2962f3e2021-03-15 11:05:35 +0300374 :param: timeout float: The time, in seconds, to wait for the install
375 to finish
Pedro Escaleira764d8662022-04-19 20:40:09 +0100376 :param namespace str: The namespace (model) where the Bundle was deployed
aktas2962f3e2021-03-15 11:05:35 +0300377 :param kwargs: Additional parameters
378 vca_id (str): VCA ID
379
380 :return: If successful, returns True
381 """
382
Pedro Escaleira764d8662022-04-19 20:40:09 +0100383 model_name = self._obtain_namespace(
384 kdu_instance=kdu_instance, namespace=namespace
385 )
aktas2962f3e2021-03-15 11:05:35 +0300386 try:
387 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
388 await libjuju.scale_application(
Pedro Escaleira764d8662022-04-19 20:40:09 +0100389 model_name=model_name,
aktas2962f3e2021-03-15 11:05:35 +0300390 application_name=resource_name,
391 scale=scale,
garciadeblas82b591c2021-03-24 09:22:13 +0100392 total_timeout=total_timeout,
aktas2962f3e2021-03-15 11:05:35 +0300393 )
394 except Exception as e:
Pedro Escaleira764d8662022-04-19 20:40:09 +0100395 error_msg = "Error scaling application {} of the model {} of the kdu instance {}: {}".format(
396 resource_name, model_name, kdu_instance, e
garciadeblas82b591c2021-03-24 09:22:13 +0100397 )
aktas2962f3e2021-03-15 11:05:35 +0300398 self.log.error(error_msg)
399 raise K8sException(message=error_msg)
400 return True
401
402 async def get_scale_count(
Patricia Reinoso085942e2022-12-05 16:55:51 +0000403 self, resource_name: str, kdu_instance: str, namespace: str = None, **kwargs
aktas2962f3e2021-03-15 11:05:35 +0300404 ) -> int:
405 """Get an application scale count
406
aktas867418c2021-10-19 18:26:13 +0300407 :param: resource_name str: The application name in the Juju Bundle
aktas2962f3e2021-03-15 11:05:35 +0300408 :param: kdu_instance str: KDU instance name
Pedro Escaleira764d8662022-04-19 20:40:09 +0100409 :param namespace str: The namespace (model) where the Bundle was deployed
aktas2962f3e2021-03-15 11:05:35 +0300410 :param kwargs: Additional parameters
411 vca_id (str): VCA ID
412 :return: Return application instance count
413 """
aktas867418c2021-10-19 18:26:13 +0300414
Pedro Escaleira764d8662022-04-19 20:40:09 +0100415 model_name = self._obtain_namespace(
416 kdu_instance=kdu_instance, namespace=namespace
417 )
aktas2962f3e2021-03-15 11:05:35 +0300418 try:
419 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
Pedro Escaleira764d8662022-04-19 20:40:09 +0100420 status = await libjuju.get_model_status(model_name=model_name)
aktas2962f3e2021-03-15 11:05:35 +0300421 return len(status.applications[resource_name].units)
422 except Exception as e:
Pedro Escaleira764d8662022-04-19 20:40:09 +0100423 error_msg = (
424 f"Error getting scale count from application {resource_name} of the model {model_name} of "
425 f"the kdu instance {kdu_instance}: {e}"
garciadeblas82b591c2021-03-24 09:22:13 +0100426 )
aktas2962f3e2021-03-15 11:05:35 +0300427 self.log.error(error_msg)
428 raise K8sException(message=error_msg)
429
beierlmf52cb7c2020-04-21 16:36:35 -0400430 async def instances_list(self, cluster_uuid: str) -> list:
Adam Israeld4ec83b2019-11-07 09:46:59 -0500431 """
432 returns a list of deployed releases in a cluster
433
434 :param cluster_uuid: the cluster
435 :return:
436 """
437 return []
438
439 async def upgrade(
440 self,
441 cluster_uuid: str,
442 kdu_instance: str,
443 kdu_model: str = None,
444 params: dict = None,
445 ) -> str:
446 """Upgrade a model
447
448 :param cluster_uuid str: The UUID of the cluster to upgrade
449 :param kdu_instance str: The unique name of the KDU instance
450 :param kdu_model str: The name or path of the bundle to upgrade to
451 :param params dict: Key-value pairs of instantiation parameters
452
453 :return: If successful, reference to the new revision number of the
454 KDU instance.
455 """
456
457 # TODO: Loop through the bundle and upgrade each charm individually
458
459 """
460 The API doesn't have a concept of bundle upgrades, because there are
461 many possible changes: charm revision, disk, number of units, etc.
462
463 As such, we are only supporting a limited subset of upgrades. We'll
464 upgrade the charm revision but leave storage and scale untouched.
465
466 Scale changes should happen through OSM constructs, and changes to
467 storage would require a redeployment of the service, at least in this
468 initial release.
469 """
beierlmf52cb7c2020-04-21 16:36:35 -0400470 raise MethodNotImplemented()
Adam Israeld4ec83b2019-11-07 09:46:59 -0500471
472 """Rollback"""
beierlmf52cb7c2020-04-21 16:36:35 -0400473
Adam Israeld4ec83b2019-11-07 09:46:59 -0500474 async def rollback(
Patricia Reinoso085942e2022-12-05 16:55:51 +0000475 self, cluster_uuid: str, kdu_instance: str, revision: int = 0
Adam Israeld4ec83b2019-11-07 09:46:59 -0500476 ) -> str:
477 """Rollback a model
478
479 :param cluster_uuid str: The UUID of the cluster to rollback
480 :param kdu_instance str: The unique name of the KDU instance
481 :param revision int: The revision to revert to. If omitted, rolls back
482 the previous upgrade.
483
484 :return: If successful, returns the revision of active KDU instance,
485 or raises an exception
486 """
beierlmf52cb7c2020-04-21 16:36:35 -0400487 raise MethodNotImplemented()
Adam Israeld4ec83b2019-11-07 09:46:59 -0500488
489 """Deletion"""
beierlmf52cb7c2020-04-21 16:36:35 -0400490
David Garciaeb8943a2021-04-12 12:07:37 +0200491 async def uninstall(
Patricia Reinoso085942e2022-12-05 16:55:51 +0000492 self, cluster_uuid: str, kdu_instance: str, namespace: str = None, **kwargs
David Garciaeb8943a2021-04-12 12:07:37 +0200493 ) -> bool:
Adam Israeld4ec83b2019-11-07 09:46:59 -0500494 """Uninstall a KDU instance
495
Dominik Fleischmann847f3c02020-02-04 15:32:42 +0100496 :param cluster_uuid str: The UUID of the cluster
Adam Israeld4ec83b2019-11-07 09:46:59 -0500497 :param kdu_instance str: The unique name of the KDU instance
Pedro Escaleira764d8662022-04-19 20:40:09 +0100498 :param namespace str: The namespace (model) where the Bundle was deployed
David Garciaeb8943a2021-04-12 12:07:37 +0200499 :param kwargs: Additional parameters
500 vca_id (str): VCA ID
Adam Israeld4ec83b2019-11-07 09:46:59 -0500501
502 :return: Returns True if successful, or raises an exception
503 """
Pedro Escaleira764d8662022-04-19 20:40:09 +0100504 model_name = self._obtain_namespace(
505 kdu_instance=kdu_instance, namespace=namespace
506 )
David Garcia4f74f592020-07-23 15:04:19 +0200507
Pedro Escaleira764d8662022-04-19 20:40:09 +0100508 self.log.debug(f"[uninstall] Destroying model: {model_name}")
Dominik Fleischmann1ac78b32020-02-26 19:58:25 +0100509
David Garciacd986062022-05-05 09:46:06 +0200510 will_not_delete = False
Pedro Escaleira764d8662022-04-19 20:40:09 +0100511 if model_name not in self.uninstall_locks:
512 self.uninstall_locks[model_name] = asyncio.Lock(loop=self.loop)
513 delete_lock = self.uninstall_locks[model_name]
Adam Israeld4ec83b2019-11-07 09:46:59 -0500514
David Garciacd986062022-05-05 09:46:06 +0200515 while delete_lock.locked():
516 will_not_delete = True
517 await asyncio.sleep(0.1)
Dominik Fleischmann1ac78b32020-02-26 19:58:25 +0100518
David Garciacd986062022-05-05 09:46:06 +0200519 if will_not_delete:
Pedro Escaleira764d8662022-04-19 20:40:09 +0100520 self.log.info("Model {} deleted by another worker.".format(model_name))
David Garciacd986062022-05-05 09:46:06 +0200521 return True
522
523 try:
524 async with delete_lock:
525 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
526
Pedro Escaleira764d8662022-04-19 20:40:09 +0100527 await libjuju.destroy_model(model_name, total_timeout=3600)
David Garciacd986062022-05-05 09:46:06 +0200528 finally:
Pedro Escaleira764d8662022-04-19 20:40:09 +0100529 self.uninstall_locks.pop(model_name)
David Garciacd986062022-05-05 09:46:06 +0200530
Pedro Escaleira764d8662022-04-19 20:40:09 +0100531 self.log.debug(f"[uninstall] Model {model_name} destroyed")
Dominik Fleischmann847f3c02020-02-04 15:32:42 +0100532 return True
Adam Israeld4ec83b2019-11-07 09:46:59 -0500533
aticig8070c3c2022-04-18 00:31:42 +0300534 async def upgrade_charm(
535 self,
536 ee_id: str = None,
537 path: str = None,
538 charm_id: str = None,
539 charm_type: str = None,
540 timeout: float = None,
541 ) -> str:
542 """This method upgrade charms in VNFs
543
544 Args:
545 ee_id: Execution environment id
546 path: Local path to the charm
547 charm_id: charm-id
548 charm_type: Charm type can be lxc-proxy-charm, native-charm or k8s-proxy-charm
549 timeout: (Float) Timeout for the ns update operation
550
551 Returns:
552 The output of the update operation if status equals to "completed"
553 """
554 raise K8sException(
555 "KDUs deployed with Juju Bundle do not support charm upgrade"
556 )
557
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200558 async def exec_primitive(
559 self,
560 cluster_uuid: str = None,
561 kdu_instance: str = None,
562 primitive_name: str = None,
563 timeout: float = 300,
564 params: dict = None,
565 db_dict: dict = None,
Pedro Escaleira764d8662022-04-19 20:40:09 +0100566 namespace: str = None,
David Garciaeb8943a2021-04-12 12:07:37 +0200567 **kwargs,
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200568 ) -> str:
569 """Exec primitive (Juju action)
570
571 :param cluster_uuid str: The UUID of the cluster
572 :param kdu_instance str: The unique name of the KDU instance
573 :param primitive_name: Name of action that will be executed
574 :param timeout: Timeout for action execution
575 :param params: Dictionary of all the parameters needed for the action
David Garciaeb8943a2021-04-12 12:07:37 +0200576 :param db_dict: Dictionary for any additional data
Pedro Escaleira764d8662022-04-19 20:40:09 +0100577 :param namespace str: The namespace (model) where the Bundle was deployed
David Garciaeb8943a2021-04-12 12:07:37 +0200578 :param kwargs: Additional parameters
579 vca_id (str): VCA ID
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200580
581 :return: Returns the output of the action
582 """
David Garciaeb8943a2021-04-12 12:07:37 +0200583 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
David Garcia4f74f592020-07-23 15:04:19 +0200584
Pedro Escaleira764d8662022-04-19 20:40:09 +0100585 namespace = self._obtain_namespace(
586 kdu_instance=kdu_instance, namespace=namespace
587 )
588
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200589 if not params or "application-name" not in params:
beierlmf52cb7c2020-04-21 16:36:35 -0400590 raise K8sException(
591 "Missing application-name argument, \
592 argument needed for K8s actions"
593 )
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200594 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400595 self.log.debug(
596 "[exec_primitive] Getting model "
Pedro Escaleira764d8662022-04-19 20:40:09 +0100597 "{} for the kdu_instance: {}".format(namespace, kdu_instance)
beierlmf52cb7c2020-04-21 16:36:35 -0400598 )
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200599 application_name = params["application-name"]
Pedro Escaleira764d8662022-04-19 20:40:09 +0100600 actions = await libjuju.get_actions(
601 application_name=application_name, model_name=namespace
602 )
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200603 if primitive_name not in actions:
604 raise K8sException("Primitive {} not found".format(primitive_name))
David Garciaeb8943a2021-04-12 12:07:37 +0200605 output, status = await libjuju.execute_action(
Pedro Escaleira764d8662022-04-19 20:40:09 +0100606 application_name=application_name,
607 model_name=namespace,
608 action_name=primitive_name,
609 **params,
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200610 )
611
612 if status != "completed":
beierlmf52cb7c2020-04-21 16:36:35 -0400613 raise K8sException(
614 "status is not completed: {} output: {}".format(status, output)
615 )
ksaikiranrb816d822021-03-17 12:50:20 +0530616 if self.on_update_db:
garciadeblas82b591c2021-03-24 09:22:13 +0100617 await self.on_update_db(
Pedro Escaleira764d8662022-04-19 20:40:09 +0100618 cluster_uuid=cluster_uuid,
619 kdu_instance=kdu_instance,
620 filter=db_dict["filter"],
garciadeblas82b591c2021-03-24 09:22:13 +0100621 )
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200622
623 return output
624
625 except Exception as e:
626 error_msg = "Error executing primitive {}: {}".format(primitive_name, e)
627 self.log.error(error_msg)
628 raise K8sException(message=error_msg)
629
Adam Israeld4ec83b2019-11-07 09:46:59 -0500630 """Introspection"""
beierlmf52cb7c2020-04-21 16:36:35 -0400631
Patricia Reinoso085942e2022-12-05 16:55:51 +0000632 async def inspect_kdu(self, kdu_model: str) -> dict:
Adam Israeld4ec83b2019-11-07 09:46:59 -0500633 """Inspect a KDU
634
635 Inspects a bundle and returns a dictionary of config parameters and
636 their default values.
637
638 :param kdu_model str: The name or path of the bundle to inspect.
639
640 :return: If successful, returns a dictionary of available parameters
641 and their default values.
642 """
643
644 kdu = {}
David Garcia667696e2020-09-22 14:52:32 +0200645 if not os.path.exists(kdu_model):
646 raise K8sException("file {} not found".format(kdu_model))
647
beierlmf52cb7c2020-04-21 16:36:35 -0400648 with open(kdu_model, "r") as f:
David Garcia667696e2020-09-22 14:52:32 +0200649 bundle = yaml.safe_load(f.read())
Adam Israeld4ec83b2019-11-07 09:46:59 -0500650
651 """
652 {
653 'description': 'Test bundle',
654 'bundle': 'kubernetes',
655 'applications': {
656 'mariadb-k8s': {
657 'charm': 'cs:~charmed-osm/mariadb-k8s-20',
658 'scale': 1,
659 'options': {
660 'password': 'manopw',
661 'root_password': 'osm4u',
662 'user': 'mano'
663 },
664 'series': 'kubernetes'
665 }
666 }
667 }
668 """
669 # TODO: This should be returned in an agreed-upon format
beierlmf52cb7c2020-04-21 16:36:35 -0400670 kdu = bundle["applications"]
Adam Israeld4ec83b2019-11-07 09:46:59 -0500671
672 return kdu
673
Patricia Reinoso085942e2022-12-05 16:55:51 +0000674 async def help_kdu(self, kdu_model: str) -> str:
Adam Israeld4ec83b2019-11-07 09:46:59 -0500675 """View the README
676
Pedro Escaleira764d8662022-04-19 20:40:09 +0100677 If available, returns the README of the bundle.
Adam Israeld4ec83b2019-11-07 09:46:59 -0500678
Pedro Escaleira764d8662022-04-19 20:40:09 +0100679 :param kdu_model str: The name or path of a bundle
680 f
681 :return: If found, returns the contents of the README.
Adam Israeld4ec83b2019-11-07 09:46:59 -0500682 """
683 readme = None
684
beierlmf52cb7c2020-04-21 16:36:35 -0400685 files = ["README", "README.txt", "README.md"]
Adam Israeld4ec83b2019-11-07 09:46:59 -0500686 path = os.path.dirname(kdu_model)
687 for file in os.listdir(path):
688 if file in files:
beierlmf52cb7c2020-04-21 16:36:35 -0400689 with open(file, "r") as f:
Adam Israeld4ec83b2019-11-07 09:46:59 -0500690 readme = f.read()
691 break
692
693 return readme
694
David Garcia667696e2020-09-22 14:52:32 +0200695 async def status_kdu(
696 self,
697 cluster_uuid: str,
698 kdu_instance: str,
ksaikiranrb816d822021-03-17 12:50:20 +0530699 complete_status: bool = False,
David Garciaeb8943a2021-04-12 12:07:37 +0200700 yaml_format: bool = False,
Pedro Escaleira764d8662022-04-19 20:40:09 +0100701 namespace: str = None,
David Garciaeb8943a2021-04-12 12:07:37 +0200702 **kwargs,
Pedro Escaleiraa8980cc2022-04-05 17:32:13 +0100703 ) -> Union[str, dict]:
Adam Israeld4ec83b2019-11-07 09:46:59 -0500704 """Get the status of the KDU
705
706 Get the current status of the KDU instance.
707
708 :param cluster_uuid str: The UUID of the cluster
709 :param kdu_instance str: The unique id of the KDU instance
ksaikiranrb816d822021-03-17 12:50:20 +0530710 :param complete_status: To get the complete_status of the KDU
711 :param yaml_format: To get the status in proper format for NSR record
Pedro Escaleira764d8662022-04-19 20:40:09 +0100712 :param namespace str: The namespace (model) where the Bundle was deployed
David Garciaeb8943a2021-04-12 12:07:37 +0200713 :param: kwargs: Additional parameters
714 vca_id (str): VCA ID
Adam Israeld4ec83b2019-11-07 09:46:59 -0500715
716 :return: Returns a dictionary containing namespace, state, resources,
ksaikiranrb816d822021-03-17 12:50:20 +0530717 and deployment_time and returns complete_status if complete_status is True
Adam Israeld4ec83b2019-11-07 09:46:59 -0500718 """
David Garciaeb8943a2021-04-12 12:07:37 +0200719 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
Adam Israeld4ec83b2019-11-07 09:46:59 -0500720 status = {}
ksaikiranrb816d822021-03-17 12:50:20 +0530721
Pedro Escaleira764d8662022-04-19 20:40:09 +0100722 model_name = self._obtain_namespace(
723 kdu_instance=kdu_instance, namespace=namespace
724 )
725 model_status = await libjuju.get_model_status(model_name=model_name)
ksaikiranrb816d822021-03-17 12:50:20 +0530726
727 if not complete_status:
728 for name in model_status.applications:
729 application = model_status.applications[name]
730 status[name] = {"status": application["status"]["status"]}
731 else:
732 if yaml_format:
733 return obj_to_yaml(model_status)
734 else:
735 return obj_to_dict(model_status)
Adam Israeld4ec83b2019-11-07 09:46:59 -0500736
Adam Israeld4ec83b2019-11-07 09:46:59 -0500737 return status
738
David Garcia582b9232021-10-26 12:30:44 +0200739 async def add_relation(
Patricia Reinoso085942e2022-12-05 16:55:51 +0000740 self, provider: RelationEndpoint, requirer: RelationEndpoint
David Garcia582b9232021-10-26 12:30:44 +0200741 ):
742 """
743 Add relation between two charmed endpoints
744
745 :param: provider: Provider relation endpoint
746 :param: requirer: Requirer relation endpoint
747 """
748 self.log.debug(f"adding new relation between {provider} and {requirer}")
749 cross_model_relation = (
750 provider.model_name != requirer.model_name
Patricia Reinoso085942e2022-12-05 16:55:51 +0000751 or provider.vca_id != requirer.vca_id
David Garcia582b9232021-10-26 12:30:44 +0200752 )
753 try:
754 if cross_model_relation:
755 # Cross-model relation
756 provider_libjuju = await self._get_libjuju(provider.vca_id)
757 requirer_libjuju = await self._get_libjuju(requirer.vca_id)
758 offer = await provider_libjuju.offer(provider)
759 if offer:
760 saas_name = await requirer_libjuju.consume(
761 requirer.model_name, offer, provider_libjuju
762 )
763 await requirer_libjuju.add_relation(
Patricia Reinoso085942e2022-12-05 16:55:51 +0000764 requirer.model_name, requirer.endpoint, saas_name
David Garcia582b9232021-10-26 12:30:44 +0200765 )
766 else:
767 # Standard relation
768 vca_id = provider.vca_id
769 model = provider.model_name
770 libjuju = await self._get_libjuju(vca_id)
771 # add juju relations between two applications
772 await libjuju.add_relation(
773 model_name=model,
774 endpoint_1=provider.endpoint,
775 endpoint_2=requirer.endpoint,
776 )
777 except Exception as e:
778 message = f"Error adding relation between {provider} and {requirer}: {e}"
779 self.log.error(message)
780 raise Exception(message=message)
781
Pedro Escaleira764d8662022-04-19 20:40:09 +0100782 async def update_vca_status(
783 self, vcastatus: dict, kdu_instance: str, namespace: str = None, **kwargs
784 ):
ksaikiranrb816d822021-03-17 12:50:20 +0530785 """
786 Add all configs, actions, executed actions of all applications in a model to vcastatus dict
787
788 :param vcastatus dict: dict containing vcastatus
789 :param kdu_instance str: The unique id of the KDU instance
Pedro Escaleira764d8662022-04-19 20:40:09 +0100790 :param namespace str: The namespace (model) where the Bundle was deployed
David Garciaeb8943a2021-04-12 12:07:37 +0200791 :param: kwargs: Additional parameters
792 vca_id (str): VCA ID
ksaikiranrb816d822021-03-17 12:50:20 +0530793
794 :return: None
795 """
Pedro Escaleira764d8662022-04-19 20:40:09 +0100796
797 model_name = self._obtain_namespace(
798 kdu_instance=kdu_instance, namespace=namespace
799 )
800
David Garciaeb8943a2021-04-12 12:07:37 +0200801 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
ksaikiranrb816d822021-03-17 12:50:20 +0530802 try:
Pedro Escaleira764d8662022-04-19 20:40:09 +0100803 for vca_model_name in vcastatus:
ksaikiranrb816d822021-03-17 12:50:20 +0530804 # Adding executed actions
Pedro Escaleira764d8662022-04-19 20:40:09 +0100805 vcastatus[vca_model_name][
garciadeblas82b591c2021-03-24 09:22:13 +0100806 "executedActions"
Pedro Escaleira764d8662022-04-19 20:40:09 +0100807 ] = await libjuju.get_executed_actions(model_name=model_name)
ksaikiranrb816d822021-03-17 12:50:20 +0530808
Pedro Escaleira764d8662022-04-19 20:40:09 +0100809 for application in vcastatus[vca_model_name]["applications"]:
ksaikiranrb816d822021-03-17 12:50:20 +0530810 # Adding application actions
Pedro Escaleira764d8662022-04-19 20:40:09 +0100811 vcastatus[vca_model_name]["applications"][application][
812 "actions"
813 ] = {}
ksaikiranrb816d822021-03-17 12:50:20 +0530814 # Adding application configs
Pedro Escaleira764d8662022-04-19 20:40:09 +0100815 vcastatus[vca_model_name]["applications"][application][
garciadeblas82b591c2021-03-24 09:22:13 +0100816 "configs"
Pedro Escaleira764d8662022-04-19 20:40:09 +0100817 ] = await libjuju.get_application_configs(
818 model_name=model_name, application_name=application
819 )
ksaikiranrb816d822021-03-17 12:50:20 +0530820
821 except Exception as e:
822 self.log.debug("Error in updating vca status: {}".format(str(e)))
823
David Garcia5d799392020-07-02 13:56:58 +0200824 async def get_services(
825 self, cluster_uuid: str, kdu_instance: str, namespace: str
826 ) -> list:
827 """Return a list of services of a kdu_instance"""
lloretgallegd99f3f22020-06-29 14:18:30 +0000828
Pedro Escaleira764d8662022-04-19 20:40:09 +0100829 namespace = self._obtain_namespace(
830 kdu_instance=kdu_instance, namespace=namespace
831 )
832
David Garcia2c791b32020-07-22 17:56:12 +0200833 credentials = self.get_credentials(cluster_uuid=cluster_uuid)
David Garciad8d4b6e2021-06-24 18:47:22 +0200834 kubectl = self._get_kubectl(credentials)
David Garcia5d799392020-07-02 13:56:58 +0200835 return kubectl.get_services(
Pedro Escaleira764d8662022-04-19 20:40:09 +0100836 field_selector="metadata.namespace={}".format(namespace)
David Garcia5d799392020-07-02 13:56:58 +0200837 )
838
839 async def get_service(
840 self, cluster_uuid: str, service_name: str, namespace: str
841 ) -> object:
842 """Return data for a specific service inside a namespace"""
843
David Garcia2c791b32020-07-22 17:56:12 +0200844 credentials = self.get_credentials(cluster_uuid=cluster_uuid)
David Garciad8d4b6e2021-06-24 18:47:22 +0200845 kubectl = self._get_kubectl(credentials)
David Garcia5d799392020-07-02 13:56:58 +0200846 return kubectl.get_services(
847 field_selector="metadata.name={},metadata.namespace={}".format(
848 service_name, namespace
849 )
850 )[0]
lloretgallegd99f3f22020-06-29 14:18:30 +0000851
David Garcia2c791b32020-07-22 17:56:12 +0200852 def get_credentials(self, cluster_uuid: str) -> str:
David Garcia5d799392020-07-02 13:56:58 +0200853 """
David Garcia2c791b32020-07-22 17:56:12 +0200854 Get Cluster Kubeconfig
David Garcia5d799392020-07-02 13:56:58 +0200855 """
David Garcia2c791b32020-07-22 17:56:12 +0200856 k8scluster = self.db.get_one(
857 "k8sclusters", q_filter={"_id": cluster_uuid}, fail_on_empty=False
858 )
859
860 self.db.encrypt_decrypt_fields(
861 k8scluster.get("credentials"),
862 "decrypt",
863 ["password", "secret"],
864 schema_version=k8scluster["schema_version"],
865 salt=k8scluster["_id"],
866 )
867
868 return yaml.safe_dump(k8scluster.get("credentials"))
David Garcia5d799392020-07-02 13:56:58 +0200869
David Garcia667696e2020-09-22 14:52:32 +0200870 def _get_credential_name(self, cluster_uuid: str) -> str:
Adam Israeld4ec83b2019-11-07 09:46:59 -0500871 """
David Garcia667696e2020-09-22 14:52:32 +0200872 Get credential name for a k8s cloud
David Garcia4f74f592020-07-23 15:04:19 +0200873
David Garcia667696e2020-09-22 14:52:32 +0200874 We cannot use the cluster_uuid for the credential name directly,
875 because it cannot start with a number, it must start with a letter.
876 Therefore, the k8s cloud credential name will be "cred-" followed
877 by the cluster uuid.
Adam Israeld4ec83b2019-11-07 09:46:59 -0500878
David Garcia667696e2020-09-22 14:52:32 +0200879 :param: cluster_uuid: Cluster UUID of the kubernetes cloud (=cloud_name)
Adam Israeld4ec83b2019-11-07 09:46:59 -0500880
David Garcia667696e2020-09-22 14:52:32 +0200881 :return: Name to use for the credential name.
Adam Israeld4ec83b2019-11-07 09:46:59 -0500882 """
David Garcia667696e2020-09-22 14:52:32 +0200883 return "cred-{}".format(cluster_uuid)
Adam Israeld4ec83b2019-11-07 09:46:59 -0500884
Patricia Reinoso085942e2022-12-05 16:55:51 +0000885 def get_namespace(self, cluster_uuid: str) -> str:
Adam Israeld4ec83b2019-11-07 09:46:59 -0500886 """Get the namespace UUID
887 Gets the namespace's unique name
888
889 :param cluster_uuid str: The UUID of the cluster
890 :returns: The namespace UUID, or raises an exception
891 """
David Garcia667696e2020-09-22 14:52:32 +0200892 pass
Adam Israeld4ec83b2019-11-07 09:46:59 -0500893
David Garciac4da25c2021-02-23 11:47:29 +0100894 @staticmethod
895 def generate_kdu_instance_name(**kwargs):
896 db_dict = kwargs.get("db_dict")
897 kdu_name = kwargs.get("kdu_name", None)
898 if kdu_name:
899 kdu_instance = "{}-{}".format(kdu_name, db_dict["filter"]["_id"])
900 else:
901 kdu_instance = db_dict["filter"]["_id"]
902 return kdu_instance
David Garciaeb8943a2021-04-12 12:07:37 +0200903
904 async def _get_libjuju(self, vca_id: str = None) -> Libjuju:
905 """
906 Get libjuju object
907
908 :param: vca_id: VCA ID
909 If None, get a libjuju object with a Connection to the default VCA
910 Else, geta libjuju object with a Connection to the specified VCA
911 """
912 if not vca_id:
913 while self.loading_libjuju.locked():
914 await asyncio.sleep(0.1)
915 if not self.libjuju:
916 async with self.loading_libjuju:
917 vca_connection = await get_connection(self._store)
918 self.libjuju = Libjuju(vca_connection, loop=self.loop, log=self.log)
919 return self.libjuju
920 else:
921 vca_connection = await get_connection(self._store, vca_id)
Patricia Reinoso085942e2022-12-05 16:55:51 +0000922 return Libjuju(vca_connection, loop=self.loop, log=self.log, n2vc=self)
David Garciad8d4b6e2021-06-24 18:47:22 +0200923
924 def _get_kubectl(self, credentials: str) -> Kubectl:
925 """
926 Get Kubectl object
927
928 :param: kubeconfig_credentials: Kubeconfig credentials
929 """
930 kubecfg = tempfile.NamedTemporaryFile()
931 with open(kubecfg.name, "w") as kubecfg_file:
932 kubecfg_file.write(credentials)
933 return Kubectl(config_file=kubecfg.name)
Pedro Escaleira764d8662022-04-19 20:40:09 +0100934
935 def _obtain_namespace(self, kdu_instance: str, namespace: str = None) -> str:
936 """
937 Obtain the namespace/model name to use in the instantiation of a Juju Bundle in K8s. The default namespace is
938 the kdu_instance name. However, if the user passes the namespace where he wants to deploy the bundle,
939 that namespace will be used.
940
941 :param kdu_instance: the default KDU instance name
942 :param namespace: the namespace passed by the User
943 """
944
945 # deault the namespace/model name to the kdu_instance name TODO -> this should be the real return... But
946 # once the namespace is not passed in most methods, I had to do this in another way. But I think this should
947 # be the procedure in the future return namespace if namespace else kdu_instance
948
949 # TODO -> has referred above, this should be avoided in the future, this is temporary, in order to avoid
950 # compatibility issues
951 return (
952 namespace
953 if namespace
954 else self._obtain_namespace_from_db(kdu_instance=kdu_instance)
955 )
956
957 def _obtain_namespace_from_db(self, kdu_instance: str) -> str:
958 db_nsrs = self.db.get_one(
959 table="nsrs", q_filter={"_admin.deployed.K8s.kdu-instance": kdu_instance}
960 )
961 for k8s in db_nsrs["_admin"]["deployed"]["K8s"]:
962 if k8s.get("kdu-instance") == kdu_instance:
963 return k8s.get("namespace")
964 return ""