Allow endless retry loops in the utility function

This can be used if an endless loop is needed.
Also add a new parameter to allow a maximum backoff sleep time.

Partial-Bug: #1690159
Change-Id: Ib544b5bd4781d116dd3dffc8f35f43323cc9e2db
This commit is contained in:
Thomas Bechtold 2017-05-17 13:48:17 +02:00 committed by Jan Provaznik
parent d98455163f
commit 1cf5ccdbdd
2 changed files with 32 additions and 6 deletions

View File

@ -649,6 +649,28 @@ class TestRetryDecorator(test.TestCase):
self.assertRaises(ValueError, raise_unexpected_error) self.assertRaises(ValueError, raise_unexpected_error)
self.assertFalse(mock_sleep.called) self.assertFalse(mock_sleep.called)
def test_wrong_retries_num(self):
self.assertRaises(ValueError, utils.retry, exception.ManilaException,
retries=-1)
def test_max_backoff_sleep(self):
self.counter = 0
with mock.patch.object(time, 'sleep') as mock_sleep:
@utils.retry(exception.ManilaException,
retries=0,
backoff_rate=2,
backoff_sleep_max=4)
def fails_then_passes():
self.counter += 1
if self.counter < 5:
raise exception.ManilaException(data='fake')
else:
return 'success'
self.assertEqual('success', fails_then_passes())
mock_sleep.assert_has_calls(map(mock.call, [2, 4, 4, 4]))
@ddt.ddt @ddt.ddt
class RequireDriverInitializedTestCase(test.TestCase): class RequireDriverInitializedTestCase(test.TestCase):

View File

@ -432,7 +432,7 @@ class ComparableMixin(object):
def retry(exception, interval=1, retries=10, backoff_rate=2, def retry(exception, interval=1, retries=10, backoff_rate=2,
wait_random=False): wait_random=False, backoff_sleep_max=None):
"""A wrapper around retrying library. """A wrapper around retrying library.
This decorator allows to log and to check 'retries' input param. This decorator allows to log and to check 'retries' input param.
@ -445,12 +445,13 @@ def retry(exception, interval=1, retries=10, backoff_rate=2,
:param interval: param 'interval' is used to calculate time interval :param interval: param 'interval' is used to calculate time interval
between retries: between retries:
interval * backoff_rate ^ previous_attempt_number interval * backoff_rate ^ previous_attempt_number
:param retries: number of retries. :param retries: number of retries. Use 0 for an infinite retry loop.
:param backoff_rate: param 'backoff_rate' is used to calculate time :param backoff_rate: param 'backoff_rate' is used to calculate time
interval between retries: interval between retries:
interval * backoff_rate ^ previous_attempt_number interval * backoff_rate ^ previous_attempt_number
:param wait_random: boolean value to enable retry with random wait timer. :param wait_random: boolean value to enable retry with random wait timer.
:param backoff_sleep_max: Maximum number of seconds for the calculated
backoff sleep. Use None if no maximum is needed.
""" """
def _retry_on_exception(e): def _retry_on_exception(e):
return isinstance(e, exception) return isinstance(e, exception)
@ -464,6 +465,9 @@ def retry(exception, interval=1, retries=10, backoff_rate=2,
else: else:
wait_val = wait_for * 1000.0 wait_val = wait_for * 1000.0
if backoff_sleep_max:
wait_val = min(backoff_sleep_max * 1000.0, wait_val)
LOG.debug("Sleeping for %s seconds.", (wait_val / 1000.0)) LOG.debug("Sleeping for %s seconds.", (wait_val / 1000.0))
return wait_val return wait_val
@ -472,11 +476,11 @@ def retry(exception, interval=1, retries=10, backoff_rate=2,
LOG.debug("Failed attempt %s", previous_attempt_number) LOG.debug("Failed attempt %s", previous_attempt_number)
LOG.debug("Have been at this for %s seconds", LOG.debug("Have been at this for %s seconds",
delay_since_first_attempt) delay_since_first_attempt)
return previous_attempt_number == retries return retries > 0 and previous_attempt_number == retries
if retries < 1: if retries < 0:
raise ValueError(_('Retries must be greater than or ' raise ValueError(_('Retries must be greater than or '
'equal to 1 (received: %s).') % retries) 'equal to 0 (received: %s).') % retries)
def _decorator(f): def _decorator(f):