X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;f=n2vc%2Fn2vc_juju_conn.py;h=55220d64ab389f178fd4ac13dea63a5809f88b49;hb=HEAD;hp=f0569b18b0fd0b81f86a9aa8ee7929382a6e6ec9;hpb=4c856b3bae4f30d9e1bdd429884c1ae84bc629f0;p=osm%2FN2VC.git diff --git a/n2vc/n2vc_juju_conn.py b/n2vc/n2vc_juju_conn.py index f0569b1..f28a9bd 100644 --- a/n2vc/n2vc_juju_conn.py +++ b/n2vc/n2vc_juju_conn.py @@ -37,7 +37,7 @@ from n2vc.exceptions import ( ) from n2vc.n2vc_conn import N2VCConnector from n2vc.n2vc_conn import obj_to_dict, obj_to_yaml -from n2vc.libjuju import Libjuju +from n2vc.libjuju import Libjuju, retry_callback from n2vc.store import MotorStore from n2vc.utils import get_ee_id_components, generate_random_alfanum_string from n2vc.vca.connection import get_connection @@ -61,7 +61,6 @@ class N2VCJujuConnector(N2VCConnector): db: object, fs: object, log: object = None, - loop: object = None, on_update_db=None, ): """ @@ -70,19 +69,11 @@ class N2VCJujuConnector(N2VCConnector): :param: db: Database object from osm_common :param: fs: Filesystem object from osm_common :param: log: Logger - :param: loop: Asyncio loop :param: on_update_db: Callback function to be called for updating the database. """ # parent class constructor - N2VCConnector.__init__( - self, - db=db, - fs=fs, - log=log, - loop=loop, - on_update_db=on_update_db, - ) + N2VCConnector.__init__(self, db=db, fs=fs, log=log, on_update_db=on_update_db) # silence websocket traffic log logging.getLogger("websockets.protocol").setLevel(logging.INFO) @@ -93,7 +84,7 @@ class N2VCJujuConnector(N2VCConnector): db_uri = EnvironConfig(prefixes=["OSMLCM_", "OSMMON_"]).get("database_uri") self._store = MotorStore(db_uri) - self.loading_libjuju = asyncio.Lock(loop=self.loop) + self.loading_libjuju = asyncio.Lock() self.delete_namespace_locks = {} self.log.info("N2VC juju connector initialized") @@ -227,10 +218,7 @@ class N2VCJujuConnector(N2VCConnector): # create or reuse a new juju machine try: if not await libjuju.model_exists(model_name): - await libjuju.add_model( - model_name, - libjuju.vca_connection.lxd_cloud, - ) + await libjuju.add_model(model_name, libjuju.vca_connection.lxd_cloud) machine, new = await libjuju.create_machine( model_name=model_name, machine_id=machine_id, @@ -256,9 +244,7 @@ class N2VCJujuConnector(N2VCConnector): raise N2VCException(message=message) # new machine credentials - credentials = { - "hostname": machine.dns_name, - } + credentials = {"hostname": machine.dns_name} self.log.info( "Execution environment created. ee_id: {}, credentials: {}".format( @@ -338,10 +324,7 @@ class N2VCJujuConnector(N2VCConnector): # register machine on juju try: if not await libjuju.model_exists(model_name): - await libjuju.add_model( - model_name, - libjuju.vca_connection.lxd_cloud, - ) + await libjuju.add_model(model_name, libjuju.vca_connection.lxd_cloud) machine_id = await libjuju.provision_machine( model_name=model_name, hostname=hostname, @@ -372,7 +355,13 @@ class N2VCJujuConnector(N2VCConnector): # In case of native_charm is being deployed, if JujuApplicationExists error happens # it will try to add_unit - @retry(attempts=3, delay=5, retry_exceptions=(N2VCApplicationExists,), timeout=None) + @retry( + attempts=3, + delay=5, + retry_exceptions=(N2VCApplicationExists,), + timeout=None, + callback=retry_callback, + ) async def install_configuration_sw( self, ee_id: str, @@ -565,10 +554,7 @@ class N2VCJujuConnector(N2VCConnector): _, ns_id, _, _, _ = self._get_namespace_components(namespace=namespace) model_name = "{}-k8s".format(ns_id) if not await libjuju.model_exists(model_name): - await libjuju.add_model( - model_name, - libjuju.vca_connection.k8s_cloud, - ) + await libjuju.add_model(model_name, libjuju.vca_connection.k8s_cloud) application_name = self._get_application_name(namespace) try: @@ -587,9 +573,7 @@ class N2VCJujuConnector(N2VCConnector): self.log.info("K8s proxy charm installed") ee_id = N2VCJujuConnector._build_ee_id( - model_name=model_name, - application_name=application_name, - machine_id="k8s", + model_name=model_name, application_name=application_name, machine_id="k8s" ) self._write_ee_id_db(db_dict=db_dict, ee_id=ee_id) @@ -718,9 +702,7 @@ class N2VCJujuConnector(N2VCConnector): return await libjuju.get_metrics(model_name, application_name) async def add_relation( - self, - provider: RelationEndpoint, - requirer: RelationEndpoint, + self, provider: RelationEndpoint, requirer: RelationEndpoint ): """ Add relation between two charmed endpoints @@ -731,7 +713,7 @@ class N2VCJujuConnector(N2VCConnector): self.log.debug(f"adding new relation between {provider} and {requirer}") cross_model_relation = ( provider.model_name != requirer.model_name - or requirer.vca_id != requirer.vca_id + or provider.vca_id != requirer.vca_id ) try: if cross_model_relation: @@ -744,9 +726,7 @@ class N2VCJujuConnector(N2VCConnector): requirer.model_name, offer, provider_libjuju ) await requirer_libjuju.add_relation( - requirer.model_name, - requirer.endpoint, - saas_name, + requirer.model_name, requirer.endpoint, saas_name ) else: # Standard relation @@ -794,7 +774,7 @@ class N2VCJujuConnector(N2VCConnector): self.log.info("Deleting namespace={}".format(namespace)) will_not_delete = False if namespace not in self.delete_namespace_locks: - self.delete_namespace_locks[namespace] = asyncio.Lock(loop=self.loop) + self.delete_namespace_locks[namespace] = asyncio.Lock() delete_lock = self.delete_namespace_locks[namespace] while delete_lock.locked(): @@ -856,6 +836,7 @@ class N2VCJujuConnector(N2VCConnector): scaling_in: bool = False, vca_type: str = None, vca_id: str = None, + application_to_delete: str = None, ): """ Delete an execution environment @@ -865,10 +846,11 @@ class N2VCJujuConnector(N2VCConnector): {collection: , filter: {}, path: }, e.g. {collection: "nsrs", filter: {_id: , path: "_admin.deployed.VCA.3"} - :param: total_timeout: Total timeout - :param: scaling_in: Boolean to indicate if it is a scaling in operation - :param: vca_type: VCA type - :param: vca_id: VCA ID + :param total_timeout: Total timeout + :param scaling_in: Boolean to indicate if it is a scaling in operation + :param vca_type: VCA type + :param vca_id: VCA ID + :param application_to_delete: name of the single application to be deleted """ self.log.info("Deleting execution environment ee_id={}".format(ee_id)) libjuju = await self._get_libjuju(vca_id) @@ -883,12 +865,30 @@ class N2VCJujuConnector(N2VCConnector): ee_id=ee_id ) try: - if not scaling_in: - # destroy the model - await libjuju.destroy_model( + if application_to_delete == application_name: + # destroy the application + await libjuju.destroy_application( model_name=model_name, + application_name=application_name, total_timeout=total_timeout, ) + # if model is empty delete it + controller = await libjuju.get_controller() + model = await libjuju.get_model( + controller=controller, + model_name=model_name, + ) + if not model.applications: + self.log.info("Model {} is empty, deleting it".format(model_name)) + await libjuju.destroy_model( + model_name=model_name, + total_timeout=total_timeout, + ) + elif not scaling_in: + # destroy the model + await libjuju.destroy_model( + model_name=model_name, total_timeout=total_timeout + ) elif vca_type == "native_charm" and scaling_in: # destroy the unit in the application await libjuju.destroy_unit( @@ -991,8 +991,7 @@ class N2VCJujuConnector(N2VCConnector): config=params_dict, ) actions = await libjuju.get_actions( - application_name=application_name, - model_name=model_name, + application_name=application_name, model_name=model_name ) self.log.debug( "Application {} has these actions: {}".format( @@ -1061,15 +1060,17 @@ class N2VCJujuConnector(N2VCConnector): if status == "completed": return output else: - raise Exception("status is not completed: {}".format(status)) + if "output" in output: + raise Exception(f'{status}: {output["output"]}') + else: + raise Exception( + f"{status}: No further information received from action" + ) + except Exception as e: - self.log.error( - "Error executing primitive {}: {}".format(primitive_name, e) - ) + self.log.error(f"Error executing primitive {primitive_name}: {e}") raise N2VCExecutionException( - message="Error executing primitive {} into ee={} : {}".format( - primitive_name, ee_id, e - ), + message=f"Error executing primitive {primitive_name} in ee={ee_id}: {e}", primitive_name=primitive_name, ) @@ -1118,7 +1119,6 @@ class N2VCJujuConnector(N2VCConnector): ) try: - await libjuju.upgrade_charm( application_name=application_name, path=path, @@ -1171,19 +1171,13 @@ class N2VCJujuConnector(N2VCConnector): if not self.libjuju: async with self.loading_libjuju: vca_connection = await get_connection(self._store) - self.libjuju = Libjuju(vca_connection, loop=self.loop, log=self.log) + self.libjuju = Libjuju(vca_connection, log=self.log) return self.libjuju else: vca_connection = await get_connection(self._store, vca_id) - return Libjuju( - vca_connection, - loop=self.loop, - log=self.log, - n2vc=self, - ) + return Libjuju(vca_connection, log=self.log, n2vc=self) def _write_ee_id_db(self, db_dict: dict, ee_id: str): - # write ee_id to database: _admin.deployed.VCA.x try: the_table = db_dict["collection"] @@ -1286,12 +1280,30 @@ class N2VCJujuConnector(N2VCConnector): ) return application_name + @staticmethod + def _get_vca_record(search_key: str, vca_records: list, vdu_id: str) -> dict: + """Get the correct VCA record dict depending on the search key + + Args: + search_key (str): keyword to find the correct VCA record + vca_records (list): All VCA records as list + vdu_id (str): VDU ID + + Returns: + vca_record (dict): Dictionary which includes the correct VCA record + + """ + return next( + filter(lambda record: record[search_key] == vdu_id, vca_records), {} + ) + @staticmethod def _generate_application_name( charm_level: str, vnfrs: dict, vca_records: list, vnf_count: str = None, + vdu_id: str = None, vdu_count: str = None, ) -> str: """Generate application name to make the relevant charm of VDU/KDU @@ -1299,10 +1311,11 @@ class N2VCJujuConnector(N2VCConnector): Limiting the app name to 50 characters. Args: - charm_level (str): VNF ID - vnfrs (dict): VDU ID + charm_level (str): level of charm + vnfrs (dict): vnf record dict vca_records (list): db_nsr["_admin"]["deployed"]["VCA"] as list vnf_count (str): vnf count index + vdu_id (str): VDU ID vdu_count (str): vdu count index Returns: @@ -1342,26 +1355,33 @@ class N2VCJujuConnector(N2VCConnector): # Charms are also used for deployments with Helm charts. # If deployment unit is a Helm chart/KDU, # vdu_profile_id and vdu_count will be empty string. - vdu_profile_id = "" - if vdu_count is None: vdu_count = "" - elif vdu_count: - vdu_profile_id = vnfrs["vdur"][int(vdu_count)]["vdu-id-ref"] - # If vnf/vdu is scaled, more than one VCA record may be included in vca_records # but ee_descriptor_id is same. # Shorten the ee_descriptor_id, member-vnf-index-ref and vdu_profile_id # to first 12 characters. + if not vdu_id: + raise N2VCException(message="vdu-id should be provided.") + + vca_record = N2VCJujuConnector._get_vca_record( + "vdu_id", vca_records, vdu_id + ) + + if not vca_record: + vca_record = N2VCJujuConnector._get_vca_record( + "kdu_name", vca_records, vdu_id + ) + application_name = ( - vca_records[0]["ee_descriptor_id"][:12] + vca_record["ee_descriptor_id"][:12] + "-" + vnf_count + "-" + vnfrs["member-vnf-index-ref"][:12] + "-" - + vdu_profile_id[:12] + + vdu_id[:12] + "-" + vdu_count + "-vdu" @@ -1472,6 +1492,7 @@ class N2VCJujuConnector(N2VCConnector): db_vnfr, vca_records, vnf_count=vnf_count, + vdu_id=vdu_id, vdu_count=vdu_count, ) else: @@ -1538,6 +1559,6 @@ class N2VCJujuConnector(N2VCConnector): :param: vca_id: VCA ID """ vca_connection = await get_connection(self._store, vca_id=vca_id) - libjuju = Libjuju(vca_connection, loop=self.loop, log=self.log, n2vc=self) + libjuju = Libjuju(vca_connection, log=self.log, n2vc=self) controller = await libjuju.get_controller() await libjuju.disconnect_controller(controller)