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
,
35 from n2vc
.n2vc_conn
import N2VCConnector
36 from n2vc
.n2vc_conn
import obj_to_dict
, obj_to_yaml
37 from n2vc
.libjuju
import Libjuju
38 from n2vc
.store
import MotorStore
39 from n2vc
.vca
.connection
import get_connection
42 class N2VCJujuConnector(N2VCConnector
):
45 ####################################################################################
46 ################################### P U B L I C ####################################
47 ####################################################################################
50 BUILT_IN_CLOUDS
= ["localhost", "microk8s"]
64 :param: db: Database object from osm_common
65 :param: fs: Filesystem object from osm_common
67 :param: loop: Asyncio loop
68 :param: on_update_db: Callback function to be called for updating the database.
71 # parent class constructor
72 N2VCConnector
.__init
__(
78 on_update_db
=on_update_db
,
81 # silence websocket traffic log
82 logging
.getLogger("websockets.protocol").setLevel(logging
.INFO
)
83 logging
.getLogger("juju.client.connection").setLevel(logging
.WARN
)
84 logging
.getLogger("model").setLevel(logging
.WARN
)
86 self
.log
.info("Initializing N2VC juju connector...")
88 db_uri
= EnvironConfig(prefixes
=["OSMLCM_", "OSMMON_"]).get("database_uri")
89 self
._store
= MotorStore(db_uri
)
90 self
.loading_libjuju
= asyncio
.Lock(loop
=self
.loop
)
92 self
.log
.info("N2VC juju connector initialized")
95 self
, namespace
: str, yaml_format
: bool = True, vca_id
: str = None
98 Get status from all juju models from a VCA
100 :param namespace: we obtain ns from namespace
101 :param yaml_format: returns a yaml string
102 :param: vca_id: VCA ID from which the status will be retrieved.
104 # TODO: Review where is this function used. It is not optimal at all to get the status
105 # from all the juju models of a particular VCA. Additionally, these models might
106 # not have been deployed by OSM, in that case we are getting information from
107 # deployments outside of OSM's scope.
109 # self.log.info('Getting NS status. namespace: {}'.format(namespace))
110 libjuju
= await self
._get
_libjuju
(vca_id
)
112 _nsi_id
, ns_id
, _vnf_id
, _vdu_id
, _vdu_count
= self
._get
_namespace
_components
(
115 # model name is ns_id
117 if model_name
is None:
118 msg
= "Namespace {} not valid".format(namespace
)
120 raise N2VCBadArgumentsException(msg
, ["namespace"])
123 models
= await libjuju
.list_models(contains
=ns_id
)
126 status
[m
] = await libjuju
.get_model_status(m
)
129 return obj_to_yaml(status
)
131 return obj_to_dict(status
)
133 async def update_vca_status(self
, vcastatus
: dict, vca_id
: str = None):
135 Add all configs, actions, executed actions of all applications in a model to vcastatus dict.
137 :param vcastatus: dict containing vcaStatus
138 :param: vca_id: VCA ID
143 libjuju
= await self
._get
_libjuju
(vca_id
)
144 for model_name
in vcastatus
:
145 # Adding executed actions
146 vcastatus
[model_name
]["executedActions"] = \
147 await libjuju
.get_executed_actions(model_name
)
148 for application
in vcastatus
[model_name
]["applications"]:
149 # Adding application actions
150 vcastatus
[model_name
]["applications"][application
]["actions"] = \
151 await libjuju
.get_actions(application
, model_name
)
152 # Adding application configs
153 vcastatus
[model_name
]["applications"][application
]["configs"] = \
154 await libjuju
.get_application_configs(model_name
, application
)
155 except Exception as e
:
156 self
.log
.debug("Error in updating vca status: {}".format(str(e
)))
158 async def create_execution_environment(
162 reuse_ee_id
: str = None,
163 progress_timeout
: float = None,
164 total_timeout
: float = None,
168 Create an Execution Environment. Returns when it is created or raises an
171 :param: namespace: Contains a dot separate string.
172 LCM will use: [<nsi-id>].<ns-id>.<vnf-id>.<vdu-id>[-<count>]
173 :param: db_dict: where to write to database when the status changes.
174 It contains a dictionary with {collection: str, filter: {}, path: str},
175 e.g. {collection: "nsrs", filter: {_id: <nsd-id>, path:
176 "_admin.deployed.VCA.3"}
177 :param: reuse_ee_id: ee id from an older execution. It allows us to reuse an
179 :param: progress_timeout: Progress timeout
180 :param: total_timeout: Total timeout
181 :param: vca_id: VCA ID
183 :returns: id of the new execution environment and credentials for it
184 (credentials can contains hostname, username, etc depending on underlying cloud)
188 "Creating execution environment. namespace: {}, reuse_ee_id: {}".format(
189 namespace
, reuse_ee_id
192 libjuju
= await self
._get
_libjuju
(vca_id
)
196 model_name
, application_name
, machine_id
= self
._get
_ee
_id
_components
(
206 ) = self
._get
_namespace
_components
(namespace
=namespace
)
207 # model name is ns_id
210 application_name
= self
._get
_application
_name
(namespace
=namespace
)
213 "model name: {}, application name: {}, machine_id: {}".format(
214 model_name
, application_name
, machine_id
218 # create or reuse a new juju machine
220 if not await libjuju
.model_exists(model_name
):
221 await libjuju
.add_model(
223 libjuju
.vca_connection
.lxd_cloud
,
225 machine
, new
= await libjuju
.create_machine(
226 model_name
=model_name
,
227 machine_id
=machine_id
,
229 progress_timeout
=progress_timeout
,
230 total_timeout
=total_timeout
,
232 # id for the execution environment
233 ee_id
= N2VCJujuConnector
._build
_ee
_id
(
234 model_name
=model_name
,
235 application_name
=application_name
,
236 machine_id
=str(machine
.entity_id
),
238 self
.log
.debug("ee_id: {}".format(ee_id
))
241 # write ee_id in database
242 self
._write
_ee
_id
_db
(db_dict
=db_dict
, ee_id
=ee_id
)
244 except Exception as e
:
245 message
= "Error creating machine on juju: {}".format(e
)
246 self
.log
.error(message
)
247 raise N2VCException(message
=message
)
249 # new machine credentials
251 "hostname": machine
.dns_name
,
255 "Execution environment created. ee_id: {}, credentials: {}".format(
260 return ee_id
, credentials
262 async def register_execution_environment(
267 progress_timeout
: float = None,
268 total_timeout
: float = None,
272 Register an existing execution environment at the VCA
274 :param: namespace: Contains a dot separate string.
275 LCM will use: [<nsi-id>].<ns-id>.<vnf-id>.<vdu-id>[-<count>]
276 :param: credentials: credentials to access the existing execution environment
277 (it can contains hostname, username, path to private key,
278 etc depending on underlying cloud)
279 :param: db_dict: where to write to database when the status changes.
280 It contains a dictionary with {collection: str, filter: {}, path: str},
281 e.g. {collection: "nsrs", filter: {_id: <nsd-id>, path:
282 "_admin.deployed.VCA.3"}
283 :param: reuse_ee_id: ee id from an older execution. It allows us to reuse an
285 :param: progress_timeout: Progress timeout
286 :param: total_timeout: Total timeout
287 :param: vca_id: VCA ID
289 :returns: id of the execution environment
292 "Registering execution environment. namespace={}, credentials={}".format(
293 namespace
, credentials
296 libjuju
= await self
._get
_libjuju
(vca_id
)
298 if credentials
is None:
299 raise N2VCBadArgumentsException(
300 message
="credentials are mandatory", bad_args
=["credentials"]
302 if credentials
.get("hostname"):
303 hostname
= credentials
["hostname"]
305 raise N2VCBadArgumentsException(
306 message
="hostname is mandatory", bad_args
=["credentials.hostname"]
308 if credentials
.get("username"):
309 username
= credentials
["username"]
311 raise N2VCBadArgumentsException(
312 message
="username is mandatory", bad_args
=["credentials.username"]
314 if "private_key_path" in credentials
:
315 private_key_path
= credentials
["private_key_path"]
317 # if not passed as argument, use generated private key path
318 private_key_path
= self
.private_key_path
320 _nsi_id
, ns_id
, _vnf_id
, _vdu_id
, _vdu_count
= self
._get
_namespace
_components
(
327 application_name
= self
._get
_application
_name
(namespace
=namespace
)
329 # register machine on juju
331 if not await libjuju
.model_exists(model_name
):
332 await libjuju
.add_model(
334 libjuju
.vca_connection
.lxd_cloud
,
336 machine_id
= await libjuju
.provision_machine(
337 model_name
=model_name
,
340 private_key_path
=private_key_path
,
342 progress_timeout
=progress_timeout
,
343 total_timeout
=total_timeout
,
345 except Exception as e
:
346 self
.log
.error("Error registering machine: {}".format(e
))
348 message
="Error registering machine on juju: {}".format(e
)
351 self
.log
.info("Machine registered: {}".format(machine_id
))
353 # id for the execution environment
354 ee_id
= N2VCJujuConnector
._build
_ee
_id
(
355 model_name
=model_name
,
356 application_name
=application_name
,
357 machine_id
=str(machine_id
),
360 self
.log
.info("Execution environment registered. ee_id: {}".format(ee_id
))
364 async def install_configuration_sw(
369 progress_timeout
: float = None,
370 total_timeout
: float = None,
376 Install the software inside the execution environment identified by ee_id
378 :param: ee_id: the id of the execution environment returned by
379 create_execution_environment or register_execution_environment
380 :param: artifact_path: where to locate the artifacts (parent folder) using
382 the final artifact path will be a combination of this
383 artifact_path and additional string from the config_dict
385 :param: db_dict: where to write into database when the status changes.
386 It contains a dict with
387 {collection: <str>, filter: {}, path: <str>},
388 e.g. {collection: "nsrs", filter:
389 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
390 :param: progress_timeout: Progress timeout
391 :param: total_timeout: Total timeout
392 :param: config: Dictionary with deployment config information.
393 :param: num_units: Number of units to deploy of a particular charm.
394 :param: vca_id: VCA ID
399 "Installing configuration sw on ee_id: {}, "
400 "artifact path: {}, db_dict: {}"
401 ).format(ee_id
, artifact_path
, db_dict
)
403 libjuju
= await self
._get
_libjuju
(vca_id
)
406 if ee_id
is None or len(ee_id
) == 0:
407 raise N2VCBadArgumentsException(
408 message
="ee_id is mandatory", bad_args
=["ee_id"]
410 if artifact_path
is None or len(artifact_path
) == 0:
411 raise N2VCBadArgumentsException(
412 message
="artifact_path is mandatory", bad_args
=["artifact_path"]
415 raise N2VCBadArgumentsException(
416 message
="db_dict is mandatory", bad_args
=["db_dict"]
424 ) = N2VCJujuConnector
._get
_ee
_id
_components
(ee_id
=ee_id
)
426 "model: {}, application: {}, machine: {}".format(
427 model_name
, application_name
, machine_id
431 raise N2VCBadArgumentsException(
432 message
="ee_id={} is not a valid execution environment id".format(
438 # remove // in charm path
439 while artifact_path
.find("//") >= 0:
440 artifact_path
= artifact_path
.replace("//", "/")
443 if not self
.fs
.file_exists(artifact_path
, mode
="dir"):
444 msg
= "artifact path does not exist: {}".format(artifact_path
)
445 raise N2VCBadArgumentsException(message
=msg
, bad_args
=["artifact_path"])
447 if artifact_path
.startswith("/"):
448 full_path
= self
.fs
.path
+ artifact_path
450 full_path
= self
.fs
.path
+ "/" + artifact_path
453 await libjuju
.deploy_charm(
454 model_name
=model_name
,
455 application_name
=application_name
,
457 machine_id
=machine_id
,
459 progress_timeout
=progress_timeout
,
460 total_timeout
=total_timeout
,
464 except Exception as e
:
466 message
="Error desploying charm into ee={} : {}".format(ee_id
, e
)
469 self
.log
.info("Configuration sw installed")
471 async def install_k8s_proxy_charm(
477 progress_timeout
: float = None,
478 total_timeout
: float = None,
483 Install a k8s proxy charm
485 :param charm_name: Name of the charm being deployed
486 :param namespace: collection of all the uuids related to the charm.
487 :param str artifact_path: where to locate the artifacts (parent folder) using
489 the final artifact path will be a combination of this artifact_path and
490 additional string from the config_dict (e.g. charm name)
491 :param dict db_dict: where to write into database when the status changes.
492 It contains a dict with
493 {collection: <str>, filter: {}, path: <str>},
494 e.g. {collection: "nsrs", filter:
495 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
496 :param: progress_timeout: Progress timeout
497 :param: total_timeout: Total timeout
498 :param config: Dictionary with additional configuration
499 :param vca_id: VCA ID
501 :returns ee_id: execution environment id.
504 "Installing k8s proxy charm: {}, artifact path: {}, db_dict: {}".format(
505 charm_name
, artifact_path
, db_dict
508 libjuju
= await self
._get
_libjuju
(vca_id
)
510 if artifact_path
is None or len(artifact_path
) == 0:
511 raise N2VCBadArgumentsException(
512 message
="artifact_path is mandatory", bad_args
=["artifact_path"]
515 raise N2VCBadArgumentsException(
516 message
="db_dict is mandatory", bad_args
=["db_dict"]
519 # remove // in charm path
520 while artifact_path
.find("//") >= 0:
521 artifact_path
= artifact_path
.replace("//", "/")
524 if not self
.fs
.file_exists(artifact_path
, mode
="dir"):
525 msg
= "artifact path does not exist: {}".format(artifact_path
)
526 raise N2VCBadArgumentsException(message
=msg
, bad_args
=["artifact_path"])
528 if artifact_path
.startswith("/"):
529 full_path
= self
.fs
.path
+ artifact_path
531 full_path
= self
.fs
.path
+ "/" + artifact_path
533 _
, ns_id
, _
, _
, _
= self
._get
_namespace
_components
(namespace
=namespace
)
534 model_name
= "{}-k8s".format(ns_id
)
535 if not await libjuju
.model_exists(model_name
):
536 await libjuju
.add_model(
538 libjuju
.vca_connection
.k8s_cloud
,
540 application_name
= self
._get
_application
_name
(namespace
)
543 await libjuju
.deploy_charm(
544 model_name
=model_name
,
545 application_name
=application_name
,
549 progress_timeout
=progress_timeout
,
550 total_timeout
=total_timeout
,
553 except Exception as e
:
554 raise N2VCException(message
="Error deploying charm: {}".format(e
))
556 self
.log
.info("K8s proxy charm installed")
557 ee_id
= N2VCJujuConnector
._build
_ee
_id
(
558 model_name
=model_name
,
559 application_name
=application_name
,
563 self
._write
_ee
_id
_db
(db_dict
=db_dict
, ee_id
=ee_id
)
567 async def get_ee_ssh_public__key(
571 progress_timeout
: float = None,
572 total_timeout
: float = None,
576 Get Execution environment ssh public key
578 :param: ee_id: the id of the execution environment returned by
579 create_execution_environment or register_execution_environment
580 :param: db_dict: where to write into database when the status changes.
581 It contains a dict with
582 {collection: <str>, filter: {}, path: <str>},
583 e.g. {collection: "nsrs", filter:
584 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
585 :param: progress_timeout: Progress timeout
586 :param: total_timeout: Total timeout
587 :param vca_id: VCA ID
588 :returns: public key of the execution environment
589 For the case of juju proxy charm ssh-layered, it is the one
590 returned by 'get-ssh-public-key' primitive.
591 It raises a N2VC exception if fails
596 "Generating priv/pub key pair and get pub key on ee_id: {}, db_dict: {}"
597 ).format(ee_id
, db_dict
)
599 libjuju
= await self
._get
_libjuju
(vca_id
)
602 if ee_id
is None or len(ee_id
) == 0:
603 raise N2VCBadArgumentsException(
604 message
="ee_id is mandatory", bad_args
=["ee_id"]
607 raise N2VCBadArgumentsException(
608 message
="db_dict is mandatory", bad_args
=["db_dict"]
616 ) = N2VCJujuConnector
._get
_ee
_id
_components
(ee_id
=ee_id
)
618 "model: {}, application: {}, machine: {}".format(
619 model_name
, application_name
, machine_id
623 raise N2VCBadArgumentsException(
624 message
="ee_id={} is not a valid execution environment id".format(
630 # try to execute ssh layer primitives (if exist):
636 application_name
= N2VCJujuConnector
._format
_app
_name
(application_name
)
638 # execute action: generate-ssh-key
640 output
, _status
= await libjuju
.execute_action(
641 model_name
=model_name
,
642 application_name
=application_name
,
643 action_name
="generate-ssh-key",
645 progress_timeout
=progress_timeout
,
646 total_timeout
=total_timeout
,
648 except Exception as e
:
650 "Skipping exception while executing action generate-ssh-key: {}".format(
655 # execute action: get-ssh-public-key
657 output
, _status
= await libjuju
.execute_action(
658 model_name
=model_name
,
659 application_name
=application_name
,
660 action_name
="get-ssh-public-key",
662 progress_timeout
=progress_timeout
,
663 total_timeout
=total_timeout
,
665 except Exception as e
:
666 msg
= "Cannot execute action get-ssh-public-key: {}\n".format(e
)
668 raise N2VCExecutionException(e
, primitive_name
="get-ssh-public-key")
670 # return public key if exists
671 return output
["pubkey"] if "pubkey" in output
else output
673 async def get_metrics(
674 self
, model_name
: str, application_name
: str, vca_id
: str = None
677 Get metrics from application
679 :param: model_name: Model name
680 :param: application_name: Application name
681 :param: vca_id: VCA ID
683 :return: Dictionary with obtained metrics
685 libjuju
= await self
._get
_libjuju
(vca_id
)
686 return await libjuju
.get_metrics(model_name
, application_name
)
688 async def add_relation(
697 Add relation between two charmed endpoints
699 :param: ee_id_1: The id of the first execution environment
700 :param: ee_id_2: The id of the second execution environment
701 :param: endpoint_1: The endpoint in the first execution environment
702 :param: endpoint_2: The endpoint in the second execution environment
703 :param: vca_id: VCA ID
706 "adding new relation between {} and {}, endpoints: {}, {}".format(
707 ee_id_1
, ee_id_2
, endpoint_1
, endpoint_2
710 libjuju
= await self
._get
_libjuju
(vca_id
)
714 message
= "EE 1 is mandatory"
715 self
.log
.error(message
)
716 raise N2VCBadArgumentsException(message
=message
, bad_args
=["ee_id_1"])
718 message
= "EE 2 is mandatory"
719 self
.log
.error(message
)
720 raise N2VCBadArgumentsException(message
=message
, bad_args
=["ee_id_2"])
722 message
= "endpoint 1 is mandatory"
723 self
.log
.error(message
)
724 raise N2VCBadArgumentsException(message
=message
, bad_args
=["endpoint_1"])
726 message
= "endpoint 2 is mandatory"
727 self
.log
.error(message
)
728 raise N2VCBadArgumentsException(message
=message
, bad_args
=["endpoint_2"])
730 # get the model, the applications and the machines from the ee_id's
731 model_1
, app_1
, _machine_1
= self
._get
_ee
_id
_components
(ee_id_1
)
732 model_2
, app_2
, _machine_2
= self
._get
_ee
_id
_components
(ee_id_2
)
734 # model must be the same
735 if model_1
!= model_2
:
736 message
= "EE models are not the same: {} vs {}".format(ee_id_1
, ee_id_2
)
737 self
.log
.error(message
)
738 raise N2VCBadArgumentsException(
739 message
=message
, bad_args
=["ee_id_1", "ee_id_2"]
742 # add juju relations between two applications
744 await libjuju
.add_relation(
746 endpoint_1
="{}:{}".format(app_1
, endpoint_1
),
747 endpoint_2
="{}:{}".format(app_2
, endpoint_2
),
749 except Exception as e
:
750 message
= "Error adding relation between {} and {}: {}".format(
753 self
.log
.error(message
)
754 raise N2VCException(message
=message
)
756 async def remove_relation(self
):
758 self
.log
.info("Method not implemented yet")
759 raise MethodNotImplemented()
761 async def deregister_execution_environments(self
):
762 self
.log
.info("Method not implemented yet")
763 raise MethodNotImplemented()
765 async def delete_namespace(
768 db_dict
: dict = None,
769 total_timeout
: float = None,
773 Remove a network scenario and its execution environments
774 :param: namespace: [<nsi-id>].<ns-id>
775 :param: db_dict: where to write into database when the status changes.
776 It contains a dict with
777 {collection: <str>, filter: {}, path: <str>},
778 e.g. {collection: "nsrs", filter:
779 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
780 :param: total_timeout: Total timeout
781 :param: vca_id: VCA ID
783 self
.log
.info("Deleting namespace={}".format(namespace
))
784 libjuju
= await self
._get
_libjuju
(vca_id
)
787 if namespace
is None:
788 raise N2VCBadArgumentsException(
789 message
="namespace is mandatory", bad_args
=["namespace"]
792 _nsi_id
, ns_id
, _vnf_id
, _vdu_id
, _vdu_count
= self
._get
_namespace
_components
(
795 if ns_id
is not None:
797 models
= await libjuju
.list_models(contains
=ns_id
)
799 await libjuju
.destroy_model(
800 model_name
=model
, total_timeout
=total_timeout
802 except Exception as e
:
804 message
="Error deleting namespace {} : {}".format(namespace
, e
)
807 raise N2VCBadArgumentsException(
808 message
="only ns_id is permitted to delete yet", bad_args
=["namespace"]
811 self
.log
.info("Namespace {} deleted".format(namespace
))
813 async def delete_execution_environment(
816 db_dict
: dict = None,
817 total_timeout
: float = None,
818 scaling_in
: bool = False,
822 Delete an execution environment
823 :param str ee_id: id of the execution environment to delete
824 :param dict db_dict: where to write into database when the status changes.
825 It contains a dict with
826 {collection: <str>, filter: {}, path: <str>},
827 e.g. {collection: "nsrs", filter:
828 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
829 :param: total_timeout: Total timeout
830 :param: scaling_in: Boolean to indicate if is it a scaling in operation
831 :param: vca_id: VCA ID
833 self
.log
.info("Deleting execution environment ee_id={}".format(ee_id
))
834 libjuju
= await self
._get
_libjuju
(vca_id
)
838 raise N2VCBadArgumentsException(
839 message
="ee_id is mandatory", bad_args
=["ee_id"]
842 model_name
, application_name
, _machine_id
= self
._get
_ee
_id
_components
(
848 # TODO: should this be removed?
849 await libjuju
.destroy_model(
850 model_name
=model_name
,
851 total_timeout
=total_timeout
,
854 # destroy the application
855 await libjuju
.destroy_application(
856 model_name
=model_name
,
857 application_name
=application_name
,
858 total_timeout
=total_timeout
,
860 except Exception as e
:
863 "Error deleting execution environment {} (application {}) : {}"
864 ).format(ee_id
, application_name
, e
)
867 self
.log
.info("Execution environment {} deleted".format(ee_id
))
869 async def exec_primitive(
874 db_dict
: dict = None,
875 progress_timeout
: float = None,
876 total_timeout
: float = None,
880 Execute a primitive in the execution environment
882 :param: ee_id: the one returned by create_execution_environment or
883 register_execution_environment
884 :param: primitive_name: must be one defined in the software. There is one
885 called 'config', where, for the proxy case, the 'credentials' of VM are
887 :param: params_dict: parameters of the action
888 :param: db_dict: where to write into database when the status changes.
889 It contains a dict with
890 {collection: <str>, filter: {}, path: <str>},
891 e.g. {collection: "nsrs", filter:
892 {_id: <nsd-id>, path: "_admin.deployed.VCA.3"}
893 :param: progress_timeout: Progress timeout
894 :param: total_timeout: Total timeout
895 :param: vca_id: VCA ID
896 :returns str: primitive result, if ok. It raises exceptions in case of fail
900 "Executing primitive: {} on ee: {}, params: {}".format(
901 primitive_name
, ee_id
, params_dict
904 libjuju
= await self
._get
_libjuju
(vca_id
)
907 if ee_id
is None or len(ee_id
) == 0:
908 raise N2VCBadArgumentsException(
909 message
="ee_id is mandatory", bad_args
=["ee_id"]
911 if primitive_name
is None or len(primitive_name
) == 0:
912 raise N2VCBadArgumentsException(
913 message
="action_name is mandatory", bad_args
=["action_name"]
915 if params_dict
is None:
923 ) = N2VCJujuConnector
._get
_ee
_id
_components
(ee_id
=ee_id
)
925 raise N2VCBadArgumentsException(
926 message
="ee_id={} is not a valid execution environment id".format(
932 if primitive_name
== "config":
933 # Special case: config primitive
935 await libjuju
.configure_application(
936 model_name
=model_name
,
937 application_name
=application_name
,
940 actions
= await libjuju
.get_actions(
941 application_name
=application_name
,
942 model_name
=model_name
,
945 "Application {} has these actions: {}".format(
946 application_name
, actions
949 if "verify-ssh-credentials" in actions
:
950 # execute verify-credentials
953 for _
in range(num_retries
):
955 self
.log
.debug("Executing action verify-ssh-credentials...")
956 output
, ok
= await libjuju
.execute_action(
957 model_name
=model_name
,
958 application_name
=application_name
,
959 action_name
="verify-ssh-credentials",
961 progress_timeout
=progress_timeout
,
962 total_timeout
=total_timeout
,
967 "Error executing verify-ssh-credentials: {}. Retrying..."
969 await asyncio
.sleep(retry_timeout
)
972 self
.log
.debug("Result: {}, output: {}".format(ok
, output
))
974 except asyncio
.CancelledError
:
978 "Error executing verify-ssh-credentials after {} retries. ".format(
983 msg
= "Action verify-ssh-credentials does not exist in application {}".format(
986 self
.log
.debug(msg
=msg
)
987 except Exception as e
:
988 self
.log
.error("Error configuring juju application: {}".format(e
))
989 raise N2VCExecutionException(
990 message
="Error configuring application into ee={} : {}".format(
993 primitive_name
=primitive_name
,
998 output
, status
= await libjuju
.execute_action(
999 model_name
=model_name
,
1000 application_name
=application_name
,
1001 action_name
=primitive_name
,
1003 progress_timeout
=progress_timeout
,
1004 total_timeout
=total_timeout
,
1007 if status
== "completed":
1010 raise Exception("status is not completed: {}".format(status
))
1011 except Exception as e
:
1013 "Error executing primitive {}: {}".format(primitive_name
, e
)
1015 raise N2VCExecutionException(
1016 message
="Error executing primitive {} into ee={} : {}".format(
1017 primitive_name
, ee_id
, e
1019 primitive_name
=primitive_name
,
1022 async def disconnect(self
, vca_id
: str = None):
1026 :param: vca_id: VCA ID
1028 self
.log
.info("closing juju N2VC...")
1029 libjuju
= await self
._get
_libjuju
(vca_id
)
1031 await libjuju
.disconnect()
1032 except Exception as e
:
1033 raise N2VCConnectionException(
1034 message
="Error disconnecting controller: {}".format(e
),
1035 url
=libjuju
.vca_connection
.data
.endpoints
,
1039 ####################################################################################
1040 ################################### P R I V A T E ##################################
1041 ####################################################################################
1044 async def _get_libjuju(self
, vca_id
: str = None) -> Libjuju
:
1048 :param: vca_id: VCA ID
1049 If None, get a libjuju object with a Connection to the default VCA
1050 Else, geta libjuju object with a Connection to the specified VCA
1053 while self
.loading_libjuju
.locked():
1054 await asyncio
.sleep(0.1)
1055 if not self
.libjuju
:
1056 async with self
.loading_libjuju
:
1057 vca_connection
= await get_connection(self
._store
)
1058 self
.libjuju
= Libjuju(vca_connection
, loop
=self
.loop
, log
=self
.log
)
1061 vca_connection
= await get_connection(self
._store
, vca_id
)
1069 def _write_ee_id_db(self
, db_dict
: dict, ee_id
: str):
1071 # write ee_id to database: _admin.deployed.VCA.x
1073 the_table
= db_dict
["collection"]
1074 the_filter
= db_dict
["filter"]
1075 the_path
= db_dict
["path"]
1076 if not the_path
[-1] == ".":
1077 the_path
= the_path
+ "."
1078 update_dict
= {the_path
+ "ee_id": ee_id
}
1079 # self.log.debug('Writing ee_id to database: {}'.format(the_path))
1082 q_filter
=the_filter
,
1083 update_dict
=update_dict
,
1086 except asyncio
.CancelledError
:
1088 except Exception as e
:
1089 self
.log
.error("Error writing ee_id to database: {}".format(e
))
1092 def _build_ee_id(model_name
: str, application_name
: str, machine_id
: str):
1094 Build an execution environment id form model, application and machine
1096 :param application_name:
1100 # id for the execution environment
1101 return "{}.{}.{}".format(model_name
, application_name
, machine_id
)
1104 def _get_ee_id_components(ee_id
: str) -> (str, str, str):
1106 Get model, application and machine components from an execution environment id
1108 :return: model_name, application_name, machine_id
1112 return None, None, None
1114 # split components of id
1115 parts
= ee_id
.split(".")
1116 model_name
= parts
[0]
1117 application_name
= parts
[1]
1118 machine_id
= parts
[2]
1119 return model_name
, application_name
, machine_id
1121 def _get_application_name(self
, namespace
: str) -> str:
1123 Build application name from namespace
1125 :return: app-vnf-<vnf id>-vdu-<vdu-id>-cnt-<vdu-count>
1128 # TODO: Enforce the Juju 50-character application limit
1130 # split namespace components
1131 _
, _
, vnf_id
, vdu_id
, vdu_count
= self
._get
_namespace
_components
(
1135 if vnf_id
is None or len(vnf_id
) == 0:
1138 # Shorten the vnf_id to its last twelve characters
1139 vnf_id
= "vnf-" + vnf_id
[-12:]
1141 if vdu_id
is None or len(vdu_id
) == 0:
1144 # Shorten the vdu_id to its last twelve characters
1145 vdu_id
= "-vdu-" + vdu_id
[-12:]
1147 if vdu_count
is None or len(vdu_count
) == 0:
1150 vdu_count
= "-cnt-" + vdu_count
1152 application_name
= "app-{}{}{}".format(vnf_id
, vdu_id
, vdu_count
)
1154 return N2VCJujuConnector
._format
_app
_name
(application_name
)
1157 def _format_model_name(name
: str) -> str:
1158 """Format the name of the model.
1160 Model names may only contain lowercase letters, digits and hyphens
1163 return name
.replace("_", "-").replace(" ", "-").lower()
1166 def _format_app_name(name
: str) -> str:
1167 """Format the name of the application (in order to assure valid application name).
1169 Application names have restrictions (run juju deploy --help):
1170 - contains lowercase letters 'a'-'z'
1171 - contains numbers '0'-'9'
1172 - contains hyphens '-'
1173 - starts with a lowercase letter
1174 - not two or more consecutive hyphens
1175 - after a hyphen, not a group with all numbers
1178 def all_numbers(s
: str) -> bool:
1184 new_name
= name
.replace("_", "-")
1185 new_name
= new_name
.replace(" ", "-")
1186 new_name
= new_name
.lower()
1187 while new_name
.find("--") >= 0:
1188 new_name
= new_name
.replace("--", "-")
1189 groups
= new_name
.split("-")
1191 # find 'all numbers' groups and prefix them with a letter
1193 for i
in range(len(groups
)):
1195 if all_numbers(group
):
1201 if app_name
[0].isdigit():
1202 app_name
= "z" + app_name
1206 async def validate_vca(self
, vca_id
: str):
1208 Validate a VCA by connecting/disconnecting to/from it
1210 :param: vca_id: VCA ID
1212 vca_connection
= await get_connection(self
._store
, vca_id
=vca_id
)
1213 libjuju
= Libjuju(vca_connection
, loop
=self
.loop
, log
=self
.log
, n2vc
=self
)
1214 controller
= await libjuju
.get_controller()
1215 await libjuju
.disconnect_controller(controller
)