+++ /dev/null
-# 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)
+++ /dev/null
-#######################################################################################
-# Copyright ETSI Contributors and Others.
-#
-# 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 asyncio
-import asynctest
-import logging
-import juju
-
-from n2vc.temporal_libjuju import Libjuju, ConnectionInfo
-from n2vc.exceptions import (
- JujuControllerFailedConnecting,
- JujuModelAlreadyExists,
-)
-
-cacert = """-----BEGIN CERTIFICATE-----
-SOMECERT
------END CERTIFICATE-----"""
-
-
-@asynctest.mock.patch("n2vc.temporal_libjuju.Controller")
-class LibjujuTestCase(asynctest.TestCase):
- def setUp(
- self,
- mock_base64_to_cacert=None,
- ):
- self.loop = asyncio.get_event_loop()
- self.connection_info = ConnectionInfo(
- "1.2.3.4:17070", "user", "secret", cacert, "k8s_cloud", "k8s_credentials"
- )
- logging.disable(logging.CRITICAL)
- self.libjuju = Libjuju(self.connection_info)
-
-
-@asynctest.mock.patch("juju.controller.Controller.connect")
-class GetControllerTest(LibjujuTestCase):
- def setUp(self):
- super(GetControllerTest, self).setUp()
-
- def test_get_controller(self, mock_connect):
- controller = self.loop.run_until_complete(self.libjuju.get_controller())
- self.assertIsInstance(controller, juju.controller.Controller)
- mock_connect.assert_called_with(
- endpoint=self.connection_info.endpoint,
- username=self.connection_info.user,
- password=self.connection_info.password,
- cacert=self.connection_info.cacert,
- )
-
- @asynctest.mock.patch("n2vc.temporal_libjuju.Libjuju.disconnect_controller")
- def test_exception(
- self,
- mock_disconnect_controller,
- mock_connect,
- ):
- mock_connect.side_effect = Exception()
- controller = None
- with self.assertRaises(JujuControllerFailedConnecting):
- controller = self.loop.run_until_complete(self.libjuju.get_controller())
- self.assertIsNone(controller)
- mock_disconnect_controller.assert_called()
-
-
-class DisconnectTest(LibjujuTestCase):
- def setUp(self):
- super(DisconnectTest, self).setUp()
-
- @asynctest.mock.patch("juju.model.Model.disconnect")
- def test_disconnect_model(self, mock_disconnect):
- self.loop.run_until_complete(self.libjuju.disconnect_model(juju.model.Model()))
- mock_disconnect.assert_called_once()
-
- @asynctest.mock.patch("juju.controller.Controller.disconnect")
- def test_disconnect_controller(self, mock_disconnect):
- self.loop.run_until_complete(
- self.libjuju.disconnect_controller(juju.controller.Controller())
- )
- mock_disconnect.assert_called_once()
-
-
-@asynctest.mock.patch("n2vc.temporal_libjuju.Libjuju.get_controller")
-@asynctest.mock.patch("n2vc.temporal_libjuju.Libjuju.model_exists")
-@asynctest.mock.patch("juju.controller.Controller.add_model")
-@asynctest.mock.patch("n2vc.temporal_libjuju.Libjuju.disconnect_controller")
-@asynctest.mock.patch("n2vc.temporal_libjuju.Libjuju.disconnect_model")
-class AddModelTest(LibjujuTestCase):
- def setUp(self):
- super(AddModelTest, self).setUp()
-
- def test_existing_model_raises_exception(
- self,
- mock_disconnect_model,
- mock_disconnect_controller,
- mock_add_model,
- mock_model_exists,
- mock_get_controller,
- ):
- mock_model_exists.return_value = True
- mock_get_controller.return_value = juju.controller.Controller()
- with self.assertRaises(JujuModelAlreadyExists):
- self.loop.run_until_complete(self.libjuju.add_model("existing_model"))
- mock_get_controller.assert_called()
- mock_add_model.assert_not_called()
- mock_disconnect_controller.assert_called()
-
- def test_non_existing_model(
- self,
- mock_disconnect_model,
- mock_disconnect_controller,
- mock_add_model,
- mock_model_exists,
- mock_get_controller,
- ):
- mock_model_exists.return_value = False
- mock_get_controller.return_value = juju.controller.Controller()
- new_model_name = "nonexisting_model"
- self.loop.run_until_complete(self.libjuju.add_model(new_model_name))
- mock_add_model.assert_called_once_with(
- new_model_name,
- cloud_name=self.connection_info.cloud_name,
- credential_name=self.connection_info.cloud_credentials,
- )
- mock_disconnect_controller.assert_called()
- mock_disconnect_model.assert_called()
-
-
-@asynctest.mock.patch("n2vc.temporal_libjuju.Libjuju.get_controller")
-@asynctest.mock.patch("juju.controller.Controller.list_models")
-class ModelExistsTest(LibjujuTestCase):
- def setUp(self):
- super(ModelExistsTest, self).setUp()
-
- async def test_existing_model(
- self,
- mock_list_models,
- mock_get_controller,
- ):
- model_name = "existing_model"
- mock_list_models.return_value = [model_name]
- self.assertTrue(
- await self.libjuju.model_exists(model_name, juju.controller.Controller())
- )
-
- @asynctest.mock.patch("n2vc.temporal_libjuju.Libjuju.disconnect_controller")
- async def test_no_controller(
- self,
- mock_disconnect_controller,
- mock_list_models,
- mock_get_controller,
- ):
- model_name = "existing_model"
- mock_list_models.return_value = [model_name]
- mock_get_controller.return_value = juju.controller.Controller()
- self.assertTrue(await self.libjuju.model_exists(model_name))
- mock_disconnect_controller.assert_called_once()
-
- async def test_non_existing_model(
- self,
- mock_list_models,
- mock_get_controller,
- ):
- mock_list_models.return_value = []
- self.assertFalse(
- await self.libjuju.model_exists(
- "not_existing_model", juju.controller.Controller()
- )
- )
-
-
-@asynctest.mock.patch("juju.controller.Controller.get_model")
-class GetModelTest(LibjujuTestCase):
- def setUp(self):
- super(GetModelTest, self).setUp()
-
- def test_get_model(
- self,
- mock_get_model,
- ):
- mock_get_model.return_value = juju.model.Model()
- model = self.loop.run_until_complete(
- self.libjuju.get_model(juju.controller.Controller(), "model")
- )
- self.assertIsInstance(model, juju.model.Model)
-
-
-@asynctest.mock.patch("n2vc.temporal_libjuju.Libjuju.get_controller")
-@asynctest.mock.patch("n2vc.temporal_libjuju.Libjuju.disconnect_controller")
-@asynctest.mock.patch("juju.controller.Controller.list_models")
-class ListModelsTest(LibjujuTestCase):
- def setUp(self):
- super(ListModelsTest, self).setUp()
-
- def test_containing(
- self,
- mock_list_models,
- mock_disconnect_controller,
- mock_get_controller,
- ):
- expected_list = ["existingmodel"]
- mock_get_controller.return_value = juju.controller.Controller()
- mock_list_models.return_value = expected_list
- models = self.loop.run_until_complete(self.libjuju.list_models())
-
- mock_disconnect_controller.assert_called_once()
- self.assertEquals(models, expected_list)
-
-
-@asynctest.mock.patch("n2vc.temporal_libjuju.Libjuju.get_controller")
-@asynctest.mock.patch("n2vc.temporal_libjuju.Libjuju.get_model")
-@asynctest.mock.patch("n2vc.temporal_libjuju.Libjuju.model_exists")
-@asynctest.mock.patch("n2vc.temporal_libjuju.Libjuju.disconnect_model")
-@asynctest.mock.patch("n2vc.temporal_libjuju.Libjuju.disconnect_controller")
-@asynctest.mock.patch("juju.controller.Controller.destroy_model")
-class DestroyModelTest(LibjujuTestCase):
- def setUp(self):
- super(DestroyModelTest, self).setUp()
-
- def test_model_is_destroyed(
- self,
- mock_destroy,
- mock_disconnect_controller,
- mock_disconnect_model,
- mock_model_exists,
- mock_get_model,
- mock_get_controller,
- ):
- mock_get_controller.return_value = juju.controller.Controller()
- mock_model_exists.return_value = True
- mock_get_model.return_value = juju.model.Model()
- model_name = "model_to_destroy"
- force = True
-
- self.loop.run_until_complete(self.libjuju.destroy_model(model_name, force))
- mock_destroy.assert_called_with(
- model_name, destroy_storage=True, force=force, max_wait=60
- )
- mock_disconnect_controller.assert_called()
- mock_disconnect_model.assert_called()
-
- def test_not_existing_model(
- self,
- mock_destroy,
- mock_disconnect_controller,
- mock_disconnect_model,
- mock_model_exists,
- mock_get_model,
- mock_get_controller,
- ):
- mock_get_controller.return_value = juju.controller.Controller()
- mock_model_exists.return_value = False
- model_name = "model_to_destroy"
- force = True
-
- self.loop.run_until_complete(self.libjuju.destroy_model(model_name, force))
- mock_destroy.assert_not_called()
- mock_get_model.assert_not_called()
- mock_disconnect_controller.assert_called()
- mock_disconnect_model.assert_called()
-
- def test_raise_exception(
- self,
- mock_destroy,
- mock_disconnect_controller,
- mock_disconnect_model,
- mock_model_exists,
- mock_get_model,
- mock_get_controller,
- ):
- mock_get_controller.return_value = juju.controller.Controller()
- mock_model_exists.return_value = True
- mock_get_model.side_effect = Exception()
- model_name = "model_to_destroy"
- force = False
-
- with self.assertRaises(Exception):
- self.loop.run_until_complete(self.libjuju.destroy_model(model_name, force))
- mock_destroy.assert_not_called()
- mock_disconnect_controller.assert_called()
- mock_disconnect_model.assert_called()