blob: c197221ffb3afe95a4caeb29594eb62ca47e0c56 [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,
54 on_update_db=None,
Adam Israeld4ec83b2019-11-07 09:46:59 -050055 ):
56 """
David Garcia667696e2020-09-22 14:52:32 +020057 :param fs: file system for kubernetes and helm configuration
58 :param db: Database object
Adam Israeld4ec83b2019-11-07 09:46:59 -050059 :param kubectl_command: path to kubectl executable
60 :param helm_command: path to helm executable
Adam Israeld4ec83b2019-11-07 09:46:59 -050061 :param log: logger
62 """
63
64 # parent class
Patricia Reinoso085942e2022-12-05 16:55:51 +000065 K8sConnector.__init__(self, db, log=log, on_update_db=on_update_db)
Adam Israeld4ec83b2019-11-07 09:46:59 -050066
Adam Israeleef68932019-11-28 16:27:46 -050067 self.fs = fs
beierlmf52cb7c2020-04-21 16:36:35 -040068 self.log.debug("Initializing K8S Juju connector")
Adam Israeld4ec83b2019-11-07 09:46:59 -050069
David Garciaeb8943a2021-04-12 12:07:37 +020070 db_uri = EnvironConfig(prefixes=["OSMLCM_", "OSMMON_"]).get("database_uri")
71 self._store = MotorStore(db_uri)
Guillermo Calvino474fd952023-04-28 11:51:43 +020072 self.loading_libjuju = asyncio.Lock()
David Garciacd986062022-05-05 09:46:06 +020073 self.uninstall_locks = {}
Adam Israeleef68932019-11-28 16:27:46 -050074
beierlmf52cb7c2020-04-21 16:36:35 -040075 self.log.debug("K8S Juju connector initialized")
David Garcia4f74f592020-07-23 15:04:19 +020076 # TODO: Remove these commented lines:
77 # self.authenticated = False
78 # self.models = {}
79 # self.juju_secret = ""
Adam Israeld4ec83b2019-11-07 09:46:59 -050080
81 """Initialization"""
beierlmf52cb7c2020-04-21 16:36:35 -040082
Adam Israeld4ec83b2019-11-07 09:46:59 -050083 async def init_env(
84 self,
Adam Israeleef68932019-11-28 16:27:46 -050085 k8s_creds: str,
beierlmf52cb7c2020-04-21 16:36:35 -040086 namespace: str = "kube-system",
Adam Israeld4ec83b2019-11-07 09:46:59 -050087 reuse_cluster_uuid: str = None,
David Garciaeb8943a2021-04-12 12:07:37 +020088 **kwargs,
Adam Israeleef68932019-11-28 16:27:46 -050089 ) -> (str, bool):
garciadeblas54771fa2019-12-13 13:39:03 +010090 """
91 It prepares a given K8s cluster environment to run Juju bundles.
Adam Israeld4ec83b2019-11-07 09:46:59 -050092
beierlmf52cb7c2020-04-21 16:36:35 -040093 :param k8s_creds: credentials to access a given K8s cluster, i.e. a valid
94 '.kube/config'
95 :param namespace: optional namespace to be used for juju. By default,
96 'kube-system' will be used
garciadeblas54771fa2019-12-13 13:39:03 +010097 :param reuse_cluster_uuid: existing cluster uuid for reuse
David Garciaeb8943a2021-04-12 12:07:37 +020098 :param: kwargs: Additional parameters
99 vca_id (str): VCA ID
100
beierlmf52cb7c2020-04-21 16:36:35 -0400101 :return: uuid of the K8s cluster and True if connector has installed some
102 software in the cluster
103 (on error, an exception will be raised)
Adam Israeld4ec83b2019-11-07 09:46:59 -0500104 """
David Garciaeb8943a2021-04-12 12:07:37 +0200105 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
Adam Israeld4ec83b2019-11-07 09:46:59 -0500106
David Garcia37004982020-07-16 17:53:20 +0200107 cluster_uuid = reuse_cluster_uuid or str(uuid.uuid4())
David Garciad8d4b6e2021-06-24 18:47:22 +0200108 kubectl = self._get_kubectl(k8s_creds)
Adam Israeld4ec83b2019-11-07 09:46:59 -0500109
David Garciaf6e9b002020-11-27 15:32:02 +0100110 # CREATING RESOURCES IN K8S
111 rbac_id = generate_rbac_id()
112 metadata_name = "{}-{}".format(RBAC_STACK_PREFIX, rbac_id)
113 labels = {RBAC_STACK_PREFIX: rbac_id}
David Garcia4f74f592020-07-23 15:04:19 +0200114
David Garciaf6e9b002020-11-27 15:32:02 +0100115 # Create cleanup dictionary to clean up created resources
116 # if it fails in the middle of the process
117 cleanup_data = []
118 try:
garciadeblas47f65382021-05-31 15:49:15 +0200119 self.log.debug("Initializing K8s cluster for juju")
Patricia Reinoso085942e2022-12-05 16:55:51 +0000120 kubectl.create_cluster_role(name=metadata_name, labels=labels)
garciadeblas47f65382021-05-31 15:49:15 +0200121 self.log.debug("Cluster role created")
David Garciaf6e9b002020-11-27 15:32:02 +0100122 cleanup_data.append(
Patricia Reinoso085942e2022-12-05 16:55:51 +0000123 {"delete": kubectl.delete_cluster_role, "args": (metadata_name,)}
David Garciaf6e9b002020-11-27 15:32:02 +0100124 )
Adam Israeld4ec83b2019-11-07 09:46:59 -0500125
Patricia Reinoso085942e2022-12-05 16:55:51 +0000126 kubectl.create_service_account(name=metadata_name, labels=labels)
garciadeblas47f65382021-05-31 15:49:15 +0200127 self.log.debug("Service account created")
David Garciaf6e9b002020-11-27 15:32:02 +0100128 cleanup_data.append(
Patricia Reinoso085942e2022-12-05 16:55:51 +0000129 {"delete": kubectl.delete_service_account, "args": (metadata_name,)}
David Garciaf6e9b002020-11-27 15:32:02 +0100130 )
Adam Israeld4ec83b2019-11-07 09:46:59 -0500131
Patricia Reinoso085942e2022-12-05 16:55:51 +0000132 kubectl.create_cluster_role_binding(name=metadata_name, labels=labels)
garciadeblas47f65382021-05-31 15:49:15 +0200133 self.log.debug("Role binding created")
David Garciaf6e9b002020-11-27 15:32:02 +0100134 cleanup_data.append(
135 {
Patricia Reinoso4b68cb62022-08-30 16:08:48 +0000136 "delete": kubectl.delete_cluster_role_binding,
Pedro Escaleira15667792022-03-17 23:59:29 +0000137 "args": (metadata_name,),
David Garciaf6e9b002020-11-27 15:32:02 +0100138 }
139 )
Patricia Reinoso085942e2022-12-05 16:55:51 +0000140 token, client_cert_data = await kubectl.get_secret_data(metadata_name)
Adam Israeld4ec83b2019-11-07 09:46:59 -0500141
David Garciaf6e9b002020-11-27 15:32:02 +0100142 default_storage_class = kubectl.get_default_storage_class()
garciadeblas47f65382021-05-31 15:49:15 +0200143 self.log.debug("Default storage class: {}".format(default_storage_class))
David Garciaeb8943a2021-04-12 12:07:37 +0200144 await libjuju.add_k8s(
David Garciaf6e9b002020-11-27 15:32:02 +0100145 name=cluster_uuid,
146 rbac_id=rbac_id,
147 token=token,
148 client_cert_data=client_cert_data,
149 configuration=kubectl.configuration,
150 storage_class=default_storage_class,
151 credential_name=self._get_credential_name(cluster_uuid),
152 )
garciadeblas47f65382021-05-31 15:49:15 +0200153 self.log.debug("K8s cluster added to juju controller")
David Garciaf6e9b002020-11-27 15:32:02 +0100154 return cluster_uuid, True
155 except Exception as e:
garciadeblas47f65382021-05-31 15:49:15 +0200156 self.log.error("Error initializing k8scluster: {}".format(e), exc_info=True)
David Garciaf6e9b002020-11-27 15:32:02 +0100157 if len(cleanup_data) > 0:
158 self.log.debug("Cleaning up created resources in k8s cluster...")
159 for item in cleanup_data:
160 delete_function = item["delete"]
161 delete_args = item["args"]
162 delete_function(*delete_args)
163 self.log.debug("Cleanup finished")
164 raise e
Adam Israeld4ec83b2019-11-07 09:46:59 -0500165
166 """Repo Management"""
beierlmf52cb7c2020-04-21 16:36:35 -0400167
Adam Israeld4ec83b2019-11-07 09:46:59 -0500168 async def repo_add(
David Garcia667696e2020-09-22 14:52:32 +0200169 self,
170 name: str,
171 url: str,
172 _type: str = "charm",
bravof0ab522f2021-11-23 19:33:18 -0300173 cert: str = None,
174 user: str = None,
175 password: str = None,
Adam Israeld4ec83b2019-11-07 09:46:59 -0500176 ):
beierlmf52cb7c2020-04-21 16:36:35 -0400177 raise MethodNotImplemented()
Adam Israeld4ec83b2019-11-07 09:46:59 -0500178
179 async def repo_list(self):
beierlmf52cb7c2020-04-21 16:36:35 -0400180 raise MethodNotImplemented()
Adam Israeld4ec83b2019-11-07 09:46:59 -0500181
Patricia Reinoso085942e2022-12-05 16:55:51 +0000182 async def repo_remove(self, name: str):
beierlmf52cb7c2020-04-21 16:36:35 -0400183 raise MethodNotImplemented()
Adam Israeld4ec83b2019-11-07 09:46:59 -0500184
beierlmf52cb7c2020-04-21 16:36:35 -0400185 async def synchronize_repos(self, cluster_uuid: str, name: str):
lloretgalleg65ddf852020-02-20 12:01:17 +0100186 """
187 Returns None as currently add_repo is not implemented
188 """
189 return None
190
Adam Israeld4ec83b2019-11-07 09:46:59 -0500191 """Reset"""
beierlmf52cb7c2020-04-21 16:36:35 -0400192
Adam Israeld4ec83b2019-11-07 09:46:59 -0500193 async def reset(
David Garciaeb8943a2021-04-12 12:07:37 +0200194 self,
195 cluster_uuid: str,
196 force: bool = False,
197 uninstall_sw: bool = False,
198 **kwargs,
Adam Israeld4ec83b2019-11-07 09:46:59 -0500199 ) -> bool:
200 """Reset a cluster
201
202 Resets the Kubernetes cluster by removing the model that represents it.
203
204 :param cluster_uuid str: The UUID of the cluster to reset
David Garciaeb8943a2021-04-12 12:07:37 +0200205 :param force: Force reset
206 :param uninstall_sw: Boolean to uninstall sw
207 :param: kwargs: Additional parameters
208 vca_id (str): VCA ID
209
Adam Israeld4ec83b2019-11-07 09:46:59 -0500210 :return: Returns True if successful or raises an exception.
211 """
212
213 try:
David Garcia4f74f592020-07-23 15:04:19 +0200214 self.log.debug("[reset] Removing k8s cloud")
David Garciaeb8943a2021-04-12 12:07:37 +0200215 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
David Garciaf6e9b002020-11-27 15:32:02 +0100216
David Garciaeb8943a2021-04-12 12:07:37 +0200217 cloud = Cloud(cluster_uuid, self._get_credential_name(cluster_uuid))
David Garciaf6e9b002020-11-27 15:32:02 +0100218
David Garciaeb8943a2021-04-12 12:07:37 +0200219 cloud_creds = await libjuju.get_cloud_credentials(cloud)
220
221 await libjuju.remove_cloud(cluster_uuid)
Adam Israeld4ec83b2019-11-07 09:46:59 -0500222
David Garciad8d4b6e2021-06-24 18:47:22 +0200223 credentials = self.get_credentials(cluster_uuid=cluster_uuid)
David Garciaf6e9b002020-11-27 15:32:02 +0100224
David Garciad8d4b6e2021-06-24 18:47:22 +0200225 kubectl = self._get_kubectl(credentials)
David Garciaf6e9b002020-11-27 15:32:02 +0100226
227 delete_functions = [
David Garciad8d4b6e2021-06-24 18:47:22 +0200228 kubectl.delete_cluster_role_binding,
229 kubectl.delete_service_account,
230 kubectl.delete_cluster_role,
David Garciaf6e9b002020-11-27 15:32:02 +0100231 ]
232
233 credential_attrs = cloud_creds[0].result["attrs"]
234 if RBAC_LABEL_KEY_NAME in credential_attrs:
235 rbac_id = credential_attrs[RBAC_LABEL_KEY_NAME]
236 metadata_name = "{}-{}".format(RBAC_STACK_PREFIX, rbac_id)
David Garciaf6e9b002020-11-27 15:32:02 +0100237 for delete_func in delete_functions:
238 try:
David Garciad8d4b6e2021-06-24 18:47:22 +0200239 delete_func(metadata_name)
David Garciaf6e9b002020-11-27 15:32:02 +0100240 except Exception as e:
241 self.log.warning("Cannot remove resource in K8s {}".format(e))
242
David Garcia667696e2020-09-22 14:52:32 +0200243 except Exception as e:
244 self.log.debug("Caught exception during reset: {}".format(e))
245 raise e
Dominik Fleischmann1ac78b32020-02-26 19:58:25 +0100246 return True
247
Adam Israeld4ec83b2019-11-07 09:46:59 -0500248 """Deployment"""
Adam Israeleef68932019-11-28 16:27:46 -0500249
Adam Israeld4ec83b2019-11-07 09:46:59 -0500250 async def install(
251 self,
252 cluster_uuid: str,
253 kdu_model: str,
David Garciac4da25c2021-02-23 11:47:29 +0100254 kdu_instance: str,
Adam Israeld4ec83b2019-11-07 09:46:59 -0500255 atomic: bool = True,
David Garcia667696e2020-09-22 14:52:32 +0200256 timeout: float = 1800,
Adam Israeld4ec83b2019-11-07 09:46:59 -0500257 params: dict = None,
Dominik Fleischmann847f3c02020-02-04 15:32:42 +0100258 db_dict: dict = None,
tierno53555f62020-04-07 11:08:16 +0000259 kdu_name: str = None,
beierlmf52cb7c2020-04-21 16:36:35 -0400260 namespace: str = None,
David Garciaeb8943a2021-04-12 12:07:37 +0200261 **kwargs,
Adam Israeleef68932019-11-28 16:27:46 -0500262 ) -> bool:
Adam Israeld4ec83b2019-11-07 09:46:59 -0500263 """Install a bundle
264
265 :param cluster_uuid str: The UUID of the cluster to install to
266 :param kdu_model str: The name or path of a bundle to install
David Garciac4da25c2021-02-23 11:47:29 +0100267 :param kdu_instance: Kdu instance name
Adam Israeld4ec83b2019-11-07 09:46:59 -0500268 :param atomic bool: If set, waits until the model is active and resets
269 the cluster on failure.
270 :param timeout int: The time, in seconds, to wait for the install
271 to finish
272 :param params dict: Key-value pairs of instantiation parameters
Dominik Fleischmann847f3c02020-02-04 15:32:42 +0100273 :param kdu_name: Name of the KDU instance to be installed
tierno53555f62020-04-07 11:08:16 +0000274 :param namespace: K8s namespace to use for the KDU instance
David Garciaeb8943a2021-04-12 12:07:37 +0200275 :param kwargs: Additional parameters
276 vca_id (str): VCA ID
Adam Israeld4ec83b2019-11-07 09:46:59 -0500277
278 :return: If successful, returns ?
279 """
David Garciaeb8943a2021-04-12 12:07:37 +0200280 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
David Garcia667696e2020-09-22 14:52:32 +0200281 bundle = kdu_model
Adam Israeld4ec83b2019-11-07 09:46:59 -0500282
David Garcia667696e2020-09-22 14:52:32 +0200283 if not db_dict:
284 raise K8sException("db_dict must be set")
285 if not bundle:
286 raise K8sException("bundle must be set")
287
288 if bundle.startswith("cs:"):
Pedro Escaleira86a63142022-04-05 21:01:37 +0100289 # For Juju Bundles provided by the Charm Store
290 pass
291 elif bundle.startswith("ch:"):
292 # For Juju Bundles provided by the Charm Hub (this only works for juju version >= 2.9)
David Garcia667696e2020-09-22 14:52:32 +0200293 pass
294 elif bundle.startswith("http"):
295 # Download the file
296 pass
297 else:
298 new_workdir = kdu_model.strip(kdu_model.split("/")[-1])
299 os.chdir(new_workdir)
300 bundle = "local:{}".format(kdu_model)
301
Pedro Escaleira764d8662022-04-19 20:40:09 +0100302 # default namespace to kdu_instance
303 if not namespace:
304 namespace = kdu_instance
305
306 self.log.debug("Checking for model named {}".format(namespace))
Dominik Fleischmann1ac78b32020-02-26 19:58:25 +0100307
308 # Create the new model
Pedro Escaleira764d8662022-04-19 20:40:09 +0100309 self.log.debug("Adding model: {}".format(namespace))
David Garciaeb8943a2021-04-12 12:07:37 +0200310 cloud = Cloud(cluster_uuid, self._get_credential_name(cluster_uuid))
Pedro Escaleira764d8662022-04-19 20:40:09 +0100311 await libjuju.add_model(namespace, cloud)
Adam Israeld4ec83b2019-11-07 09:46:59 -0500312
David Garcia667696e2020-09-22 14:52:32 +0200313 # if model:
314 # TODO: Instantiation parameters
Adam Israeld4ec83b2019-11-07 09:46:59 -0500315
David Garcia667696e2020-09-22 14:52:32 +0200316 """
317 "Juju bundle that models the KDU, in any of the following ways:
318 - <juju-repo>/<juju-bundle>
319 - <juju-bundle folder under k8s_models folder in the package>
320 - <juju-bundle tgz file (w/ or w/o extension) under k8s_models folder
321 in the package>
322 - <URL_where_to_fetch_juju_bundle>
323 """
324 try:
325 previous_workdir = os.getcwd()
326 except FileNotFoundError:
327 previous_workdir = "/app/storage"
Dominik Fleischmann45d95772020-03-26 12:21:42 +0100328
David Garcia667696e2020-09-22 14:52:32 +0200329 self.log.debug("[install] deploying {}".format(bundle))
Patricia Reinosofedf9152023-01-17 08:39:44 +0000330 instantiation_params = params.get("overlay") if params else None
331 await libjuju.deploy(
332 bundle,
333 model_name=namespace,
334 wait=atomic,
335 timeout=timeout,
336 instantiation_params=instantiation_params,
337 )
David Garcia667696e2020-09-22 14:52:32 +0200338 os.chdir(previous_workdir)
Pedro Escaleira764d8662022-04-19 20:40:09 +0100339
340 # update information in the database (first, the VCA status, and then, the namespace)
ksaikiranrb816d822021-03-17 12:50:20 +0530341 if self.on_update_db:
David Garciaeb8943a2021-04-12 12:07:37 +0200342 await self.on_update_db(
343 cluster_uuid,
344 kdu_instance,
345 filter=db_dict["filter"],
garciadeblas82b591c2021-03-24 09:22:13 +0100346 vca_id=kwargs.get("vca_id"),
David Garciaeb8943a2021-04-12 12:07:37 +0200347 )
Pedro Escaleira764d8662022-04-19 20:40:09 +0100348
349 self.db.set_one(
350 table="nsrs",
351 q_filter={"_admin.deployed.K8s.kdu-instance": kdu_instance},
352 update_dict={"_admin.deployed.K8s.$.namespace": namespace},
353 )
354
David Garciac4da25c2021-02-23 11:47:29 +0100355 return True
Adam Israeld4ec83b2019-11-07 09:46:59 -0500356
aktas2962f3e2021-03-15 11:05:35 +0300357 async def scale(
garciadeblas82b591c2021-03-24 09:22:13 +0100358 self,
359 kdu_instance: str,
360 scale: int,
361 resource_name: str,
362 total_timeout: float = 1800,
Pedro Escaleira764d8662022-04-19 20:40:09 +0100363 namespace: str = None,
garciadeblas82b591c2021-03-24 09:22:13 +0100364 **kwargs,
aktas2962f3e2021-03-15 11:05:35 +0300365 ) -> bool:
366 """Scale an application in a model
367
368 :param: kdu_instance str: KDU instance name
aktas867418c2021-10-19 18:26:13 +0300369 :param: scale int: Scale to which to set the application
370 :param: resource_name str: The application name in the Juju Bundle
aktas2962f3e2021-03-15 11:05:35 +0300371 :param: timeout float: The time, in seconds, to wait for the install
372 to finish
Pedro Escaleira764d8662022-04-19 20:40:09 +0100373 :param namespace str: The namespace (model) where the Bundle was deployed
aktas2962f3e2021-03-15 11:05:35 +0300374 :param kwargs: Additional parameters
375 vca_id (str): VCA ID
376
377 :return: If successful, returns True
378 """
379
Pedro Escaleira764d8662022-04-19 20:40:09 +0100380 model_name = self._obtain_namespace(
381 kdu_instance=kdu_instance, namespace=namespace
382 )
aktas2962f3e2021-03-15 11:05:35 +0300383 try:
384 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
385 await libjuju.scale_application(
Pedro Escaleira764d8662022-04-19 20:40:09 +0100386 model_name=model_name,
aktas2962f3e2021-03-15 11:05:35 +0300387 application_name=resource_name,
388 scale=scale,
garciadeblas82b591c2021-03-24 09:22:13 +0100389 total_timeout=total_timeout,
aktas2962f3e2021-03-15 11:05:35 +0300390 )
391 except Exception as e:
Pedro Escaleira764d8662022-04-19 20:40:09 +0100392 error_msg = "Error scaling application {} of the model {} of the kdu instance {}: {}".format(
393 resource_name, model_name, kdu_instance, e
garciadeblas82b591c2021-03-24 09:22:13 +0100394 )
aktas2962f3e2021-03-15 11:05:35 +0300395 self.log.error(error_msg)
396 raise K8sException(message=error_msg)
397 return True
398
399 async def get_scale_count(
Patricia Reinoso085942e2022-12-05 16:55:51 +0000400 self, resource_name: str, kdu_instance: str, namespace: str = None, **kwargs
aktas2962f3e2021-03-15 11:05:35 +0300401 ) -> int:
402 """Get an application scale count
403
aktas867418c2021-10-19 18:26:13 +0300404 :param: resource_name str: The application name in the Juju Bundle
aktas2962f3e2021-03-15 11:05:35 +0300405 :param: kdu_instance str: KDU instance name
Pedro Escaleira764d8662022-04-19 20:40:09 +0100406 :param namespace str: The namespace (model) where the Bundle was deployed
aktas2962f3e2021-03-15 11:05:35 +0300407 :param kwargs: Additional parameters
408 vca_id (str): VCA ID
409 :return: Return application instance count
410 """
aktas867418c2021-10-19 18:26:13 +0300411
Pedro Escaleira764d8662022-04-19 20:40:09 +0100412 model_name = self._obtain_namespace(
413 kdu_instance=kdu_instance, namespace=namespace
414 )
aktas2962f3e2021-03-15 11:05:35 +0300415 try:
416 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
Pedro Escaleira764d8662022-04-19 20:40:09 +0100417 status = await libjuju.get_model_status(model_name=model_name)
aktas2962f3e2021-03-15 11:05:35 +0300418 return len(status.applications[resource_name].units)
419 except Exception as e:
Pedro Escaleira764d8662022-04-19 20:40:09 +0100420 error_msg = (
421 f"Error getting scale count from application {resource_name} of the model {model_name} of "
422 f"the kdu instance {kdu_instance}: {e}"
garciadeblas82b591c2021-03-24 09:22:13 +0100423 )
aktas2962f3e2021-03-15 11:05:35 +0300424 self.log.error(error_msg)
425 raise K8sException(message=error_msg)
426
beierlmf52cb7c2020-04-21 16:36:35 -0400427 async def instances_list(self, cluster_uuid: str) -> list:
Adam Israeld4ec83b2019-11-07 09:46:59 -0500428 """
429 returns a list of deployed releases in a cluster
430
431 :param cluster_uuid: the cluster
432 :return:
433 """
434 return []
435
436 async def upgrade(
437 self,
438 cluster_uuid: str,
439 kdu_instance: str,
440 kdu_model: str = None,
441 params: dict = None,
442 ) -> str:
443 """Upgrade a model
444
445 :param cluster_uuid str: The UUID of the cluster to upgrade
446 :param kdu_instance str: The unique name of the KDU instance
447 :param kdu_model str: The name or path of the bundle to upgrade to
448 :param params dict: Key-value pairs of instantiation parameters
449
450 :return: If successful, reference to the new revision number of the
451 KDU instance.
452 """
453
454 # TODO: Loop through the bundle and upgrade each charm individually
455
456 """
457 The API doesn't have a concept of bundle upgrades, because there are
458 many possible changes: charm revision, disk, number of units, etc.
459
460 As such, we are only supporting a limited subset of upgrades. We'll
461 upgrade the charm revision but leave storage and scale untouched.
462
463 Scale changes should happen through OSM constructs, and changes to
464 storage would require a redeployment of the service, at least in this
465 initial release.
466 """
beierlmf52cb7c2020-04-21 16:36:35 -0400467 raise MethodNotImplemented()
Adam Israeld4ec83b2019-11-07 09:46:59 -0500468
469 """Rollback"""
beierlmf52cb7c2020-04-21 16:36:35 -0400470
Adam Israeld4ec83b2019-11-07 09:46:59 -0500471 async def rollback(
Patricia Reinoso085942e2022-12-05 16:55:51 +0000472 self, cluster_uuid: str, kdu_instance: str, revision: int = 0
Adam Israeld4ec83b2019-11-07 09:46:59 -0500473 ) -> str:
474 """Rollback a model
475
476 :param cluster_uuid str: The UUID of the cluster to rollback
477 :param kdu_instance str: The unique name of the KDU instance
478 :param revision int: The revision to revert to. If omitted, rolls back
479 the previous upgrade.
480
481 :return: If successful, returns the revision of active KDU instance,
482 or raises an exception
483 """
beierlmf52cb7c2020-04-21 16:36:35 -0400484 raise MethodNotImplemented()
Adam Israeld4ec83b2019-11-07 09:46:59 -0500485
486 """Deletion"""
beierlmf52cb7c2020-04-21 16:36:35 -0400487
David Garciaeb8943a2021-04-12 12:07:37 +0200488 async def uninstall(
Patricia Reinoso085942e2022-12-05 16:55:51 +0000489 self, cluster_uuid: str, kdu_instance: str, namespace: str = None, **kwargs
David Garciaeb8943a2021-04-12 12:07:37 +0200490 ) -> bool:
Adam Israeld4ec83b2019-11-07 09:46:59 -0500491 """Uninstall a KDU instance
492
Dominik Fleischmann847f3c02020-02-04 15:32:42 +0100493 :param cluster_uuid str: The UUID of the cluster
Adam Israeld4ec83b2019-11-07 09:46:59 -0500494 :param kdu_instance str: The unique name of the KDU instance
Pedro Escaleira764d8662022-04-19 20:40:09 +0100495 :param namespace str: The namespace (model) where the Bundle was deployed
David Garciaeb8943a2021-04-12 12:07:37 +0200496 :param kwargs: Additional parameters
497 vca_id (str): VCA ID
Adam Israeld4ec83b2019-11-07 09:46:59 -0500498
499 :return: Returns True if successful, or raises an exception
500 """
Pedro Escaleira764d8662022-04-19 20:40:09 +0100501 model_name = self._obtain_namespace(
502 kdu_instance=kdu_instance, namespace=namespace
503 )
David Garcia4f74f592020-07-23 15:04:19 +0200504
Pedro Escaleira764d8662022-04-19 20:40:09 +0100505 self.log.debug(f"[uninstall] Destroying model: {model_name}")
Dominik Fleischmann1ac78b32020-02-26 19:58:25 +0100506
David Garciacd986062022-05-05 09:46:06 +0200507 will_not_delete = False
Pedro Escaleira764d8662022-04-19 20:40:09 +0100508 if model_name not in self.uninstall_locks:
Guillermo Calvino474fd952023-04-28 11:51:43 +0200509 self.uninstall_locks[model_name] = asyncio.Lock()
Pedro Escaleira764d8662022-04-19 20:40:09 +0100510 delete_lock = self.uninstall_locks[model_name]
Adam Israeld4ec83b2019-11-07 09:46:59 -0500511
David Garciacd986062022-05-05 09:46:06 +0200512 while delete_lock.locked():
513 will_not_delete = True
514 await asyncio.sleep(0.1)
Dominik Fleischmann1ac78b32020-02-26 19:58:25 +0100515
David Garciacd986062022-05-05 09:46:06 +0200516 if will_not_delete:
Pedro Escaleira764d8662022-04-19 20:40:09 +0100517 self.log.info("Model {} deleted by another worker.".format(model_name))
David Garciacd986062022-05-05 09:46:06 +0200518 return True
519
520 try:
521 async with delete_lock:
522 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
523
Pedro Escaleira764d8662022-04-19 20:40:09 +0100524 await libjuju.destroy_model(model_name, total_timeout=3600)
David Garciacd986062022-05-05 09:46:06 +0200525 finally:
Pedro Escaleira764d8662022-04-19 20:40:09 +0100526 self.uninstall_locks.pop(model_name)
David Garciacd986062022-05-05 09:46:06 +0200527
Pedro Escaleira764d8662022-04-19 20:40:09 +0100528 self.log.debug(f"[uninstall] Model {model_name} destroyed")
Dominik Fleischmann847f3c02020-02-04 15:32:42 +0100529 return True
Adam Israeld4ec83b2019-11-07 09:46:59 -0500530
aticig8070c3c2022-04-18 00:31:42 +0300531 async def upgrade_charm(
532 self,
533 ee_id: str = None,
534 path: str = None,
535 charm_id: str = None,
536 charm_type: str = None,
537 timeout: float = None,
538 ) -> str:
539 """This method upgrade charms in VNFs
540
541 Args:
542 ee_id: Execution environment id
543 path: Local path to the charm
544 charm_id: charm-id
545 charm_type: Charm type can be lxc-proxy-charm, native-charm or k8s-proxy-charm
546 timeout: (Float) Timeout for the ns update operation
547
548 Returns:
549 The output of the update operation if status equals to "completed"
550 """
551 raise K8sException(
552 "KDUs deployed with Juju Bundle do not support charm upgrade"
553 )
554
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200555 async def exec_primitive(
556 self,
557 cluster_uuid: str = None,
558 kdu_instance: str = None,
559 primitive_name: str = None,
560 timeout: float = 300,
561 params: dict = None,
562 db_dict: dict = None,
Pedro Escaleira764d8662022-04-19 20:40:09 +0100563 namespace: str = None,
David Garciaeb8943a2021-04-12 12:07:37 +0200564 **kwargs,
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200565 ) -> str:
566 """Exec primitive (Juju action)
567
568 :param cluster_uuid str: The UUID of the cluster
569 :param kdu_instance str: The unique name of the KDU instance
570 :param primitive_name: Name of action that will be executed
571 :param timeout: Timeout for action execution
572 :param params: Dictionary of all the parameters needed for the action
David Garciaeb8943a2021-04-12 12:07:37 +0200573 :param db_dict: Dictionary for any additional data
Pedro Escaleira764d8662022-04-19 20:40:09 +0100574 :param namespace str: The namespace (model) where the Bundle was deployed
David Garciaeb8943a2021-04-12 12:07:37 +0200575 :param kwargs: Additional parameters
576 vca_id (str): VCA ID
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200577
578 :return: Returns the output of the action
579 """
David Garciaeb8943a2021-04-12 12:07:37 +0200580 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
David Garcia4f74f592020-07-23 15:04:19 +0200581
Pedro Escaleira764d8662022-04-19 20:40:09 +0100582 namespace = self._obtain_namespace(
583 kdu_instance=kdu_instance, namespace=namespace
584 )
585
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200586 if not params or "application-name" not in params:
beierlmf52cb7c2020-04-21 16:36:35 -0400587 raise K8sException(
588 "Missing application-name argument, \
589 argument needed for K8s actions"
590 )
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200591 try:
beierlmf52cb7c2020-04-21 16:36:35 -0400592 self.log.debug(
593 "[exec_primitive] Getting model "
Pedro Escaleira764d8662022-04-19 20:40:09 +0100594 "{} for the kdu_instance: {}".format(namespace, kdu_instance)
beierlmf52cb7c2020-04-21 16:36:35 -0400595 )
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200596 application_name = params["application-name"]
Pedro Escaleira764d8662022-04-19 20:40:09 +0100597 actions = await libjuju.get_actions(
598 application_name=application_name, model_name=namespace
599 )
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200600 if primitive_name not in actions:
601 raise K8sException("Primitive {} not found".format(primitive_name))
David Garciaeb8943a2021-04-12 12:07:37 +0200602 output, status = await libjuju.execute_action(
Pedro Escaleira764d8662022-04-19 20:40:09 +0100603 application_name=application_name,
604 model_name=namespace,
605 action_name=primitive_name,
606 **params,
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200607 )
608
609 if status != "completed":
beierlmf52cb7c2020-04-21 16:36:35 -0400610 raise K8sException(
611 "status is not completed: {} output: {}".format(status, output)
612 )
ksaikiranrb816d822021-03-17 12:50:20 +0530613 if self.on_update_db:
garciadeblas82b591c2021-03-24 09:22:13 +0100614 await self.on_update_db(
Pedro Escaleira764d8662022-04-19 20:40:09 +0100615 cluster_uuid=cluster_uuid,
616 kdu_instance=kdu_instance,
617 filter=db_dict["filter"],
garciadeblas82b591c2021-03-24 09:22:13 +0100618 )
Dominik Fleischmannfc796cc2020-04-06 14:51:00 +0200619
620 return output
621
622 except Exception as e:
623 error_msg = "Error executing primitive {}: {}".format(primitive_name, e)
624 self.log.error(error_msg)
625 raise K8sException(message=error_msg)
626
Adam Israeld4ec83b2019-11-07 09:46:59 -0500627 """Introspection"""
beierlmf52cb7c2020-04-21 16:36:35 -0400628
Patricia Reinoso085942e2022-12-05 16:55:51 +0000629 async def inspect_kdu(self, kdu_model: str) -> dict:
Adam Israeld4ec83b2019-11-07 09:46:59 -0500630 """Inspect a KDU
631
632 Inspects a bundle and returns a dictionary of config parameters and
633 their default values.
634
635 :param kdu_model str: The name or path of the bundle to inspect.
636
637 :return: If successful, returns a dictionary of available parameters
638 and their default values.
639 """
640
641 kdu = {}
David Garcia667696e2020-09-22 14:52:32 +0200642 if not os.path.exists(kdu_model):
643 raise K8sException("file {} not found".format(kdu_model))
644
beierlmf52cb7c2020-04-21 16:36:35 -0400645 with open(kdu_model, "r") as f:
David Garcia667696e2020-09-22 14:52:32 +0200646 bundle = yaml.safe_load(f.read())
Adam Israeld4ec83b2019-11-07 09:46:59 -0500647
648 """
649 {
650 'description': 'Test bundle',
651 'bundle': 'kubernetes',
652 'applications': {
653 'mariadb-k8s': {
654 'charm': 'cs:~charmed-osm/mariadb-k8s-20',
655 'scale': 1,
656 'options': {
657 'password': 'manopw',
658 'root_password': 'osm4u',
659 'user': 'mano'
660 },
661 'series': 'kubernetes'
662 }
663 }
664 }
665 """
666 # TODO: This should be returned in an agreed-upon format
beierlmf52cb7c2020-04-21 16:36:35 -0400667 kdu = bundle["applications"]
Adam Israeld4ec83b2019-11-07 09:46:59 -0500668
669 return kdu
670
Patricia Reinoso085942e2022-12-05 16:55:51 +0000671 async def help_kdu(self, kdu_model: str) -> str:
Adam Israeld4ec83b2019-11-07 09:46:59 -0500672 """View the README
673
Pedro Escaleira764d8662022-04-19 20:40:09 +0100674 If available, returns the README of the bundle.
Adam Israeld4ec83b2019-11-07 09:46:59 -0500675
Pedro Escaleira764d8662022-04-19 20:40:09 +0100676 :param kdu_model str: The name or path of a bundle
677 f
678 :return: If found, returns the contents of the README.
Adam Israeld4ec83b2019-11-07 09:46:59 -0500679 """
680 readme = None
681
beierlmf52cb7c2020-04-21 16:36:35 -0400682 files = ["README", "README.txt", "README.md"]
Adam Israeld4ec83b2019-11-07 09:46:59 -0500683 path = os.path.dirname(kdu_model)
684 for file in os.listdir(path):
685 if file in files:
beierlmf52cb7c2020-04-21 16:36:35 -0400686 with open(file, "r") as f:
Adam Israeld4ec83b2019-11-07 09:46:59 -0500687 readme = f.read()
688 break
689
690 return readme
691
David Garcia667696e2020-09-22 14:52:32 +0200692 async def status_kdu(
693 self,
694 cluster_uuid: str,
695 kdu_instance: str,
ksaikiranrb816d822021-03-17 12:50:20 +0530696 complete_status: bool = False,
David Garciaeb8943a2021-04-12 12:07:37 +0200697 yaml_format: bool = False,
Pedro Escaleira764d8662022-04-19 20:40:09 +0100698 namespace: str = None,
David Garciaeb8943a2021-04-12 12:07:37 +0200699 **kwargs,
Pedro Escaleiraa8980cc2022-04-05 17:32:13 +0100700 ) -> Union[str, dict]:
Adam Israeld4ec83b2019-11-07 09:46:59 -0500701 """Get the status of the KDU
702
703 Get the current status of the KDU instance.
704
705 :param cluster_uuid str: The UUID of the cluster
706 :param kdu_instance str: The unique id of the KDU instance
ksaikiranrb816d822021-03-17 12:50:20 +0530707 :param complete_status: To get the complete_status of the KDU
708 :param yaml_format: To get the status in proper format for NSR record
Pedro Escaleira764d8662022-04-19 20:40:09 +0100709 :param namespace str: The namespace (model) where the Bundle was deployed
David Garciaeb8943a2021-04-12 12:07:37 +0200710 :param: kwargs: Additional parameters
711 vca_id (str): VCA ID
Adam Israeld4ec83b2019-11-07 09:46:59 -0500712
713 :return: Returns a dictionary containing namespace, state, resources,
ksaikiranrb816d822021-03-17 12:50:20 +0530714 and deployment_time and returns complete_status if complete_status is True
Adam Israeld4ec83b2019-11-07 09:46:59 -0500715 """
David Garciaeb8943a2021-04-12 12:07:37 +0200716 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
Adam Israeld4ec83b2019-11-07 09:46:59 -0500717 status = {}
ksaikiranrb816d822021-03-17 12:50:20 +0530718
Pedro Escaleira764d8662022-04-19 20:40:09 +0100719 model_name = self._obtain_namespace(
720 kdu_instance=kdu_instance, namespace=namespace
721 )
722 model_status = await libjuju.get_model_status(model_name=model_name)
ksaikiranrb816d822021-03-17 12:50:20 +0530723
724 if not complete_status:
725 for name in model_status.applications:
726 application = model_status.applications[name]
727 status[name] = {"status": application["status"]["status"]}
728 else:
729 if yaml_format:
730 return obj_to_yaml(model_status)
731 else:
732 return obj_to_dict(model_status)
Adam Israeld4ec83b2019-11-07 09:46:59 -0500733
Adam Israeld4ec83b2019-11-07 09:46:59 -0500734 return status
735
David Garcia582b9232021-10-26 12:30:44 +0200736 async def add_relation(
Patricia Reinoso085942e2022-12-05 16:55:51 +0000737 self, provider: RelationEndpoint, requirer: RelationEndpoint
David Garcia582b9232021-10-26 12:30:44 +0200738 ):
739 """
740 Add relation between two charmed endpoints
741
742 :param: provider: Provider relation endpoint
743 :param: requirer: Requirer relation endpoint
744 """
745 self.log.debug(f"adding new relation between {provider} and {requirer}")
746 cross_model_relation = (
747 provider.model_name != requirer.model_name
Patricia Reinoso085942e2022-12-05 16:55:51 +0000748 or provider.vca_id != requirer.vca_id
David Garcia582b9232021-10-26 12:30:44 +0200749 )
750 try:
751 if cross_model_relation:
752 # Cross-model relation
753 provider_libjuju = await self._get_libjuju(provider.vca_id)
754 requirer_libjuju = await self._get_libjuju(requirer.vca_id)
755 offer = await provider_libjuju.offer(provider)
756 if offer:
757 saas_name = await requirer_libjuju.consume(
758 requirer.model_name, offer, provider_libjuju
759 )
760 await requirer_libjuju.add_relation(
Patricia Reinoso085942e2022-12-05 16:55:51 +0000761 requirer.model_name, requirer.endpoint, saas_name
David Garcia582b9232021-10-26 12:30:44 +0200762 )
763 else:
764 # Standard relation
765 vca_id = provider.vca_id
766 model = provider.model_name
767 libjuju = await self._get_libjuju(vca_id)
768 # add juju relations between two applications
769 await libjuju.add_relation(
770 model_name=model,
771 endpoint_1=provider.endpoint,
772 endpoint_2=requirer.endpoint,
773 )
774 except Exception as e:
775 message = f"Error adding relation between {provider} and {requirer}: {e}"
776 self.log.error(message)
777 raise Exception(message=message)
778
Pedro Escaleira764d8662022-04-19 20:40:09 +0100779 async def update_vca_status(
780 self, vcastatus: dict, kdu_instance: str, namespace: str = None, **kwargs
781 ):
ksaikiranrb816d822021-03-17 12:50:20 +0530782 """
783 Add all configs, actions, executed actions of all applications in a model to vcastatus dict
784
785 :param vcastatus dict: dict containing vcastatus
786 :param kdu_instance str: The unique id of the KDU instance
Pedro Escaleira764d8662022-04-19 20:40:09 +0100787 :param namespace str: The namespace (model) where the Bundle was deployed
David Garciaeb8943a2021-04-12 12:07:37 +0200788 :param: kwargs: Additional parameters
789 vca_id (str): VCA ID
ksaikiranrb816d822021-03-17 12:50:20 +0530790
791 :return: None
792 """
Pedro Escaleira764d8662022-04-19 20:40:09 +0100793
794 model_name = self._obtain_namespace(
795 kdu_instance=kdu_instance, namespace=namespace
796 )
797
David Garciaeb8943a2021-04-12 12:07:37 +0200798 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
ksaikiranrb816d822021-03-17 12:50:20 +0530799 try:
Pedro Escaleira764d8662022-04-19 20:40:09 +0100800 for vca_model_name in vcastatus:
ksaikiranrb816d822021-03-17 12:50:20 +0530801 # Adding executed actions
Pedro Escaleira764d8662022-04-19 20:40:09 +0100802 vcastatus[vca_model_name][
garciadeblas82b591c2021-03-24 09:22:13 +0100803 "executedActions"
Pedro Escaleira764d8662022-04-19 20:40:09 +0100804 ] = await libjuju.get_executed_actions(model_name=model_name)
ksaikiranrb816d822021-03-17 12:50:20 +0530805
Pedro Escaleira764d8662022-04-19 20:40:09 +0100806 for application in vcastatus[vca_model_name]["applications"]:
ksaikiranrb816d822021-03-17 12:50:20 +0530807 # Adding application actions
Pedro Escaleira764d8662022-04-19 20:40:09 +0100808 vcastatus[vca_model_name]["applications"][application][
809 "actions"
810 ] = {}
ksaikiranrb816d822021-03-17 12:50:20 +0530811 # Adding application configs
Pedro Escaleira764d8662022-04-19 20:40:09 +0100812 vcastatus[vca_model_name]["applications"][application][
garciadeblas82b591c2021-03-24 09:22:13 +0100813 "configs"
Pedro Escaleira764d8662022-04-19 20:40:09 +0100814 ] = await libjuju.get_application_configs(
815 model_name=model_name, application_name=application
816 )
ksaikiranrb816d822021-03-17 12:50:20 +0530817
818 except Exception as e:
819 self.log.debug("Error in updating vca status: {}".format(str(e)))
820
David Garcia5d799392020-07-02 13:56:58 +0200821 async def get_services(
822 self, cluster_uuid: str, kdu_instance: str, namespace: str
823 ) -> list:
824 """Return a list of services of a kdu_instance"""
lloretgallegd99f3f22020-06-29 14:18:30 +0000825
Pedro Escaleira764d8662022-04-19 20:40:09 +0100826 namespace = self._obtain_namespace(
827 kdu_instance=kdu_instance, namespace=namespace
828 )
829
David Garcia2c791b32020-07-22 17:56:12 +0200830 credentials = self.get_credentials(cluster_uuid=cluster_uuid)
David Garciad8d4b6e2021-06-24 18:47:22 +0200831 kubectl = self._get_kubectl(credentials)
David Garcia5d799392020-07-02 13:56:58 +0200832 return kubectl.get_services(
Pedro Escaleira764d8662022-04-19 20:40:09 +0100833 field_selector="metadata.namespace={}".format(namespace)
David Garcia5d799392020-07-02 13:56:58 +0200834 )
835
836 async def get_service(
837 self, cluster_uuid: str, service_name: str, namespace: str
838 ) -> object:
839 """Return data for a specific service inside a namespace"""
840
David Garcia2c791b32020-07-22 17:56:12 +0200841 credentials = self.get_credentials(cluster_uuid=cluster_uuid)
David Garciad8d4b6e2021-06-24 18:47:22 +0200842 kubectl = self._get_kubectl(credentials)
David Garcia5d799392020-07-02 13:56:58 +0200843 return kubectl.get_services(
844 field_selector="metadata.name={},metadata.namespace={}".format(
845 service_name, namespace
846 )
847 )[0]
lloretgallegd99f3f22020-06-29 14:18:30 +0000848
David Garcia2c791b32020-07-22 17:56:12 +0200849 def get_credentials(self, cluster_uuid: str) -> str:
David Garcia5d799392020-07-02 13:56:58 +0200850 """
David Garcia2c791b32020-07-22 17:56:12 +0200851 Get Cluster Kubeconfig
David Garcia5d799392020-07-02 13:56:58 +0200852 """
David Garcia2c791b32020-07-22 17:56:12 +0200853 k8scluster = self.db.get_one(
854 "k8sclusters", q_filter={"_id": cluster_uuid}, fail_on_empty=False
855 )
856
857 self.db.encrypt_decrypt_fields(
858 k8scluster.get("credentials"),
859 "decrypt",
860 ["password", "secret"],
861 schema_version=k8scluster["schema_version"],
862 salt=k8scluster["_id"],
863 )
864
865 return yaml.safe_dump(k8scluster.get("credentials"))
David Garcia5d799392020-07-02 13:56:58 +0200866
David Garcia667696e2020-09-22 14:52:32 +0200867 def _get_credential_name(self, cluster_uuid: str) -> str:
Adam Israeld4ec83b2019-11-07 09:46:59 -0500868 """
David Garcia667696e2020-09-22 14:52:32 +0200869 Get credential name for a k8s cloud
David Garcia4f74f592020-07-23 15:04:19 +0200870
David Garcia667696e2020-09-22 14:52:32 +0200871 We cannot use the cluster_uuid for the credential name directly,
872 because it cannot start with a number, it must start with a letter.
873 Therefore, the k8s cloud credential name will be "cred-" followed
874 by the cluster uuid.
Adam Israeld4ec83b2019-11-07 09:46:59 -0500875
David Garcia667696e2020-09-22 14:52:32 +0200876 :param: cluster_uuid: Cluster UUID of the kubernetes cloud (=cloud_name)
Adam Israeld4ec83b2019-11-07 09:46:59 -0500877
David Garcia667696e2020-09-22 14:52:32 +0200878 :return: Name to use for the credential name.
Adam Israeld4ec83b2019-11-07 09:46:59 -0500879 """
David Garcia667696e2020-09-22 14:52:32 +0200880 return "cred-{}".format(cluster_uuid)
Adam Israeld4ec83b2019-11-07 09:46:59 -0500881
Patricia Reinoso085942e2022-12-05 16:55:51 +0000882 def get_namespace(self, cluster_uuid: str) -> str:
Adam Israeld4ec83b2019-11-07 09:46:59 -0500883 """Get the namespace UUID
884 Gets the namespace's unique name
885
886 :param cluster_uuid str: The UUID of the cluster
887 :returns: The namespace UUID, or raises an exception
888 """
David Garcia667696e2020-09-22 14:52:32 +0200889 pass
Adam Israeld4ec83b2019-11-07 09:46:59 -0500890
David Garciac4da25c2021-02-23 11:47:29 +0100891 @staticmethod
892 def generate_kdu_instance_name(**kwargs):
893 db_dict = kwargs.get("db_dict")
894 kdu_name = kwargs.get("kdu_name", None)
895 if kdu_name:
896 kdu_instance = "{}-{}".format(kdu_name, db_dict["filter"]["_id"])
897 else:
898 kdu_instance = db_dict["filter"]["_id"]
899 return kdu_instance
David Garciaeb8943a2021-04-12 12:07:37 +0200900
901 async def _get_libjuju(self, vca_id: str = None) -> Libjuju:
902 """
903 Get libjuju object
904
905 :param: vca_id: VCA ID
906 If None, get a libjuju object with a Connection to the default VCA
907 Else, geta libjuju object with a Connection to the specified VCA
908 """
909 if not vca_id:
910 while self.loading_libjuju.locked():
911 await asyncio.sleep(0.1)
912 if not self.libjuju:
913 async with self.loading_libjuju:
914 vca_connection = await get_connection(self._store)
Mark Beierl2c3c1462023-05-15 16:17:02 -0400915 self.libjuju = Libjuju(vca_connection, log=self.log)
David Garciaeb8943a2021-04-12 12:07:37 +0200916 return self.libjuju
917 else:
918 vca_connection = await get_connection(self._store, vca_id)
Mark Beierl2c3c1462023-05-15 16:17:02 -0400919 return Libjuju(vca_connection, log=self.log, n2vc=self)
David Garciad8d4b6e2021-06-24 18:47:22 +0200920
921 def _get_kubectl(self, credentials: str) -> Kubectl:
922 """
923 Get Kubectl object
924
925 :param: kubeconfig_credentials: Kubeconfig credentials
926 """
927 kubecfg = tempfile.NamedTemporaryFile()
928 with open(kubecfg.name, "w") as kubecfg_file:
929 kubecfg_file.write(credentials)
930 return Kubectl(config_file=kubecfg.name)
Pedro Escaleira764d8662022-04-19 20:40:09 +0100931
932 def _obtain_namespace(self, kdu_instance: str, namespace: str = None) -> str:
933 """
934 Obtain the namespace/model name to use in the instantiation of a Juju Bundle in K8s. The default namespace is
935 the kdu_instance name. However, if the user passes the namespace where he wants to deploy the bundle,
936 that namespace will be used.
937
938 :param kdu_instance: the default KDU instance name
939 :param namespace: the namespace passed by the User
940 """
941
942 # deault the namespace/model name to the kdu_instance name TODO -> this should be the real return... But
943 # once the namespace is not passed in most methods, I had to do this in another way. But I think this should
944 # be the procedure in the future return namespace if namespace else kdu_instance
945
946 # TODO -> has referred above, this should be avoided in the future, this is temporary, in order to avoid
947 # compatibility issues
948 return (
949 namespace
950 if namespace
951 else self._obtain_namespace_from_db(kdu_instance=kdu_instance)
952 )
953
954 def _obtain_namespace_from_db(self, kdu_instance: str) -> str:
955 db_nsrs = self.db.get_one(
956 table="nsrs", q_filter={"_admin.deployed.K8s.kdu-instance": kdu_instance}
957 )
958 for k8s in db_nsrs["_admin"]["deployed"]["K8s"]:
959 if k8s.get("kdu-instance") == kdu_instance:
960 return k8s.get("namespace")
961 return ""