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