--- /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 unittest.mock import Mock
+
+from n2vc.temporal_libjuju import Libjuju, ConnectionInfo
+from n2vc.exceptions import (
+ JujuApplicationExists,
+ 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.disconnect_model")
+@asynctest.mock.patch("n2vc.temporal_libjuju.Libjuju.disconnect_controller")
+@asynctest.mock.patch("n2vc.temporal_libjuju.Libjuju.wait_app_deployment_completion")
+@asynctest.mock.patch(
+ "juju.model.Model.applications", new_callable=asynctest.PropertyMock
+)
+@asynctest.mock.patch("juju.model.Model.deploy")
+class DeployCharmTest(LibjujuTestCase):
+ def setUp(self):
+ super(DeployCharmTest, self).setUp()
+
+ def test_existing_app_is_not_deployed(
+ self,
+ mock_deploy,
+ mock_applications,
+ mock_wait_app_deployment,
+ mock_disconnect_controller,
+ mock_disconnect_model,
+ mock_get_model,
+ mock_get_controller,
+ ):
+ mock_get_model.return_value = juju.model.Model()
+ mock_applications.return_value = {"existing_app"}
+
+ application = None
+ with self.assertRaises(JujuApplicationExists):
+ application = self.loop.run_until_complete(
+ self.libjuju.deploy_charm(
+ "existing_app",
+ "path",
+ "model",
+ "machine",
+ )
+ )
+ self.assertIsNone(application)
+
+ mock_disconnect_controller.assert_called()
+ mock_disconnect_model.assert_called()
+
+ def test_app_is_deployed(
+ self,
+ mock_deploy,
+ mock_applications,
+ mock_wait_app_deployment,
+ mock_disconnect_controller,
+ mock_disconnect_model,
+ mock_get_model,
+ mock_get_controller,
+ ):
+ mock_get_model.return_value = juju.model.Model()
+ mock_deploy.return_value = Mock()
+ self.loop.run_until_complete(
+ self.libjuju.deploy_charm(
+ "app",
+ "path",
+ "model",
+ series="series",
+ num_units=2,
+ )
+ )
+ mock_deploy.assert_called_once_with(
+ entity_url="path",
+ application_name="app",
+ channel="stable",
+ num_units=2,
+ series="series",
+ config=None,
+ )
+ mock_wait_app_deployment.assert_called()
+ mock_disconnect_controller.assert_called()
+ mock_disconnect_model.assert_called()
+
+
+@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()