X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FN2VC.git;a=blobdiff_plain;f=n2vc%2Fn2vc_juju_conn.py;h=fb368096413d306c0a13ea9cd0d2f17242994695;hp=af40aee1ea038b295b7ea118845287972503081a;hb=cd986064a281ab1658023fa7172a57612f270684;hpb=eacf5a7724815fb33802ceb0af246dfc959eb021 diff --git a/n2vc/n2vc_juju_conn.py b/n2vc/n2vc_juju_conn.py index af40aee..fb36809 100644 --- a/n2vc/n2vc_juju_conn.py +++ b/n2vc/n2vc_juju_conn.py @@ -24,6 +24,7 @@ import asyncio import logging from n2vc.config import EnvironConfig +from n2vc.definitions import RelationEndpoint from n2vc.exceptions import ( N2VCBadArgumentsException, N2VCException, @@ -38,6 +39,7 @@ from n2vc.n2vc_conn import N2VCConnector from n2vc.n2vc_conn import obj_to_dict, obj_to_yaml from n2vc.libjuju import Libjuju from n2vc.store import MotorStore +from n2vc.utils import get_ee_id_components, generate_random_alfanum_string from n2vc.vca.connection import get_connection from retrying_async import retry @@ -91,7 +93,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.delete_namespace_locks = {} self.log.info("N2VC juju connector initialized") async def get_status( @@ -453,7 +455,7 @@ class N2VCJujuConnector(N2VCConnector): artifact_path = artifact_path.replace("//", "/") # check charm path - if not self.fs.file_exists(artifact_path, mode="dir"): + if not self.fs.file_exists(artifact_path): msg = "artifact path does not exist: {}".format(artifact_path) raise N2VCBadArgumentsException(message=msg, bad_args=["artifact_path"]) @@ -550,7 +552,7 @@ class N2VCJujuConnector(N2VCConnector): artifact_path = artifact_path.replace("//", "/") # check charm path - if not self.fs.file_exists(artifact_path, mode="dir"): + if not self.fs.file_exists(artifact_path): msg = "artifact path does not exist: {}".format(artifact_path) raise N2VCBadArgumentsException(message=msg, bad_args=["artifact_path"]) @@ -716,69 +718,48 @@ class N2VCJujuConnector(N2VCConnector): async def add_relation( self, - ee_id_1: str, - ee_id_2: str, - endpoint_1: str, - endpoint_2: str, - vca_id: str = None, + provider: RelationEndpoint, + requirer: RelationEndpoint, ): """ Add relation between two charmed endpoints - :param: ee_id_1: The id of the first execution environment - :param: ee_id_2: The id of the second execution environment - :param: endpoint_1: The endpoint in the first execution environment - :param: endpoint_2: The endpoint in the second execution environment - :param: vca_id: VCA ID + :param: provider: Provider relation endpoint + :param: requirer: Requirer relation endpoint """ - self.log.debug( - "adding new relation between {} and {}, endpoints: {}, {}".format( - ee_id_1, ee_id_2, endpoint_1, endpoint_2 - ) + 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 ) - libjuju = await self._get_libjuju(vca_id) - - # check arguments - if not ee_id_1: - message = "EE 1 is mandatory" - self.log.error(message) - raise N2VCBadArgumentsException(message=message, bad_args=["ee_id_1"]) - if not ee_id_2: - message = "EE 2 is mandatory" - self.log.error(message) - raise N2VCBadArgumentsException(message=message, bad_args=["ee_id_2"]) - if not endpoint_1: - message = "endpoint 1 is mandatory" - self.log.error(message) - raise N2VCBadArgumentsException(message=message, bad_args=["endpoint_1"]) - if not endpoint_2: - message = "endpoint 2 is mandatory" - self.log.error(message) - raise N2VCBadArgumentsException(message=message, bad_args=["endpoint_2"]) - - # get the model, the applications and the machines from the ee_id's - model_1, app_1, _machine_1 = self._get_ee_id_components(ee_id_1) - model_2, app_2, _machine_2 = self._get_ee_id_components(ee_id_2) - - # model must be the same - if model_1 != model_2: - message = "EE models are not the same: {} vs {}".format(ee_id_1, ee_id_2) - self.log.error(message) - raise N2VCBadArgumentsException( - message=message, bad_args=["ee_id_1", "ee_id_2"] - ) - - # add juju relations between two applications try: - await libjuju.add_relation( - model_name=model_1, - endpoint_1="{}:{}".format(app_1, endpoint_1), - endpoint_2="{}:{}".format(app_2, endpoint_2), - ) + if cross_model_relation: + # Cross-model relation + provider_libjuju = await self._get_libjuju(provider.vca_id) + requirer_libjuju = await self._get_libjuju(requirer.vca_id) + offer = await provider_libjuju.offer(provider) + if offer: + saas_name = await requirer_libjuju.consume( + requirer.model_name, offer, provider_libjuju + ) + await requirer_libjuju.add_relation( + requirer.model_name, + requirer.endpoint, + saas_name, + ) + else: + # Standard relation + vca_id = provider.vca_id + model = provider.model_name + libjuju = await self._get_libjuju(vca_id) + # add juju relations between two applications + await libjuju.add_relation( + model_name=model, + endpoint_1=provider.endpoint, + endpoint_2=requirer.endpoint, + ) except Exception as e: - message = "Error adding relation between {} and {}: {}".format( - ee_id_1, ee_id_2, e - ) + message = f"Error adding relation between {provider} and {requirer}: {e}" self.log.error(message) raise N2VCException(message=message) @@ -810,33 +791,56 @@ class N2VCJujuConnector(N2VCConnector): :param: vca_id: VCA ID """ self.log.info("Deleting namespace={}".format(namespace)) - libjuju = await self._get_libjuju(vca_id) + will_not_delete = False + if namespace not in self.delete_namespace_locks: + self.delete_namespace_locks[namespace] = asyncio.Lock(loop=self.loop) + delete_lock = self.delete_namespace_locks[namespace] - # check arguments - if namespace is None: - raise N2VCBadArgumentsException( - message="namespace is mandatory", bad_args=["namespace"] - ) + while delete_lock.locked(): + will_not_delete = True + await asyncio.sleep(0.1) - _nsi_id, ns_id, _vnf_id, _vdu_id, _vdu_count = self._get_namespace_components( - namespace=namespace - ) - if ns_id is not None: - try: - models = await libjuju.list_models(contains=ns_id) - for model in models: - await libjuju.destroy_model( - model_name=model, total_timeout=total_timeout + if will_not_delete: + self.log.info("Namespace {} deleted by another worker.".format(namespace)) + return + + try: + async with delete_lock: + libjuju = await self._get_libjuju(vca_id) + + # check arguments + if namespace is None: + raise N2VCBadArgumentsException( + message="namespace is mandatory", bad_args=["namespace"] ) - except Exception as e: - raise N2VCException( - message="Error deleting namespace {} : {}".format(namespace, e) - ) - else: - raise N2VCBadArgumentsException( - message="only ns_id is permitted to delete yet", bad_args=["namespace"] - ) + ( + _nsi_id, + ns_id, + _vnf_id, + _vdu_id, + _vdu_count, + ) = self._get_namespace_components(namespace=namespace) + if ns_id is not None: + try: + models = await libjuju.list_models(contains=ns_id) + for model in models: + await libjuju.destroy_model( + model_name=model, total_timeout=total_timeout + ) + except Exception as e: + raise N2VCException( + message="Error deleting namespace {} : {}".format( + namespace, e + ) + ) + else: + raise N2VCBadArgumentsException( + message="only ns_id is permitted to delete yet", + bad_args=["namespace"], + ) + finally: + self.delete_namespace_locks.pop(namespace) self.log.info("Namespace {} deleted".format(namespace)) async def delete_execution_environment( @@ -1047,7 +1051,7 @@ class N2VCJujuConnector(N2VCConnector): machine_id=machine_id, progress_timeout=progress_timeout, total_timeout=total_timeout, - **params_dict + **params_dict, ) if status == "completed": return output @@ -1153,21 +1157,13 @@ class N2VCJujuConnector(N2VCConnector): :return: model_name, application_name, machine_id """ - if ee_id is None: - return None, None, None - - # split components of id - parts = ee_id.split(".") - model_name = parts[0] - application_name = parts[1] - machine_id = parts[2] - return model_name, application_name, machine_id + return get_ee_id_components(ee_id) def _get_application_name(self, namespace: str) -> str: """ Build application name from namespace :param namespace: - :return: app-vnf--vdu--cnt- + :return: app-vnf--vdu--cnt-- """ # TODO: Enforce the Juju 50-character application limit @@ -1194,7 +1190,12 @@ class N2VCJujuConnector(N2VCConnector): else: vdu_count = "-cnt-" + vdu_count - application_name = "app-{}{}{}".format(vnf_id, vdu_id, vdu_count) + # Generate a random suffix with 5 characters (the default size used by K8s) + random_suffix = generate_random_alfanum_string(size=5) + + application_name = "app-{}{}{}-{}".format( + vnf_id, vdu_id, vdu_count, random_suffix + ) return N2VCJujuConnector._format_app_name(application_name)