X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;f=n2vc%2Flibjuju.py;h=9f73425257d37c8f8e2bc29e30d2131a3d05c823;hb=60a3a96717d7c36ba7a65573da59a6bc039f5e28;hp=6aa31cc9f0d96fde376a458dd2746930592b94a7;hpb=bcd65c7ff17d30c3370bed9b5f28e40dd38b26f8;p=osm%2FN2VC.git diff --git a/n2vc/libjuju.py b/n2vc/libjuju.py index 6aa31cc..9f73425 100644 --- a/n2vc/libjuju.py +++ b/n2vc/libjuju.py @@ -94,7 +94,7 @@ class Libjuju: """ controller = None try: - controller = Controller(loop=self.loop) + controller = Controller() await asyncio.wait_for( controller.connect( endpoint=self.vca_connection.data.endpoints, @@ -121,7 +121,10 @@ class Libjuju: ) if controller: await self.disconnect_controller(controller) - raise JujuControllerFailedConnecting(e) + + raise JujuControllerFailedConnecting( + f"Error connecting to Juju controller: {e}" + ) async def disconnect(self): """Disconnect""" @@ -558,7 +561,7 @@ class Libjuju: controller = await self.get_controller() model = await self.get_model(controller, model_name) try: - await model.deploy(uri) + await model.deploy(uri, trust=True) if wait: await JujuModelWatcher.wait_for_model(model, timeout=timeout) self.log.debug("All units active in model {}".format(model_name)) @@ -594,7 +597,6 @@ class Libjuju: application = self._get_application(model, application_name) if application is not None: - # Checks if the given machine id in the model, # otherwise function raises an error _machine, _series = self._get_machine_info(model, machine_id) @@ -749,7 +751,6 @@ class Libjuju: try: if application_name not in model.applications: - if machine_id is not None: machine, series = self._get_machine_info(model, machine_id) @@ -804,6 +805,34 @@ class Libjuju: return application + async def resolve(self, model_name: str): + controller = await self.get_controller() + model = await self.get_model(controller, model_name) + all_units_active = False + try: + while not all_units_active: + all_units_active = True + for application_name, application in model.applications.items(): + if application.status == "error": + for unit in application.units: + if unit.workload_status == "error": + self.log.debug( + "Model {}, Application {}, Unit {} in error state, resolving".format( + model_name, application_name, unit.entity_id + ) + ) + try: + await unit.resolved(retry=False) + all_units_active = False + except Exception: + pass + + if not all_units_active: + await asyncio.sleep(5) + finally: + await self.disconnect_model(model) + await self.disconnect_controller(controller) + async def scale_application( self, model_name: str, @@ -1152,7 +1181,7 @@ class Libjuju: await self.disconnect_model(model) await self.disconnect_controller(controller) - async def destroy_model(self, model_name: str, total_timeout: float): + async def destroy_model(self, model_name: str, total_timeout: float = 1800): """ Destroy model @@ -1166,43 +1195,75 @@ class Libjuju: if not await self.model_exists(model_name, controller=controller): return - model = await self.get_model(controller, model_name) self.log.debug("Destroying model {}".format(model_name)) - uuid = model.info.uuid + model = await self.get_model(controller, model_name) # Destroy machines that are manually provisioned # and still are in pending state await self._destroy_pending_machines(model, only_manual=True) - - # Disconnect model await self.disconnect_model(model) - await controller.destroy_model(uuid, force=True, max_wait=0) - - # Wait until model is destroyed - self.log.debug("Waiting for model {} to be destroyed...".format(model_name)) - - if total_timeout is None: - total_timeout = 3600 - end = time.time() + total_timeout - while time.time() < end: - models = await controller.list_models() - if model_name not in models: - self.log.debug( - "The model {} ({}) was destroyed".format(model_name, uuid) - ) - return - await asyncio.sleep(5) - raise Exception( - "Timeout waiting for model {} to be destroyed".format(model_name) + await asyncio.wait_for( + self._destroy_model(model_name, controller), + timeout=total_timeout, ) except Exception as e: - if model: - await self.disconnect_model(model) + if not await self.model_exists(model_name, controller=controller): + return raise e finally: + if model: + await self.disconnect_model(model) await self.disconnect_controller(controller) + async def _destroy_model( + self, + model_name: str, + controller: Controller, + ): + """ + Destroy model from controller + + :param: model: Model name to be removed + :param: controller: Controller object + :param: timeout: Timeout in seconds + """ + + async def _destroy_model_gracefully(model_name: str, controller: Controller): + self.log.info(f"Gracefully deleting model {model_name}") + resolved = False + while model_name in await controller.list_models(): + if not resolved: + await self.resolve(model_name) + resolved = True + await controller.destroy_model(model_name, destroy_storage=True) + + await asyncio.sleep(5) + self.log.info(f"Model {model_name} deleted gracefully") + + async def _destroy_model_forcefully(model_name: str, controller: Controller): + self.log.info(f"Forcefully deleting model {model_name}") + while model_name in await controller.list_models(): + await controller.destroy_model( + model_name, destroy_storage=True, force=True, max_wait=60 + ) + await asyncio.sleep(5) + self.log.info(f"Model {model_name} deleted forcefully") + + try: + try: + await asyncio.wait_for( + _destroy_model_gracefully(model_name, controller), timeout=120 + ) + except asyncio.TimeoutError: + await _destroy_model_forcefully(model_name, controller) + except juju.errors.JujuError as e: + if any("has been removed" in error for error in e.errors): + return + if any("model not found" in error for error in e.errors): + return + raise e + async def destroy_application( self, model_name: str, application_name: str, total_timeout: float ):