+ async def upgrade_charm(
+ self,
+ application_name: str,
+ path: str,
+ model_name: str,
+ total_timeout: float = None,
+ **kwargs,
+ ):
+ """Upgrade Charm
+
+ :param: application_name: Application name
+ :param: model_name: Model name
+ :param: path: Local path to the charm
+ :param: total_timeout: Timeout for the entity to be active
+
+ :return: (str, str): (output and status)
+ """
+
+ self.log.debug(
+ "Upgrading charm {} in model {} from path {}".format(
+ application_name, model_name, path
+ )
+ )
+
+ await self.resolve_application(
+ model_name=model_name, application_name=application_name
+ )
+
+ # Get controller
+ controller = await self.get_controller()
+
+ # Get model
+ model = await self.get_model(controller, model_name)
+
+ try:
+ # Get application
+ application = self._get_application(
+ model,
+ application_name=application_name,
+ )
+ if application is None:
+ raise JujuApplicationNotFound(
+ "Cannot find application {} to upgrade".format(application_name)
+ )
+
+ await application.refresh(path=path)
+
+ self.log.debug(
+ "Wait until charm upgrade is completed for application {} (model={})".format(
+ application_name, model_name
+ )
+ )
+
+ await JujuModelWatcher.ensure_units_idle(
+ model=model, application=application
+ )
+
+ if application.status == "error":
+ error_message = "Unknown"
+ for unit in application.units:
+ if (
+ unit.workload_status == "error"
+ and unit.workload_status_message != ""
+ ):
+ error_message = unit.workload_status_message
+
+ message = "Application {} failed update in {}: {}".format(
+ application_name, model_name, error_message
+ )
+ self.log.error(message)
+ raise JujuError(message=message)
+
+ self.log.debug(
+ "Application {} is ready in model {}".format(
+ application_name, model_name
+ )
+ )
+
+ finally:
+ await self.disconnect_model(model)
+ await self.disconnect_controller(controller)
+
+ return application
+
+ async def resolve_application(self, model_name: str, application_name: str):
+
+ controller = await self.get_controller()
+ model = await self.get_model(controller, model_name)
+
+ try:
+ application = self._get_application(
+ model,
+ application_name=application_name,
+ )
+ if application is None:
+ raise JujuApplicationNotFound(
+ "Cannot find application {} to resolve".format(application_name)
+ )
+
+ while 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)
+ except Exception:
+ pass
+
+ await asyncio.sleep(1)
+
+ finally:
+ await self.disconnect_model(model)
+ await self.disconnect_controller(controller)
+
+ 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)
+