X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FN2VC.git;a=blobdiff_plain;f=n2vc%2Ftests%2Funit%2Ftest_libjuju.py;h=67cd19f896716c04116cfcf307d55e637dfd7ef4;hp=5b120b1b21365c3b1f5007baacbc698c47750ba5;hb=HEAD;hpb=2962f3e7aba84b4584d2deac30d1c163f6441a03 diff --git a/n2vc/tests/unit/test_libjuju.py b/n2vc/tests/unit/test_libjuju.py index 5b120b1..38d8d0e 100644 --- a/n2vc/tests/unit/test_libjuju.py +++ b/n2vc/tests/unit/test_libjuju.py @@ -20,7 +20,14 @@ import juju import kubernetes from juju.errors import JujuAPIError import logging -from .utils import FakeMachine, FakeApplication + +from n2vc.definitions import Offer, RelationEndpoint +from .utils import ( + FakeApplication, + FakeMachine, + FakeManualMachine, + FakeUnit, +) from n2vc.libjuju import Libjuju from n2vc.exceptions import ( JujuControllerFailedConnecting, @@ -53,7 +60,7 @@ class LibjujuTestCase(asynctest.TestCase): self.loop = asyncio.get_event_loop() self.db = Mock() mock_base64_to_cacert.return_value = cacert - Connection._load_vca_connection_data = Mock() + # Connection._load_vca_connection_data = Mock() vca_connection = Connection(AsyncMock()) vca_connection._data = ConnectionData( **{ @@ -71,7 +78,7 @@ class LibjujuTestCase(asynctest.TestCase): } ) logging.disable(logging.CRITICAL) - self.libjuju = Libjuju(vca_connection, self.loop) + self.libjuju = Libjuju(vca_connection) self.loop.run_until_complete(self.libjuju.disconnect()) @@ -103,7 +110,7 @@ class GetControllerTest(LibjujuTestCase): with self.assertRaises(JujuControllerFailedConnecting): controller = self.loop.run_until_complete(self.libjuju.get_controller()) self.assertIsNone(controller) - mock_disconnect_controller.assert_called_once() + mock_disconnect_controller.assert_called() def test_same_endpoint_get_controller(self, mock_api_endpoints, mock_connect): self.libjuju.endpoints = ["127.0.0.1:17070"] @@ -228,8 +235,14 @@ class GetExecutedActionsTest(LibjujuTestCase): executed_actions = self.loop.run_until_complete( self.libjuju.get_executed_actions("model") ) - expected_result = [{'id': 'id', 'action': 'action_name', - 'status': 'status', 'output': 'completed'}] + expected_result = [ + { + "id": "id", + "action": "action_name", + "status": "status", + "output": "completed", + } + ] self.assertListEqual(expected_result, executed_actions) self.assertIsInstance(executed_actions, list) @@ -259,7 +272,8 @@ class GetApplicationConfigsTest(LibjujuTestCase): mock_get_model.return_value = None with self.assertRaises(JujuError): self.loop.run_until_complete( - self.libjuju.get_application_configs("model", "app")) + self.libjuju.get_application_configs("model", "app") + ) mock_get_controller.assert_called_once() mock_disconnect_controller.assert_called_once() @@ -275,8 +289,9 @@ class GetApplicationConfigsTest(LibjujuTestCase): mock_get_controller, ): mock_get_application.return_value = FakeApplication() - application_configs = self.loop.run_until_complete(self.libjuju - .get_application_configs("model", "app")) + application_configs = self.loop.run_until_complete( + self.libjuju.get_application_configs("model", "app") + ) self.assertEqual(application_configs, ["app_config"]) @@ -481,70 +496,408 @@ class CreateMachineTest(LibjujuTestCase): # TODO test provision machine +@asynctest.mock.patch("os.remove") +@asynctest.mock.patch("n2vc.libjuju.yaml.dump") +@asynctest.mock.patch("builtins.open", create=True) @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_controller") @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_model") @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_model") @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_controller") @asynctest.mock.patch("n2vc.juju_watcher.JujuModelWatcher.wait_for_model") @asynctest.mock.patch("juju.model.Model.deploy") +@asynctest.mock.patch("juju.model.CharmhubDeployType.resolve") +@asynctest.mock.patch("n2vc.libjuju.BundleHandler") +@asynctest.mock.patch("juju.url.URL.parse") class DeployTest(LibjujuTestCase): def setUp(self): super(DeployTest, self).setUp() + self.instantiation_params = {"applications": {"squid": {"scale": 2}}} + self.architecture = "amd64" + self.uri = "cs:osm" + self.url = AsyncMock() + self.url.schema = juju.url.Schema.CHARM_HUB + self.bundle_instance = None + + def setup_bundle_download_mocks( + self, mock_url_parse, mock_bundle, mock_resolve, mock_get_model + ): + mock_url_parse.return_value = self.url + mock_bundle.return_value = AsyncMock() + mock_resolve.return_value = AsyncMock() + mock_resolve.origin = AsyncMock() + mock_get_model.return_value = juju.model.Model() + self.bundle_instance = mock_bundle.return_value + self.bundle_instance.applications = {"squid"} + + def assert_overlay_file_is_written(self, filename, mocked_file, mock_yaml, mock_os): + mocked_file.assert_called_once_with(filename, "w") + mock_yaml.assert_called_once_with( + self.instantiation_params, mocked_file.return_value.__enter__.return_value + ) + mock_os.assert_called_once_with(filename) + + def assert_overlay_file_is_not_written(self, mocked_file, mock_yaml, mock_os): + mocked_file.assert_not_called() + mock_yaml.assert_not_called() + mock_os.assert_not_called() + + def assert_bundle_is_downloaded(self, mock_resolve, mock_url_parse): + mock_resolve.assert_called_once_with( + self.url, self.architecture, entity_url=self.uri + ) + mock_url_parse.assert_called_once_with(self.uri) + self.bundle_instance.fetch_plan.assert_called_once_with( + self.url, mock_resolve.origin + ) + + def assert_bundle_is_not_downloaded(self, mock_resolve, mock_url_parse): + mock_resolve.assert_not_called() + mock_url_parse.assert_not_called() + self.bundle_instance.fetch_plan.assert_not_called() def test_deploy( self, + mock_url_parse, + mock_bundle, + mock_resolve, mock_deploy, mock_wait_for_model, mock_disconnect_controller, mock_disconnect_model, mock_get_model, mock_get_controller, + mocked_file, + mock_yaml, + mock_os, ): - mock_get_model.return_value = juju.model.Model() + self.setup_bundle_download_mocks( + mock_url_parse, mock_bundle, mock_resolve, mock_get_model + ) + model_name = "model1" + self.loop.run_until_complete( - self.libjuju.deploy("cs:osm", "model", wait=True, timeout=0) + self.libjuju.deploy( + "cs:osm", + model_name, + wait=True, + timeout=0, + instantiation_params=None, + ) ) - mock_deploy.assert_called_once() + self.assert_overlay_file_is_not_written(mocked_file, mock_yaml, mock_os) + self.assert_bundle_is_not_downloaded(mock_resolve, mock_url_parse) + mock_deploy.assert_called_once_with("cs:osm", trust=True, overlays=[]) mock_wait_for_model.assert_called_once() mock_disconnect_controller.assert_called_once() mock_disconnect_model.assert_called_once() def test_deploy_no_wait( self, + mock_url_parse, + mock_bundle, + mock_resolve, mock_deploy, mock_wait_for_model, mock_disconnect_controller, mock_disconnect_model, mock_get_model, mock_get_controller, + mocked_file, + mock_yaml, + mock_os, ): - mock_get_model.return_value = juju.model.Model() + self.setup_bundle_download_mocks( + mock_url_parse, mock_bundle, mock_resolve, mock_get_model + ) self.loop.run_until_complete( - self.libjuju.deploy("cs:osm", "model", wait=False, timeout=0) + self.libjuju.deploy( + "cs:osm", "model", wait=False, timeout=0, instantiation_params={} + ) ) - mock_deploy.assert_called_once() + self.assert_overlay_file_is_not_written(mocked_file, mock_yaml, mock_os) + self.assert_bundle_is_not_downloaded(mock_resolve, mock_url_parse) + mock_deploy.assert_called_once_with("cs:osm", trust=True, overlays=[]) mock_wait_for_model.assert_not_called() mock_disconnect_controller.assert_called_once() mock_disconnect_model.assert_called_once() def test_deploy_exception( self, + mock_url_parse, + mock_bundle, + mock_resolve, mock_deploy, mock_wait_for_model, mock_disconnect_controller, mock_disconnect_model, mock_get_model, mock_get_controller, + mocked_file, + mock_yaml, + mock_os, ): + self.setup_bundle_download_mocks( + mock_url_parse, mock_bundle, mock_resolve, mock_get_model + ) mock_deploy.side_effect = Exception() - mock_get_model.return_value = juju.model.Model() with self.assertRaises(Exception): self.loop.run_until_complete(self.libjuju.deploy("cs:osm", "model")) + self.assert_overlay_file_is_not_written(mocked_file, mock_yaml, mock_os) + self.assert_bundle_is_not_downloaded(mock_resolve, mock_url_parse) mock_deploy.assert_called_once() mock_wait_for_model.assert_not_called() mock_disconnect_controller.assert_called_once() mock_disconnect_model.assert_called_once() + def test_deploy_with_instantiation_params( + self, + mock_url_parse, + mock_bundle, + mock_resolve, + mock_deploy, + mock_wait_for_model, + mock_disconnect_controller, + mock_disconnect_model, + mock_get_model, + mock_get_controller, + mocked_file, + mock_yaml, + mock_os, + ): + self.setup_bundle_download_mocks( + mock_url_parse, mock_bundle, mock_resolve, mock_get_model + ) + model_name = "model1" + expected_filename = "{}-overlay.yaml".format(model_name) + self.loop.run_until_complete( + self.libjuju.deploy( + self.uri, + model_name, + wait=True, + timeout=0, + instantiation_params=self.instantiation_params, + ) + ) + self.assert_overlay_file_is_written( + expected_filename, mocked_file, mock_yaml, mock_os + ) + self.assert_bundle_is_downloaded(mock_resolve, mock_url_parse) + mock_deploy.assert_called_once_with( + self.uri, trust=True, overlays=[expected_filename] + ) + mock_wait_for_model.assert_called_once() + mock_disconnect_controller.assert_called_once() + mock_disconnect_model.assert_called_once() + + def test_deploy_with_instantiation_params_no_applications( + self, + mock_url_parse, + mock_bundle, + mock_resolve, + mock_deploy, + mock_wait_for_model, + mock_disconnect_controller, + mock_disconnect_model, + mock_get_model, + mock_get_controller, + mocked_file, + mock_yaml, + mock_os, + ): + self.instantiation_params = {"applications": {}} + self.setup_bundle_download_mocks( + mock_url_parse, mock_bundle, mock_resolve, mock_get_model + ) + + model_name = "model3" + expected_filename = "{}-overlay.yaml".format(model_name) + self.loop.run_until_complete( + self.libjuju.deploy( + self.uri, + model_name, + wait=False, + timeout=0, + instantiation_params=self.instantiation_params, + ) + ) + + self.assert_overlay_file_is_written( + expected_filename, mocked_file, mock_yaml, mock_os + ) + self.assert_bundle_is_not_downloaded(mock_resolve, mock_url_parse) + mock_deploy.assert_called_once_with( + self.uri, trust=True, overlays=[expected_filename] + ) + mock_wait_for_model.assert_not_called() + mock_disconnect_controller.assert_called_once() + mock_disconnect_model.assert_called_once() + + def test_deploy_with_instantiation_params_applications_not_found( + self, + mock_url_parse, + mock_bundle, + mock_resolve, + mock_deploy, + mock_wait_for_model, + mock_disconnect_controller, + mock_disconnect_model, + mock_get_model, + mock_get_controller, + mocked_file, + mock_yaml, + mock_os, + ): + self.instantiation_params = {"some_key": {"squid": {"scale": 2}}} + self.setup_bundle_download_mocks( + mock_url_parse, mock_bundle, mock_resolve, mock_get_model + ) + + with self.assertRaises(JujuError): + self.loop.run_until_complete( + self.libjuju.deploy( + self.uri, + "model1", + wait=True, + timeout=0, + instantiation_params=self.instantiation_params, + ) + ) + + self.assert_overlay_file_is_not_written(mocked_file, mock_yaml, mock_os) + self.assert_bundle_is_not_downloaded(mock_resolve, mock_url_parse) + mock_deploy.assert_not_called() + mock_wait_for_model.assert_not_called() + mock_disconnect_controller.assert_called_once() + mock_disconnect_model.assert_called_once() + + def test_deploy_overlay_contains_invalid_app( + self, + mock_url_parse, + mock_bundle, + mock_resolve, + mock_deploy, + mock_wait_for_model, + mock_disconnect_controller, + mock_disconnect_model, + mock_get_model, + mock_get_controller, + mocked_file, + mock_yaml, + mock_os, + ): + self.setup_bundle_download_mocks( + mock_url_parse, mock_bundle, mock_resolve, mock_get_model + ) + self.bundle_instance.applications = {"new_app"} + + with self.assertRaises(JujuApplicationNotFound) as error: + self.loop.run_until_complete( + self.libjuju.deploy( + self.uri, + "model2", + wait=True, + timeout=0, + instantiation_params=self.instantiation_params, + ) + ) + error_msg = "Cannot find application ['squid'] in original bundle {'new_app'}" + self.assertEqual(str(error.exception), error_msg) + + self.assert_overlay_file_is_not_written(mocked_file, mock_yaml, mock_os) + self.assert_bundle_is_downloaded(mock_resolve, mock_url_parse) + mock_deploy.assert_not_called() + mock_wait_for_model.assert_not_called() + mock_disconnect_controller.assert_called_once() + mock_disconnect_model.assert_called_once() + + def test_deploy_exception_with_instantiation_params( + self, + mock_url_parse, + mock_bundle, + mock_resolve, + mock_deploy, + mock_wait_for_model, + mock_disconnect_controller, + mock_disconnect_model, + mock_get_model, + mock_get_controller, + mocked_file, + mock_yaml, + mock_os, + ): + self.setup_bundle_download_mocks( + mock_url_parse, mock_bundle, mock_resolve, mock_get_model + ) + + mock_deploy.side_effect = Exception() + model_name = "model2" + expected_filename = "{}-overlay.yaml".format(model_name) + with self.assertRaises(Exception): + self.loop.run_until_complete( + self.libjuju.deploy( + self.uri, + model_name, + instantiation_params=self.instantiation_params, + ) + ) + + self.assert_overlay_file_is_written( + expected_filename, mocked_file, mock_yaml, mock_os + ) + self.assert_bundle_is_downloaded(mock_resolve, mock_url_parse) + mock_deploy.assert_called_once_with( + self.uri, trust=True, overlays=[expected_filename] + ) + mock_wait_for_model.assert_not_called() + mock_disconnect_controller.assert_called_once() + mock_disconnect_model.assert_called_once() + + @asynctest.mock.patch("logging.Logger.warning") + def test_deploy_exception_when_deleting_file_is_not_propagated( + self, + mock_warning, + mock_url_parse, + mock_bundle, + mock_resolve, + mock_deploy, + mock_wait_for_model, + mock_disconnect_controller, + mock_disconnect_model, + mock_get_model, + mock_get_controller, + mocked_file, + mock_yaml, + mock_os, + ): + self.setup_bundle_download_mocks( + mock_url_parse, mock_bundle, mock_resolve, mock_get_model + ) + + mock_os.side_effect = OSError("Error") + model_name = "model2" + expected_filename = "{}-overlay.yaml".format(model_name) + self.loop.run_until_complete( + self.libjuju.deploy( + self.uri, + model_name, + instantiation_params=self.instantiation_params, + ) + ) + + self.assert_overlay_file_is_written( + expected_filename, mocked_file, mock_yaml, mock_os + ) + self.assert_bundle_is_downloaded(mock_resolve, mock_url_parse) + mock_deploy.assert_called_once_with( + self.uri, trust=True, overlays=[expected_filename] + ) + mock_wait_for_model.assert_called_once() + mock_disconnect_controller.assert_called_once() + mock_disconnect_model.assert_called_once() + mock_warning.assert_called_with( + "Overlay file {} could not be removed: Error".format(expected_filename) + ) + @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_controller") @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_model") @@ -763,7 +1116,6 @@ class ExecuteActionTest(LibjujuTestCase): mock_get_model, mock_get_controller, ): - mock_get_model.return_value = juju.model.Model() mock__get_application.return_value = FakeApplication() output = None @@ -816,7 +1168,7 @@ class ExecuteActionTest(LibjujuTestCase): mock_disconnect_controller.assert_called() mock_disconnect_model.assert_called() - def test_succesful_exec( + def test_successful_exec( self, mock_get_action_status, mock_get_action_output, @@ -992,6 +1344,38 @@ class AddRelationTest(LibjujuTestCase): mock_disconnect_controller.assert_called_once() mock_disconnect_model.assert_called_once() + @asynctest.mock.patch("logging.Logger.warning") + def test_not_found_in_error_code( + self, + mock_warning, + mock_add_relation, + mock_disconnect_controller, + mock_disconnect_model, + mock_get_model, + mock_get_controller, + ): + result = { + "error": "relation cannot be added", + "error-code": "not found", + "response": "response", + "request-id": 1, + } + + mock_get_model.return_value = juju.model.Model() + mock_add_relation.side_effect = JujuAPIError(result) + + self.loop.run_until_complete( + self.libjuju.add_relation( + "model", + "app1:relation1", + "app2:relation2", + ) + ) + + mock_warning.assert_called_with("Relation not found: relation cannot be added") + mock_disconnect_controller.assert_called_once() + mock_disconnect_model.assert_called_once() + @asynctest.mock.patch("logging.Logger.warning") def test_already_exists( self, @@ -1020,6 +1404,40 @@ class AddRelationTest(LibjujuTestCase): mock_disconnect_controller.assert_called_once() mock_disconnect_model.assert_called_once() + @asynctest.mock.patch("logging.Logger.warning") + def test_already_exists_error_code( + self, + mock_warning, + mock_add_relation, + mock_disconnect_controller, + mock_disconnect_model, + mock_get_model, + mock_get_controller, + ): + result = { + "error": "relation cannot be added", + "error-code": "already exists", + "response": "response", + "request-id": 1, + } + + mock_get_model.return_value = juju.model.Model() + mock_add_relation.side_effect = JujuAPIError(result) + + self.loop.run_until_complete( + self.libjuju.add_relation( + "model", + "app1:relation1", + "app2:relation2", + ) + ) + + mock_warning.assert_called_with( + "Relation already exists: relation cannot be added" + ) + mock_disconnect_controller.assert_called_once() + mock_disconnect_model.assert_called_once() + def test_exception( self, mock_add_relation, @@ -1102,12 +1520,12 @@ class DestroyApplicationTest(LibjujuTestCase): super(DestroyApplicationTest, self).setUp() def test_success( - self, - mock_get_controller, - mock_get_model, - mock_disconnect_controller, - mock_get_application, - mock_disconnect_model, + self, + mock_get_controller, + mock_get_model, + mock_disconnect_controller, + mock_get_application, + mock_disconnect_model, ): mock_get_application.return_value = FakeApplication() mock_get_model.return_value = None @@ -1123,12 +1541,12 @@ class DestroyApplicationTest(LibjujuTestCase): mock_disconnect_model.assert_called_once() def test_no_application( - self, - mock_get_controller, - mock_get_model, - mock_disconnect_controller, - mock_get_application, - mock_disconnect_model, + self, + mock_get_controller, + mock_get_model, + mock_disconnect_controller, + mock_get_application, + mock_disconnect_model, ): mock_get_model.return_value = None mock_get_application.return_value = None @@ -1143,12 +1561,12 @@ class DestroyApplicationTest(LibjujuTestCase): mock_get_application.assert_called() def test_exception( - self, - mock_get_controller, - mock_get_model, - mock_disconnect_controller, - mock_get_application, - mock_disconnect_model, + self, + mock_get_controller, + mock_get_model, + mock_disconnect_controller, + mock_get_application, + mock_disconnect_model, ): mock_get_application.return_value = FakeApplication mock_get_model.return_value = None @@ -1214,7 +1632,6 @@ class ConfigureApplicationTest(LibjujuTestCase): mock_get_model, mock_get_controller, ): - mock_get_application.return_value = FakeApplication() self.loop.run_until_complete( @@ -1236,7 +1653,6 @@ class ConfigureApplicationTest(LibjujuTestCase): mock_get_model, mock_get_controller, ): - mock_get_application.side_effect = Exception() with self.assertRaises(Exception): @@ -1258,7 +1674,6 @@ class ConfigureApplicationTest(LibjujuTestCase): mock_get_model, mock_get_controller, ): - result = {"error": "not found", "response": "response", "request-id": 1} mock_get_controller.side_effect = JujuAPIError(result) @@ -1283,7 +1698,6 @@ class ConfigureApplicationTest(LibjujuTestCase): mock_get_model, mock_get_controller, ): - result = {"error": "not found", "response": "response", "request-id": 1} mock_get_model.side_effect = JujuAPIError(result) @@ -1402,7 +1816,7 @@ class ListOffers(LibjujuTestCase): mock_get_controller.return_value = juju.controller.Controller() mock_list_offers.side_effect = Exception() with self.assertRaises(Exception): - self.loop.run_until_complete(self.libjuju.list_offers("model")) + self.loop.run_until_complete(self.libjuju._list_offers("model")) mock_disconnect_controller.assert_called_once() def test_empty_list( @@ -1412,8 +1826,10 @@ class ListOffers(LibjujuTestCase): mock_get_controller, ): mock_get_controller.return_value = juju.controller.Controller() - mock_list_offers.return_value = [] - offers = self.loop.run_until_complete(self.libjuju.list_offers("model")) + offer_results = Mock() + offer_results.results = [] + mock_list_offers.return_value = offer_results + offers = self.loop.run_until_complete(self.libjuju._list_offers("model")) self.assertEqual(offers, []) mock_disconnect_controller.assert_called_once() @@ -1424,9 +1840,52 @@ class ListOffers(LibjujuTestCase): mock_get_controller, ): mock_get_controller.return_value = juju.controller.Controller() - mock_list_offers.return_value = ["offer"] - offers = self.loop.run_until_complete(self.libjuju.list_offers("model")) - self.assertEqual(offers, ["offer"]) + offer = Mock() + offer_results = Mock() + offer_results.results = [offer] + mock_list_offers.return_value = offer_results + offers = self.loop.run_until_complete(self.libjuju._list_offers("model")) + self.assertEqual(offers, [offer]) + mock_disconnect_controller.assert_called_once() + + def test_matching_offer_name( + self, + mock_list_offers, + mock_disconnect_controller, + mock_get_controller, + ): + mock_get_controller.return_value = juju.controller.Controller() + offer_1 = Mock() + offer_1.offer_name = "offer1" + offer_2 = Mock() + offer_2.offer_name = "offer2" + offer_results = Mock() + offer_results.results = [offer_1, offer_2] + mock_list_offers.return_value = offer_results + offers = self.loop.run_until_complete( + self.libjuju._list_offers("model", offer_name="offer2") + ) + self.assertEqual(offers, [offer_2]) + mock_disconnect_controller.assert_called_once() + + def test_not_matching_offer_name( + self, + mock_list_offers, + mock_disconnect_controller, + mock_get_controller, + ): + mock_get_controller.return_value = juju.controller.Controller() + offer_1 = Mock() + offer_1.offer_name = "offer1" + offer_2 = Mock() + offer_2.offer_name = "offer2" + offer_results = Mock() + offer_results.results = [offer_1, offer_2] + mock_list_offers.return_value = offer_results + offers = self.loop.run_until_complete( + self.libjuju._list_offers("model", offer_name="offer3") + ) + self.assertEqual(offers, []) mock_disconnect_controller.assert_called_once() @@ -1434,46 +1893,117 @@ class ListOffers(LibjujuTestCase): @asynctest.mock.patch("juju.controller.Controller.get_model") @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_model") @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_controller") -@asynctest.mock.patch("juju.model.Model.consume") -class ConsumeTest(LibjujuTestCase): +@asynctest.mock.patch("n2vc.libjuju.Libjuju._list_offers") +@asynctest.mock.patch("juju.model.Model.create_offer") +class OfferTest(LibjujuTestCase): def setUp(self): - super(ConsumeTest, self).setUp() + super(OfferTest, self).setUp() - def test_consume( + def test_offer( self, - mock_consume, + mock_create_offer, + mock__list_offers, mock_disconnect_controller, mock_disconnect_model, mock_get_model, mock_get_controller, ): - mock_get_controller.return_value = juju.controller.Controller() - mock_get_model.return_value = juju.model.Model() - - self.loop.run_until_complete(self.libjuju.consume("offer_url", "model_name")) - mock_consume.assert_called_once() - mock_disconnect_model.assert_called_once() - mock_disconnect_controller.assert_called_once() + controller = juju.controller.Controller() + model = juju.model.Model() + mock_get_controller.return_value = controller + mock_get_model.return_value = model + endpoint = RelationEndpoint("model.app-name.0", "vca", "endpoint") + self.loop.run_until_complete(self.libjuju.offer(endpoint)) + mock_create_offer.assert_called_with( + "app-name:endpoint", offer_name="app-name-endpoint" + ) + mock_disconnect_model.assert_called_once_with(model) + mock_disconnect_controller.assert_called_once_with(controller) - def test_parsing_error_exception( + def test_offer_exception( self, - mock_consume, + mock_create_offer, + mock__list_offers, mock_disconnect_controller, mock_disconnect_model, mock_get_model, mock_get_controller, ): - mock_get_controller.return_value = juju.controller.Controller() - mock_get_model.return_value = juju.model.Model() + controller = juju.controller.Controller() + model = juju.model.Model() + mock_get_controller.return_value = controller + mock_get_model.return_value = model + mock__list_offers.return_value = [] + endpoint = RelationEndpoint("model.app-name.0", "vca", "endpoint") + with self.assertRaises(Exception): + self.loop.run_until_complete(self.libjuju.offer(endpoint)) + mock_create_offer.assert_called_with( + "app-name:endpoint", offer_name="app-name-endpoint" + ) + mock_disconnect_model.assert_called_once_with(model) + mock_disconnect_controller.assert_called_once_with(controller) + + +@asynctest.mock.patch("n2vc.libjuju.Libjuju.get_controller") +@asynctest.mock.patch("juju.controller.Controller.get_model") +@asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_model") +@asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_controller") +@asynctest.mock.patch("juju.model.Model.consume") +class ConsumeTest(LibjujuTestCase): + def setUp(self): + self.offer_url = "admin/model.offer_name" + super(ConsumeTest, self).setUp() + self.provider_libjuju = self.libjuju + + def test_consume( + self, + mock_consume, + mock_disconnect_controller, + mock_disconnect_model, + mock_get_model, + mock_get_controller, + ): + self_controller = juju.controller.Controller() + provider_controller = juju.controller.Controller() + mock_get_controller.side_effect = [self_controller, provider_controller] + mock_get_model.return_value = juju.model.Model() + + self.loop.run_until_complete( + self.libjuju.consume( + "model_name", + Offer(self.offer_url, vca_id="vca-id"), + self.provider_libjuju, + ) + ) + mock_consume.assert_called_once_with( + "admin/model.offer_name", + application_alias="offer_name-model-vca-id", + controller=provider_controller, + ) + mock_disconnect_model.assert_called_once() + self.assertEqual(mock_disconnect_controller.call_count, 2) + + def test_parsing_error_exception( + self, + mock_consume, + mock_disconnect_controller, + mock_disconnect_model, + mock_get_model, + mock_get_controller, + ): + mock_get_controller.return_value = juju.controller.Controller() + mock_get_model.return_value = juju.model.Model() mock_consume.side_effect = juju.offerendpoints.ParseError("") with self.assertRaises(juju.offerendpoints.ParseError): self.loop.run_until_complete( - self.libjuju.consume("offer_url", "model_name") + self.libjuju.consume( + "model_name", Offer(self.offer_url), self.provider_libjuju + ) ) mock_consume.assert_called_once() mock_disconnect_model.assert_called_once() - mock_disconnect_controller.assert_called_once() + self.assertEqual(mock_disconnect_controller.call_count, 2) def test_juju_error_exception( self, @@ -1489,11 +2019,13 @@ class ConsumeTest(LibjujuTestCase): with self.assertRaises(juju.errors.JujuError): self.loop.run_until_complete( - self.libjuju.consume("offer_url", "model_name") + self.libjuju.consume( + "model_name", Offer(self.offer_url), self.provider_libjuju + ) ) mock_consume.assert_called_once() mock_disconnect_model.assert_called_once() - mock_disconnect_controller.assert_called_once() + self.assertEqual(mock_disconnect_controller.call_count, 2) def test_juju_api_error_exception( self, @@ -1511,11 +2043,13 @@ class ConsumeTest(LibjujuTestCase): with self.assertRaises(juju.errors.JujuAPIError): self.loop.run_until_complete( - self.libjuju.consume("offer_url", "model_name") + self.libjuju.consume( + "model_name", Offer(self.offer_url), self.provider_libjuju + ) ) mock_consume.assert_called_once() mock_disconnect_model.assert_called_once() - mock_disconnect_controller.assert_called_once() + self.assertEqual(mock_disconnect_controller.call_count, 2) @asynctest.mock.patch("n2vc.libjuju.Libjuju.get_k8s_cloud_credential") @@ -1912,13 +2446,7 @@ class ScaleApplicationTest(LibjujuTestCase): ): mock_get_model.return_value = juju.model.Model() mock_get_application.return_value = FakeApplication() - self.loop.run_until_complete( - self.libjuju.scale_application( - "model", - "app", - 2 - ) - ) + self.loop.run_until_complete(self.libjuju.scale_application("model", "app", 2)) mock_wait_for_model.assert_called_once() mock_disconnect_controller.assert_called_once() mock_disconnect_model.assert_called_once() @@ -1936,34 +2464,25 @@ class ScaleApplicationTest(LibjujuTestCase): mock_get_model.return_value = juju.model.Model() with self.assertRaises(JujuApplicationNotFound): self.loop.run_until_complete( - self.libjuju.scale_application( - "model", - "app", - 2 - ) + self.libjuju.scale_application("model", "app", 2) ) mock_disconnect_controller.assert_called() mock_disconnect_model.assert_called() def test_exception( - self, - mock_wait_for, - mock_disconnect_controller, - mock_disconnect_model, - mock_get_application, - mock_get_model, - mock_get_controller, + self, + mock_wait_for, + mock_disconnect_controller, + mock_disconnect_model, + mock_get_application, + mock_get_model, + mock_get_controller, ): mock_get_model.return_value = None mock_get_application.return_value = FakeApplication() with self.assertRaises(Exception): self.loop.run_until_complete( - self.libjuju.scale_application( - "model", - "app", - 2, - total_timeout=0 - ) + self.libjuju.scale_application("model", "app", 2, total_timeout=0) ) mock_disconnect_controller.assert_called_once() @@ -1973,7 +2492,7 @@ class GetUnitNumberTest(LibjujuTestCase): def setUp(self): super(GetUnitNumberTest, self).setUp() - def test_succesful_get_unit_number( + def test_successful_get_unit_number( self, mock_get_applications, ): @@ -1990,3 +2509,287 @@ class GetUnitNumberTest(LibjujuTestCase): model = juju.model.Model() result = self.libjuju._get_application_count(model, "app") self.assertEqual(result, None) + + +@asynctest.mock.patch("juju.model.Model.machines", new_callable=asynctest.PropertyMock) +class GetMachineInfoTest(LibjujuTestCase): + def setUp(self): + super(GetMachineInfoTest, self).setUp() + + def test_successful( + self, + mock_machines, + ): + machine_id = "existing_machine" + model = juju.model.Model() + mock_machines.return_value = {"existing_machine": FakeManualMachine()} + machine, series = self.libjuju._get_machine_info( + machine_id=machine_id, + model=model, + ) + self.assertIsNotNone(machine, series) + + def test_exception( + self, + mock_machines, + ): + machine_id = "not_existing_machine" + machine = series = None + model = juju.model.Model() + mock_machines.return_value = {"existing_machine": FakeManualMachine()} + with self.assertRaises(JujuMachineNotFound): + machine, series = self.libjuju._get_machine_info( + machine_id=machine_id, + model=model, + ) + self.assertIsNone(machine, series) + + +class GetUnitTest(LibjujuTestCase): + def setUp(self): + super(GetUnitTest, self).setUp() + + def test_successful(self): + result = self.libjuju._get_unit(FakeApplication(), "existing_machine_id") + self.assertIsInstance(result, FakeUnit) + + def test_return_none(self): + result = self.libjuju._get_unit(FakeApplication(), "not_existing_machine_id") + self.assertIsNone(result) + + +@asynctest.mock.patch("n2vc.libjuju.Libjuju.get_controller") +@asynctest.mock.patch("n2vc.libjuju.Libjuju.get_model") +@asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_model") +@asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_controller") +@asynctest.mock.patch("n2vc.libjuju.Libjuju._get_application") +class CheckApplicationExists(LibjujuTestCase): + def setUp(self): + super(CheckApplicationExists, self).setUp() + + def test_successful( + self, + mock_get_application, + mock_disconnect_controller, + mock_disconnect_model, + mock_get_model, + mock_get_controller, + ): + mock_get_model.return_value = juju.model.Model() + mock_get_application.return_value = FakeApplication() + result = self.loop.run_until_complete( + self.libjuju.check_application_exists( + "model", + "app", + ) + ) + self.assertEqual(result, True) + + mock_get_application.assert_called_once() + mock_get_controller.assert_called_once() + mock_get_model.assert_called_once() + mock_disconnect_controller.assert_called_once() + mock_disconnect_model.assert_called_once() + + def test_no_application( + self, + mock_get_application, + mock_disconnect_controller, + mock_disconnect_model, + mock_get_model, + mock_get_controller, + ): + mock_get_model.return_value = juju.model.Model() + mock_get_application.return_value = None + result = self.loop.run_until_complete( + self.libjuju.check_application_exists( + "model", + "app", + ) + ) + self.assertEqual(result, False) + + mock_get_application.assert_called_once() + mock_get_controller.assert_called_once() + mock_get_model.assert_called_once() + mock_disconnect_controller.assert_called_once() + mock_disconnect_model.assert_called_once() + + +@asynctest.mock.patch("n2vc.libjuju.Libjuju.get_controller") +@asynctest.mock.patch("n2vc.libjuju.Libjuju.get_model") +@asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_model") +@asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_controller") +@asynctest.mock.patch("n2vc.libjuju.Libjuju._get_application") +@asynctest.mock.patch("n2vc.libjuju.Libjuju._get_machine_info") +class AddUnitTest(LibjujuTestCase): + def setUp(self): + super(AddUnitTest, self).setUp() + + @asynctest.mock.patch("n2vc.juju_watcher.JujuModelWatcher.wait_for") + @asynctest.mock.patch("asyncio.sleep") + def test_successful( + self, + mock_sleep, + mock_wait_for, + mock_get_machine_info, + mock_get_application, + mock_disconnect_controller, + mock_disconnect_model, + mock_get_model, + mock_get_controller, + ): + mock_get_model.return_value = juju.model.Model() + mock_get_application.return_value = FakeApplication() + mock_get_machine_info.return_value = FakeMachine(), "series" + self.loop.run_until_complete( + self.libjuju.add_unit( + "existing_app", + "model", + "machine", + ) + ) + + mock_wait_for.assert_called_once() + mock_get_application.assert_called_once() + mock_get_controller.assert_called_once() + mock_get_model.assert_called_once() + mock_disconnect_controller.assert_called_once() + mock_disconnect_model.assert_called_once() + + def test_no_app( + self, + mock_get_machine_info, + mock_get_application, + mock_disconnect_controller, + mock_disconnect_model, + mock_get_model, + mock_get_controller, + ): + mock_get_model.return_value = juju.model.Model() + mock_get_application.return_value = None + with self.assertRaises(JujuApplicationNotFound): + self.loop.run_until_complete( + self.libjuju.add_unit( + "existing_app", + "model", + "machine", + ) + ) + + mock_get_application.assert_called_once() + mock_get_controller.assert_called_once() + mock_get_model.assert_called_once() + mock_disconnect_controller.assert_called_once() + mock_disconnect_model.assert_called_once() + + def test_no_machine( + self, + mock_get_machine_info, + mock_get_application, + mock_disconnect_controller, + mock_disconnect_model, + mock_get_model, + mock_get_controller, + ): + mock_get_model.return_value = juju.model.Model() + mock_get_application.return_value = FakeApplication() + mock_get_machine_info.side_effect = JujuMachineNotFound() + with self.assertRaises(JujuMachineNotFound): + self.loop.run_until_complete( + self.libjuju.add_unit( + "existing_app", + "model", + "machine", + ) + ) + + mock_get_application.assert_called_once() + mock_get_controller.assert_called_once() + mock_get_model.assert_called_once() + mock_disconnect_controller.assert_called_once() + mock_disconnect_model.assert_called_once() + + +@asynctest.mock.patch("n2vc.libjuju.Libjuju.get_controller") +@asynctest.mock.patch("n2vc.libjuju.Libjuju.get_model") +@asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_model") +@asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_controller") +@asynctest.mock.patch("n2vc.libjuju.Libjuju._get_application") +@asynctest.mock.patch("n2vc.libjuju.Libjuju._get_unit") +class DestroyUnitTest(LibjujuTestCase): + def setUp(self): + super(DestroyUnitTest, self).setUp() + + @asynctest.mock.patch("asyncio.sleep") + def test_successful( + self, + mock_sleep, + mock_get_unit, + mock_get_application, + mock_disconnect_controller, + mock_disconnect_model, + mock_get_model, + mock_get_controller, + ): + mock_get_model.return_value = juju.model.Model() + mock_get_application.return_value = FakeApplication() + + self.loop.run_until_complete( + self.libjuju.destroy_unit("app", "model", "machine", 0) + ) + + mock_get_unit.assert_called() + mock_get_application.assert_called_once() + mock_get_controller.assert_called_once() + mock_get_model.assert_called_once() + mock_disconnect_controller.assert_called_once() + mock_disconnect_model.assert_called_once() + + def test_no_app( + self, + mock_get_unit, + mock_get_application, + mock_disconnect_controller, + mock_disconnect_model, + mock_get_model, + mock_get_controller, + ): + mock_get_model.return_value = juju.model.Model() + mock_get_application.return_value = None + + with self.assertRaises(JujuApplicationNotFound): + self.loop.run_until_complete( + self.libjuju.destroy_unit("app", "model", "machine") + ) + + mock_get_application.assert_called_once() + mock_get_controller.assert_called_once() + mock_get_model.assert_called_once() + mock_disconnect_controller.assert_called_once() + mock_disconnect_model.assert_called_once() + + def test_no_unit( + self, + mock_get_unit, + mock_get_application, + mock_disconnect_controller, + mock_disconnect_model, + mock_get_model, + mock_get_controller, + ): + mock_get_model.return_value = juju.model.Model() + mock_get_application.return_value = FakeApplication() + mock_get_unit.return_value = None + + with self.assertRaises(JujuError): + self.loop.run_until_complete( + self.libjuju.destroy_unit("app", "model", "machine") + ) + + mock_get_unit.assert_called_once() + mock_get_application.assert_called_once() + mock_get_controller.assert_called_once() + mock_get_model.assert_called_once() + mock_disconnect_controller.assert_called_once() + mock_disconnect_model.assert_called_once()