Improved wait_for benchmark helper
wait_for helper improved to handle and log the status changes of the resource. wait_for now accepts active_statuses, failure_statuses and attribute name to perform status checks on the resource. The wait_for method change is backward compatible so the current usages are not affected. The new methods added to handle specific wait cases: * wait_is_ready - old wait_for symantics. Checks a resource with a user-provided is_ready callable * wait_for_status - implements waiting for specific resource status. Logs the status changes. Change-Id: I04f917033a02ca21fa1ad6eb55fcdb727ce82090
This commit is contained in:
parent
4a2d02b75d
commit
1bd6977194
@ -30,16 +30,28 @@ from rally import exceptions
|
|||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_status(resource):
|
def get_status(resource, status_attr="status"):
|
||||||
# workaround for heat resources - using stack_status instead of status
|
"""Get the status of a given resource object.
|
||||||
if ((hasattr(resource, "stack_status") and
|
|
||||||
isinstance(resource.stack_status, six.string_types))):
|
The status is returned in upper case. The status is checked for the
|
||||||
return resource.stack_status.upper()
|
standard field names with special cases for Heat and Ceilometer.
|
||||||
# workaround for ceilometer alarms - using state instead of status
|
|
||||||
if ((hasattr(resource, "state") and
|
:param resource: The resource object or dict.
|
||||||
isinstance(resource.state, six.string_types))):
|
:param status_attr: Allows to specify non-standard status fields.
|
||||||
return resource.state.upper()
|
:return: The status or "NONE" if it is not available.
|
||||||
return getattr(resource, "status", "NONE").upper()
|
"""
|
||||||
|
|
||||||
|
for s_attr in ["stack_status", "state", status_attr]:
|
||||||
|
status = getattr(resource, s_attr, None)
|
||||||
|
if isinstance(status, six.string_types):
|
||||||
|
return status.upper()
|
||||||
|
|
||||||
|
# Dict case
|
||||||
|
if ((isinstance(resource, dict) and status_attr in resource.keys() and
|
||||||
|
isinstance(resource[status_attr], six.string_types))):
|
||||||
|
return resource[status_attr].upper()
|
||||||
|
|
||||||
|
return "NONE"
|
||||||
|
|
||||||
|
|
||||||
class resource_is(object):
|
class resource_is(object):
|
||||||
@ -87,15 +99,27 @@ def manager_list_size(sizes):
|
|||||||
return _list
|
return _list
|
||||||
|
|
||||||
|
|
||||||
def wait_for(resource, is_ready, update_resource=None, timeout=60,
|
def wait_for(resource, is_ready=None, ready_statuses=None,
|
||||||
check_interval=1):
|
failure_statuses=None, status_attr="status", update_resource=None,
|
||||||
"""Waits for the given resource to come into the desired state.
|
timeout=60, check_interval=1):
|
||||||
|
"""Waits for the given resource to come into the one of the given statuses.
|
||||||
|
|
||||||
Uses the readiness check function passed as a parameter and (optionally)
|
The method can be used to check resource for status with a `is_ready`
|
||||||
a function that updates the resource being waited for.
|
function or with a list of expected statuses and the status attribute
|
||||||
|
|
||||||
|
In case when the is_ready checker is not provided the resource should have
|
||||||
|
status_attr. It may be an object attribute or a dictionary key. The value
|
||||||
|
of the attribute is checked against ready statuses list and failure
|
||||||
|
statuses. In case of a failure the wait exits with an exception. The
|
||||||
|
resource is updated between iterations with an update_resource call.
|
||||||
|
|
||||||
:param is_ready: A predicate that should take the resource object and
|
:param is_ready: A predicate that should take the resource object and
|
||||||
return True iff it is ready to be returned
|
return True iff it is ready to be returned
|
||||||
|
:param ready_statuses: List of statuses which mean that the resource is
|
||||||
|
ready
|
||||||
|
:param failure_statuses: List of statuses which mean that an error has
|
||||||
|
occurred while waiting for the resource
|
||||||
|
:param status_attr: The name of the status attribute of the resource
|
||||||
:param update_resource: Function that should take the resource object
|
:param update_resource: Function that should take the resource object
|
||||||
and return an 'updated' resource. If set to
|
and return an 'updated' resource. If set to
|
||||||
None, no result updating is performed
|
None, no result updating is performed
|
||||||
@ -107,23 +131,110 @@ def wait_for(resource, is_ready, update_resource=None, timeout=60,
|
|||||||
:returns: The "ready" resource object
|
:returns: The "ready" resource object
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if is_ready is not None:
|
||||||
|
return wait_is_ready(resource=resource, is_ready=is_ready,
|
||||||
|
update_resource=update_resource, timeout=timeout,
|
||||||
|
check_interval=check_interval)
|
||||||
|
else:
|
||||||
|
return wait_for_status(resource=resource,
|
||||||
|
ready_statuses=ready_statuses,
|
||||||
|
failure_statuses=failure_statuses,
|
||||||
|
status_attr=status_attr,
|
||||||
|
update_resource=update_resource,
|
||||||
|
timeout=timeout,
|
||||||
|
check_interval=check_interval)
|
||||||
|
|
||||||
|
|
||||||
|
def wait_is_ready(resource, is_ready, update_resource=None,
|
||||||
|
timeout=60, check_interval=1):
|
||||||
|
|
||||||
|
resource_repr = getattr(resource, "name", repr(resource))
|
||||||
start = time.time()
|
start = time.time()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
# NOTE(boden): mitigate 1st iteration waits by updating immediately
|
if update_resource is not None:
|
||||||
if update_resource:
|
|
||||||
resource = update_resource(resource)
|
resource = update_resource(resource)
|
||||||
|
|
||||||
if is_ready(resource):
|
if is_ready(resource):
|
||||||
break
|
return resource
|
||||||
|
|
||||||
time.sleep(check_interval)
|
time.sleep(check_interval)
|
||||||
if time.time() - start > timeout:
|
if time.time() - start > timeout:
|
||||||
raise exceptions.TimeoutException(
|
raise exceptions.TimeoutException(
|
||||||
desired_status=str(is_ready),
|
desired_status=str(is_ready),
|
||||||
resource_name=getattr(resource, "name", repr(resource)),
|
resource_name=resource_repr,
|
||||||
resource_type=resource.__class__.__name__,
|
resource_type=resource.__class__.__name__,
|
||||||
resource_id=getattr(resource, "id", "<no id>"),
|
resource_id=getattr(resource, "id", "<no id>"),
|
||||||
resource_status=get_status(resource))
|
resource_status=get_status(resource))
|
||||||
|
|
||||||
return resource
|
|
||||||
|
def wait_for_status(resource, ready_statuses, failure_statuses=None,
|
||||||
|
status_attr="status", update_resource=None,
|
||||||
|
timeout=60, check_interval=1):
|
||||||
|
|
||||||
|
resource_repr = getattr(resource, "name", repr(resource))
|
||||||
|
if not isinstance(ready_statuses, (set, list, tuple)):
|
||||||
|
raise ValueError("Ready statuses should be supplied as set, list or "
|
||||||
|
"tuple")
|
||||||
|
if failure_statuses and not isinstance(failure_statuses,
|
||||||
|
(set, list, tuple)):
|
||||||
|
raise ValueError("Failure statuses should be supplied as set, list or "
|
||||||
|
"tuple")
|
||||||
|
|
||||||
|
# make all statuses upper case
|
||||||
|
ready_statuses = set([s.upper() for s in ready_statuses or []])
|
||||||
|
failure_statuses = set([s.upper() for s in failure_statuses or []])
|
||||||
|
|
||||||
|
if len(ready_statuses & failure_statuses) > 0:
|
||||||
|
raise ValueError(
|
||||||
|
"Can't wait for resource's %s status. Ready and Failure"
|
||||||
|
"statuses conflict." % resource_repr)
|
||||||
|
if not ready_statuses:
|
||||||
|
raise ValueError(
|
||||||
|
"Can't wait for resource's %s status. No ready "
|
||||||
|
"statuses provided" % resource_repr)
|
||||||
|
if not update_resource:
|
||||||
|
raise ValueError(
|
||||||
|
"Can't wait for resource's %s status. No update method."
|
||||||
|
% resource_repr)
|
||||||
|
|
||||||
|
start = time.time()
|
||||||
|
|
||||||
|
latest_status = get_status(resource, status_attr)
|
||||||
|
latest_status_update = start
|
||||||
|
|
||||||
|
while True:
|
||||||
|
resource = update_resource(resource)
|
||||||
|
status = get_status(resource, status_attr)
|
||||||
|
|
||||||
|
if status != latest_status:
|
||||||
|
current_time = time.time()
|
||||||
|
delta = current_time - latest_status_update
|
||||||
|
LOG.debug(
|
||||||
|
"Waiting for resource %(resource)s. Status changed: "
|
||||||
|
"%(latest)s => %(current)s in %(delta)s" %
|
||||||
|
{"resource": resource_repr, "latest": latest_status,
|
||||||
|
"current": status, "delta": delta})
|
||||||
|
|
||||||
|
latest_status = status
|
||||||
|
latest_status_update = current_time
|
||||||
|
|
||||||
|
if status in ready_statuses:
|
||||||
|
return resource
|
||||||
|
if status in failure_statuses:
|
||||||
|
raise exceptions.GetResourceErrorStatus(
|
||||||
|
resource=resource,
|
||||||
|
status=status,
|
||||||
|
fault="Status in failure list %s" % str(failure_statuses))
|
||||||
|
|
||||||
|
time.sleep(check_interval)
|
||||||
|
if time.time() - start > timeout:
|
||||||
|
raise exceptions.TimeoutException(
|
||||||
|
desired_status=ready_statuses,
|
||||||
|
resource_name=resource_repr,
|
||||||
|
resource_type=resource.__class__.__name__,
|
||||||
|
resource_id=getattr(resource, "id", "<no id>"),
|
||||||
|
resource_status=get_status(resource))
|
||||||
|
|
||||||
|
|
||||||
def wait_for_delete(resource, update_resource=None, timeout=60,
|
def wait_for_delete(resource, update_resource=None, timeout=60,
|
||||||
|
@ -203,15 +203,17 @@ class WaitForTestCase(test.TestCase):
|
|||||||
|
|
||||||
def test_wait_for_with_updater(self):
|
def test_wait_for_with_updater(self):
|
||||||
loaded_resource = utils.wait_for(self.resource,
|
loaded_resource = utils.wait_for(self.resource,
|
||||||
self.fake_checker_delayed,
|
is_ready=self.fake_checker_delayed,
|
||||||
self.fake_updater,
|
update_resource=self.fake_updater,
|
||||||
1, self.load_secs / 3)
|
timeout=1,
|
||||||
|
check_interval=self.load_secs / 3)
|
||||||
self.assertEqual(loaded_resource, self.resource)
|
self.assertEqual(loaded_resource, self.resource)
|
||||||
|
|
||||||
def test_wait_for_no_updater(self):
|
def test_wait_for_no_updater(self):
|
||||||
loaded_resource = utils.wait_for(self.resource,
|
loaded_resource = utils.wait_for(self.resource,
|
||||||
self.fake_checker_delayed,
|
is_ready=self.fake_checker_delayed,
|
||||||
None, 1, self.load_secs / 3)
|
update_resource=None, timeout=1,
|
||||||
|
check_interval=self.load_secs / 3)
|
||||||
self.assertEqual(loaded_resource, self.resource)
|
self.assertEqual(loaded_resource, self.resource)
|
||||||
|
|
||||||
def test_wait_for_timeout_failure(self):
|
def test_wait_for_timeout_failure(self):
|
||||||
@ -222,9 +224,9 @@ class WaitForTestCase(test.TestCase):
|
|||||||
is_ready = utils.resource_is("fake_new_status")
|
is_ready = utils.resource_is("fake_new_status")
|
||||||
exc = self.assertRaises(
|
exc = self.assertRaises(
|
||||||
exceptions.TimeoutException, utils.wait_for,
|
exceptions.TimeoutException, utils.wait_for,
|
||||||
self.resource, is_ready,
|
self.resource, is_ready=is_ready,
|
||||||
self.fake_updater, self.load_secs,
|
update_resource=self.fake_updater, timeout=self.load_secs,
|
||||||
self.load_secs / 3)
|
check_interval=self.load_secs / 3)
|
||||||
|
|
||||||
self.assertEqual(exc.kwargs["resource_name"], "fake_name")
|
self.assertEqual(exc.kwargs["resource_name"], "fake_name")
|
||||||
self.assertEqual(exc.kwargs["resource_id"], "fake_id")
|
self.assertEqual(exc.kwargs["resource_id"], "fake_id")
|
||||||
@ -398,3 +400,61 @@ class ActionBuilderTestCase(test.TestCase):
|
|||||||
for i in range(3):
|
for i in range(3):
|
||||||
mock_calls.append(mock.call("two", "three", c=3, d=4))
|
mock_calls.append(mock.call("two", "three", c=3, d=4))
|
||||||
mock_action_two.assert_has_calls(mock_calls)
|
mock_action_two.assert_has_calls(mock_calls)
|
||||||
|
|
||||||
|
|
||||||
|
class WaitForStatusTestCase(test.TestCase):
|
||||||
|
|
||||||
|
def test_wrong_ready_statuses_type(self):
|
||||||
|
self.assertRaises(ValueError,
|
||||||
|
utils.wait_for, {}, ready_statuses="abc")
|
||||||
|
|
||||||
|
def test_wrong_failure_statuses_type(self):
|
||||||
|
self.assertRaises(ValueError,
|
||||||
|
utils.wait_for, {}, ready_statuses=["abc"],
|
||||||
|
failure_statuses="abc")
|
||||||
|
|
||||||
|
def test_no_ready_statuses(self):
|
||||||
|
self.assertRaises(ValueError,
|
||||||
|
utils.wait_for, {}, ready_statuses=[])
|
||||||
|
|
||||||
|
def test_no_update(self):
|
||||||
|
self.assertRaises(ValueError,
|
||||||
|
utils.wait_for, {}, ready_statuses=["ready"])
|
||||||
|
|
||||||
|
@mock.patch("rally.benchmark.utils.time.sleep")
|
||||||
|
def test_exit_instantly(self, mock_sleep):
|
||||||
|
res = {"status": "ready"}
|
||||||
|
upd = mock.MagicMock(return_value=res)
|
||||||
|
|
||||||
|
utils.wait_for(resource=res, ready_statuses=["ready"],
|
||||||
|
update_resource=upd)
|
||||||
|
|
||||||
|
upd.assert_called_once_with(res)
|
||||||
|
self.assertFalse(mock_sleep.called)
|
||||||
|
|
||||||
|
@mock.patch("rally.benchmark.utils.time.sleep")
|
||||||
|
@mock.patch("rally.benchmark.utils.time.time", return_value=1)
|
||||||
|
def test_wait_successful(self, mock_time, mock_sleep):
|
||||||
|
res = {"status": "not_ready"}
|
||||||
|
upd = mock.MagicMock(side_effect=[{"status": "not_ready"},
|
||||||
|
{"status": "not_ready_yet"},
|
||||||
|
{"status": "still_not_ready"},
|
||||||
|
{"status": "almost_ready"},
|
||||||
|
{"status": "ready"}])
|
||||||
|
utils.wait_for(resource=res, ready_statuses=["ready"],
|
||||||
|
update_resource=upd)
|
||||||
|
upd.assert_has_calls([mock.call({"status": "not_ready"}),
|
||||||
|
mock.call({"status": "not_ready"}),
|
||||||
|
mock.call({"status": "not_ready_yet"}),
|
||||||
|
mock.call({"status": "still_not_ready"}),
|
||||||
|
mock.call({"status": "almost_ready"})])
|
||||||
|
|
||||||
|
@mock.patch("rally.benchmark.utils.time.sleep")
|
||||||
|
@mock.patch("rally.benchmark.utils.time.time", return_value=1)
|
||||||
|
def test_wait_failure(self, mock_time, mock_sleep):
|
||||||
|
res = {"status": "not_ready"}
|
||||||
|
upd = mock.MagicMock(side_effect=[{"status": "not_ready"},
|
||||||
|
{"status": "fail"}])
|
||||||
|
self.assertRaises(exceptions.GetResourceErrorStatus, utils.wait_for,
|
||||||
|
resource=res, ready_statuses=["ready"],
|
||||||
|
failure_statuses=["fail"], update_resource=upd)
|
||||||
|
Loading…
Reference in New Issue
Block a user