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
)
211 return obj_to_yaml(status
)
213 return obj_to_dict(status
)
215 async def update_vca_status(self
, vcastatus
: dict):
217 Add all configs, actions, executed actions of all applications in a model to vcastatus dict.
218 :param vcastatus: dict containing vcaStatus
222 for model_name
in vcastatus
:
223 # Adding executed actions
224 vcastatus
[model_name
]["executedActions"] = \
225 await self
.libjuju
.get_executed_actions(model_name
)
226 for application
in vcastatus
[model_name
]["applications"]:
227 # Adding application actions
228 vcastatus
[model_name
]["applications"][application
]["actions"] = \
229 await self
.libjuju
.get_actions(application
, model_name
)
230 # Adding application configs
231 vcastatus
[model_name
]["applications"][application
]["configs"] = \
232 await self
.libjuju
.get_application_configs(model_name
, application
)
233 except Exception as e
:
234 self
.log
.debug("Error in updating vca status: {}".format(str(e
)))
236 async def create_execution_environment(
240 reuse_ee_id
: str = None,
241 progress_timeout
: float = None,
242 total_timeout
: float = None,
243 cloud_name
: str = None,
244 credential_name
: str = None,
248 "Creating execution environment. namespace: {}, reuse_ee_id: {}".format(
249 namespace
, reuse_ee_id
255 model_name
, application_name
, machine_id
= self
._get
_ee
_id
_components
(
265 ) = self
._get
_namespace
_components
(namespace
=namespace
)
266 # model name is ns_id
269 application_name
= self
._get
_application
_name
(namespace
=namespace
)
272 "model name: {}, application name: {}, machine_id: {}".format(
273 model_name
, application_name
, machine_id
277 # create or reuse a new juju machine
279 if not await self
.libjuju
.model_exists(model_name
):
280 cloud
= cloud_name
or self
.cloud
281 credential
= credential_name
or cloud_name
if cloud_name
else self
.cloud
282 await self
.libjuju
.add_model(
285 credential_name
=credential
287 machine
, new
= await self
.libjuju
.create_machine(
288 model_name
=model_name
,
289 machine_id
=machine_id
,
291 progress_timeout
=progress_timeout
,
292 total_timeout
=total_timeout
,
294 # id for the execution environment
295 ee_id
= N2VCJujuConnector
._build
_ee
_id
(
296 model_name
=model_name
,
297 application_name
=application_name
,
298 machine_id
=str(machine
.entity_id
),
300 self
.log
.debug("ee_id: {}".format(ee_id
))
303 # write ee_id in database
304 self
._write
_ee
_id
_db
(db_dict
=db_dict
, ee_id
=ee_id
)
306 except Exception as e
:
307 message
= "Error creating machine on juju: {}".format(e
)
308 self
.log
.error(message
)
309 raise N2VCException(message
=message
)
311 # new machine credentials
313 "hostname": machine
.dns_name
,
317 "Execution environment created. ee_id: {}, credentials: {}".format(
322 return ee_id
, credentials
324 async def register_execution_environment(
329 progress_timeout
: float = None,
330 total_timeout
: float = None,
331 cloud_name
: str = None,
332 credential_name
: str = None,
336 "Registering execution environment. namespace={}, credentials={}".format(
337 namespace
, credentials
341 if credentials
is None:
342 raise N2VCBadArgumentsException(
343 message
="credentials are mandatory", bad_args
=["credentials"]
345 if credentials
.get("hostname"):
346 hostname
= credentials
["hostname"]
348 raise N2VCBadArgumentsException(
349 message
="hostname is mandatory", bad_args
=["credentials.hostname"]
351 if credentials
.get("username"):
352 username
= credentials
["username"]
354 raise N2VCBadArgumentsException(
355 message
="username is mandatory", bad_args
=["credentials.username"]
357 if "private_key_path" in credentials
:
358 private_key_path
= credentials
["private_key_path"]
360 # if not passed as argument, use generated private key path
361 private_key_path
= self
.private_key_path
363 _nsi_id
, ns_id
, _vnf_id
, _vdu_id
, _vdu_count
= self
._get
_namespace
_components
(
370 application_name
= self
._get
_application
_name
(namespace
=namespace
)
372 # register machine on juju
374 if not await self
.libjuju
.model_exists(model_name
):
375 cloud
= cloud_name
or self
.cloud
376 credential
= credential_name
or cloud_name
if cloud_name
else self
.cloud
377 await self
.libjuju
.add_model(
380 credential_name
=credential
382 machine_id
= await self
.libjuju
.provision_machine(
383 model_name
=model_name
,
386 private_key_path
=private_key_path
,
388 progress_timeout
=progress_timeout
,
389 total_timeout
=total_timeout
,
391 except Exception as e
:
392 self
.log
.error("Error registering machine: {}".format(e
))
394 message
="Error registering machine on juju: {}".format(e
)
397 self
.log
.info("Machine registered: {}".format(machine_id
))
399 # id for the execution environment
400 ee_id
= N2VCJujuConnector
._build
_ee
_id
(
401 model_name
=model_name
,
402 application_name
=application_name
,
403 machine_id
=str(machine_id
),
406 self
.log
.info("Execution environment registered. ee_id: {}".format(ee_id
))
410 async def install_configuration_sw(
415 progress_timeout
: float = None,
416 total_timeout
: float = None,
423 "Installing configuration sw on ee_id: {}, "
424 "artifact path: {}, db_dict: {}"
425 ).format(ee_id
, artifact_path
, db_dict
)
429 if ee_id
is None or len(ee_id
) == 0:
430 raise N2VCBadArgumentsException(
431 message
="ee_id is mandatory", bad_args
=["ee_id"]
433 if artifact_path
is None or len(artifact_path
) == 0:
434 raise N2VCBadArgumentsException(
435 message
="artifact_path is mandatory", bad_args
=["artifact_path"]
438 raise N2VCBadArgumentsException(
439 message
="db_dict is mandatory", bad_args
=["db_dict"]
447 ) = N2VCJujuConnector
._get
_ee
_id
_components
(ee_id
=ee_id
)
449 "model: {}, application: {}, machine: {}".format(
450 model_name
, application_name
, machine_id
454 raise N2VCBadArgumentsException(
455 message
="ee_id={} is not a valid execution environment id".format(
461 # remove // in charm path
462 while artifact_path
.find("//") >= 0:
463 artifact_path
= artifact_path
.replace("//", "/")
466 if not self
.fs
.file_exists(artifact_path
, mode
="dir"):
467 msg
= "artifact path does not exist: {}".format(artifact_path
)
468 raise N2VCBadArgumentsException(message
=msg
, bad_args
=["artifact_path"])
470 if artifact_path
.startswith("/"):
471 full_path
= self
.fs
.path
+ artifact_path
473 full_path
= self
.fs
.path
+ "/" + artifact_path
476 await self
.libjuju
.deploy_charm(
477 model_name
=model_name
,
478 application_name
=application_name
,
480 machine_id
=machine_id
,
482 progress_timeout
=progress_timeout
,
483 total_timeout
=total_timeout
,
487 except Exception as e
:
489 message
="Error desploying charm into ee={} : {}".format(ee_id
, e
)
492 self
.log
.info("Configuration sw installed")
494 async def install_k8s_proxy_charm(
500 progress_timeout
: float = None,
501 total_timeout
: float = None,
503 cloud_name
: str = None,
504 credential_name
: str = None,
507 Install a k8s proxy charm
509 :param charm_name: Name of the charm being deployed
510 :param namespace: collection of all the uuids related to the charm.
511 :param str artifact_path: where to locate the artifacts (parent folder) using
513 the final artifact path will be a combination of this artifact_path and
514 additional string from the config_dict (e.g. charm name)
515 :param dict db_dict: where to write into database when the status changes.
516 It contains a dict with
517 {collection: <str>, filter: {}, path: <str>},
518 e.g. {collection: "nsrs", filter:
519 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
520 :param float progress_timeout:
521 :param float total_timeout:
522 :param config: Dictionary with additional configuration
523 :param cloud_name: Cloud Name in which the charms will be deployed
524 :param credential_name: Credential Name to use in the cloud_name.
525 If not set, cloud_name will be used as credential_name
527 :returns ee_id: execution environment id.
529 self
.log
.info('Installing k8s proxy charm: {}, artifact path: {}, db_dict: {}'
530 .format(charm_name
, artifact_path
, db_dict
))
532 if not self
.k8s_cloud
:
533 raise JujuK8sProxycharmNotSupported("There is not k8s_cloud available")
535 if artifact_path
is None or len(artifact_path
) == 0:
536 raise N2VCBadArgumentsException(
537 message
="artifact_path is mandatory", bad_args
=["artifact_path"]
540 raise N2VCBadArgumentsException(message
='db_dict is mandatory', bad_args
=['db_dict'])
542 # remove // in charm path
543 while artifact_path
.find('//') >= 0:
544 artifact_path
= artifact_path
.replace('//', '/')
547 if not self
.fs
.file_exists(artifact_path
, mode
="dir"):
548 msg
= 'artifact path does not exist: {}'.format(artifact_path
)
549 raise N2VCBadArgumentsException(message
=msg
, bad_args
=['artifact_path'])
551 if artifact_path
.startswith('/'):
552 full_path
= self
.fs
.path
+ artifact_path
554 full_path
= self
.fs
.path
+ '/' + artifact_path
556 _
, ns_id
, _
, _
, _
= self
._get
_namespace
_components
(namespace
=namespace
)
557 model_name
= '{}-k8s'.format(ns_id
)
558 if not await self
.libjuju
.model_exists(model_name
):
559 cloud
= cloud_name
or self
.k8s_cloud
560 credential
= credential_name
or cloud_name
if cloud_name
else self
.k8s_cloud
561 await self
.libjuju
.add_model(
564 credential_name
=credential
566 application_name
= self
._get
_application
_name
(namespace
)
569 await self
.libjuju
.deploy_charm(
570 model_name
=model_name
,
571 application_name
=application_name
,
575 progress_timeout
=progress_timeout
,
576 total_timeout
=total_timeout
,
579 except Exception as e
:
580 raise N2VCException(message
='Error deploying charm: {}'.format(e
))
582 self
.log
.info('K8s proxy charm installed')
583 ee_id
= N2VCJujuConnector
._build
_ee
_id
(
584 model_name
=model_name
,
585 application_name
=application_name
,
589 self
._write
_ee
_id
_db
(db_dict
=db_dict
, ee_id
=ee_id
)
593 async def get_ee_ssh_public__key(
597 progress_timeout
: float = None,
598 total_timeout
: float = None,
603 "Generating priv/pub key pair and get pub key on ee_id: {}, db_dict: {}"
604 ).format(ee_id
, db_dict
)
608 if ee_id
is None or len(ee_id
) == 0:
609 raise N2VCBadArgumentsException(
610 message
="ee_id is mandatory", bad_args
=["ee_id"]
613 raise N2VCBadArgumentsException(
614 message
="db_dict is mandatory", bad_args
=["db_dict"]
622 ) = N2VCJujuConnector
._get
_ee
_id
_components
(ee_id
=ee_id
)
624 "model: {}, application: {}, machine: {}".format(
625 model_name
, application_name
, machine_id
629 raise N2VCBadArgumentsException(
630 message
="ee_id={} is not a valid execution environment id".format(
636 # try to execute ssh layer primitives (if exist):
642 application_name
= N2VCJujuConnector
._format
_app
_name
(application_name
)
644 # execute action: generate-ssh-key
646 output
, _status
= await self
.libjuju
.execute_action(
647 model_name
=model_name
,
648 application_name
=application_name
,
649 action_name
="generate-ssh-key",
651 progress_timeout
=progress_timeout
,
652 total_timeout
=total_timeout
,
654 except Exception as e
:
656 "Skipping exception while executing action generate-ssh-key: {}".format(
661 # execute action: get-ssh-public-key
663 output
, _status
= await self
.libjuju
.execute_action(
664 model_name
=model_name
,
665 application_name
=application_name
,
666 action_name
="get-ssh-public-key",
668 progress_timeout
=progress_timeout
,
669 total_timeout
=total_timeout
,
671 except Exception as e
:
672 msg
= "Cannot execute action get-ssh-public-key: {}\n".format(e
)
674 raise N2VCExecutionException(e
, primitive_name
="get-ssh-public-key")
676 # return public key if exists
677 return output
["pubkey"] if "pubkey" in output
else output
679 async def get_metrics(self
, model_name
: str, application_name
: str) -> dict:
680 return await self
.libjuju
.get_metrics(model_name
, application_name
)
682 async def add_relation(
683 self
, ee_id_1
: str, ee_id_2
: str, endpoint_1
: str, endpoint_2
: str
687 "adding new relation between {} and {}, endpoints: {}, {}".format(
688 ee_id_1
, ee_id_2
, endpoint_1
, endpoint_2
694 message
= "EE 1 is mandatory"
695 self
.log
.error(message
)
696 raise N2VCBadArgumentsException(message
=message
, bad_args
=["ee_id_1"])
698 message
= "EE 2 is mandatory"
699 self
.log
.error(message
)
700 raise N2VCBadArgumentsException(message
=message
, bad_args
=["ee_id_2"])
702 message
= "endpoint 1 is mandatory"
703 self
.log
.error(message
)
704 raise N2VCBadArgumentsException(message
=message
, bad_args
=["endpoint_1"])
706 message
= "endpoint 2 is mandatory"
707 self
.log
.error(message
)
708 raise N2VCBadArgumentsException(message
=message
, bad_args
=["endpoint_2"])
710 # get the model, the applications and the machines from the ee_id's
711 model_1
, app_1
, _machine_1
= self
._get
_ee
_id
_components
(ee_id_1
)
712 model_2
, app_2
, _machine_2
= self
._get
_ee
_id
_components
(ee_id_2
)
714 # model must be the same
715 if model_1
!= model_2
:
716 message
= "EE models are not the same: {} vs {}".format(ee_id_1
, ee_id_2
)
717 self
.log
.error(message
)
718 raise N2VCBadArgumentsException(
719 message
=message
, bad_args
=["ee_id_1", "ee_id_2"]
722 # add juju relations between two applications
724 await self
.libjuju
.add_relation(
726 endpoint_1
="{}:{}".format(app_1
, endpoint_1
),
727 endpoint_2
="{}:{}".format(app_2
, endpoint_2
),
729 except Exception as e
:
730 message
= "Error adding relation between {} and {}: {}".format(
733 self
.log
.error(message
)
734 raise N2VCException(message
=message
)
736 async def remove_relation(self
):
738 self
.log
.info("Method not implemented yet")
739 raise MethodNotImplemented()
741 async def deregister_execution_environments(self
):
742 self
.log
.info("Method not implemented yet")
743 raise MethodNotImplemented()
745 async def delete_namespace(
746 self
, namespace
: str, db_dict
: dict = None, total_timeout
: float = None
748 self
.log
.info("Deleting namespace={}".format(namespace
))
751 if namespace
is None:
752 raise N2VCBadArgumentsException(
753 message
="namespace is mandatory", bad_args
=["namespace"]
756 _nsi_id
, ns_id
, _vnf_id
, _vdu_id
, _vdu_count
= self
._get
_namespace
_components
(
759 if ns_id
is not None:
761 models
= await self
.libjuju
.list_models(contains
=ns_id
)
763 await self
.libjuju
.destroy_model(
764 model_name
=model
, total_timeout
=total_timeout
766 except Exception as e
:
768 message
="Error deleting namespace {} : {}".format(namespace
, e
)
771 raise N2VCBadArgumentsException(
772 message
="only ns_id is permitted to delete yet", bad_args
=["namespace"]
775 self
.log
.info("Namespace {} deleted".format(namespace
))
777 async def delete_execution_environment(
778 self
, ee_id
: str, db_dict
: dict = None, total_timeout
: float = None,
779 scaling_in
: bool = False
781 self
.log
.info("Deleting execution environment ee_id={}".format(ee_id
))
785 raise N2VCBadArgumentsException(
786 message
="ee_id is mandatory", bad_args
=["ee_id"]
789 model_name
, application_name
, _machine_id
= self
._get
_ee
_id
_components
(
795 # TODO: should this be removed?
796 await self
.libjuju
.destroy_model(
797 model_name
=model_name
, total_timeout
=total_timeout
800 # get juju model and observer
801 controller
= await self
.libjuju
.get_controller()
802 model
= await self
.libjuju
.get_model(controller
, model_name
)
803 # destroy the application
804 await self
.libjuju
.destroy_application(
805 model
=model
, application_name
=application_name
)
806 except Exception as e
:
809 "Error deleting execution environment {} (application {}) : {}"
810 ).format(ee_id
, application_name
, 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