From 561202994dc290e20a5f15ae8ffd07f20fb84069 Mon Sep 17 00:00:00 2001 From: aktas Date: Fri, 26 Feb 2021 15:32:39 +0300 Subject: [PATCH] Fix 1462 Wait until Juju application is destroyed in n2vc.libjuju.Libjuju.destroy_application() Change-Id: Ie7992f229872ffffbea87ca3e8e20825bfec7b6d Signed-off-by: aktas --- n2vc/libjuju.py | 58 +++++++++++++++++++++----- n2vc/n2vc_juju_conn.py | 11 ++--- n2vc/tests/unit/test_libjuju.py | 72 +++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 16 deletions(-) diff --git a/n2vc/libjuju.py b/n2vc/libjuju.py index 0fb82e7..eb0fa72 100644 --- a/n2vc/libjuju.py +++ b/n2vc/libjuju.py @@ -944,23 +944,59 @@ class Libjuju: finally: await self.disconnect_controller(controller) - async def destroy_application(self, model: Model, application_name: str): + async def destroy_application( + self, model_name: str, application_name: str, total_timeout: float + ): """ Destroy application - :param: model: Model object + :param: model_name: Model name :param: application_name: Application name + :param: total_timeout: Timeout """ - self.log.debug( - "Destroying application {} in model {}".format( - application_name, model.info.name + + controller = await self.get_controller() + model = None + + try: + model = await self.get_model(controller, model_name) + self.log.debug( + "Destroying application {} in model {}".format( + application_name, model_name + ) ) - ) - application = model.applications.get(application_name) - if application: - await application.destroy() - else: - self.log.warning("Application not found: {}".format(application_name)) + application = self._get_application(model, application_name) + if application: + await application.destroy() + else: + self.log.warning("Application not found: {}".format(application_name)) + + self.log.debug( + "Waiting for application {} to be destroyed in model {}...".format( + application_name, model_name + ) + ) + if total_timeout is None: + total_timeout = 3600 + end = time.time() + total_timeout + while time.time() < end: + if not self._get_application(model, application_name): + self.log.debug( + "The application {} was destroyed in model {} ".format( + application_name, model_name + ) + ) + return + await asyncio.sleep(5) + raise Exception( + "Timeout waiting for application {} to be destroyed in model {}".format( + application_name, model_name + ) + ) + finally: + if model is not None: + await self.disconnect_model(model) + await self.disconnect_controller(controller) async def _destroy_pending_machines(self, model: Model, only_manual: bool = False): """ diff --git a/n2vc/n2vc_juju_conn.py b/n2vc/n2vc_juju_conn.py index d31c169..c09c263 100644 --- a/n2vc/n2vc_juju_conn.py +++ b/n2vc/n2vc_juju_conn.py @@ -794,15 +794,16 @@ class N2VCJujuConnector(N2VCConnector): # destroy the model # TODO: should this be removed? await self.libjuju.destroy_model( - model_name=model_name, total_timeout=total_timeout + model_name=model_name, + total_timeout=total_timeout, ) else: - # get juju model and observer - controller = await self.libjuju.get_controller() - model = await self.libjuju.get_model(controller, model_name) # destroy the application await self.libjuju.destroy_application( - model=model, application_name=application_name) + model_name=model_name, + application_name=application_name, + total_timeout=total_timeout, + ) except Exception as e: raise N2VCException( message=( diff --git a/n2vc/tests/unit/test_libjuju.py b/n2vc/tests/unit/test_libjuju.py index 36110f6..29bcb7b 100644 --- a/n2vc/tests/unit/test_libjuju.py +++ b/n2vc/tests/unit/test_libjuju.py @@ -1177,6 +1177,78 @@ class AddRelationTest(LibjujuTestCase): # TODO destroy_model testcase +@asynctest.mock.patch("n2vc.libjuju.Libjuju.get_controller") +@asynctest.mock.patch("n2vc.libjuju.Libjuju.get_model") +@asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_controller") +@asynctest.mock.patch("n2vc.libjuju.Libjuju._get_application") +@asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_model") +class DestroyApplicationTest(LibjujuTestCase): + def setUp(self): + super(DestroyApplicationTest, self).setUp() + + def test_success( + 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 + self.loop.run_until_complete( + self.libjuju.destroy_application( + "existing_model", + "existing_app", + 3600, + ) + ) + mock_get_application.assert_called() + mock_disconnect_controller.assert_called_once() + 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, + ): + mock_get_model.return_value = None + mock_get_application.return_value = None + + self.loop.run_until_complete( + self.libjuju.destroy_application( + "existing_model", + "existing_app", + 3600, + ) + ) + mock_get_application.assert_called() + + def test_exception( + 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 + + with self.assertRaises(Exception): + self.loop.run_until_complete( + self.libjuju.destroy_application( + "existing_model", + "existing_app", + 0, + ) + ) + mock_get_application.assert_called_once() + + # @asynctest.mock.patch("juju.model.Model.get_machines") # @asynctest.mock.patch("logging.Logger.debug") # class DestroyMachineTest(LibjujuTestCase): -- 2.17.1