blob: 5f8429a34c742607347b467e9d37c76fe365a158 [file] [log] [blame]
# Copyright 2023 Canonical Ltd.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
from dataclasses import dataclass
from typing import List
from juju.controller import Controller
from juju.model import Model
from n2vc.exceptions import (
JujuControllerFailedConnecting,
JujuModelAlreadyExists,
)
@dataclass
class ConnectionInfo:
"""Information to connect to juju controller"""
endpoint: str
user: str
password: str
cacert: str
cloud_name: str
cloud_credentials: str
def __repr__(self):
return f"{self.__class__.__name__}(endpoint: {self.endpoint}, user: {self.user}, password: ******, caert: ******)"
def __str__(self):
return f"{self.__class__.__name__}(endpoint: {self.endpoint}, user: {self.user}, password: ******, caert: ******)"
class Libjuju:
def __init__(self, connection_info: ConnectionInfo) -> None:
self.logger = logging.getLogger("temporal_libjuju")
self.connection_info = connection_info
async def get_controller(self) -> Controller:
controller = Controller()
try:
await controller.connect(
endpoint=self.connection_info.endpoint,
username=self.connection_info.user,
password=self.connection_info.password,
cacert=self.connection_info.cacert,
)
return controller
except Exception as e:
self.logger.error(
"Error connecting to controller={}: {}".format(
self.connection_info.endpoint, e
)
)
await self.disconnect_controller(controller)
raise JujuControllerFailedConnecting(str(e))
async def disconnect_controller(self, controller: Controller) -> None:
if controller:
await controller.disconnect()
async def disconnect_model(self, model: Model):
if model:
await model.disconnect()
async def add_model(self, model_name: str):
"""Exception is raised if model_name already exists"""
model = None
controller = None
try:
controller = await self.get_controller()
if await self.model_exists(model_name, controller=controller):
raise JujuModelAlreadyExists(
"Cannot create model {}".format(model_name)
)
self.logger.debug("Creating model {}".format(model_name))
model = await controller.add_model(
model_name,
cloud_name=self.connection_info.cloud_name,
credential_name=self.connection_info.cloud_credentials,
)
finally:
await self.disconnect_model(model)
await self.disconnect_controller(controller)
async def model_exists(
self, model_name: str, controller: Controller = None
) -> bool:
"""Returns True if model exists. False otherwhise."""
need_to_disconnect = False
try:
if not controller:
controller = await self.get_controller()
need_to_disconnect = True
return model_name in await controller.list_models()
finally:
if need_to_disconnect:
await self.disconnect_controller(controller)
async def get_model(self, controller: Controller, model_name: str) -> Model:
return await controller.get_model(model_name)
async def list_models(self) -> List[str]:
"""List models in controller."""
try:
controller = await self.get_controller()
return await controller.list_models()
finally:
await self.disconnect_controller(controller)
async def destroy_model(self, model_name: str, force=False) -> None:
controller = None
model = None
try:
controller = await self.get_controller()
if not await self.model_exists(model_name, controller=controller):
self.logger.warn(f"Model {model_name} doesn't exist")
return
self.logger.debug(f"Getting model {model_name} to destroy")
model = await self.get_model(controller, model_name)
await self.disconnect_model(model)
await controller.destroy_model(
model_name, destroy_storage=True, force=force, max_wait=60
)
except Exception as e:
self.logger.warn(f"Failed deleting model {model_name}: {e}")
raise e
finally:
await self.disconnect_model(model)
await self.disconnect_controller(controller)