Bug 1980 fixed
[osm/N2VC.git] / n2vc / k8s_juju_conn.py
1 # 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
15 import asyncio
16 from typing import Union
17 import os
18 import uuid
19 import yaml
20 import tempfile
21 import binascii
22
23 from n2vc.config import EnvironConfig
24 from n2vc.definitions import RelationEndpoint
25 from n2vc.exceptions import K8sException
26 from n2vc.k8s_conn import K8sConnector
27 from n2vc.kubectl import Kubectl
28 from .exceptions import MethodNotImplemented
29 from n2vc.libjuju import Libjuju
30 from n2vc.utils import obj_to_dict, obj_to_yaml
31 from n2vc.store import MotorStore
32 from n2vc.vca.cloud import Cloud
33 from n2vc.vca.connection import get_connection
34
35
36 RBAC_LABEL_KEY_NAME = "rbac-id"
37 RBAC_STACK_PREFIX = "juju-credential"
38
39
40 def generate_rbac_id():
41 return binascii.hexlify(os.urandom(4)).decode()
42
43
44 class K8sJujuConnector(K8sConnector):
45 libjuju = None
46
47 def __init__(
48 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 loop: object = None,
55 on_update_db=None,
56 ):
57 """
58 :param fs: file system for kubernetes and helm configuration
59 :param db: Database object
60 :param kubectl_command: path to kubectl executable
61 :param helm_command: path to helm executable
62 :param log: logger
63 :param: loop: Asyncio loop
64 """
65
66 # parent class
67 K8sConnector.__init__(
68 self,
69 db,
70 log=log,
71 on_update_db=on_update_db,
72 )
73
74 self.fs = fs
75 self.loop = loop or asyncio.get_event_loop()
76 self.log.debug("Initializing K8S Juju connector")
77
78 db_uri = EnvironConfig(prefixes=["OSMLCM_", "OSMMON_"]).get("database_uri")
79 self._store = MotorStore(db_uri)
80 self.loading_libjuju = asyncio.Lock(loop=self.loop)
81
82 self.log.debug("K8S Juju connector initialized")
83 # TODO: Remove these commented lines:
84 # self.authenticated = False
85 # self.models = {}
86 # self.juju_secret = ""
87
88 """Initialization"""
89
90 async def init_env(
91 self,
92 k8s_creds: str,
93 namespace: str = "kube-system",
94 reuse_cluster_uuid: str = None,
95 **kwargs,
96 ) -> (str, bool):
97 """
98 It prepares a given K8s cluster environment to run Juju bundles.
99
100 :param k8s_creds: credentials to access a given K8s cluster, i.e. a valid
101 '.kube/config'
102 :param namespace: optional namespace to be used for juju. By default,
103 'kube-system' will be used
104 :param reuse_cluster_uuid: existing cluster uuid for reuse
105 :param: kwargs: Additional parameters
106 vca_id (str): VCA ID
107
108 :return: uuid of the K8s cluster and True if connector has installed some
109 software in the cluster
110 (on error, an exception will be raised)
111 """
112 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
113
114 cluster_uuid = reuse_cluster_uuid or str(uuid.uuid4())
115 kubectl = self._get_kubectl(k8s_creds)
116
117 # CREATING RESOURCES IN K8S
118 rbac_id = generate_rbac_id()
119 metadata_name = "{}-{}".format(RBAC_STACK_PREFIX, rbac_id)
120 labels = {RBAC_STACK_PREFIX: rbac_id}
121
122 # Create cleanup dictionary to clean up created resources
123 # if it fails in the middle of the process
124 cleanup_data = []
125 try:
126 self.log.debug("Initializing K8s cluster for juju")
127 kubectl.create_cluster_role(
128 name=metadata_name,
129 labels=labels,
130 )
131 self.log.debug("Cluster role created")
132 cleanup_data.append(
133 {
134 "delete": kubectl.delete_cluster_role,
135 "args": (metadata_name,),
136 }
137 )
138
139 kubectl.create_service_account(
140 name=metadata_name,
141 labels=labels,
142 )
143 self.log.debug("Service account created")
144 cleanup_data.append(
145 {
146 "delete": kubectl.delete_service_account,
147 "args": (metadata_name,),
148 }
149 )
150
151 kubectl.create_cluster_role_binding(
152 name=metadata_name,
153 labels=labels,
154 )
155 self.log.debug("Role binding created")
156 cleanup_data.append(
157 {
158 "delete": kubectl.delete_service_account,
159 "args": (metadata_name,),
160 }
161 )
162 token, client_cert_data = await kubectl.get_secret_data(
163 metadata_name,
164 )
165
166 default_storage_class = kubectl.get_default_storage_class()
167 self.log.debug("Default storage class: {}".format(default_storage_class))
168 await libjuju.add_k8s(
169 name=cluster_uuid,
170 rbac_id=rbac_id,
171 token=token,
172 client_cert_data=client_cert_data,
173 configuration=kubectl.configuration,
174 storage_class=default_storage_class,
175 credential_name=self._get_credential_name(cluster_uuid),
176 )
177 self.log.debug("K8s cluster added to juju controller")
178 return cluster_uuid, True
179 except Exception as e:
180 self.log.error("Error initializing k8scluster: {}".format(e), exc_info=True)
181 if len(cleanup_data) > 0:
182 self.log.debug("Cleaning up created resources in k8s cluster...")
183 for item in cleanup_data:
184 delete_function = item["delete"]
185 delete_args = item["args"]
186 delete_function(*delete_args)
187 self.log.debug("Cleanup finished")
188 raise e
189
190 """Repo Management"""
191
192 async def repo_add(
193 self,
194 name: str,
195 url: str,
196 _type: str = "charm",
197 cert: str = None,
198 user: str = None,
199 password: str = None,
200 ):
201 raise MethodNotImplemented()
202
203 async def repo_list(self):
204 raise MethodNotImplemented()
205
206 async def repo_remove(
207 self,
208 name: str,
209 ):
210 raise MethodNotImplemented()
211
212 async def synchronize_repos(self, cluster_uuid: str, name: str):
213 """
214 Returns None as currently add_repo is not implemented
215 """
216 return None
217
218 """Reset"""
219
220 async def reset(
221 self,
222 cluster_uuid: str,
223 force: bool = False,
224 uninstall_sw: bool = False,
225 **kwargs,
226 ) -> bool:
227 """Reset a cluster
228
229 Resets the Kubernetes cluster by removing the model that represents it.
230
231 :param cluster_uuid str: The UUID of the cluster to reset
232 :param force: Force reset
233 :param uninstall_sw: Boolean to uninstall sw
234 :param: kwargs: Additional parameters
235 vca_id (str): VCA ID
236
237 :return: Returns True if successful or raises an exception.
238 """
239
240 try:
241 self.log.debug("[reset] Removing k8s cloud")
242 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
243
244 cloud = Cloud(cluster_uuid, self._get_credential_name(cluster_uuid))
245
246 cloud_creds = await libjuju.get_cloud_credentials(cloud)
247
248 await libjuju.remove_cloud(cluster_uuid)
249
250 credentials = self.get_credentials(cluster_uuid=cluster_uuid)
251
252 kubectl = self._get_kubectl(credentials)
253
254 delete_functions = [
255 kubectl.delete_cluster_role_binding,
256 kubectl.delete_service_account,
257 kubectl.delete_cluster_role,
258 ]
259
260 credential_attrs = cloud_creds[0].result["attrs"]
261 if RBAC_LABEL_KEY_NAME in credential_attrs:
262 rbac_id = credential_attrs[RBAC_LABEL_KEY_NAME]
263 metadata_name = "{}-{}".format(RBAC_STACK_PREFIX, rbac_id)
264 for delete_func in delete_functions:
265 try:
266 delete_func(metadata_name)
267 except Exception as e:
268 self.log.warning("Cannot remove resource in K8s {}".format(e))
269
270 except Exception as e:
271 self.log.debug("Caught exception during reset: {}".format(e))
272 raise e
273 return True
274
275 """Deployment"""
276
277 async def install(
278 self,
279 cluster_uuid: str,
280 kdu_model: str,
281 kdu_instance: str,
282 atomic: bool = True,
283 timeout: float = 1800,
284 params: dict = None,
285 db_dict: dict = None,
286 kdu_name: str = None,
287 namespace: str = None,
288 **kwargs,
289 ) -> bool:
290 """Install a bundle
291
292 :param cluster_uuid str: The UUID of the cluster to install to
293 :param kdu_model str: The name or path of a bundle to install
294 :param kdu_instance: Kdu instance name
295 :param atomic bool: If set, waits until the model is active and resets
296 the cluster on failure.
297 :param timeout int: The time, in seconds, to wait for the install
298 to finish
299 :param params dict: Key-value pairs of instantiation parameters
300 :param kdu_name: Name of the KDU instance to be installed
301 :param namespace: K8s namespace to use for the KDU instance
302 :param kwargs: Additional parameters
303 vca_id (str): VCA ID
304
305 :return: If successful, returns ?
306 """
307 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
308 bundle = kdu_model
309
310 if not db_dict:
311 raise K8sException("db_dict must be set")
312 if not bundle:
313 raise K8sException("bundle must be set")
314
315 if bundle.startswith("cs:"):
316 # For Juju Bundles provided by the Charm Store
317 pass
318 elif bundle.startswith("ch:"):
319 # For Juju Bundles provided by the Charm Hub (this only works for juju version >= 2.9)
320 pass
321 elif bundle.startswith("http"):
322 # Download the file
323 pass
324 else:
325 new_workdir = kdu_model.strip(kdu_model.split("/")[-1])
326 os.chdir(new_workdir)
327 bundle = "local:{}".format(kdu_model)
328
329 self.log.debug("Checking for model named {}".format(kdu_instance))
330
331 # Create the new model
332 self.log.debug("Adding model: {}".format(kdu_instance))
333 cloud = Cloud(cluster_uuid, self._get_credential_name(cluster_uuid))
334 await libjuju.add_model(kdu_instance, cloud)
335
336 # if model:
337 # TODO: Instantiation parameters
338
339 """
340 "Juju bundle that models the KDU, in any of the following ways:
341 - <juju-repo>/<juju-bundle>
342 - <juju-bundle folder under k8s_models folder in the package>
343 - <juju-bundle tgz file (w/ or w/o extension) under k8s_models folder
344 in the package>
345 - <URL_where_to_fetch_juju_bundle>
346 """
347 try:
348 previous_workdir = os.getcwd()
349 except FileNotFoundError:
350 previous_workdir = "/app/storage"
351
352 self.log.debug("[install] deploying {}".format(bundle))
353 await libjuju.deploy(
354 bundle, model_name=kdu_instance, wait=atomic, timeout=timeout
355 )
356 os.chdir(previous_workdir)
357 if self.on_update_db:
358 await self.on_update_db(
359 cluster_uuid,
360 kdu_instance,
361 filter=db_dict["filter"],
362 vca_id=kwargs.get("vca_id"),
363 )
364 return True
365
366 async def scale(
367 self,
368 kdu_instance: str,
369 scale: int,
370 resource_name: str,
371 total_timeout: float = 1800,
372 **kwargs,
373 ) -> bool:
374 """Scale an application in a model
375
376 :param: kdu_instance str: KDU instance name
377 :param: scale int: Scale to which to set the application
378 :param: resource_name str: The application name in the Juju Bundle
379 :param: timeout float: The time, in seconds, to wait for the install
380 to finish
381 :param kwargs: Additional parameters
382 vca_id (str): VCA ID
383
384 :return: If successful, returns True
385 """
386
387 try:
388 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
389 await libjuju.scale_application(
390 model_name=kdu_instance,
391 application_name=resource_name,
392 scale=scale,
393 total_timeout=total_timeout,
394 )
395 except Exception as e:
396 error_msg = "Error scaling application {} in kdu instance {}: {}".format(
397 resource_name, kdu_instance, e
398 )
399 self.log.error(error_msg)
400 raise K8sException(message=error_msg)
401 return True
402
403 async def get_scale_count(
404 self,
405 resource_name: str,
406 kdu_instance: str,
407 **kwargs,
408 ) -> int:
409 """Get an application scale count
410
411 :param: resource_name str: The application name in the Juju Bundle
412 :param: kdu_instance str: KDU instance name
413 :param kwargs: Additional parameters
414 vca_id (str): VCA ID
415 :return: Return application instance count
416 """
417
418 try:
419 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
420 status = await libjuju.get_model_status(kdu_instance)
421 return len(status.applications[resource_name].units)
422 except Exception as e:
423 error_msg = "Error getting scale count from application {} in kdu instance {}: {}".format(
424 resource_name, kdu_instance, e
425 )
426 self.log.error(error_msg)
427 raise K8sException(message=error_msg)
428
429 async def instances_list(self, cluster_uuid: str) -> list:
430 """
431 returns a list of deployed releases in a cluster
432
433 :param cluster_uuid: the cluster
434 :return:
435 """
436 return []
437
438 async def upgrade(
439 self,
440 cluster_uuid: str,
441 kdu_instance: str,
442 kdu_model: str = None,
443 params: dict = None,
444 ) -> str:
445 """Upgrade a model
446
447 :param cluster_uuid str: The UUID of the cluster to upgrade
448 :param kdu_instance str: The unique name of the KDU instance
449 :param kdu_model str: The name or path of the bundle to upgrade to
450 :param params dict: Key-value pairs of instantiation parameters
451
452 :return: If successful, reference to the new revision number of the
453 KDU instance.
454 """
455
456 # TODO: Loop through the bundle and upgrade each charm individually
457
458 """
459 The API doesn't have a concept of bundle upgrades, because there are
460 many possible changes: charm revision, disk, number of units, etc.
461
462 As such, we are only supporting a limited subset of upgrades. We'll
463 upgrade the charm revision but leave storage and scale untouched.
464
465 Scale changes should happen through OSM constructs, and changes to
466 storage would require a redeployment of the service, at least in this
467 initial release.
468 """
469 raise MethodNotImplemented()
470
471 """Rollback"""
472
473 async def rollback(
474 self,
475 cluster_uuid: str,
476 kdu_instance: str,
477 revision: int = 0,
478 ) -> str:
479 """Rollback a model
480
481 :param cluster_uuid str: The UUID of the cluster to rollback
482 :param kdu_instance str: The unique name of the KDU instance
483 :param revision int: The revision to revert to. If omitted, rolls back
484 the previous upgrade.
485
486 :return: If successful, returns the revision of active KDU instance,
487 or raises an exception
488 """
489 raise MethodNotImplemented()
490
491 """Deletion"""
492
493 async def uninstall(
494 self,
495 cluster_uuid: str,
496 kdu_instance: str,
497 **kwargs,
498 ) -> bool:
499 """Uninstall a KDU instance
500
501 :param cluster_uuid str: The UUID of the cluster
502 :param kdu_instance str: The unique name of the KDU instance
503 :param kwargs: Additional parameters
504 vca_id (str): VCA ID
505
506 :return: Returns True if successful, or raises an exception
507 """
508
509 self.log.debug("[uninstall] Destroying model")
510 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
511
512 await libjuju.destroy_model(kdu_instance, total_timeout=3600)
513
514 # self.log.debug("[uninstall] Model destroyed and disconnecting")
515 # await controller.disconnect()
516
517 return True
518 # TODO: Remove these commented lines
519 # if not self.authenticated:
520 # self.log.debug("[uninstall] Connecting to controller")
521 # await self.login(cluster_uuid)
522
523 async def exec_primitive(
524 self,
525 cluster_uuid: str = None,
526 kdu_instance: str = None,
527 primitive_name: str = None,
528 timeout: float = 300,
529 params: dict = None,
530 db_dict: dict = None,
531 **kwargs,
532 ) -> str:
533 """Exec primitive (Juju action)
534
535 :param cluster_uuid str: The UUID of the cluster
536 :param kdu_instance str: The unique name of the KDU instance
537 :param primitive_name: Name of action that will be executed
538 :param timeout: Timeout for action execution
539 :param params: Dictionary of all the parameters needed for the action
540 :param db_dict: Dictionary for any additional data
541 :param kwargs: Additional parameters
542 vca_id (str): VCA ID
543
544 :return: Returns the output of the action
545 """
546 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
547
548 if not params or "application-name" not in params:
549 raise K8sException(
550 "Missing application-name argument, \
551 argument needed for K8s actions"
552 )
553 try:
554 self.log.debug(
555 "[exec_primitive] Getting model "
556 "kdu_instance: {}".format(kdu_instance)
557 )
558 application_name = params["application-name"]
559 actions = await libjuju.get_actions(application_name, kdu_instance)
560 if primitive_name not in actions:
561 raise K8sException("Primitive {} not found".format(primitive_name))
562 output, status = await libjuju.execute_action(
563 application_name, kdu_instance, primitive_name, **params
564 )
565
566 if status != "completed":
567 raise K8sException(
568 "status is not completed: {} output: {}".format(status, output)
569 )
570 if self.on_update_db:
571 await self.on_update_db(
572 cluster_uuid, kdu_instance, filter=db_dict["filter"]
573 )
574
575 return output
576
577 except Exception as e:
578 error_msg = "Error executing primitive {}: {}".format(primitive_name, e)
579 self.log.error(error_msg)
580 raise K8sException(message=error_msg)
581
582 """Introspection"""
583
584 async def inspect_kdu(
585 self,
586 kdu_model: str,
587 ) -> dict:
588 """Inspect a KDU
589
590 Inspects a bundle and returns a dictionary of config parameters and
591 their default values.
592
593 :param kdu_model str: The name or path of the bundle to inspect.
594
595 :return: If successful, returns a dictionary of available parameters
596 and their default values.
597 """
598
599 kdu = {}
600 if not os.path.exists(kdu_model):
601 raise K8sException("file {} not found".format(kdu_model))
602
603 with open(kdu_model, "r") as f:
604 bundle = yaml.safe_load(f.read())
605
606 """
607 {
608 'description': 'Test bundle',
609 'bundle': 'kubernetes',
610 'applications': {
611 'mariadb-k8s': {
612 'charm': 'cs:~charmed-osm/mariadb-k8s-20',
613 'scale': 1,
614 'options': {
615 'password': 'manopw',
616 'root_password': 'osm4u',
617 'user': 'mano'
618 },
619 'series': 'kubernetes'
620 }
621 }
622 }
623 """
624 # TODO: This should be returned in an agreed-upon format
625 kdu = bundle["applications"]
626
627 return kdu
628
629 async def help_kdu(
630 self,
631 kdu_model: str,
632 ) -> str:
633 """View the README
634
635 If available, returns the README of the bundle.
636
637 :param kdu_model str: The name or path of a bundle
638
639 :return: If found, returns the contents of the README.
640 """
641 readme = None
642
643 files = ["README", "README.txt", "README.md"]
644 path = os.path.dirname(kdu_model)
645 for file in os.listdir(path):
646 if file in files:
647 with open(file, "r") as f:
648 readme = f.read()
649 break
650
651 return readme
652
653 async def status_kdu(
654 self,
655 cluster_uuid: str,
656 kdu_instance: str,
657 complete_status: bool = False,
658 yaml_format: bool = False,
659 **kwargs,
660 ) -> Union[str, dict]:
661 """Get the status of the KDU
662
663 Get the current status of the KDU instance.
664
665 :param cluster_uuid str: The UUID of the cluster
666 :param kdu_instance str: The unique id of the KDU instance
667 :param complete_status: To get the complete_status of the KDU
668 :param yaml_format: To get the status in proper format for NSR record
669 :param: kwargs: Additional parameters
670 vca_id (str): VCA ID
671
672 :return: Returns a dictionary containing namespace, state, resources,
673 and deployment_time and returns complete_status if complete_status is True
674 """
675 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
676 status = {}
677
678 model_status = await libjuju.get_model_status(kdu_instance)
679
680 if not complete_status:
681 for name in model_status.applications:
682 application = model_status.applications[name]
683 status[name] = {"status": application["status"]["status"]}
684 else:
685 if yaml_format:
686 return obj_to_yaml(model_status)
687 else:
688 return obj_to_dict(model_status)
689
690 return status
691
692 async def add_relation(
693 self,
694 provider: RelationEndpoint,
695 requirer: RelationEndpoint,
696 ):
697 """
698 Add relation between two charmed endpoints
699
700 :param: provider: Provider relation endpoint
701 :param: requirer: Requirer relation endpoint
702 """
703 self.log.debug(f"adding new relation between {provider} and {requirer}")
704 cross_model_relation = (
705 provider.model_name != requirer.model_name
706 or requirer.vca_id != requirer.vca_id
707 )
708 try:
709 if cross_model_relation:
710 # Cross-model relation
711 provider_libjuju = await self._get_libjuju(provider.vca_id)
712 requirer_libjuju = await self._get_libjuju(requirer.vca_id)
713 offer = await provider_libjuju.offer(provider)
714 if offer:
715 saas_name = await requirer_libjuju.consume(
716 requirer.model_name, offer, provider_libjuju
717 )
718 await requirer_libjuju.add_relation(
719 requirer.model_name,
720 requirer.endpoint,
721 saas_name,
722 )
723 else:
724 # Standard relation
725 vca_id = provider.vca_id
726 model = provider.model_name
727 libjuju = await self._get_libjuju(vca_id)
728 # add juju relations between two applications
729 await libjuju.add_relation(
730 model_name=model,
731 endpoint_1=provider.endpoint,
732 endpoint_2=requirer.endpoint,
733 )
734 except Exception as e:
735 message = f"Error adding relation between {provider} and {requirer}: {e}"
736 self.log.error(message)
737 raise Exception(message=message)
738
739 async def update_vca_status(self, vcastatus: dict, kdu_instance: str, **kwargs):
740 """
741 Add all configs, actions, executed actions of all applications in a model to vcastatus dict
742
743 :param vcastatus dict: dict containing vcastatus
744 :param kdu_instance str: The unique id of the KDU instance
745 :param: kwargs: Additional parameters
746 vca_id (str): VCA ID
747
748 :return: None
749 """
750 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
751 try:
752 for model_name in vcastatus:
753 # Adding executed actions
754 vcastatus[model_name][
755 "executedActions"
756 ] = await libjuju.get_executed_actions(kdu_instance)
757
758 for application in vcastatus[model_name]["applications"]:
759 # Adding application actions
760 vcastatus[model_name]["applications"][application][
761 "actions"
762 ] = await libjuju.get_actions(application, kdu_instance)
763 # Adding application configs
764 vcastatus[model_name]["applications"][application][
765 "configs"
766 ] = await libjuju.get_application_configs(kdu_instance, application)
767
768 except Exception as e:
769 self.log.debug("Error in updating vca status: {}".format(str(e)))
770
771 async def get_services(
772 self, cluster_uuid: str, kdu_instance: str, namespace: str
773 ) -> list:
774 """Return a list of services of a kdu_instance"""
775
776 credentials = self.get_credentials(cluster_uuid=cluster_uuid)
777 kubectl = self._get_kubectl(credentials)
778 return kubectl.get_services(
779 field_selector="metadata.namespace={}".format(kdu_instance)
780 )
781
782 async def get_service(
783 self, cluster_uuid: str, service_name: str, namespace: str
784 ) -> object:
785 """Return data for a specific service inside a namespace"""
786
787 credentials = self.get_credentials(cluster_uuid=cluster_uuid)
788 kubectl = self._get_kubectl(credentials)
789 return kubectl.get_services(
790 field_selector="metadata.name={},metadata.namespace={}".format(
791 service_name, namespace
792 )
793 )[0]
794
795 def get_credentials(self, cluster_uuid: str) -> str:
796 """
797 Get Cluster Kubeconfig
798 """
799 k8scluster = self.db.get_one(
800 "k8sclusters", q_filter={"_id": cluster_uuid}, fail_on_empty=False
801 )
802
803 self.db.encrypt_decrypt_fields(
804 k8scluster.get("credentials"),
805 "decrypt",
806 ["password", "secret"],
807 schema_version=k8scluster["schema_version"],
808 salt=k8scluster["_id"],
809 )
810
811 return yaml.safe_dump(k8scluster.get("credentials"))
812
813 def _get_credential_name(self, cluster_uuid: str) -> str:
814 """
815 Get credential name for a k8s cloud
816
817 We cannot use the cluster_uuid for the credential name directly,
818 because it cannot start with a number, it must start with a letter.
819 Therefore, the k8s cloud credential name will be "cred-" followed
820 by the cluster uuid.
821
822 :param: cluster_uuid: Cluster UUID of the kubernetes cloud (=cloud_name)
823
824 :return: Name to use for the credential name.
825 """
826 return "cred-{}".format(cluster_uuid)
827
828 def get_namespace(
829 self,
830 cluster_uuid: str,
831 ) -> str:
832 """Get the namespace UUID
833 Gets the namespace's unique name
834
835 :param cluster_uuid str: The UUID of the cluster
836 :returns: The namespace UUID, or raises an exception
837 """
838 pass
839
840 @staticmethod
841 def generate_kdu_instance_name(**kwargs):
842 db_dict = kwargs.get("db_dict")
843 kdu_name = kwargs.get("kdu_name", None)
844 if kdu_name:
845 kdu_instance = "{}-{}".format(kdu_name, db_dict["filter"]["_id"])
846 else:
847 kdu_instance = db_dict["filter"]["_id"]
848 return kdu_instance
849
850 async def _get_libjuju(self, vca_id: str = None) -> Libjuju:
851 """
852 Get libjuju object
853
854 :param: vca_id: VCA ID
855 If None, get a libjuju object with a Connection to the default VCA
856 Else, geta libjuju object with a Connection to the specified VCA
857 """
858 if not vca_id:
859 while self.loading_libjuju.locked():
860 await asyncio.sleep(0.1)
861 if not self.libjuju:
862 async with self.loading_libjuju:
863 vca_connection = await get_connection(self._store)
864 self.libjuju = Libjuju(vca_connection, loop=self.loop, log=self.log)
865 return self.libjuju
866 else:
867 vca_connection = await get_connection(self._store, vca_id)
868 return Libjuju(
869 vca_connection,
870 loop=self.loop,
871 log=self.log,
872 n2vc=self,
873 )
874
875 def _get_kubectl(self, credentials: str) -> Kubectl:
876 """
877 Get Kubectl object
878
879 :param: kubeconfig_credentials: Kubeconfig credentials
880 """
881 kubecfg = tempfile.NamedTemporaryFile()
882 with open(kubecfg.name, "w") as kubecfg_file:
883 kubecfg_file.write(credentials)
884 return Kubectl(config_file=kubecfg.name)