"""
controller = None
try:
- controller = Controller(loop=self.loop)
+ controller = Controller()
await asyncio.wait_for(
controller.connect(
endpoint=self.vca_connection.data.endpoints,
)
if controller:
await self.disconnect_controller(controller)
- raise JujuControllerFailedConnecting(e)
+
+ raise JujuControllerFailedConnecting(
+ f"Error connecting to Juju controller: {e}"
+ )
async def disconnect(self):
"""Disconnect"""
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))
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)
try:
if application_name not in model.applications:
-
if machine_id is not None:
machine, series = self._get_machine_info(model, machine_id)
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,
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
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
):