2 # Copyright 2019 Telefonica Investigacion y Desarrollo, S.A.U.
3 # This file is part of OSM
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
10 # http://www.apache.org/licenses/LICENSE-2.0
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
19 # For those usages not covered by the Apache License, Version 2.0 please
20 # contact with: nfvlabs@tid.es
27 from n2vc
.config
import ModelConfig
28 from n2vc
.exceptions
import (
29 N2VCBadArgumentsException
,
31 N2VCConnectionException
,
32 N2VCExecutionException
,
35 JujuK8sProxycharmNotSupported
,
37 from n2vc
.n2vc_conn
import N2VCConnector
38 from n2vc
.n2vc_conn
import obj_to_dict
, obj_to_yaml
39 from n2vc
.libjuju
import Libjuju
40 from n2vc
.utils
import base64_to_cacert
43 class N2VCJujuConnector(N2VCConnector
):
46 ####################################################################################
47 ################################### P U B L I C ####################################
48 ####################################################################################
51 BUILT_IN_CLOUDS
= ["localhost", "microk8s"]
59 url
: str = "127.0.0.1:17070",
60 username
: str = "admin",
61 vca_config
: dict = None,
64 """Initialize juju N2VC connector
67 # parent class constructor
68 N2VCConnector
.__init
__(
76 vca_config
=vca_config
,
77 on_update_db
=on_update_db
,
80 # silence websocket traffic log
81 logging
.getLogger("websockets.protocol").setLevel(logging
.INFO
)
82 logging
.getLogger("juju.client.connection").setLevel(logging
.WARN
)
83 logging
.getLogger("model").setLevel(logging
.WARN
)
85 self
.log
.info("Initializing N2VC juju connector...")
88 ##############################################################
90 ##############################################################
95 raise N2VCBadArgumentsException("Argument url is mandatory", ["url"])
96 url_parts
= url
.split(":")
97 if len(url_parts
) != 2:
98 raise N2VCBadArgumentsException(
99 "Argument url: bad format (localhost:port) -> {}".format(url
), ["url"]
101 self
.hostname
= url_parts
[0]
103 self
.port
= int(url_parts
[1])
105 raise N2VCBadArgumentsException(
106 "url port must be a number -> {}".format(url
), ["url"]
111 raise N2VCBadArgumentsException(
112 "Argument username is mandatory", ["username"]
116 if vca_config
is None:
117 raise N2VCBadArgumentsException(
118 "Argument vca_config is mandatory", ["vca_config"]
121 if "secret" in vca_config
:
122 self
.secret
= vca_config
["secret"]
124 raise N2VCBadArgumentsException(
125 "Argument vca_config.secret is mandatory", ["vca_config.secret"]
128 # pubkey of juju client in osm machine: ~/.local/share/juju/ssh/juju_id_rsa.pub
129 # if exists, it will be written in lcm container: _create_juju_public_key()
130 if "public_key" in vca_config
:
131 self
.public_key
= vca_config
["public_key"]
133 self
.public_key
= None
135 # TODO: Verify ca_cert is valid before using. VCA will crash
136 # if the ca_cert isn't formatted correctly.
138 self
.ca_cert
= vca_config
.get("ca_cert")
140 self
.ca_cert
= base64_to_cacert(vca_config
["ca_cert"])
142 if "api_proxy" in vca_config
and vca_config
["api_proxy"] != "":
143 self
.api_proxy
= vca_config
["api_proxy"]
145 "api_proxy for native charms configured: {}".format(self
.api_proxy
)
149 "api_proxy is not configured"
151 self
.api_proxy
= None
153 model_config
= ModelConfig(vca_config
)
155 self
.cloud
= vca_config
.get('cloud')
156 self
.k8s_cloud
= None
157 if "k8s_cloud" in vca_config
:
158 self
.k8s_cloud
= vca_config
.get("k8s_cloud")
159 self
.log
.debug('Arguments have been checked')
162 self
.controller
= None # it will be filled when connect to juju
163 self
.juju_models
= {} # model objects for every model_name
164 self
.juju_observers
= {} # model observers for every model_name
166 False # while connecting to juju (to avoid duplicate connections)
168 self
._authenticated
= (
169 False # it will be True when juju connection be stablished
171 self
._creating
_model
= False # True during model creation
172 self
.libjuju
= Libjuju(
174 api_proxy
=self
.api_proxy
,
175 username
=self
.username
,
176 password
=self
.secret
,
182 model_config
=model_config
,
185 # create juju pub key file in lcm container at
186 # ./local/share/juju/ssh/juju_id_rsa.pub
187 self
._create
_juju
_public
_key
()
189 self
.log
.info("N2VC juju connector initialized")
191 async def get_status(self
, namespace
: str, yaml_format
: bool = True):
193 # self.log.info('Getting NS status. namespace: {}'.format(namespace))
195 _nsi_id
, ns_id
, _vnf_id
, _vdu_id
, _vdu_count
= self
._get
_namespace
_components
(
198 # model name is ns_id
200 if model_name
is None:
201 msg
= "Namespace {} not valid".format(namespace
)
203 raise N2VCBadArgumentsException(msg
, ["namespace"])
206 models
= await self
.libjuju
.list_models(contains
=ns_id
)
209 status
[m
] = await self
.libjuju
.get_model_status(m
)
212 return obj_to_yaml(status
)
214 return obj_to_dict(status
)
216 async def create_execution_environment(
220 reuse_ee_id
: str = None,
221 progress_timeout
: float = None,
222 total_timeout
: float = None,
223 cloud_name
: str = None,
224 credential_name
: str = None,
228 "Creating execution environment. namespace: {}, reuse_ee_id: {}".format(
229 namespace
, reuse_ee_id
235 model_name
, application_name
, machine_id
= self
._get
_ee
_id
_components
(
245 ) = self
._get
_namespace
_components
(namespace
=namespace
)
246 # model name is ns_id
249 application_name
= self
._get
_application
_name
(namespace
=namespace
)
252 "model name: {}, application name: {}, machine_id: {}".format(
253 model_name
, application_name
, machine_id
257 # create or reuse a new juju machine
259 if not await self
.libjuju
.model_exists(model_name
):
260 cloud
= cloud_name
or self
.cloud
261 credential
= credential_name
or cloud_name
if cloud_name
else self
.cloud
262 await self
.libjuju
.add_model(
265 credential_name
=credential
267 machine
, new
= await self
.libjuju
.create_machine(
268 model_name
=model_name
,
269 machine_id
=machine_id
,
271 progress_timeout
=progress_timeout
,
272 total_timeout
=total_timeout
,
274 # id for the execution environment
275 ee_id
= N2VCJujuConnector
._build
_ee
_id
(
276 model_name
=model_name
,
277 application_name
=application_name
,
278 machine_id
=str(machine
.entity_id
),
280 self
.log
.debug("ee_id: {}".format(ee_id
))
283 # write ee_id in database
284 self
._write
_ee
_id
_db
(db_dict
=db_dict
, ee_id
=ee_id
)
286 except Exception as e
:
287 message
= "Error creating machine on juju: {}".format(e
)
288 self
.log
.error(message
)
289 raise N2VCException(message
=message
)
291 # new machine credentials
293 "hostname": machine
.dns_name
,
297 "Execution environment created. ee_id: {}, credentials: {}".format(
302 return ee_id
, credentials
304 async def register_execution_environment(
309 progress_timeout
: float = None,
310 total_timeout
: float = None,
311 cloud_name
: str = None,
312 credential_name
: str = None,
316 "Registering execution environment. namespace={}, credentials={}".format(
317 namespace
, credentials
321 if credentials
is None:
322 raise N2VCBadArgumentsException(
323 message
="credentials are mandatory", bad_args
=["credentials"]
325 if credentials
.get("hostname"):
326 hostname
= credentials
["hostname"]
328 raise N2VCBadArgumentsException(
329 message
="hostname is mandatory", bad_args
=["credentials.hostname"]
331 if credentials
.get("username"):
332 username
= credentials
["username"]
334 raise N2VCBadArgumentsException(
335 message
="username is mandatory", bad_args
=["credentials.username"]
337 if "private_key_path" in credentials
:
338 private_key_path
= credentials
["private_key_path"]
340 # if not passed as argument, use generated private key path
341 private_key_path
= self
.private_key_path
343 _nsi_id
, ns_id
, _vnf_id
, _vdu_id
, _vdu_count
= self
._get
_namespace
_components
(
350 application_name
= self
._get
_application
_name
(namespace
=namespace
)
352 # register machine on juju
354 if not await self
.libjuju
.model_exists(model_name
):
355 cloud
= cloud_name
or self
.cloud
356 credential
= credential_name
or cloud_name
if cloud_name
else self
.cloud
357 await self
.libjuju
.add_model(
360 credential_name
=credential
362 machine_id
= await self
.libjuju
.provision_machine(
363 model_name
=model_name
,
366 private_key_path
=private_key_path
,
368 progress_timeout
=progress_timeout
,
369 total_timeout
=total_timeout
,
371 except Exception as e
:
372 self
.log
.error("Error registering machine: {}".format(e
))
374 message
="Error registering machine on juju: {}".format(e
)
377 self
.log
.info("Machine registered: {}".format(machine_id
))
379 # id for the execution environment
380 ee_id
= N2VCJujuConnector
._build
_ee
_id
(
381 model_name
=model_name
,
382 application_name
=application_name
,
383 machine_id
=str(machine_id
),
386 self
.log
.info("Execution environment registered. ee_id: {}".format(ee_id
))
390 async def install_configuration_sw(
395 progress_timeout
: float = None,
396 total_timeout
: float = None,
403 "Installing configuration sw on ee_id: {}, "
404 "artifact path: {}, db_dict: {}"
405 ).format(ee_id
, artifact_path
, db_dict
)
409 if ee_id
is None or len(ee_id
) == 0:
410 raise N2VCBadArgumentsException(
411 message
="ee_id is mandatory", bad_args
=["ee_id"]
413 if artifact_path
is None or len(artifact_path
) == 0:
414 raise N2VCBadArgumentsException(
415 message
="artifact_path is mandatory", bad_args
=["artifact_path"]
418 raise N2VCBadArgumentsException(
419 message
="db_dict is mandatory", bad_args
=["db_dict"]
427 ) = N2VCJujuConnector
._get
_ee
_id
_components
(ee_id
=ee_id
)
429 "model: {}, application: {}, machine: {}".format(
430 model_name
, application_name
, machine_id
434 raise N2VCBadArgumentsException(
435 message
="ee_id={} is not a valid execution environment id".format(
441 # remove // in charm path
442 while artifact_path
.find("//") >= 0:
443 artifact_path
= artifact_path
.replace("//", "/")
446 if not self
.fs
.file_exists(artifact_path
, mode
="dir"):
447 msg
= "artifact path does not exist: {}".format(artifact_path
)
448 raise N2VCBadArgumentsException(message
=msg
, bad_args
=["artifact_path"])
450 if artifact_path
.startswith("/"):
451 full_path
= self
.fs
.path
+ artifact_path
453 full_path
= self
.fs
.path
+ "/" + artifact_path
456 await self
.libjuju
.deploy_charm(
457 model_name
=model_name
,
458 application_name
=application_name
,
460 machine_id
=machine_id
,
462 progress_timeout
=progress_timeout
,
463 total_timeout
=total_timeout
,
467 except Exception as e
:
469 message
="Error desploying charm into ee={} : {}".format(ee_id
, e
)
472 self
.log
.info("Configuration sw installed")
474 async def install_k8s_proxy_charm(
480 progress_timeout
: float = None,
481 total_timeout
: float = None,
483 cloud_name
: str = None,
484 credential_name
: str = None,
487 Install a k8s proxy charm
489 :param charm_name: Name of the charm being deployed
490 :param namespace: collection of all the uuids related to the charm.
491 :param str artifact_path: where to locate the artifacts (parent folder) using
493 the final artifact path will be a combination of this artifact_path and
494 additional string from the config_dict (e.g. charm name)
495 :param dict db_dict: where to write into database when the status changes.
496 It contains a dict with
497 {collection: <str>, filter: {}, path: <str>},
498 e.g. {collection: "nsrs", filter:
499 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
500 :param float progress_timeout:
501 :param float total_timeout:
502 :param config: Dictionary with additional configuration
503 :param cloud_name: Cloud Name in which the charms will be deployed
504 :param credential_name: Credential Name to use in the cloud_name.
505 If not set, cloud_name will be used as credential_name
507 :returns ee_id: execution environment id.
509 self
.log
.info('Installing k8s proxy charm: {}, artifact path: {}, db_dict: {}'
510 .format(charm_name
, artifact_path
, db_dict
))
512 if not self
.k8s_cloud
:
513 raise JujuK8sProxycharmNotSupported("There is not k8s_cloud available")
515 if artifact_path
is None or len(artifact_path
) == 0:
516 raise N2VCBadArgumentsException(
517 message
="artifact_path is mandatory", bad_args
=["artifact_path"]
520 raise N2VCBadArgumentsException(message
='db_dict is mandatory', bad_args
=['db_dict'])
522 # remove // in charm path
523 while artifact_path
.find('//') >= 0:
524 artifact_path
= artifact_path
.replace('//', '/')
527 if not self
.fs
.file_exists(artifact_path
, mode
="dir"):
528 msg
= 'artifact path does not exist: {}'.format(artifact_path
)
529 raise N2VCBadArgumentsException(message
=msg
, bad_args
=['artifact_path'])
531 if artifact_path
.startswith('/'):
532 full_path
= self
.fs
.path
+ artifact_path
534 full_path
= self
.fs
.path
+ '/' + artifact_path
536 _
, ns_id
, _
, _
, _
= self
._get
_namespace
_components
(namespace
=namespace
)
537 model_name
= '{}-k8s'.format(ns_id
)
538 if not await self
.libjuju
.model_exists(model_name
):
539 cloud
= cloud_name
or self
.k8s_cloud
540 credential
= credential_name
or cloud_name
if cloud_name
else self
.k8s_cloud
541 await self
.libjuju
.add_model(
544 credential_name
=credential
546 application_name
= self
._get
_application
_name
(namespace
)
549 await self
.libjuju
.deploy_charm(
550 model_name
=model_name
,
551 application_name
=application_name
,
555 progress_timeout
=progress_timeout
,
556 total_timeout
=total_timeout
,
559 except Exception as e
:
560 raise N2VCException(message
='Error deploying charm: {}'.format(e
))
562 self
.log
.info('K8s proxy charm installed')
563 ee_id
= N2VCJujuConnector
._build
_ee
_id
(
564 model_name
=model_name
,
565 application_name
=application_name
,
569 self
._write
_ee
_id
_db
(db_dict
=db_dict
, ee_id
=ee_id
)
573 async def get_ee_ssh_public__key(
577 progress_timeout
: float = None,
578 total_timeout
: float = None,
583 "Generating priv/pub key pair and get pub key on ee_id: {}, db_dict: {}"
584 ).format(ee_id
, db_dict
)
588 if ee_id
is None or len(ee_id
) == 0:
589 raise N2VCBadArgumentsException(
590 message
="ee_id is mandatory", bad_args
=["ee_id"]
593 raise N2VCBadArgumentsException(
594 message
="db_dict is mandatory", bad_args
=["db_dict"]
602 ) = N2VCJujuConnector
._get
_ee
_id
_components
(ee_id
=ee_id
)
604 "model: {}, application: {}, machine: {}".format(
605 model_name
, application_name
, machine_id
609 raise N2VCBadArgumentsException(
610 message
="ee_id={} is not a valid execution environment id".format(
616 # try to execute ssh layer primitives (if exist):
622 application_name
= N2VCJujuConnector
._format
_app
_name
(application_name
)
624 # execute action: generate-ssh-key
626 output
, _status
= await self
.libjuju
.execute_action(
627 model_name
=model_name
,
628 application_name
=application_name
,
629 action_name
="generate-ssh-key",
631 progress_timeout
=progress_timeout
,
632 total_timeout
=total_timeout
,
634 except Exception as e
:
636 "Skipping exception while executing action generate-ssh-key: {}".format(
641 # execute action: get-ssh-public-key
643 output
, _status
= await self
.libjuju
.execute_action(
644 model_name
=model_name
,
645 application_name
=application_name
,
646 action_name
="get-ssh-public-key",
648 progress_timeout
=progress_timeout
,
649 total_timeout
=total_timeout
,
651 except Exception as e
:
652 msg
= "Cannot execute action get-ssh-public-key: {}\n".format(e
)
654 raise N2VCExecutionException(e
, primitive_name
="get-ssh-public-key")
656 # return public key if exists
657 return output
["pubkey"] if "pubkey" in output
else output
659 async def get_metrics(self
, model_name
: str, application_name
: str) -> dict:
660 return await self
.libjuju
.get_metrics(model_name
, application_name
)
662 async def add_relation(
663 self
, ee_id_1
: str, ee_id_2
: str, endpoint_1
: str, endpoint_2
: str
667 "adding new relation between {} and {}, endpoints: {}, {}".format(
668 ee_id_1
, ee_id_2
, endpoint_1
, endpoint_2
674 message
= "EE 1 is mandatory"
675 self
.log
.error(message
)
676 raise N2VCBadArgumentsException(message
=message
, bad_args
=["ee_id_1"])
678 message
= "EE 2 is mandatory"
679 self
.log
.error(message
)
680 raise N2VCBadArgumentsException(message
=message
, bad_args
=["ee_id_2"])
682 message
= "endpoint 1 is mandatory"
683 self
.log
.error(message
)
684 raise N2VCBadArgumentsException(message
=message
, bad_args
=["endpoint_1"])
686 message
= "endpoint 2 is mandatory"
687 self
.log
.error(message
)
688 raise N2VCBadArgumentsException(message
=message
, bad_args
=["endpoint_2"])
690 # get the model, the applications and the machines from the ee_id's
691 model_1
, app_1
, _machine_1
= self
._get
_ee
_id
_components
(ee_id_1
)
692 model_2
, app_2
, _machine_2
= self
._get
_ee
_id
_components
(ee_id_2
)
694 # model must be the same
695 if model_1
!= model_2
:
696 message
= "EE models are not the same: {} vs {}".format(ee_id_1
, ee_id_2
)
697 self
.log
.error(message
)
698 raise N2VCBadArgumentsException(
699 message
=message
, bad_args
=["ee_id_1", "ee_id_2"]
702 # add juju relations between two applications
704 await self
.libjuju
.add_relation(
706 endpoint_1
="{}:{}".format(app_1
, endpoint_1
),
707 endpoint_2
="{}:{}".format(app_2
, endpoint_2
),
709 except Exception as e
:
710 message
= "Error adding relation between {} and {}: {}".format(
713 self
.log
.error(message
)
714 raise N2VCException(message
=message
)
716 async def remove_relation(self
):
718 self
.log
.info("Method not implemented yet")
719 raise MethodNotImplemented()
721 async def deregister_execution_environments(self
):
722 self
.log
.info("Method not implemented yet")
723 raise MethodNotImplemented()
725 async def delete_namespace(
726 self
, namespace
: str, db_dict
: dict = None, total_timeout
: float = None
728 self
.log
.info("Deleting namespace={}".format(namespace
))
731 if namespace
is None:
732 raise N2VCBadArgumentsException(
733 message
="namespace is mandatory", bad_args
=["namespace"]
736 _nsi_id
, ns_id
, _vnf_id
, _vdu_id
, _vdu_count
= self
._get
_namespace
_components
(
739 if ns_id
is not None:
741 models
= await self
.libjuju
.list_models(contains
=ns_id
)
743 await self
.libjuju
.destroy_model(
744 model_name
=model
, total_timeout
=total_timeout
746 except Exception as e
:
748 message
="Error deleting namespace {} : {}".format(namespace
, e
)
751 raise N2VCBadArgumentsException(
752 message
="only ns_id is permitted to delete yet", bad_args
=["namespace"]
755 self
.log
.info("Namespace {} deleted".format(namespace
))
757 async def delete_execution_environment(
758 self
, ee_id
: str, db_dict
: dict = None, total_timeout
: float = None,
759 scaling_in
: bool = False
761 self
.log
.info("Deleting execution environment ee_id={}".format(ee_id
))
765 raise N2VCBadArgumentsException(
766 message
="ee_id is mandatory", bad_args
=["ee_id"]
769 model_name
, application_name
, _machine_id
= self
._get
_ee
_id
_components
(
775 # TODO: should this be removed?
776 await self
.libjuju
.destroy_model(
777 model_name
=model_name
, total_timeout
=total_timeout
780 # get juju model and observer
781 controller
= await self
.libjuju
.get_controller()
782 model
= await self
.libjuju
.get_model(controller
, model_name
)
783 # destroy the application
784 await self
.libjuju
.destroy_application(
785 model
=model
, application_name
=application_name
)
786 except Exception as e
:
789 "Error deleting execution environment {} (application {}) : {}"
790 ).format(ee_id
, application_name
, e
)
793 # destroy the machine
795 # await self._juju_destroy_machine(
796 # model_name=model_name,
797 # machine_id=machine_id,
798 # total_timeout=total_timeout
800 # except Exception as e:
801 # raise N2VCException(
802 # message='Error deleting execution environment {} (machine {}) : {}'
803 # .format(ee_id, machine_id, e))
805 self
.log
.info("Execution environment {} deleted".format(ee_id
))
807 async def exec_primitive(
812 db_dict
: dict = None,
813 progress_timeout
: float = None,
814 total_timeout
: float = None,
818 "Executing primitive: {} on ee: {}, params: {}".format(
819 primitive_name
, ee_id
, params_dict
824 if ee_id
is None or len(ee_id
) == 0:
825 raise N2VCBadArgumentsException(
826 message
="ee_id is mandatory", bad_args
=["ee_id"]
828 if primitive_name
is None or len(primitive_name
) == 0:
829 raise N2VCBadArgumentsException(
830 message
="action_name is mandatory", bad_args
=["action_name"]
832 if params_dict
is None:
840 ) = N2VCJujuConnector
._get
_ee
_id
_components
(ee_id
=ee_id
)
842 raise N2VCBadArgumentsException(
843 message
="ee_id={} is not a valid execution environment id".format(
849 if primitive_name
== "config":
850 # Special case: config primitive
852 await self
.libjuju
.configure_application(
853 model_name
=model_name
,
854 application_name
=application_name
,
857 actions
= await self
.libjuju
.get_actions(
858 application_name
=application_name
, model_name
=model_name
,
861 "Application {} has these actions: {}".format(
862 application_name
, actions
865 if "verify-ssh-credentials" in actions
:
866 # execute verify-credentials
869 for _
in range(num_retries
):
871 self
.log
.debug("Executing action verify-ssh-credentials...")
872 output
, ok
= await self
.libjuju
.execute_action(
873 model_name
=model_name
,
874 application_name
=application_name
,
875 action_name
="verify-ssh-credentials",
877 progress_timeout
=progress_timeout
,
878 total_timeout
=total_timeout
,
883 "Error executing verify-ssh-credentials: {}. Retrying..."
885 await asyncio
.sleep(retry_timeout
)
888 self
.log
.debug("Result: {}, output: {}".format(ok
, output
))
890 except asyncio
.CancelledError
:
894 "Error executing verify-ssh-credentials after {} retries. ".format(
899 msg
= "Action verify-ssh-credentials does not exist in application {}".format(
902 self
.log
.debug(msg
=msg
)
903 except Exception as e
:
904 self
.log
.error("Error configuring juju application: {}".format(e
))
905 raise N2VCExecutionException(
906 message
="Error configuring application into ee={} : {}".format(
909 primitive_name
=primitive_name
,
914 output
, status
= await self
.libjuju
.execute_action(
915 model_name
=model_name
,
916 application_name
=application_name
,
917 action_name
=primitive_name
,
919 progress_timeout
=progress_timeout
,
920 total_timeout
=total_timeout
,
923 if status
== "completed":
926 raise Exception("status is not completed: {}".format(status
))
927 except Exception as e
:
929 "Error executing primitive {}: {}".format(primitive_name
, e
)
931 raise N2VCExecutionException(
932 message
="Error executing primitive {} into ee={} : {}".format(
933 primitive_name
, ee_id
, e
935 primitive_name
=primitive_name
,
938 async def disconnect(self
):
939 self
.log
.info("closing juju N2VC...")
941 await self
.libjuju
.disconnect()
942 except Exception as e
:
943 raise N2VCConnectionException(
944 message
="Error disconnecting controller: {}".format(e
), url
=self
.url
948 ####################################################################################
949 ################################### P R I V A T E ##################################
950 ####################################################################################
953 def _write_ee_id_db(self
, db_dict
: dict, ee_id
: str):
955 # write ee_id to database: _admin.deployed.VCA.x
957 the_table
= db_dict
["collection"]
958 the_filter
= db_dict
["filter"]
959 the_path
= db_dict
["path"]
960 if not the_path
[-1] == ".":
961 the_path
= the_path
+ "."
962 update_dict
= {the_path
+ "ee_id": ee_id
}
963 # self.log.debug('Writing ee_id to database: {}'.format(the_path))
967 update_dict
=update_dict
,
970 except asyncio
.CancelledError
:
972 except Exception as e
:
973 self
.log
.error("Error writing ee_id to database: {}".format(e
))
976 def _build_ee_id(model_name
: str, application_name
: str, machine_id
: str):
978 Build an execution environment id form model, application and machine
980 :param application_name:
984 # id for the execution environment
985 return "{}.{}.{}".format(model_name
, application_name
, machine_id
)
988 def _get_ee_id_components(ee_id
: str) -> (str, str, str):
990 Get model, application and machine components from an execution environment id
992 :return: model_name, application_name, machine_id
996 return None, None, None
998 # split components of id
999 parts
= ee_id
.split(".")
1000 model_name
= parts
[0]
1001 application_name
= parts
[1]
1002 machine_id
= parts
[2]
1003 return model_name
, application_name
, machine_id
1005 def _get_application_name(self
, namespace
: str) -> str:
1007 Build application name from namespace
1009 :return: app-vnf-<vnf id>-vdu-<vdu-id>-cnt-<vdu-count>
1012 # TODO: Enforce the Juju 50-character application limit
1014 # split namespace components
1015 _
, _
, vnf_id
, vdu_id
, vdu_count
= self
._get
_namespace
_components
(
1019 if vnf_id
is None or len(vnf_id
) == 0:
1022 # Shorten the vnf_id to its last twelve characters
1023 vnf_id
= "vnf-" + vnf_id
[-12:]
1025 if vdu_id
is None or len(vdu_id
) == 0:
1028 # Shorten the vdu_id to its last twelve characters
1029 vdu_id
= "-vdu-" + vdu_id
[-12:]
1031 if vdu_count
is None or len(vdu_count
) == 0:
1034 vdu_count
= "-cnt-" + vdu_count
1036 application_name
= "app-{}{}{}".format(vnf_id
, vdu_id
, vdu_count
)
1038 return N2VCJujuConnector
._format
_app
_name
(application_name
)
1040 def _create_juju_public_key(self
):
1041 """Recreate the Juju public key on lcm container, if needed
1042 Certain libjuju commands expect to be run from the same machine as Juju
1043 is bootstrapped to. This method will write the public key to disk in
1044 that location: ~/.local/share/juju/ssh/juju_id_rsa.pub
1047 # Make sure that we have a public key before writing to disk
1048 if self
.public_key
is None or len(self
.public_key
) == 0:
1049 if "OSMLCM_VCA_PUBKEY" in os
.environ
:
1050 self
.public_key
= os
.getenv("OSMLCM_VCA_PUBKEY", "")
1051 if len(self
.public_key
) == 0:
1056 pk_path
= "{}/.local/share/juju/ssh".format(os
.path
.expanduser("~"))
1057 file_path
= "{}/juju_id_rsa.pub".format(pk_path
)
1059 "writing juju public key to file:\n{}\npublic key: {}".format(
1060 file_path
, self
.public_key
1063 if not os
.path
.exists(pk_path
):
1064 # create path and write file
1065 os
.makedirs(pk_path
)
1066 with
open(file_path
, "w") as f
:
1067 self
.log
.debug("Creating juju public key file: {}".format(file_path
))
1068 f
.write(self
.public_key
)
1070 self
.log
.debug("juju public key file already exists: {}".format(file_path
))
1073 def _format_model_name(name
: str) -> str:
1074 """Format the name of the model.
1076 Model names may only contain lowercase letters, digits and hyphens
1079 return name
.replace("_", "-").replace(" ", "-").lower()
1082 def _format_app_name(name
: str) -> str:
1083 """Format the name of the application (in order to assure valid application name).
1085 Application names have restrictions (run juju deploy --help):
1086 - contains lowercase letters 'a'-'z'
1087 - contains numbers '0'-'9'
1088 - contains hyphens '-'
1089 - starts with a lowercase letter
1090 - not two or more consecutive hyphens
1091 - after a hyphen, not a group with all numbers
1094 def all_numbers(s
: str) -> bool:
1100 new_name
= name
.replace("_", "-")
1101 new_name
= new_name
.replace(" ", "-")
1102 new_name
= new_name
.lower()
1103 while new_name
.find("--") >= 0:
1104 new_name
= new_name
.replace("--", "-")
1105 groups
= new_name
.split("-")
1107 # find 'all numbers' groups and prefix them with a letter
1109 for i
in range(len(groups
)):
1111 if all_numbers(group
):
1117 if app_name
[0].isdigit():
1118 app_name
= "z" + app_name