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
26 from n2vc
.config
import EnvironConfig
27 from n2vc
.exceptions
import (
28 N2VCBadArgumentsException
,
30 N2VCConnectionException
,
31 N2VCExecutionException
,
32 N2VCApplicationExists
,
33 JujuApplicationExists
,
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
.store
import MotorStore
41 from n2vc
.vca
.connection
import get_connection
42 from retrying_async
import retry
45 class N2VCJujuConnector(N2VCConnector
):
48 ####################################################################################
49 ################################### P U B L I C ####################################
50 ####################################################################################
53 BUILT_IN_CLOUDS
= ["localhost", "microk8s"]
67 :param: db: Database object from osm_common
68 :param: fs: Filesystem object from osm_common
70 :param: loop: Asyncio loop
71 :param: on_update_db: Callback function to be called for updating the database.
74 # parent class constructor
75 N2VCConnector
.__init
__(
81 on_update_db
=on_update_db
,
84 # silence websocket traffic log
85 logging
.getLogger("websockets.protocol").setLevel(logging
.INFO
)
86 logging
.getLogger("juju.client.connection").setLevel(logging
.WARN
)
87 logging
.getLogger("model").setLevel(logging
.WARN
)
89 self
.log
.info("Initializing N2VC juju connector...")
91 db_uri
= EnvironConfig(prefixes
=["OSMLCM_", "OSMMON_"]).get("database_uri")
92 self
._store
= MotorStore(db_uri
)
93 self
.loading_libjuju
= asyncio
.Lock(loop
=self
.loop
)
95 self
.log
.info("N2VC juju connector initialized")
98 self
, namespace
: str, yaml_format
: bool = True, vca_id
: str = None
101 Get status from all juju models from a VCA
103 :param namespace: we obtain ns from namespace
104 :param yaml_format: returns a yaml string
105 :param: vca_id: VCA ID from which the status will be retrieved.
107 # TODO: Review where is this function used. It is not optimal at all to get the status
108 # from all the juju models of a particular VCA. Additionally, these models might
109 # not have been deployed by OSM, in that case we are getting information from
110 # deployments outside of OSM's scope.
112 # self.log.info('Getting NS status. namespace: {}'.format(namespace))
113 libjuju
= await self
._get
_libjuju
(vca_id
)
115 _nsi_id
, ns_id
, _vnf_id
, _vdu_id
, _vdu_count
= self
._get
_namespace
_components
(
118 # model name is ns_id
120 if model_name
is None:
121 msg
= "Namespace {} not valid".format(namespace
)
123 raise N2VCBadArgumentsException(msg
, ["namespace"])
126 models
= await libjuju
.list_models(contains
=ns_id
)
129 status
[m
] = await libjuju
.get_model_status(m
)
132 return obj_to_yaml(status
)
134 return obj_to_dict(status
)
136 async def update_vca_status(self
, vcastatus
: dict, vca_id
: str = None):
138 Add all configs, actions, executed actions of all applications in a model to vcastatus dict.
140 :param vcastatus: dict containing vcaStatus
141 :param: vca_id: VCA ID
146 libjuju
= await self
._get
_libjuju
(vca_id
)
147 for model_name
in vcastatus
:
148 # Adding executed actions
149 vcastatus
[model_name
][
151 ] = await libjuju
.get_executed_actions(model_name
)
152 for application
in vcastatus
[model_name
]["applications"]:
153 # Adding application actions
154 vcastatus
[model_name
]["applications"][application
][
156 ] = await libjuju
.get_actions(application
, model_name
)
157 # Adding application configs
158 vcastatus
[model_name
]["applications"][application
][
160 ] = await libjuju
.get_application_configs(model_name
, application
)
161 except Exception as e
:
162 self
.log
.debug("Error in updating vca status: {}".format(str(e
)))
164 async def create_execution_environment(
168 reuse_ee_id
: str = None,
169 progress_timeout
: float = None,
170 total_timeout
: float = None,
174 Create an Execution Environment. Returns when it is created or raises an
177 :param: namespace: Contains a dot separate string.
178 LCM will use: [<nsi-id>].<ns-id>.<vnf-id>.<vdu-id>[-<count>]
179 :param: db_dict: where to write to database when the status changes.
180 It contains a dictionary with {collection: str, filter: {}, path: str},
181 e.g. {collection: "nsrs", filter: {_id: <nsd-id>, path:
182 "_admin.deployed.VCA.3"}
183 :param: reuse_ee_id: ee id from an older execution. It allows us to reuse an
185 :param: progress_timeout: Progress timeout
186 :param: total_timeout: Total timeout
187 :param: vca_id: VCA ID
189 :returns: id of the new execution environment and credentials for it
190 (credentials can contains hostname, username, etc depending on underlying cloud)
194 "Creating execution environment. namespace: {}, reuse_ee_id: {}".format(
195 namespace
, reuse_ee_id
198 libjuju
= await self
._get
_libjuju
(vca_id
)
202 model_name
, application_name
, machine_id
= self
._get
_ee
_id
_components
(
212 ) = self
._get
_namespace
_components
(namespace
=namespace
)
213 # model name is ns_id
216 application_name
= self
._get
_application
_name
(namespace
=namespace
)
219 "model name: {}, application name: {}, machine_id: {}".format(
220 model_name
, application_name
, machine_id
224 # create or reuse a new juju machine
226 if not await libjuju
.model_exists(model_name
):
227 await libjuju
.add_model(
229 libjuju
.vca_connection
.lxd_cloud
,
231 machine
, new
= await libjuju
.create_machine(
232 model_name
=model_name
,
233 machine_id
=machine_id
,
235 progress_timeout
=progress_timeout
,
236 total_timeout
=total_timeout
,
238 # id for the execution environment
239 ee_id
= N2VCJujuConnector
._build
_ee
_id
(
240 model_name
=model_name
,
241 application_name
=application_name
,
242 machine_id
=str(machine
.entity_id
),
244 self
.log
.debug("ee_id: {}".format(ee_id
))
247 # write ee_id in database
248 self
._write
_ee
_id
_db
(db_dict
=db_dict
, ee_id
=ee_id
)
250 except Exception as e
:
251 message
= "Error creating machine on juju: {}".format(e
)
252 self
.log
.error(message
)
253 raise N2VCException(message
=message
)
255 # new machine credentials
257 "hostname": machine
.dns_name
,
261 "Execution environment created. ee_id: {}, credentials: {}".format(
266 return ee_id
, credentials
268 async def register_execution_environment(
273 progress_timeout
: float = None,
274 total_timeout
: float = None,
278 Register an existing execution environment at the VCA
280 :param: namespace: Contains a dot separate string.
281 LCM will use: [<nsi-id>].<ns-id>.<vnf-id>.<vdu-id>[-<count>]
282 :param: credentials: credentials to access the existing execution environment
283 (it can contains hostname, username, path to private key,
284 etc depending on underlying cloud)
285 :param: db_dict: where to write to database when the status changes.
286 It contains a dictionary with {collection: str, filter: {}, path: str},
287 e.g. {collection: "nsrs", filter: {_id: <nsd-id>, path:
288 "_admin.deployed.VCA.3"}
289 :param: reuse_ee_id: ee id from an older execution. It allows us to reuse an
291 :param: progress_timeout: Progress timeout
292 :param: total_timeout: Total timeout
293 :param: vca_id: VCA ID
295 :returns: id of the execution environment
298 "Registering execution environment. namespace={}, credentials={}".format(
299 namespace
, credentials
302 libjuju
= await self
._get
_libjuju
(vca_id
)
304 if credentials
is None:
305 raise N2VCBadArgumentsException(
306 message
="credentials are mandatory", bad_args
=["credentials"]
308 if credentials
.get("hostname"):
309 hostname
= credentials
["hostname"]
311 raise N2VCBadArgumentsException(
312 message
="hostname is mandatory", bad_args
=["credentials.hostname"]
314 if credentials
.get("username"):
315 username
= credentials
["username"]
317 raise N2VCBadArgumentsException(
318 message
="username is mandatory", bad_args
=["credentials.username"]
320 if "private_key_path" in credentials
:
321 private_key_path
= credentials
["private_key_path"]
323 # if not passed as argument, use generated private key path
324 private_key_path
= self
.private_key_path
326 _nsi_id
, ns_id
, _vnf_id
, _vdu_id
, _vdu_count
= self
._get
_namespace
_components
(
333 application_name
= self
._get
_application
_name
(namespace
=namespace
)
335 # register machine on juju
337 if not await libjuju
.model_exists(model_name
):
338 await libjuju
.add_model(
340 libjuju
.vca_connection
.lxd_cloud
,
342 machine_id
= await libjuju
.provision_machine(
343 model_name
=model_name
,
346 private_key_path
=private_key_path
,
348 progress_timeout
=progress_timeout
,
349 total_timeout
=total_timeout
,
351 except Exception as e
:
352 self
.log
.error("Error registering machine: {}".format(e
))
354 message
="Error registering machine on juju: {}".format(e
)
357 self
.log
.info("Machine registered: {}".format(machine_id
))
359 # id for the execution environment
360 ee_id
= N2VCJujuConnector
._build
_ee
_id
(
361 model_name
=model_name
,
362 application_name
=application_name
,
363 machine_id
=str(machine_id
),
366 self
.log
.info("Execution environment registered. ee_id: {}".format(ee_id
))
370 # In case of native_charm is being deployed, if JujuApplicationExists error happens
371 # it will try to add_unit
372 @retry(attempts
=3, delay
=5, retry_exceptions
=(N2VCApplicationExists
,), timeout
=None)
373 async def install_configuration_sw(
378 progress_timeout
: float = None,
379 total_timeout
: float = None,
383 scaling_out
: bool = False,
384 vca_type
: str = None,
387 Install the software inside the execution environment identified by ee_id
389 :param: ee_id: the id of the execution environment returned by
390 create_execution_environment or register_execution_environment
391 :param: artifact_path: where to locate the artifacts (parent folder) using
393 the final artifact path will be a combination of this
394 artifact_path and additional string from the config_dict
396 :param: db_dict: where to write into database when the status changes.
397 It contains a dict with
398 {collection: <str>, filter: {}, path: <str>},
399 e.g. {collection: "nsrs", filter:
400 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
401 :param: progress_timeout: Progress timeout
402 :param: total_timeout: Total timeout
403 :param: config: Dictionary with deployment config information.
404 :param: num_units: Number of units to deploy of a particular charm.
405 :param: vca_id: VCA ID
406 :param: scaling_out: Boolean to indicate if it is a scaling out operation
407 :param: vca_type: VCA type
412 "Installing configuration sw on ee_id: {}, "
413 "artifact path: {}, db_dict: {}"
414 ).format(ee_id
, artifact_path
, db_dict
)
416 libjuju
= await self
._get
_libjuju
(vca_id
)
419 if ee_id
is None or len(ee_id
) == 0:
420 raise N2VCBadArgumentsException(
421 message
="ee_id is mandatory", bad_args
=["ee_id"]
423 if artifact_path
is None or len(artifact_path
) == 0:
424 raise N2VCBadArgumentsException(
425 message
="artifact_path is mandatory", bad_args
=["artifact_path"]
428 raise N2VCBadArgumentsException(
429 message
="db_dict is mandatory", bad_args
=["db_dict"]
437 ) = N2VCJujuConnector
._get
_ee
_id
_components
(ee_id
=ee_id
)
439 "model: {}, application: {}, machine: {}".format(
440 model_name
, application_name
, machine_id
444 raise N2VCBadArgumentsException(
445 message
="ee_id={} is not a valid execution environment id".format(
451 # remove // in charm path
452 while artifact_path
.find("//") >= 0:
453 artifact_path
= artifact_path
.replace("//", "/")
456 if not self
.fs
.file_exists(artifact_path
):
457 msg
= "artifact path does not exist: {}".format(artifact_path
)
458 raise N2VCBadArgumentsException(message
=msg
, bad_args
=["artifact_path"])
460 if artifact_path
.startswith("/"):
461 full_path
= self
.fs
.path
+ artifact_path
463 full_path
= self
.fs
.path
+ "/" + artifact_path
466 if vca_type
== "native_charm" and await libjuju
.check_application_exists(
467 model_name
, application_name
469 await libjuju
.add_unit(
470 application_name
=application_name
,
471 model_name
=model_name
,
472 machine_id
=machine_id
,
474 progress_timeout
=progress_timeout
,
475 total_timeout
=total_timeout
,
478 await libjuju
.deploy_charm(
479 model_name
=model_name
,
480 application_name
=application_name
,
482 machine_id
=machine_id
,
484 progress_timeout
=progress_timeout
,
485 total_timeout
=total_timeout
,
489 except JujuApplicationExists
as e
:
490 raise N2VCApplicationExists(
491 message
="Error deploying charm into ee={} : {}".format(ee_id
, e
.message
)
493 except Exception as e
:
495 message
="Error deploying charm into ee={} : {}".format(ee_id
, e
)
498 self
.log
.info("Configuration sw installed")
500 async def install_k8s_proxy_charm(
506 progress_timeout
: float = None,
507 total_timeout
: float = None,
512 Install a k8s proxy charm
514 :param charm_name: Name of the charm being deployed
515 :param namespace: collection of all the uuids related to the charm.
516 :param str artifact_path: where to locate the artifacts (parent folder) using
518 the final artifact path will be a combination of this artifact_path and
519 additional string from the config_dict (e.g. charm name)
520 :param dict db_dict: where to write into database when the status changes.
521 It contains a dict with
522 {collection: <str>, filter: {}, path: <str>},
523 e.g. {collection: "nsrs", filter:
524 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
525 :param: progress_timeout: Progress timeout
526 :param: total_timeout: Total timeout
527 :param config: Dictionary with additional configuration
528 :param vca_id: VCA ID
530 :returns ee_id: execution environment id.
533 "Installing k8s proxy charm: {}, artifact path: {}, db_dict: {}".format(
534 charm_name
, artifact_path
, db_dict
537 libjuju
= await self
._get
_libjuju
(vca_id
)
539 if artifact_path
is None or len(artifact_path
) == 0:
540 raise N2VCBadArgumentsException(
541 message
="artifact_path is mandatory", bad_args
=["artifact_path"]
544 raise N2VCBadArgumentsException(
545 message
="db_dict is mandatory", bad_args
=["db_dict"]
548 # remove // in charm path
549 while artifact_path
.find("//") >= 0:
550 artifact_path
= artifact_path
.replace("//", "/")
553 if not self
.fs
.file_exists(artifact_path
):
554 msg
= "artifact path does not exist: {}".format(artifact_path
)
555 raise N2VCBadArgumentsException(message
=msg
, bad_args
=["artifact_path"])
557 if artifact_path
.startswith("/"):
558 full_path
= self
.fs
.path
+ artifact_path
560 full_path
= self
.fs
.path
+ "/" + artifact_path
562 _
, ns_id
, _
, _
, _
= self
._get
_namespace
_components
(namespace
=namespace
)
563 model_name
= "{}-k8s".format(ns_id
)
564 if not await libjuju
.model_exists(model_name
):
565 await libjuju
.add_model(
567 libjuju
.vca_connection
.k8s_cloud
,
569 application_name
= self
._get
_application
_name
(namespace
)
572 await libjuju
.deploy_charm(
573 model_name
=model_name
,
574 application_name
=application_name
,
578 progress_timeout
=progress_timeout
,
579 total_timeout
=total_timeout
,
582 except Exception as e
:
583 raise N2VCException(message
="Error deploying charm: {}".format(e
))
585 self
.log
.info("K8s proxy charm installed")
586 ee_id
= N2VCJujuConnector
._build
_ee
_id
(
587 model_name
=model_name
,
588 application_name
=application_name
,
592 self
._write
_ee
_id
_db
(db_dict
=db_dict
, ee_id
=ee_id
)
596 async def get_ee_ssh_public__key(
600 progress_timeout
: float = None,
601 total_timeout
: float = None,
605 Get Execution environment ssh public key
607 :param: ee_id: the id of the execution environment returned by
608 create_execution_environment or register_execution_environment
609 :param: db_dict: where to write into database when the status changes.
610 It contains a dict with
611 {collection: <str>, filter: {}, path: <str>},
612 e.g. {collection: "nsrs", filter:
613 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
614 :param: progress_timeout: Progress timeout
615 :param: total_timeout: Total timeout
616 :param vca_id: VCA ID
617 :returns: public key of the execution environment
618 For the case of juju proxy charm ssh-layered, it is the one
619 returned by 'get-ssh-public-key' primitive.
620 It raises a N2VC exception if fails
625 "Generating priv/pub key pair and get pub key on ee_id: {}, db_dict: {}"
626 ).format(ee_id
, db_dict
)
628 libjuju
= await self
._get
_libjuju
(vca_id
)
631 if ee_id
is None or len(ee_id
) == 0:
632 raise N2VCBadArgumentsException(
633 message
="ee_id is mandatory", bad_args
=["ee_id"]
636 raise N2VCBadArgumentsException(
637 message
="db_dict is mandatory", bad_args
=["db_dict"]
645 ) = N2VCJujuConnector
._get
_ee
_id
_components
(ee_id
=ee_id
)
647 "model: {}, application: {}, machine: {}".format(
648 model_name
, application_name
, machine_id
652 raise N2VCBadArgumentsException(
653 message
="ee_id={} is not a valid execution environment id".format(
659 # try to execute ssh layer primitives (if exist):
665 application_name
= N2VCJujuConnector
._format
_app
_name
(application_name
)
667 # execute action: generate-ssh-key
669 output
, _status
= await libjuju
.execute_action(
670 model_name
=model_name
,
671 application_name
=application_name
,
672 action_name
="generate-ssh-key",
674 progress_timeout
=progress_timeout
,
675 total_timeout
=total_timeout
,
677 except Exception as e
:
679 "Skipping exception while executing action generate-ssh-key: {}".format(
684 # execute action: get-ssh-public-key
686 output
, _status
= await libjuju
.execute_action(
687 model_name
=model_name
,
688 application_name
=application_name
,
689 action_name
="get-ssh-public-key",
691 progress_timeout
=progress_timeout
,
692 total_timeout
=total_timeout
,
694 except Exception as e
:
695 msg
= "Cannot execute action get-ssh-public-key: {}\n".format(e
)
697 raise N2VCExecutionException(e
, primitive_name
="get-ssh-public-key")
699 # return public key if exists
700 return output
["pubkey"] if "pubkey" in output
else output
702 async def get_metrics(
703 self
, model_name
: str, application_name
: str, vca_id
: str = None
706 Get metrics from application
708 :param: model_name: Model name
709 :param: application_name: Application name
710 :param: vca_id: VCA ID
712 :return: Dictionary with obtained metrics
714 libjuju
= await self
._get
_libjuju
(vca_id
)
715 return await libjuju
.get_metrics(model_name
, application_name
)
717 async def add_relation(
726 Add relation between two charmed endpoints
728 :param: ee_id_1: The id of the first execution environment
729 :param: ee_id_2: The id of the second execution environment
730 :param: endpoint_1: The endpoint in the first execution environment
731 :param: endpoint_2: The endpoint in the second execution environment
732 :param: vca_id: VCA ID
735 "adding new relation between {} and {}, endpoints: {}, {}".format(
736 ee_id_1
, ee_id_2
, endpoint_1
, endpoint_2
739 libjuju
= await self
._get
_libjuju
(vca_id
)
743 message
= "EE 1 is mandatory"
744 self
.log
.error(message
)
745 raise N2VCBadArgumentsException(message
=message
, bad_args
=["ee_id_1"])
747 message
= "EE 2 is mandatory"
748 self
.log
.error(message
)
749 raise N2VCBadArgumentsException(message
=message
, bad_args
=["ee_id_2"])
751 message
= "endpoint 1 is mandatory"
752 self
.log
.error(message
)
753 raise N2VCBadArgumentsException(message
=message
, bad_args
=["endpoint_1"])
755 message
= "endpoint 2 is mandatory"
756 self
.log
.error(message
)
757 raise N2VCBadArgumentsException(message
=message
, bad_args
=["endpoint_2"])
759 # get the model, the applications and the machines from the ee_id's
760 model_1
, app_1
, _machine_1
= self
._get
_ee
_id
_components
(ee_id_1
)
761 model_2
, app_2
, _machine_2
= self
._get
_ee
_id
_components
(ee_id_2
)
763 # model must be the same
764 if model_1
!= model_2
:
765 message
= "EE models are not the same: {} vs {}".format(ee_id_1
, ee_id_2
)
766 self
.log
.error(message
)
767 raise N2VCBadArgumentsException(
768 message
=message
, bad_args
=["ee_id_1", "ee_id_2"]
771 # add juju relations between two applications
773 await libjuju
.add_relation(
775 endpoint_1
="{}:{}".format(app_1
, endpoint_1
),
776 endpoint_2
="{}:{}".format(app_2
, endpoint_2
),
778 except Exception as e
:
779 message
= "Error adding relation between {} and {}: {}".format(
782 self
.log
.error(message
)
783 raise N2VCException(message
=message
)
785 async def remove_relation(self
):
787 self
.log
.info("Method not implemented yet")
788 raise MethodNotImplemented()
790 async def deregister_execution_environments(self
):
791 self
.log
.info("Method not implemented yet")
792 raise MethodNotImplemented()
794 async def delete_namespace(
797 db_dict
: dict = None,
798 total_timeout
: float = None,
802 Remove a network scenario and its execution environments
803 :param: namespace: [<nsi-id>].<ns-id>
804 :param: db_dict: where to write into database when the status changes.
805 It contains a dict with
806 {collection: <str>, filter: {}, path: <str>},
807 e.g. {collection: "nsrs", filter:
808 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
809 :param: total_timeout: Total timeout
810 :param: vca_id: VCA ID
812 self
.log
.info("Deleting namespace={}".format(namespace
))
813 libjuju
= await self
._get
_libjuju
(vca_id
)
816 if namespace
is None:
817 raise N2VCBadArgumentsException(
818 message
="namespace is mandatory", bad_args
=["namespace"]
821 _nsi_id
, ns_id
, _vnf_id
, _vdu_id
, _vdu_count
= self
._get
_namespace
_components
(
824 if ns_id
is not None:
826 models
= await libjuju
.list_models(contains
=ns_id
)
828 await libjuju
.destroy_model(
829 model_name
=model
, total_timeout
=total_timeout
831 except Exception as e
:
833 message
="Error deleting namespace {} : {}".format(namespace
, e
)
836 raise N2VCBadArgumentsException(
837 message
="only ns_id is permitted to delete yet", bad_args
=["namespace"]
840 self
.log
.info("Namespace {} deleted".format(namespace
))
842 async def delete_execution_environment(
845 db_dict
: dict = None,
846 total_timeout
: float = None,
847 scaling_in
: bool = False,
848 vca_type
: str = None,
852 Delete an execution environment
853 :param str ee_id: id of the execution environment to delete
854 :param dict db_dict: where to write into database when the status changes.
855 It contains a dict with
856 {collection: <str>, filter: {}, path: <str>},
857 e.g. {collection: "nsrs", filter:
858 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
859 :param: total_timeout: Total timeout
860 :param: scaling_in: Boolean to indicate if it is a scaling in operation
861 :param: vca_type: VCA type
862 :param: vca_id: VCA ID
864 self
.log
.info("Deleting execution environment ee_id={}".format(ee_id
))
865 libjuju
= await self
._get
_libjuju
(vca_id
)
869 raise N2VCBadArgumentsException(
870 message
="ee_id is mandatory", bad_args
=["ee_id"]
873 model_name
, application_name
, machine_id
= self
._get
_ee
_id
_components
(
879 await libjuju
.destroy_model(
880 model_name
=model_name
,
881 total_timeout
=total_timeout
,
883 elif vca_type
== "native_charm" and scaling_in
:
884 # destroy the unit in the application
885 await libjuju
.destroy_unit(
886 application_name
=application_name
,
887 model_name
=model_name
,
888 machine_id
=machine_id
,
889 total_timeout
=total_timeout
,
892 # destroy the application
893 await libjuju
.destroy_application(
894 model_name
=model_name
,
895 application_name
=application_name
,
896 total_timeout
=total_timeout
,
898 except Exception as e
:
901 "Error deleting execution environment {} (application {}) : {}"
902 ).format(ee_id
, application_name
, e
)
905 self
.log
.info("Execution environment {} deleted".format(ee_id
))
907 async def exec_primitive(
912 db_dict
: dict = None,
913 progress_timeout
: float = None,
914 total_timeout
: float = None,
916 vca_type
: str = None,
919 Execute a primitive in the execution environment
921 :param: ee_id: the one returned by create_execution_environment or
922 register_execution_environment
923 :param: primitive_name: must be one defined in the software. There is one
924 called 'config', where, for the proxy case, the 'credentials' of VM are
926 :param: params_dict: parameters of the action
927 :param: db_dict: where to write into database when the status changes.
928 It contains a dict with
929 {collection: <str>, filter: {}, path: <str>},
930 e.g. {collection: "nsrs", filter:
931 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
932 :param: progress_timeout: Progress timeout
933 :param: total_timeout: Total timeout
934 :param: vca_id: VCA ID
935 :param: vca_type: VCA type
936 :returns str: primitive result, if ok. It raises exceptions in case of fail
940 "Executing primitive: {} on ee: {}, params: {}".format(
941 primitive_name
, ee_id
, params_dict
944 libjuju
= await self
._get
_libjuju
(vca_id
)
947 if ee_id
is None or len(ee_id
) == 0:
948 raise N2VCBadArgumentsException(
949 message
="ee_id is mandatory", bad_args
=["ee_id"]
951 if primitive_name
is None or len(primitive_name
) == 0:
952 raise N2VCBadArgumentsException(
953 message
="action_name is mandatory", bad_args
=["action_name"]
955 if params_dict
is None:
963 ) = N2VCJujuConnector
._get
_ee
_id
_components
(ee_id
=ee_id
)
964 # To run action on the leader unit in libjuju.execute_action function,
965 # machine_id must be set to None if vca_type is not native_charm
966 if vca_type
!= "native_charm":
969 raise N2VCBadArgumentsException(
970 message
="ee_id={} is not a valid execution environment id".format(
976 if primitive_name
== "config":
977 # Special case: config primitive
979 await libjuju
.configure_application(
980 model_name
=model_name
,
981 application_name
=application_name
,
984 actions
= await libjuju
.get_actions(
985 application_name
=application_name
,
986 model_name
=model_name
,
989 "Application {} has these actions: {}".format(
990 application_name
, actions
993 if "verify-ssh-credentials" in actions
:
994 # execute verify-credentials
997 for _
in range(num_retries
):
999 self
.log
.debug("Executing action verify-ssh-credentials...")
1000 output
, ok
= await libjuju
.execute_action(
1001 model_name
=model_name
,
1002 application_name
=application_name
,
1003 action_name
="verify-ssh-credentials",
1005 progress_timeout
=progress_timeout
,
1006 total_timeout
=total_timeout
,
1011 "Error executing verify-ssh-credentials: {}. Retrying..."
1013 await asyncio
.sleep(retry_timeout
)
1016 self
.log
.debug("Result: {}, output: {}".format(ok
, output
))
1018 except asyncio
.CancelledError
:
1022 "Error executing verify-ssh-credentials after {} retries. ".format(
1027 msg
= "Action verify-ssh-credentials does not exist in application {}".format(
1030 self
.log
.debug(msg
=msg
)
1031 except Exception as e
:
1032 self
.log
.error("Error configuring juju application: {}".format(e
))
1033 raise N2VCExecutionException(
1034 message
="Error configuring application into ee={} : {}".format(
1037 primitive_name
=primitive_name
,
1042 output
, status
= await libjuju
.execute_action(
1043 model_name
=model_name
,
1044 application_name
=application_name
,
1045 action_name
=primitive_name
,
1047 machine_id
=machine_id
,
1048 progress_timeout
=progress_timeout
,
1049 total_timeout
=total_timeout
,
1052 if status
== "completed":
1055 raise Exception("status is not completed: {}".format(status
))
1056 except Exception as e
:
1058 "Error executing primitive {}: {}".format(primitive_name
, e
)
1060 raise N2VCExecutionException(
1061 message
="Error executing primitive {} into ee={} : {}".format(
1062 primitive_name
, ee_id
, e
1064 primitive_name
=primitive_name
,
1067 async def disconnect(self
, vca_id
: str = None):
1071 :param: vca_id: VCA ID
1073 self
.log
.info("closing juju N2VC...")
1074 libjuju
= await self
._get
_libjuju
(vca_id
)
1076 await libjuju
.disconnect()
1077 except Exception as e
:
1078 raise N2VCConnectionException(
1079 message
="Error disconnecting controller: {}".format(e
),
1080 url
=libjuju
.vca_connection
.data
.endpoints
,
1084 ####################################################################################
1085 ################################### P R I V A T E ##################################
1086 ####################################################################################
1089 async def _get_libjuju(self
, vca_id
: str = None) -> Libjuju
:
1093 :param: vca_id: VCA ID
1094 If None, get a libjuju object with a Connection to the default VCA
1095 Else, geta libjuju object with a Connection to the specified VCA
1098 while self
.loading_libjuju
.locked():
1099 await asyncio
.sleep(0.1)
1100 if not self
.libjuju
:
1101 async with self
.loading_libjuju
:
1102 vca_connection
= await get_connection(self
._store
)
1103 self
.libjuju
= Libjuju(vca_connection
, loop
=self
.loop
, log
=self
.log
)
1106 vca_connection
= await get_connection(self
._store
, vca_id
)
1114 def _write_ee_id_db(self
, db_dict
: dict, ee_id
: str):
1116 # write ee_id to database: _admin.deployed.VCA.x
1118 the_table
= db_dict
["collection"]
1119 the_filter
= db_dict
["filter"]
1120 the_path
= db_dict
["path"]
1121 if not the_path
[-1] == ".":
1122 the_path
= the_path
+ "."
1123 update_dict
= {the_path
+ "ee_id": ee_id
}
1124 # self.log.debug('Writing ee_id to database: {}'.format(the_path))
1127 q_filter
=the_filter
,
1128 update_dict
=update_dict
,
1131 except asyncio
.CancelledError
:
1133 except Exception as e
:
1134 self
.log
.error("Error writing ee_id to database: {}".format(e
))
1137 def _build_ee_id(model_name
: str, application_name
: str, machine_id
: str):
1139 Build an execution environment id form model, application and machine
1141 :param application_name:
1145 # id for the execution environment
1146 return "{}.{}.{}".format(model_name
, application_name
, machine_id
)
1149 def _get_ee_id_components(ee_id
: str) -> (str, str, str):
1151 Get model, application and machine components from an execution environment id
1153 :return: model_name, application_name, machine_id
1157 return None, None, None
1159 # split components of id
1160 parts
= ee_id
.split(".")
1161 model_name
= parts
[0]
1162 application_name
= parts
[1]
1163 machine_id
= parts
[2]
1164 return model_name
, application_name
, machine_id
1166 def _get_application_name(self
, namespace
: str) -> str:
1168 Build application name from namespace
1170 :return: app-vnf-<vnf id>-vdu-<vdu-id>-cnt-<vdu-count>
1173 # TODO: Enforce the Juju 50-character application limit
1175 # split namespace components
1176 _
, _
, vnf_id
, vdu_id
, vdu_count
= self
._get
_namespace
_components
(
1180 if vnf_id
is None or len(vnf_id
) == 0:
1183 # Shorten the vnf_id to its last twelve characters
1184 vnf_id
= "vnf-" + vnf_id
[-12:]
1186 if vdu_id
is None or len(vdu_id
) == 0:
1189 # Shorten the vdu_id to its last twelve characters
1190 vdu_id
= "-vdu-" + vdu_id
[-12:]
1192 if vdu_count
is None or len(vdu_count
) == 0:
1195 vdu_count
= "-cnt-" + vdu_count
1197 application_name
= "app-{}{}{}".format(vnf_id
, vdu_id
, vdu_count
)
1199 return N2VCJujuConnector
._format
_app
_name
(application_name
)
1202 def _format_model_name(name
: str) -> str:
1203 """Format the name of the model.
1205 Model names may only contain lowercase letters, digits and hyphens
1208 return name
.replace("_", "-").replace(" ", "-").lower()
1211 def _format_app_name(name
: str) -> str:
1212 """Format the name of the application (in order to assure valid application name).
1214 Application names have restrictions (run juju deploy --help):
1215 - contains lowercase letters 'a'-'z'
1216 - contains numbers '0'-'9'
1217 - contains hyphens '-'
1218 - starts with a lowercase letter
1219 - not two or more consecutive hyphens
1220 - after a hyphen, not a group with all numbers
1223 def all_numbers(s
: str) -> bool:
1229 new_name
= name
.replace("_", "-")
1230 new_name
= new_name
.replace(" ", "-")
1231 new_name
= new_name
.lower()
1232 while new_name
.find("--") >= 0:
1233 new_name
= new_name
.replace("--", "-")
1234 groups
= new_name
.split("-")
1236 # find 'all numbers' groups and prefix them with a letter
1238 for i
in range(len(groups
)):
1240 if all_numbers(group
):
1246 if app_name
[0].isdigit():
1247 app_name
= "z" + app_name
1251 async def validate_vca(self
, vca_id
: str):
1253 Validate a VCA by connecting/disconnecting to/from it
1255 :param: vca_id: VCA ID
1257 vca_connection
= await get_connection(self
._store
, vca_id
=vca_id
)
1258 libjuju
= Libjuju(vca_connection
, loop
=self
.loop
, log
=self
.log
, n2vc
=self
)
1259 controller
= await libjuju
.get_controller()
1260 await libjuju
.disconnect_controller(controller
)