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