import time
-from juju.errors import JujuAPIError
+import juju.errors
from juju.model import Model
from juju.machine import Machine
from juju.application import Application
cloud_name=cloud.name,
credential_name=cloud.credential_name,
)
- except JujuAPIError as e:
+ except juju.errors.JujuAPIError as e:
if "already exists" in e.message:
pass
else:
"""
return await controller.get_model(model_name)
- async def model_exists(self, model_name: str, controller: Controller = None) -> bool:
+ async def model_exists(
+ self, model_name: str, controller: Controller = None
+ ) -> bool:
"""
Check if model exists
db_dict: dict = None,
progress_timeout: float = None,
total_timeout: float = None,
- series: str = "xenial",
+ series: str = "bionic",
wait: bool = True,
) -> (Machine, bool):
"""
return application
+ async def scale_application(
+ self,
+ model_name: str,
+ application_name: str,
+ scale: int = 1,
+ total_timeout: float = None,
+ ):
+ """
+ Scale application (K8s)
+
+ :param: model_name: Model name
+ :param: application_name: Application name
+ :param: scale: Scale to which to set this application
+ :param: total_timeout: Timeout for the entity to be active
+ """
+
+ model = None
+ controller = await self.get_controller()
+ try:
+ model = await self.get_model(controller, model_name)
+
+ self.log.debug(
+ "Scaling application {} in model {}".format(
+ application_name, model_name
+ )
+ )
+ application = self._get_application(model, application_name)
+ if application is None:
+ raise JujuApplicationNotFound("Cannot scale application")
+ await application.scale(scale=scale)
+ # Wait until application is scaled in model
+ self.log.debug(
+ "Waiting for application {} to be scaled in model {}...".format(
+ application_name, model_name
+ )
+ )
+ if total_timeout is None:
+ total_timeout = 1800
+ end = time.time() + total_timeout
+ while time.time() < end:
+ application_scale = self._get_application_count(model, application_name)
+ # Before calling wait_for_model function,
+ # wait until application unit count and scale count are equal.
+ # Because there is a delay before scaling triggers in Juju model.
+ if application_scale == scale:
+ await JujuModelWatcher.wait_for_model(
+ model=model, timeout=total_timeout
+ )
+ self.log.debug(
+ "Application {} is scaled in model {}".format(
+ application_name, model_name
+ )
+ )
+ return
+ await asyncio.sleep(5)
+ raise Exception(
+ "Timeout waiting for application {} in model {} to be scaled".format(
+ application_name, model_name
+ )
+ )
+ finally:
+ if model:
+ await self.disconnect_model(model)
+ await self.disconnect_controller(controller)
+
+ def _get_application_count(self, model: Model, application_name: str) -> int:
+ """Get number of units of the application
+
+ :param: model: Model object
+ :param: application_name: Application name
+
+ :return: int (or None if application doesn't exist)
+ """
+ application = self._get_application(model, application_name)
+ if application is not None:
+ return len(application.units)
+
def _get_application(self, model: Model, application_name: str) -> Application:
"""Get application
db_dict: dict = None,
progress_timeout: float = None,
total_timeout: float = None,
- **kwargs
+ **kwargs,
):
"""Execute action
# Add relation
try:
await model.add_relation(endpoint_1, endpoint_2)
- except JujuAPIError as e:
+ except juju.errors.JujuAPIError as e:
if "not found" in e.message:
self.log.warning("Relation not found: {}".format(e.message))
return
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)
+ await self._destroy_model(
+ model_name,
+ controller,
+ timeout=total_timeout,
+ )
+ finally:
+ if model:
+ await self.disconnect_model(model)
+ await self.disconnect_controller(controller)
- # Wait until model is destroyed
- self.log.debug("Waiting for model {} to be destroyed...".format(model_name))
+ async def _destroy_model(
+ self, model_name: str, controller: Controller, timeout: float = 1800
+ ):
+ """
+ Destroy model from controller
- 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
+ :param: model: Model name to be removed
+ :param: controller: Controller object
+ :param: timeout: Timeout in seconds
+ """
+
+ async def _destroy_model_loop(model_name: str, controller: Controller):
+ while await self.model_exists(model_name, controller=controller):
+ await controller.destroy_model(
+ model_name, destroy_storage=True, force=True, max_wait=0
+ )
await asyncio.sleep(5)
+
+ try:
+ await asyncio.wait_for(
+ _destroy_model_loop(model_name, controller), timeout=timeout
+ )
+ except asyncio.TimeoutError:
raise Exception(
"Timeout waiting for model {} to be destroyed".format(model_name)
)
- except Exception as e:
- if model:
- await self.disconnect_model(model)
- raise e
- finally:
- await self.disconnect_controller(controller)
async def destroy_application(
self, model_name: str, application_name: str, total_timeout: float
controller = await self.get_controller()
try:
await controller.remove_cloud(name)
+ except juju.errors.JujuError as e:
+ if len(e.errors) == 1 and f'cloud "{name}" not found' == e.errors[0]:
+ self.log.warning(f"Cloud {name} not found, so it could not be deleted.")
+ else:
+ raise e
finally:
await self.disconnect_controller(controller)