Copy RetryDecorator from oslo.vmware
RetryDecorator from oslo.vmware needs a better home. It allows users to specify specific exceptions upon which the request can be retried. http://git.openstack.org/cgit/openstack/oslo.vmware/tree/oslo_vmware/api.py#n48 Depends-On: I0f07858e96ea3baf46f8a453e253b9ed29c7f7e2 Depends-On: I33bd2d9dff9cb7dc1a50177db7286b7317966784 Closes-Bug: #1465629 Change-Id: Ic5d57fcf769a4af53cd1cf82a3ca93142dbdb03f
This commit is contained in:
parent
0ea491f882
commit
4d6dd3525e
@ -20,6 +20,7 @@ import sys
|
|||||||
|
|
||||||
from eventlet import event
|
from eventlet import event
|
||||||
from eventlet import greenthread
|
from eventlet import greenthread
|
||||||
|
from oslo_utils import excutils
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
|
|
||||||
from oslo_service._i18n import _LE, _LW, _
|
from oslo_service._i18n import _LE, _LW, _
|
||||||
@ -151,3 +152,82 @@ class DynamicLoopingCall(LoopingCallBase):
|
|||||||
delay = min(delay, periodic_interval_max)
|
delay = min(delay, periodic_interval_max)
|
||||||
return delay
|
return delay
|
||||||
return self._start(_idle_for, initial_delay=initial_delay)
|
return self._start(_idle_for, initial_delay=initial_delay)
|
||||||
|
|
||||||
|
|
||||||
|
class RetryDecorator(object):
|
||||||
|
"""Decorator for retrying a function upon suggested exceptions.
|
||||||
|
|
||||||
|
The decorated function is retried for the given number of times, and the
|
||||||
|
sleep time between the retries is incremented until max sleep time is
|
||||||
|
reached. If the max retry count is set to -1, then the decorated function
|
||||||
|
is invoked indefinitely until an exception is thrown, and the caught
|
||||||
|
exception is not in the list of suggested exceptions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, max_retry_count=-1, inc_sleep_time=10,
|
||||||
|
max_sleep_time=60, exceptions=()):
|
||||||
|
"""Configure the retry object using the input params.
|
||||||
|
|
||||||
|
:param max_retry_count: maximum number of times the given function must
|
||||||
|
be retried when one of the input 'exceptions'
|
||||||
|
is caught. When set to -1, it will be retried
|
||||||
|
indefinitely until an exception is thrown
|
||||||
|
and the caught exception is not in param
|
||||||
|
exceptions.
|
||||||
|
:param inc_sleep_time: incremental time in seconds for sleep time
|
||||||
|
between retries
|
||||||
|
:param max_sleep_time: max sleep time in seconds beyond which the sleep
|
||||||
|
time will not be incremented using param
|
||||||
|
inc_sleep_time. On reaching this threshold,
|
||||||
|
max_sleep_time will be used as the sleep time.
|
||||||
|
:param exceptions: suggested exceptions for which the function must be
|
||||||
|
retried
|
||||||
|
"""
|
||||||
|
self._max_retry_count = max_retry_count
|
||||||
|
self._inc_sleep_time = inc_sleep_time
|
||||||
|
self._max_sleep_time = max_sleep_time
|
||||||
|
self._exceptions = exceptions
|
||||||
|
self._retry_count = 0
|
||||||
|
self._sleep_time = 0
|
||||||
|
|
||||||
|
def __call__(self, f):
|
||||||
|
|
||||||
|
def _func(*args, **kwargs):
|
||||||
|
func_name = f.__name__
|
||||||
|
result = None
|
||||||
|
try:
|
||||||
|
if self._retry_count:
|
||||||
|
LOG.debug("Invoking %(func_name)s; retry count is "
|
||||||
|
"%(retry_count)d.",
|
||||||
|
{'func_name': func_name,
|
||||||
|
'retry_count': self._retry_count})
|
||||||
|
result = f(*args, **kwargs)
|
||||||
|
except self._exceptions:
|
||||||
|
with excutils.save_and_reraise_exception() as ctxt:
|
||||||
|
LOG.warn(_LW("Exception which is in the suggested list of "
|
||||||
|
"exceptions occurred while invoking function:"
|
||||||
|
" %s."),
|
||||||
|
func_name,
|
||||||
|
exc_info=True)
|
||||||
|
if (self._max_retry_count != -1 and
|
||||||
|
self._retry_count >= self._max_retry_count):
|
||||||
|
LOG.error(_LE("Cannot retry upon suggested exception "
|
||||||
|
"since retry count (%(retry_count)d) "
|
||||||
|
"reached max retry count "
|
||||||
|
"(%(max_retry_count)d)."),
|
||||||
|
{'retry_count': self._retry_count,
|
||||||
|
'max_retry_count': self._max_retry_count})
|
||||||
|
else:
|
||||||
|
ctxt.reraise = False
|
||||||
|
self._retry_count += 1
|
||||||
|
self._sleep_time += self._inc_sleep_time
|
||||||
|
return self._sleep_time
|
||||||
|
raise LoopingCallDone(result)
|
||||||
|
|
||||||
|
def func(*args, **kwargs):
|
||||||
|
loop = DynamicLoopingCall(_func, *args, **kwargs)
|
||||||
|
evt = loop.start(periodic_interval_max=self._max_sleep_time)
|
||||||
|
LOG.debug("Waiting for function %s to return.", f.__name__)
|
||||||
|
return evt.wait()
|
||||||
|
|
||||||
|
return func
|
||||||
|
@ -182,3 +182,76 @@ class DynamicLoopingCallTestCase(test_base.BaseTestCase):
|
|||||||
timer.start(initial_delay=3).wait()
|
timer.start(initial_delay=3).wait()
|
||||||
|
|
||||||
sleep_mock.assert_has_calls([mock.call(3), mock.call(1)])
|
sleep_mock.assert_has_calls([mock.call(3), mock.call(1)])
|
||||||
|
|
||||||
|
|
||||||
|
class AnException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UnknownException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class RetryDecoratorTest(test_base.BaseTestCase):
|
||||||
|
"""Tests for retry decorator class."""
|
||||||
|
|
||||||
|
def test_retry(self):
|
||||||
|
result = "RESULT"
|
||||||
|
|
||||||
|
@loopingcall.RetryDecorator()
|
||||||
|
def func(*args, **kwargs):
|
||||||
|
return result
|
||||||
|
|
||||||
|
self.assertEqual(result, func())
|
||||||
|
|
||||||
|
def func2(*args, **kwargs):
|
||||||
|
return result
|
||||||
|
|
||||||
|
retry = loopingcall.RetryDecorator()
|
||||||
|
self.assertEqual(result, retry(func2)())
|
||||||
|
self.assertTrue(retry._retry_count == 0)
|
||||||
|
|
||||||
|
def test_retry_with_expected_exceptions(self):
|
||||||
|
result = "RESULT"
|
||||||
|
responses = [AnException(None),
|
||||||
|
AnException(None),
|
||||||
|
result]
|
||||||
|
|
||||||
|
def func(*args, **kwargs):
|
||||||
|
response = responses.pop(0)
|
||||||
|
if isinstance(response, Exception):
|
||||||
|
raise response
|
||||||
|
return response
|
||||||
|
|
||||||
|
sleep_time_incr = 0.01
|
||||||
|
retry_count = 2
|
||||||
|
retry = loopingcall.RetryDecorator(10, sleep_time_incr, 10,
|
||||||
|
(AnException,))
|
||||||
|
self.assertEqual(result, retry(func)())
|
||||||
|
self.assertTrue(retry._retry_count == retry_count)
|
||||||
|
self.assertEqual(retry_count * sleep_time_incr, retry._sleep_time)
|
||||||
|
|
||||||
|
def test_retry_with_max_retries(self):
|
||||||
|
responses = [AnException(None),
|
||||||
|
AnException(None),
|
||||||
|
AnException(None)]
|
||||||
|
|
||||||
|
def func(*args, **kwargs):
|
||||||
|
response = responses.pop(0)
|
||||||
|
if isinstance(response, Exception):
|
||||||
|
raise response
|
||||||
|
return response
|
||||||
|
|
||||||
|
retry = loopingcall.RetryDecorator(2, 0, 0,
|
||||||
|
(AnException,))
|
||||||
|
self.assertRaises(AnException, retry(func))
|
||||||
|
self.assertTrue(retry._retry_count == 2)
|
||||||
|
|
||||||
|
def test_retry_with_unexpected_exception(self):
|
||||||
|
|
||||||
|
def func(*args, **kwargs):
|
||||||
|
raise UnknownException(None)
|
||||||
|
|
||||||
|
retry = loopingcall.RetryDecorator()
|
||||||
|
self.assertRaises(UnknownException, retry(func))
|
||||||
|
self.assertTrue(retry._retry_count == 0)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user