f8ed0e0155cca930d744fdb677e943f0c0011fe7
[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 pass
307 elif bundle.startswith("http"):
308 # Download the file
309 pass
310 else:
311 new_workdir = kdu_model.strip(kdu_model.split("/")[-1])
312 os.chdir(new_workdir)
313 bundle = "local:{}".format(kdu_model)
314
315 self.log.debug("Checking for model named {}".format(kdu_instance))
316
317 # Create the new model
318 self.log.debug("Adding model: {}".format(kdu_instance))
319 cloud = Cloud(cluster_uuid, self._get_credential_name(cluster_uuid))
320 await libjuju.add_model(kdu_instance, cloud)
321
322 # if model:
323 # TODO: Instantiation parameters
324
325 """
326 "Juju bundle that models the KDU, in any of the following ways:
327 - <juju-repo>/<juju-bundle>
328 - <juju-bundle folder under k8s_models folder in the package>
329 - <juju-bundle tgz file (w/ or w/o extension) under k8s_models folder
330 in the package>
331 - <URL_where_to_fetch_juju_bundle>
332 """
333 try:
334 previous_workdir = os.getcwd()
335 except FileNotFoundError:
336 previous_workdir = "/app/storage"
337
338 self.log.debug("[install] deploying {}".format(bundle))
339 await libjuju.deploy(
340 bundle, model_name=kdu_instance, wait=atomic, timeout=timeout
341 )
342 os.chdir(previous_workdir)
343 if self.on_update_db:
344 await self.on_update_db(
345 cluster_uuid,
346 kdu_instance,
347 filter=db_dict["filter"],
348 vca_id=kwargs.get("vca_id"),
349 )
350 return True
351
352 async def scale(
353 self,
354 kdu_instance: str,
355 scale: int,
356 resource_name: str,
357 total_timeout: float = 1800,
358 **kwargs,
359 ) -> bool:
360 """Scale an application in a model
361
362 :param: kdu_instance str: KDU instance name
363 :param: scale int: Scale to which to set this application
364 :param: resource_name str: Resource name (Application name)
365 :param: timeout float: The time, in seconds, to wait for the install
366 to finish
367 :param kwargs: Additional parameters
368 vca_id (str): VCA ID
369
370 :return: If successful, returns True
371 """
372
373 try:
374 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
375 await libjuju.scale_application(
376 model_name=kdu_instance,
377 application_name=resource_name,
378 scale=scale,
379 total_timeout=total_timeout,
380 )
381 except Exception as e:
382 error_msg = "Error scaling application {} in kdu instance {}: {}".format(
383 resource_name, kdu_instance, e
384 )
385 self.log.error(error_msg)
386 raise K8sException(message=error_msg)
387 return True
388
389 async def get_scale_count(
390 self,
391 resource_name: str,
392 kdu_instance: str,
393 **kwargs,
394 ) -> int:
395 """Get an application scale count
396
397 :param: resource_name str: Resource name (Application name)
398 :param: kdu_instance str: KDU instance name
399 :param kwargs: Additional parameters
400 vca_id (str): VCA ID
401 :return: Return application instance count
402 """
403 try:
404 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
405 status = await libjuju.get_model_status(kdu_instance)
406 return len(status.applications[resource_name].units)
407 except Exception as e:
408 error_msg = "Error getting scale count from application {} in kdu instance {}: {}".format(
409 resource_name, kdu_instance, e
410 )
411 self.log.error(error_msg)
412 raise K8sException(message=error_msg)
413
414 async def instances_list(self, cluster_uuid: str) -> list:
415 """
416 returns a list of deployed releases in a cluster
417
418 :param cluster_uuid: the cluster
419 :return:
420 """
421 return []
422
423 async def upgrade(
424 self,
425 cluster_uuid: str,
426 kdu_instance: str,
427 kdu_model: str = None,
428 params: dict = None,
429 ) -> str:
430 """Upgrade a model
431
432 :param cluster_uuid str: The UUID of the cluster to upgrade
433 :param kdu_instance str: The unique name of the KDU instance
434 :param kdu_model str: The name or path of the bundle to upgrade to
435 :param params dict: Key-value pairs of instantiation parameters
436
437 :return: If successful, reference to the new revision number of the
438 KDU instance.
439 """
440
441 # TODO: Loop through the bundle and upgrade each charm individually
442
443 """
444 The API doesn't have a concept of bundle upgrades, because there are
445 many possible changes: charm revision, disk, number of units, etc.
446
447 As such, we are only supporting a limited subset of upgrades. We'll
448 upgrade the charm revision but leave storage and scale untouched.
449
450 Scale changes should happen through OSM constructs, and changes to
451 storage would require a redeployment of the service, at least in this
452 initial release.
453 """
454 raise MethodNotImplemented()
455
456 """Rollback"""
457
458 async def rollback(
459 self,
460 cluster_uuid: str,
461 kdu_instance: str,
462 revision: int = 0,
463 ) -> str:
464 """Rollback a model
465
466 :param cluster_uuid str: The UUID of the cluster to rollback
467 :param kdu_instance str: The unique name of the KDU instance
468 :param revision int: The revision to revert to. If omitted, rolls back
469 the previous upgrade.
470
471 :return: If successful, returns the revision of active KDU instance,
472 or raises an exception
473 """
474 raise MethodNotImplemented()
475
476 """Deletion"""
477
478 async def uninstall(
479 self,
480 cluster_uuid: str,
481 kdu_instance: str,
482 **kwargs,
483 ) -> bool:
484 """Uninstall a KDU instance
485
486 :param cluster_uuid str: The UUID of the cluster
487 :param kdu_instance str: The unique name of the KDU instance
488 :param kwargs: Additional parameters
489 vca_id (str): VCA ID
490
491 :return: Returns True if successful, or raises an exception
492 """
493
494 self.log.debug("[uninstall] Destroying model")
495 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
496
497 await libjuju.destroy_model(kdu_instance, total_timeout=3600)
498
499 # self.log.debug("[uninstall] Model destroyed and disconnecting")
500 # await controller.disconnect()
501
502 return True
503 # TODO: Remove these commented lines
504 # if not self.authenticated:
505 # self.log.debug("[uninstall] Connecting to controller")
506 # await self.login(cluster_uuid)
507
508 async def exec_primitive(
509 self,
510 cluster_uuid: str = None,
511 kdu_instance: str = None,
512 primitive_name: str = None,
513 timeout: float = 300,
514 params: dict = None,
515 db_dict: dict = None,
516 **kwargs,
517 ) -> str:
518 """Exec primitive (Juju action)
519
520 :param cluster_uuid str: The UUID of the cluster
521 :param kdu_instance str: The unique name of the KDU instance
522 :param primitive_name: Name of action that will be executed
523 :param timeout: Timeout for action execution
524 :param params: Dictionary of all the parameters needed for the action
525 :param db_dict: Dictionary for any additional data
526 :param kwargs: Additional parameters
527 vca_id (str): VCA ID
528
529 :return: Returns the output of the action
530 """
531 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
532
533 if not params or "application-name" not in params:
534 raise K8sException(
535 "Missing application-name argument, \
536 argument needed for K8s actions"
537 )
538 try:
539 self.log.debug(
540 "[exec_primitive] Getting model "
541 "kdu_instance: {}".format(kdu_instance)
542 )
543 application_name = params["application-name"]
544 actions = await libjuju.get_actions(application_name, kdu_instance)
545 if primitive_name not in actions:
546 raise K8sException("Primitive {} not found".format(primitive_name))
547 output, status = await libjuju.execute_action(
548 application_name, kdu_instance, primitive_name, **params
549 )
550
551 if status != "completed":
552 raise K8sException(
553 "status is not completed: {} output: {}".format(status, output)
554 )
555 if self.on_update_db:
556 await self.on_update_db(
557 cluster_uuid, kdu_instance, filter=db_dict["filter"]
558 )
559
560 return output
561
562 except Exception as e:
563 error_msg = "Error executing primitive {}: {}".format(primitive_name, e)
564 self.log.error(error_msg)
565 raise K8sException(message=error_msg)
566
567 """Introspection"""
568
569 async def inspect_kdu(
570 self,
571 kdu_model: str,
572 ) -> dict:
573 """Inspect a KDU
574
575 Inspects a bundle and returns a dictionary of config parameters and
576 their default values.
577
578 :param kdu_model str: The name or path of the bundle to inspect.
579
580 :return: If successful, returns a dictionary of available parameters
581 and their default values.
582 """
583
584 kdu = {}
585 if not os.path.exists(kdu_model):
586 raise K8sException("file {} not found".format(kdu_model))
587
588 with open(kdu_model, "r") as f:
589 bundle = yaml.safe_load(f.read())
590
591 """
592 {
593 'description': 'Test bundle',
594 'bundle': 'kubernetes',
595 'applications': {
596 'mariadb-k8s': {
597 'charm': 'cs:~charmed-osm/mariadb-k8s-20',
598 'scale': 1,
599 'options': {
600 'password': 'manopw',
601 'root_password': 'osm4u',
602 'user': 'mano'
603 },
604 'series': 'kubernetes'
605 }
606 }
607 }
608 """
609 # TODO: This should be returned in an agreed-upon format
610 kdu = bundle["applications"]
611
612 return kdu
613
614 async def help_kdu(
615 self,
616 kdu_model: str,
617 ) -> str:
618 """View the README
619
620 If available, returns the README of the bundle.
621
622 :param kdu_model str: The name or path of a bundle
623
624 :return: If found, returns the contents of the README.
625 """
626 readme = None
627
628 files = ["README", "README.txt", "README.md"]
629 path = os.path.dirname(kdu_model)
630 for file in os.listdir(path):
631 if file in files:
632 with open(file, "r") as f:
633 readme = f.read()
634 break
635
636 return readme
637
638 async def status_kdu(
639 self,
640 cluster_uuid: str,
641 kdu_instance: str,
642 complete_status: bool = False,
643 yaml_format: bool = False,
644 **kwargs,
645 ) -> dict:
646 """Get the status of the KDU
647
648 Get the current status of the KDU instance.
649
650 :param cluster_uuid str: The UUID of the cluster
651 :param kdu_instance str: The unique id of the KDU instance
652 :param complete_status: To get the complete_status of the KDU
653 :param yaml_format: To get the status in proper format for NSR record
654 :param: kwargs: Additional parameters
655 vca_id (str): VCA ID
656
657 :return: Returns a dictionary containing namespace, state, resources,
658 and deployment_time and returns complete_status if complete_status is True
659 """
660 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
661 status = {}
662
663 model_status = await libjuju.get_model_status(kdu_instance)
664
665 if not complete_status:
666 for name in model_status.applications:
667 application = model_status.applications[name]
668 status[name] = {"status": application["status"]["status"]}
669 else:
670 if yaml_format:
671 return obj_to_yaml(model_status)
672 else:
673 return obj_to_dict(model_status)
674
675 return status
676
677 async def add_relation(
678 self,
679 provider: RelationEndpoint,
680 requirer: RelationEndpoint,
681 ):
682 """
683 Add relation between two charmed endpoints
684
685 :param: provider: Provider relation endpoint
686 :param: requirer: Requirer relation endpoint
687 """
688 self.log.debug(f"adding new relation between {provider} and {requirer}")
689 cross_model_relation = (
690 provider.model_name != requirer.model_name
691 or requirer.vca_id != requirer.vca_id
692 )
693 try:
694 if cross_model_relation:
695 # Cross-model relation
696 provider_libjuju = await self._get_libjuju(provider.vca_id)
697 requirer_libjuju = await self._get_libjuju(requirer.vca_id)
698 offer = await provider_libjuju.offer(provider)
699 if offer:
700 saas_name = await requirer_libjuju.consume(
701 requirer.model_name, offer, provider_libjuju
702 )
703 await requirer_libjuju.add_relation(
704 requirer.model_name,
705 requirer.endpoint,
706 saas_name,
707 )
708 else:
709 # Standard relation
710 vca_id = provider.vca_id
711 model = provider.model_name
712 libjuju = await self._get_libjuju(vca_id)
713 # add juju relations between two applications
714 await libjuju.add_relation(
715 model_name=model,
716 endpoint_1=provider.endpoint,
717 endpoint_2=requirer.endpoint,
718 )
719 except Exception as e:
720 message = f"Error adding relation between {provider} and {requirer}: {e}"
721 self.log.error(message)
722 raise Exception(message=message)
723
724 async def update_vca_status(self, vcastatus: dict, kdu_instance: str, **kwargs):
725 """
726 Add all configs, actions, executed actions of all applications in a model to vcastatus dict
727
728 :param vcastatus dict: dict containing vcastatus
729 :param kdu_instance str: The unique id of the KDU instance
730 :param: kwargs: Additional parameters
731 vca_id (str): VCA ID
732
733 :return: None
734 """
735 libjuju = await self._get_libjuju(kwargs.get("vca_id"))
736 try:
737 for model_name in vcastatus:
738 # Adding executed actions
739 vcastatus[model_name][
740 "executedActions"
741 ] = await libjuju.get_executed_actions(kdu_instance)
742
743 for application in vcastatus[model_name]["applications"]:
744 # Adding application actions
745 vcastatus[model_name]["applications"][application][
746 "actions"
747 ] = await libjuju.get_actions(application, kdu_instance)
748 # Adding application configs
749 vcastatus[model_name]["applications"][application][
750 "configs"
751 ] = await libjuju.get_application_configs(kdu_instance, application)
752
753 except Exception as e:
754 self.log.debug("Error in updating vca status: {}".format(str(e)))
755
756 async def get_services(
757 self, cluster_uuid: str, kdu_instance: str, namespace: str
758 ) -> list:
759 """Return a list of services of a kdu_instance"""
760
761 credentials = self.get_credentials(cluster_uuid=cluster_uuid)
762 kubectl = self._get_kubectl(credentials)
763 return kubectl.get_services(
764 field_selector="metadata.namespace={}".format(kdu_instance)
765 )
766
767 async def get_service(
768 self, cluster_uuid: str, service_name: str, namespace: str
769 ) -> object:
770 """Return data for a specific service inside a namespace"""
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.name={},metadata.namespace={}".format(
776 service_name, namespace
777 )
778 )[0]
779
780 def get_credentials(self, cluster_uuid: str) -> str:
781 """
782 Get Cluster Kubeconfig
783 """
784 k8scluster = self.db.get_one(
785 "k8sclusters", q_filter={"_id": cluster_uuid}, fail_on_empty=False
786 )
787
788 self.db.encrypt_decrypt_fields(
789 k8scluster.get("credentials"),
790 "decrypt",
791 ["password", "secret"],
792 schema_version=k8scluster["schema_version"],
793 salt=k8scluster["_id"],
794 )
795
796 return yaml.safe_dump(k8scluster.get("credentials"))
797
798 def _get_credential_name(self, cluster_uuid: str) -> str:
799 """
800 Get credential name for a k8s cloud
801
802 We cannot use the cluster_uuid for the credential name directly,
803 because it cannot start with a number, it must start with a letter.
804 Therefore, the k8s cloud credential name will be "cred-" followed
805 by the cluster uuid.
806
807 :param: cluster_uuid: Cluster UUID of the kubernetes cloud (=cloud_name)
808
809 :return: Name to use for the credential name.
810 """
811 return "cred-{}".format(cluster_uuid)
812
813 def get_namespace(
814 self,
815 cluster_uuid: str,
816 ) -> str:
817 """Get the namespace UUID
818 Gets the namespace's unique name
819
820 :param cluster_uuid str: The UUID of the cluster
821 :returns: The namespace UUID, or raises an exception
822 """
823 pass
824
825 @staticmethod
826 def generate_kdu_instance_name(**kwargs):
827 db_dict = kwargs.get("db_dict")
828 kdu_name = kwargs.get("kdu_name", None)
829 if kdu_name:
830 kdu_instance = "{}-{}".format(kdu_name, db_dict["filter"]["_id"])
831 else:
832 kdu_instance = db_dict["filter"]["_id"]
833 return kdu_instance
834
835 async def _get_libjuju(self, vca_id: str = None) -> Libjuju:
836 """
837 Get libjuju object
838
839 :param: vca_id: VCA ID
840 If None, get a libjuju object with a Connection to the default VCA
841 Else, geta libjuju object with a Connection to the specified VCA
842 """
843 if not vca_id:
844 while self.loading_libjuju.locked():
845 await asyncio.sleep(0.1)
846 if not self.libjuju:
847 async with self.loading_libjuju:
848 vca_connection = await get_connection(self._store)
849 self.libjuju = Libjuju(vca_connection, loop=self.loop, log=self.log)
850 return self.libjuju
851 else:
852 vca_connection = await get_connection(self._store, vca_id)
853 return Libjuju(
854 vca_connection,
855 loop=self.loop,
856 log=self.log,
857 n2vc=self,
858 )
859
860 def _get_kubectl(self, credentials: str) -> Kubectl:
861 """
862 Get Kubectl object
863
864 :param: kubeconfig_credentials: Kubeconfig credentials
865 """
866 kubecfg = tempfile.NamedTemporaryFile()
867 with open(kubecfg.name, "w") as kubecfg_file:
868 kubecfg_file.write(credentials)
869 return Kubectl(config_file=kubecfg.name)