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
.exceptions
import (
28 N2VCBadArgumentsException
,
30 N2VCConnectionException
,
31 N2VCExecutionException
,
34 JujuK8sProxycharmNotSupported
,
36 from n2vc
.n2vc_conn
import N2VCConnector
37 from n2vc
.n2vc_conn
import obj_to_dict
, obj_to_yaml
38 from n2vc
.libjuju
import Libjuju
39 from n2vc
.utils
import base64_to_cacert
42 class N2VCJujuConnector(N2VCConnector
):
45 ####################################################################################
46 ################################### P U B L I C ####################################
47 ####################################################################################
50 BUILT_IN_CLOUDS
= ["localhost", "microk8s"]
58 url
: str = "127.0.0.1:17070",
59 username
: str = "admin",
60 vca_config
: dict = None,
63 """Initialize juju N2VC connector
66 # parent class constructor
67 N2VCConnector
.__init
__(
75 vca_config
=vca_config
,
76 on_update_db
=on_update_db
,
79 # silence websocket traffic log
80 logging
.getLogger("websockets.protocol").setLevel(logging
.INFO
)
81 logging
.getLogger("juju.client.connection").setLevel(logging
.WARN
)
82 logging
.getLogger("model").setLevel(logging
.WARN
)
84 self
.log
.info("Initializing N2VC juju connector...")
87 ##############################################################
89 ##############################################################
94 raise N2VCBadArgumentsException("Argument url is mandatory", ["url"])
95 url_parts
= url
.split(":")
96 if len(url_parts
) != 2:
97 raise N2VCBadArgumentsException(
98 "Argument url: bad format (localhost:port) -> {}".format(url
), ["url"]
100 self
.hostname
= url_parts
[0]
102 self
.port
= int(url_parts
[1])
104 raise N2VCBadArgumentsException(
105 "url port must be a number -> {}".format(url
), ["url"]
110 raise N2VCBadArgumentsException(
111 "Argument username is mandatory", ["username"]
115 if vca_config
is None:
116 raise N2VCBadArgumentsException(
117 "Argument vca_config is mandatory", ["vca_config"]
120 if "secret" in vca_config
:
121 self
.secret
= vca_config
["secret"]
123 raise N2VCBadArgumentsException(
124 "Argument vca_config.secret is mandatory", ["vca_config.secret"]
127 # pubkey of juju client in osm machine: ~/.local/share/juju/ssh/juju_id_rsa.pub
128 # if exists, it will be written in lcm container: _create_juju_public_key()
129 if "public_key" in vca_config
:
130 self
.public_key
= vca_config
["public_key"]
132 self
.public_key
= None
134 # TODO: Verify ca_cert is valid before using. VCA will crash
135 # if the ca_cert isn't formatted correctly.
137 self
.ca_cert
= vca_config
.get("ca_cert")
139 self
.ca_cert
= base64_to_cacert(vca_config
["ca_cert"])
141 if "api_proxy" in vca_config
and vca_config
["api_proxy"] != "":
142 self
.api_proxy
= vca_config
["api_proxy"]
144 "api_proxy for native charms configured: {}".format(self
.api_proxy
)
148 "api_proxy is not configured"
150 self
.api_proxy
= None
152 if "enable_os_upgrade" in vca_config
:
153 self
.enable_os_upgrade
= vca_config
["enable_os_upgrade"]
155 self
.enable_os_upgrade
= True
157 if "apt_mirror" in vca_config
:
158 self
.apt_mirror
= vca_config
["apt_mirror"]
160 self
.apt_mirror
= None
162 self
.cloud
= vca_config
.get('cloud')
163 self
.k8s_cloud
= None
164 if "k8s_cloud" in vca_config
:
165 self
.k8s_cloud
= vca_config
.get("k8s_cloud")
166 self
.log
.debug('Arguments have been checked')
169 self
.controller
= None # it will be filled when connect to juju
170 self
.juju_models
= {} # model objects for every model_name
171 self
.juju_observers
= {} # model observers for every model_name
173 False # while connecting to juju (to avoid duplicate connections)
175 self
._authenticated
= (
176 False # it will be True when juju connection be stablished
178 self
._creating
_model
= False # True during model creation
179 self
.libjuju
= Libjuju(
181 api_proxy
=self
.api_proxy
,
182 enable_os_upgrade
=self
.enable_os_upgrade
,
183 apt_mirror
=self
.apt_mirror
,
184 username
=self
.username
,
185 password
=self
.secret
,
193 # create juju pub key file in lcm container at
194 # ./local/share/juju/ssh/juju_id_rsa.pub
195 self
._create
_juju
_public
_key
()
197 self
.log
.info("N2VC juju connector initialized")
199 async def get_status(self
, namespace
: str, yaml_format
: bool = True):
201 # self.log.info('Getting NS status. namespace: {}'.format(namespace))
203 _nsi_id
, ns_id
, _vnf_id
, _vdu_id
, _vdu_count
= self
._get
_namespace
_components
(
206 # model name is ns_id
208 if model_name
is None:
209 msg
= "Namespace {} not valid".format(namespace
)
211 raise N2VCBadArgumentsException(msg
, ["namespace"])
214 models
= await self
.libjuju
.list_models(contains
=ns_id
)
217 status
[m
] = await self
.libjuju
.get_model_status(m
)
220 return obj_to_yaml(status
)
222 return obj_to_dict(status
)
224 async def create_execution_environment(
228 reuse_ee_id
: str = None,
229 progress_timeout
: float = None,
230 total_timeout
: float = None,
231 cloud_name
: str = None,
232 credential_name
: str = None,
236 "Creating execution environment. namespace: {}, reuse_ee_id: {}".format(
237 namespace
, reuse_ee_id
243 model_name
, application_name
, machine_id
= self
._get
_ee
_id
_components
(
253 ) = self
._get
_namespace
_components
(namespace
=namespace
)
254 # model name is ns_id
257 application_name
= self
._get
_application
_name
(namespace
=namespace
)
260 "model name: {}, application name: {}, machine_id: {}".format(
261 model_name
, application_name
, machine_id
265 # create or reuse a new juju machine
267 if not await self
.libjuju
.model_exists(model_name
):
268 cloud
= cloud_name
or self
.cloud
269 credential
= credential_name
or cloud_name
if cloud_name
else self
.cloud
270 await self
.libjuju
.add_model(
273 credential_name
=credential
275 machine
, new
= await self
.libjuju
.create_machine(
276 model_name
=model_name
,
277 machine_id
=machine_id
,
279 progress_timeout
=progress_timeout
,
280 total_timeout
=total_timeout
,
282 # id for the execution environment
283 ee_id
= N2VCJujuConnector
._build
_ee
_id
(
284 model_name
=model_name
,
285 application_name
=application_name
,
286 machine_id
=str(machine
.entity_id
),
288 self
.log
.debug("ee_id: {}".format(ee_id
))
291 # write ee_id in database
292 self
._write
_ee
_id
_db
(db_dict
=db_dict
, ee_id
=ee_id
)
294 except Exception as e
:
295 message
= "Error creating machine on juju: {}".format(e
)
296 self
.log
.error(message
)
297 raise N2VCException(message
=message
)
299 # new machine credentials
301 "hostname": machine
.dns_name
,
305 "Execution environment created. ee_id: {}, credentials: {}".format(
310 return ee_id
, credentials
312 async def register_execution_environment(
317 progress_timeout
: float = None,
318 total_timeout
: float = None,
319 cloud_name
: str = None,
320 credential_name
: str = None,
324 "Registering execution environment. namespace={}, credentials={}".format(
325 namespace
, credentials
329 if credentials
is None:
330 raise N2VCBadArgumentsException(
331 message
="credentials are mandatory", bad_args
=["credentials"]
333 if credentials
.get("hostname"):
334 hostname
= credentials
["hostname"]
336 raise N2VCBadArgumentsException(
337 message
="hostname is mandatory", bad_args
=["credentials.hostname"]
339 if credentials
.get("username"):
340 username
= credentials
["username"]
342 raise N2VCBadArgumentsException(
343 message
="username is mandatory", bad_args
=["credentials.username"]
345 if "private_key_path" in credentials
:
346 private_key_path
= credentials
["private_key_path"]
348 # if not passed as argument, use generated private key path
349 private_key_path
= self
.private_key_path
351 _nsi_id
, ns_id
, _vnf_id
, _vdu_id
, _vdu_count
= self
._get
_namespace
_components
(
358 application_name
= self
._get
_application
_name
(namespace
=namespace
)
360 # register machine on juju
362 if not await self
.libjuju
.model_exists(model_name
):
363 cloud
= cloud_name
or self
.cloud
364 credential
= credential_name
or cloud_name
if cloud_name
else self
.cloud
365 await self
.libjuju
.add_model(
368 credential_name
=credential
370 machine_id
= await self
.libjuju
.provision_machine(
371 model_name
=model_name
,
374 private_key_path
=private_key_path
,
376 progress_timeout
=progress_timeout
,
377 total_timeout
=total_timeout
,
379 except Exception as e
:
380 self
.log
.error("Error registering machine: {}".format(e
))
382 message
="Error registering machine on juju: {}".format(e
)
385 self
.log
.info("Machine registered: {}".format(machine_id
))
387 # id for the execution environment
388 ee_id
= N2VCJujuConnector
._build
_ee
_id
(
389 model_name
=model_name
,
390 application_name
=application_name
,
391 machine_id
=str(machine_id
),
394 self
.log
.info("Execution environment registered. ee_id: {}".format(ee_id
))
398 async def install_configuration_sw(
403 progress_timeout
: float = None,
404 total_timeout
: float = None,
411 "Installing configuration sw on ee_id: {}, "
412 "artifact path: {}, db_dict: {}"
413 ).format(ee_id
, artifact_path
, db_dict
)
417 if ee_id
is None or len(ee_id
) == 0:
418 raise N2VCBadArgumentsException(
419 message
="ee_id is mandatory", bad_args
=["ee_id"]
421 if artifact_path
is None or len(artifact_path
) == 0:
422 raise N2VCBadArgumentsException(
423 message
="artifact_path is mandatory", bad_args
=["artifact_path"]
426 raise N2VCBadArgumentsException(
427 message
="db_dict is mandatory", bad_args
=["db_dict"]
435 ) = N2VCJujuConnector
._get
_ee
_id
_components
(ee_id
=ee_id
)
437 "model: {}, application: {}, machine: {}".format(
438 model_name
, application_name
, machine_id
442 raise N2VCBadArgumentsException(
443 message
="ee_id={} is not a valid execution environment id".format(
449 # remove // in charm path
450 while artifact_path
.find("//") >= 0:
451 artifact_path
= artifact_path
.replace("//", "/")
454 if not self
.fs
.file_exists(artifact_path
, mode
="dir"):
455 msg
= "artifact path does not exist: {}".format(artifact_path
)
456 raise N2VCBadArgumentsException(message
=msg
, bad_args
=["artifact_path"])
458 if artifact_path
.startswith("/"):
459 full_path
= self
.fs
.path
+ artifact_path
461 full_path
= self
.fs
.path
+ "/" + artifact_path
464 await self
.libjuju
.deploy_charm(
465 model_name
=model_name
,
466 application_name
=application_name
,
468 machine_id
=machine_id
,
470 progress_timeout
=progress_timeout
,
471 total_timeout
=total_timeout
,
475 except Exception as e
:
477 message
="Error desploying charm into ee={} : {}".format(ee_id
, e
)
480 self
.log
.info("Configuration sw installed")
482 async def install_k8s_proxy_charm(
488 progress_timeout
: float = None,
489 total_timeout
: float = None,
491 cloud_name
: str = None,
492 credential_name
: str = None,
495 Install a k8s proxy charm
497 :param charm_name: Name of the charm being deployed
498 :param namespace: collection of all the uuids related to the charm.
499 :param str artifact_path: where to locate the artifacts (parent folder) using
501 the final artifact path will be a combination of this artifact_path and
502 additional string from the config_dict (e.g. charm name)
503 :param dict db_dict: where to write into database when the status changes.
504 It contains a dict with
505 {collection: <str>, filter: {}, path: <str>},
506 e.g. {collection: "nsrs", filter:
507 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
508 :param float progress_timeout:
509 :param float total_timeout:
510 :param config: Dictionary with additional configuration
511 :param cloud_name: Cloud Name in which the charms will be deployed
512 :param credential_name: Credential Name to use in the cloud_name.
513 If not set, cloud_name will be used as credential_name
515 :returns ee_id: execution environment id.
517 self
.log
.info('Installing k8s proxy charm: {}, artifact path: {}, db_dict: {}'
518 .format(charm_name
, artifact_path
, db_dict
))
520 if not self
.k8s_cloud
:
521 raise JujuK8sProxycharmNotSupported("There is not k8s_cloud available")
523 if artifact_path
is None or len(artifact_path
) == 0:
524 raise N2VCBadArgumentsException(
525 message
="artifact_path is mandatory", bad_args
=["artifact_path"]
528 raise N2VCBadArgumentsException(message
='db_dict is mandatory', bad_args
=['db_dict'])
530 # remove // in charm path
531 while artifact_path
.find('//') >= 0:
532 artifact_path
= artifact_path
.replace('//', '/')
535 if not self
.fs
.file_exists(artifact_path
, mode
="dir"):
536 msg
= 'artifact path does not exist: {}'.format(artifact_path
)
537 raise N2VCBadArgumentsException(message
=msg
, bad_args
=['artifact_path'])
539 if artifact_path
.startswith('/'):
540 full_path
= self
.fs
.path
+ artifact_path
542 full_path
= self
.fs
.path
+ '/' + artifact_path
544 _
, ns_id
, _
, _
, _
= self
._get
_namespace
_components
(namespace
=namespace
)
545 model_name
= '{}-k8s'.format(ns_id
)
546 if not await self
.libjuju
.model_exists(model_name
):
547 cloud
= cloud_name
or self
.k8s_cloud
548 credential
= credential_name
or cloud_name
if cloud_name
else self
.k8s_cloud
549 await self
.libjuju
.add_model(
552 credential_name
=credential
554 application_name
= self
._get
_application
_name
(namespace
)
557 await self
.libjuju
.deploy_charm(
558 model_name
=model_name
,
559 application_name
=application_name
,
563 progress_timeout
=progress_timeout
,
564 total_timeout
=total_timeout
,
567 except Exception as e
:
568 raise N2VCException(message
='Error deploying charm: {}'.format(e
))
570 self
.log
.info('K8s proxy charm installed')
571 ee_id
= N2VCJujuConnector
._build
_ee
_id
(
572 model_name
=model_name
,
573 application_name
=application_name
,
577 self
._write
_ee
_id
_db
(db_dict
=db_dict
, ee_id
=ee_id
)
581 async def get_ee_ssh_public__key(
585 progress_timeout
: float = None,
586 total_timeout
: float = None,
591 "Generating priv/pub key pair and get pub key on ee_id: {}, db_dict: {}"
592 ).format(ee_id
, db_dict
)
596 if ee_id
is None or len(ee_id
) == 0:
597 raise N2VCBadArgumentsException(
598 message
="ee_id is mandatory", bad_args
=["ee_id"]
601 raise N2VCBadArgumentsException(
602 message
="db_dict is mandatory", bad_args
=["db_dict"]
610 ) = N2VCJujuConnector
._get
_ee
_id
_components
(ee_id
=ee_id
)
612 "model: {}, application: {}, machine: {}".format(
613 model_name
, application_name
, machine_id
617 raise N2VCBadArgumentsException(
618 message
="ee_id={} is not a valid execution environment id".format(
624 # try to execute ssh layer primitives (if exist):
630 application_name
= N2VCJujuConnector
._format
_app
_name
(application_name
)
632 # execute action: generate-ssh-key
634 output
, _status
= await self
.libjuju
.execute_action(
635 model_name
=model_name
,
636 application_name
=application_name
,
637 action_name
="generate-ssh-key",
639 progress_timeout
=progress_timeout
,
640 total_timeout
=total_timeout
,
642 except Exception as e
:
644 "Skipping exception while executing action generate-ssh-key: {}".format(
649 # execute action: get-ssh-public-key
651 output
, _status
= await self
.libjuju
.execute_action(
652 model_name
=model_name
,
653 application_name
=application_name
,
654 action_name
="get-ssh-public-key",
656 progress_timeout
=progress_timeout
,
657 total_timeout
=total_timeout
,
659 except Exception as e
:
660 msg
= "Cannot execute action get-ssh-public-key: {}\n".format(e
)
662 raise N2VCExecutionException(e
, primitive_name
="get-ssh-public-key")
664 # return public key if exists
665 return output
["pubkey"] if "pubkey" in output
else output
667 async def get_metrics(self
, model_name
: str, application_name
: str) -> dict:
668 return await self
.libjuju
.get_metrics(model_name
, application_name
)
670 async def add_relation(
671 self
, ee_id_1
: str, ee_id_2
: str, endpoint_1
: str, endpoint_2
: str
675 "adding new relation between {} and {}, endpoints: {}, {}".format(
676 ee_id_1
, ee_id_2
, endpoint_1
, endpoint_2
682 message
= "EE 1 is mandatory"
683 self
.log
.error(message
)
684 raise N2VCBadArgumentsException(message
=message
, bad_args
=["ee_id_1"])
686 message
= "EE 2 is mandatory"
687 self
.log
.error(message
)
688 raise N2VCBadArgumentsException(message
=message
, bad_args
=["ee_id_2"])
690 message
= "endpoint 1 is mandatory"
691 self
.log
.error(message
)
692 raise N2VCBadArgumentsException(message
=message
, bad_args
=["endpoint_1"])
694 message
= "endpoint 2 is mandatory"
695 self
.log
.error(message
)
696 raise N2VCBadArgumentsException(message
=message
, bad_args
=["endpoint_2"])
698 # get the model, the applications and the machines from the ee_id's
699 model_1
, app_1
, _machine_1
= self
._get
_ee
_id
_components
(ee_id_1
)
700 model_2
, app_2
, _machine_2
= self
._get
_ee
_id
_components
(ee_id_2
)
702 # model must be the same
703 if model_1
!= model_2
:
704 message
= "EE models are not the same: {} vs {}".format(ee_id_1
, ee_id_2
)
705 self
.log
.error(message
)
706 raise N2VCBadArgumentsException(
707 message
=message
, bad_args
=["ee_id_1", "ee_id_2"]
710 # add juju relations between two applications
712 await self
.libjuju
.add_relation(
714 endpoint_1
="{}:{}".format(app_1
, endpoint_1
),
715 endpoint_2
="{}:{}".format(app_2
, endpoint_2
),
717 except Exception as e
:
718 message
= "Error adding relation between {} and {}: {}".format(
721 self
.log
.error(message
)
722 raise N2VCException(message
=message
)
724 async def remove_relation(self
):
726 self
.log
.info("Method not implemented yet")
727 raise MethodNotImplemented()
729 async def deregister_execution_environments(self
):
730 self
.log
.info("Method not implemented yet")
731 raise MethodNotImplemented()
733 async def delete_namespace(
734 self
, namespace
: str, db_dict
: dict = None, total_timeout
: float = None
736 self
.log
.info("Deleting namespace={}".format(namespace
))
739 if namespace
is None:
740 raise N2VCBadArgumentsException(
741 message
="namespace is mandatory", bad_args
=["namespace"]
744 _nsi_id
, ns_id
, _vnf_id
, _vdu_id
, _vdu_count
= self
._get
_namespace
_components
(
747 if ns_id
is not None:
749 models
= await self
.libjuju
.list_models(contains
=ns_id
)
751 await self
.libjuju
.destroy_model(
752 model_name
=model
, total_timeout
=total_timeout
754 except Exception as e
:
756 message
="Error deleting namespace {} : {}".format(namespace
, e
)
759 raise N2VCBadArgumentsException(
760 message
="only ns_id is permitted to delete yet", bad_args
=["namespace"]
763 self
.log
.info("Namespace {} deleted".format(namespace
))
765 async def delete_execution_environment(
766 self
, ee_id
: str, db_dict
: dict = None, total_timeout
: float = None,
767 scaling_in
: bool = False
769 self
.log
.info("Deleting execution environment ee_id={}".format(ee_id
))
773 raise N2VCBadArgumentsException(
774 message
="ee_id is mandatory", bad_args
=["ee_id"]
777 model_name
, application_name
, _machine_id
= self
._get
_ee
_id
_components
(
783 # TODO: should this be removed?
784 await self
.libjuju
.destroy_model(
785 model_name
=model_name
, total_timeout
=total_timeout
788 # get juju model and observer
789 controller
= await self
.libjuju
.get_controller()
790 model
= await self
.libjuju
.get_model(controller
, model_name
)
791 # destroy the application
792 await self
.libjuju
.destroy_application(
793 model
=model
, application_name
=application_name
)
794 except Exception as e
:
797 "Error deleting execution environment {} (application {}) : {}"
798 ).format(ee_id
, application_name
, e
)
801 # destroy the machine
803 # await self._juju_destroy_machine(
804 # model_name=model_name,
805 # machine_id=machine_id,
806 # total_timeout=total_timeout
808 # except Exception as e:
809 # raise N2VCException(
810 # message='Error deleting execution environment {} (machine {}) : {}'
811 # .format(ee_id, machine_id, e))
813 self
.log
.info("Execution environment {} deleted".format(ee_id
))
815 async def exec_primitive(
820 db_dict
: dict = None,
821 progress_timeout
: float = None,
822 total_timeout
: float = None,
826 "Executing primitive: {} on ee: {}, params: {}".format(
827 primitive_name
, ee_id
, params_dict
832 if ee_id
is None or len(ee_id
) == 0:
833 raise N2VCBadArgumentsException(
834 message
="ee_id is mandatory", bad_args
=["ee_id"]
836 if primitive_name
is None or len(primitive_name
) == 0:
837 raise N2VCBadArgumentsException(
838 message
="action_name is mandatory", bad_args
=["action_name"]
840 if params_dict
is None:
848 ) = N2VCJujuConnector
._get
_ee
_id
_components
(ee_id
=ee_id
)
850 raise N2VCBadArgumentsException(
851 message
="ee_id={} is not a valid execution environment id".format(
857 if primitive_name
== "config":
858 # Special case: config primitive
860 await self
.libjuju
.configure_application(
861 model_name
=model_name
,
862 application_name
=application_name
,
865 actions
= await self
.libjuju
.get_actions(
866 application_name
=application_name
, model_name
=model_name
,
869 "Application {} has these actions: {}".format(
870 application_name
, actions
873 if "verify-ssh-credentials" in actions
:
874 # execute verify-credentials
877 for _
in range(num_retries
):
879 self
.log
.debug("Executing action verify-ssh-credentials...")
880 output
, ok
= await self
.libjuju
.execute_action(
881 model_name
=model_name
,
882 application_name
=application_name
,
883 action_name
="verify-ssh-credentials",
885 progress_timeout
=progress_timeout
,
886 total_timeout
=total_timeout
,
891 "Error executing verify-ssh-credentials: {}. Retrying..."
893 await asyncio
.sleep(retry_timeout
)
896 self
.log
.debug("Result: {}, output: {}".format(ok
, output
))
898 except asyncio
.CancelledError
:
902 "Error executing verify-ssh-credentials after {} retries. ".format(
907 msg
= "Action verify-ssh-credentials does not exist in application {}".format(
910 self
.log
.debug(msg
=msg
)
911 except Exception as e
:
912 self
.log
.error("Error configuring juju application: {}".format(e
))
913 raise N2VCExecutionException(
914 message
="Error configuring application into ee={} : {}".format(
917 primitive_name
=primitive_name
,
922 output
, status
= await self
.libjuju
.execute_action(
923 model_name
=model_name
,
924 application_name
=application_name
,
925 action_name
=primitive_name
,
927 progress_timeout
=progress_timeout
,
928 total_timeout
=total_timeout
,
931 if status
== "completed":
934 raise Exception("status is not completed: {}".format(status
))
935 except Exception as e
:
937 "Error executing primitive {}: {}".format(primitive_name
, e
)
939 raise N2VCExecutionException(
940 message
="Error executing primitive {} into ee={} : {}".format(
941 primitive_name
, ee_id
, e
943 primitive_name
=primitive_name
,
946 async def disconnect(self
):
947 self
.log
.info("closing juju N2VC...")
949 await self
.libjuju
.disconnect()
950 except Exception as e
:
951 raise N2VCConnectionException(
952 message
="Error disconnecting controller: {}".format(e
), url
=self
.url
956 ####################################################################################
957 ################################### P R I V A T E ##################################
958 ####################################################################################
961 def _write_ee_id_db(self
, db_dict
: dict, ee_id
: str):
963 # write ee_id to database: _admin.deployed.VCA.x
965 the_table
= db_dict
["collection"]
966 the_filter
= db_dict
["filter"]
967 the_path
= db_dict
["path"]
968 if not the_path
[-1] == ".":
969 the_path
= the_path
+ "."
970 update_dict
= {the_path
+ "ee_id": ee_id
}
971 # self.log.debug('Writing ee_id to database: {}'.format(the_path))
975 update_dict
=update_dict
,
978 except asyncio
.CancelledError
:
980 except Exception as e
:
981 self
.log
.error("Error writing ee_id to database: {}".format(e
))
984 def _build_ee_id(model_name
: str, application_name
: str, machine_id
: str):
986 Build an execution environment id form model, application and machine
988 :param application_name:
992 # id for the execution environment
993 return "{}.{}.{}".format(model_name
, application_name
, machine_id
)
996 def _get_ee_id_components(ee_id
: str) -> (str, str, str):
998 Get model, application and machine components from an execution environment id
1000 :return: model_name, application_name, machine_id
1004 return None, None, None
1006 # split components of id
1007 parts
= ee_id
.split(".")
1008 model_name
= parts
[0]
1009 application_name
= parts
[1]
1010 machine_id
= parts
[2]
1011 return model_name
, application_name
, machine_id
1013 def _get_application_name(self
, namespace
: str) -> str:
1015 Build application name from namespace
1017 :return: app-vnf-<vnf id>-vdu-<vdu-id>-cnt-<vdu-count>
1020 # TODO: Enforce the Juju 50-character application limit
1022 # split namespace components
1023 _
, _
, vnf_id
, vdu_id
, vdu_count
= self
._get
_namespace
_components
(
1027 if vnf_id
is None or len(vnf_id
) == 0:
1030 # Shorten the vnf_id to its last twelve characters
1031 vnf_id
= "vnf-" + vnf_id
[-12:]
1033 if vdu_id
is None or len(vdu_id
) == 0:
1036 # Shorten the vdu_id to its last twelve characters
1037 vdu_id
= "-vdu-" + vdu_id
[-12:]
1039 if vdu_count
is None or len(vdu_count
) == 0:
1042 vdu_count
= "-cnt-" + vdu_count
1044 application_name
= "app-{}{}{}".format(vnf_id
, vdu_id
, vdu_count
)
1046 return N2VCJujuConnector
._format
_app
_name
(application_name
)
1048 def _create_juju_public_key(self
):
1049 """Recreate the Juju public key on lcm container, if needed
1050 Certain libjuju commands expect to be run from the same machine as Juju
1051 is bootstrapped to. This method will write the public key to disk in
1052 that location: ~/.local/share/juju/ssh/juju_id_rsa.pub
1055 # Make sure that we have a public key before writing to disk
1056 if self
.public_key
is None or len(self
.public_key
) == 0:
1057 if "OSMLCM_VCA_PUBKEY" in os
.environ
:
1058 self
.public_key
= os
.getenv("OSMLCM_VCA_PUBKEY", "")
1059 if len(self
.public_key
) == 0:
1064 pk_path
= "{}/.local/share/juju/ssh".format(os
.path
.expanduser("~"))
1065 file_path
= "{}/juju_id_rsa.pub".format(pk_path
)
1067 "writing juju public key to file:\n{}\npublic key: {}".format(
1068 file_path
, self
.public_key
1071 if not os
.path
.exists(pk_path
):
1072 # create path and write file
1073 os
.makedirs(pk_path
)
1074 with
open(file_path
, "w") as f
:
1075 self
.log
.debug("Creating juju public key file: {}".format(file_path
))
1076 f
.write(self
.public_key
)
1078 self
.log
.debug("juju public key file already exists: {}".format(file_path
))
1081 def _format_model_name(name
: str) -> str:
1082 """Format the name of the model.
1084 Model names may only contain lowercase letters, digits and hyphens
1087 return name
.replace("_", "-").replace(" ", "-").lower()
1090 def _format_app_name(name
: str) -> str:
1091 """Format the name of the application (in order to assure valid application name).
1093 Application names have restrictions (run juju deploy --help):
1094 - contains lowercase letters 'a'-'z'
1095 - contains numbers '0'-'9'
1096 - contains hyphens '-'
1097 - starts with a lowercase letter
1098 - not two or more consecutive hyphens
1099 - after a hyphen, not a group with all numbers
1102 def all_numbers(s
: str) -> bool:
1108 new_name
= name
.replace("_", "-")
1109 new_name
= new_name
.replace(" ", "-")
1110 new_name
= new_name
.lower()
1111 while new_name
.find("--") >= 0:
1112 new_name
= new_name
.replace("--", "-")
1113 groups
= new_name
.split("-")
1115 # find 'all numbers' groups and prefix them with a letter
1117 for i
in range(len(groups
)):
1119 if all_numbers(group
):
1125 if app_name
[0].isdigit():
1126 app_name
= "z" + app_name