diff --git a/unit_tests/test_zaza_model.py b/unit_tests/test_zaza_model.py index fb112d9..df5c068 100644 --- a/unit_tests/test_zaza_model.py +++ b/unit_tests/test_zaza_model.py @@ -91,6 +91,8 @@ class TestModel(ut_utils.BaseTestCase): self.unit2.run_action.side_effect = _run_action self.unit1.is_leader_from_status.side_effect = _is_leader(False) self.unit2.is_leader_from_status.side_effect = _is_leader(True) + self.unit1.data = {'agent-status': {'current': 'idle'}} + self.unit2.data = {'agent-status': {'current': 'idle'}} self.units = [self.unit1, self.unit2] self.relation1 = mock.MagicMock() self.relation1.id = 42 @@ -893,6 +895,29 @@ disk_formats = ami,ari,aki,vhd,vmdk,raw,qcow2,vdi,iso,root-tar 'active', timeout=0.1) + def test_wait_for_agent_status(self): + async def _block_until(f, timeout=None): + if not f(): + raise asyncio.futures.TimeoutError + self.patch_object(model, 'get_juju_model', return_value='mname') + self.patch_object(model, 'Model') + self.unit1.data = {'agent-status': {'current': 'idle'}} + self.unit2.data = {'agent-status': {'current': 'executing'}} + self.Model.return_value = self.Model_mock + self.Model_mock.block_until.side_effect = _block_until + model.wait_for_agent_status(timeout=0.1) + + def test_wait_for_agent_status_timeout(self): + async def _block_until(f, timeout=None): + if not f(): + raise asyncio.futures.TimeoutError + self.patch_object(model, 'get_juju_model', return_value='mname') + self.patch_object(model, 'Model') + self.Model.return_value = self.Model_mock + self.Model_mock.block_until.side_effect = _block_until + with self.assertRaises(asyncio.futures.TimeoutError): + model.wait_for_agent_status(timeout=0.1) + class AsyncModelTests(aiounittest.AsyncTestCase): diff --git a/zaza/charm_tests/test_utils.py b/zaza/charm_tests/test_utils.py index 5917f63..8b40e74 100644 --- a/zaza/charm_tests/test_utils.py +++ b/zaza/charm_tests/test_utils.py @@ -77,6 +77,10 @@ class OpenStackBaseTest(unittest.TestCase): alternate_config, model_name=self.model_name) + logging.debug( + 'Waiting for units to execute config-changed hook') + model.wait_for_agent_status(model_name=self.model_name) + logging.debug( 'Waiting for units to reach target states') model.wait_for_application_states( diff --git a/zaza/model.py b/zaza/model.py index 6a89720..4fe1277 100644 --- a/zaza/model.py +++ b/zaza/model.py @@ -606,6 +606,42 @@ def check_unit_workload_status_message(model, unit, message=None, raise ValueError("Must be called with message or prefixes") +async def async_wait_for_agent_status(model_name=None, status='executing', + timeout=60): + """Wait for at least one unit to enter a specific agent status. + + This is useful for awaiting execution after mutating charm configuration. + + :param model_name: Name of model to query. + :type model_name: str + :param status: The desired agent status we are looking for. + :type status: str + :param timeout: Time to wait for status to be achieved. + :type timeout: int + """ + def one_agent_status(model, status): + check_model_for_hard_errors(model) + for app in model.applications: + for unit in model.applications[app].units: + agent_status = unit.data.get('agent-status', {}) + if agent_status.get('current', None) == status: + break + else: + continue + break + else: + return False + return True + + async with run_in_model(model_name) as model: + logging.info('Waiting for at least one unit with agent status "{}"' + .format(status)) + await model.block_until( + lambda: one_agent_status(model, status), timeout=timeout) + +wait_for_agent_status = sync_wrapper(async_wait_for_agent_status) + + async def async_wait_for_application_states(model_name=None, states=None, timeout=2700): """Wait for model to achieve the desired state.