Add more backoff functions

This patch introduces more retry backoff function the delivery 
retry policy into Zaqar. It will work when Zaqar failed to send 
the notification to the subscriber. Users can define the retry 
backoff function in the options of subscription or metadata 
of queue.

Change-Id: I0bffd9249f8a0d466ecea1ac36d8adc37b742238
Implement: blueprint support-more-backoff-functions
This commit is contained in:
gecong1973 2017-10-12 19:34:46 -07:00 committed by gecong
parent fa44489ede
commit ef8631899e
5 changed files with 72 additions and 12 deletions

View File

@ -46,7 +46,8 @@ Webhook
- 'minimum_delay' and 'maximum_delay' mean delay time in seconds.
- 'retry_backoff_function' mean name of retry backoff function.
There will be a enum in Zaqar that contain all valid values.
At first step, Zaqar only supports one function: 'linear'.
Zaqar now supports retry backoff function including 'linear',
'arithmetic','geometric' and 'exponential'.
- 'minimum_delay_retries' and 'maximum_delay_retries' mean the number of
retries with 'minimum_delay' or 'maximum_delay' delay time.

View File

@ -0,0 +1,7 @@
---
features:
- Support more retry backoff function in webhook type. It will work
when Zaqar failed to send the notification to the subscriber.
Users can define the retry backoff function in metadata of queue.
There are four retry backoff functions including 'linear',
'arithmetic', 'geometric' and 'exponential'.

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import math
import time
import json
@ -27,7 +28,33 @@ LOG = logging.getLogger(__name__)
def _Linear_function(minimum_delay, maximum_delay, times):
return range(minimum_delay, maximum_delay, times)
RETRY_BACKOFF_FUNCTION_MAP = {'linear': _Linear_function}
def _Geometric_function(minimum_delay, maximum_delay, times):
x_max = int((maximum_delay - minimum_delay) / times)
k = math.pow(10, math.log10(maximum_delay/minimum_delay)/(x_max-1))
xarray = range(1, x_max+1)
return [int(minimum_delay*math.pow(k, a-1)) for a in xarray]
def _Exponential_function(minimum_delay, maximum_delay, times):
x_max = int((maximum_delay - minimum_delay) / times)
k = math.pow(10, math.log10(maximum_delay/minimum_delay)/(x_max-1))
p = minimum_delay/k
xarray = range(1, x_max+1)
return [int(p*math.pow(k, a)) for a in xarray]
def _Arithmetic_function(minimum_delay, maximum_delay, times):
x_max = int((maximum_delay - minimum_delay) / times)
d = 2.0 * (maximum_delay - minimum_delay) / (x_max * (x_max - 1))
xarray = range(1, x_max+1)
return [int(minimum_delay+(a-1)*a*d/2) for a in xarray]
RETRY_BACKOFF_FUNCTION_MAP = {'linear': _Linear_function,
'arithmetic': _Arithmetic_function,
'geometric': _Geometric_function,
'exponential': _Exponential_function}
class WebhookTask(object):
@ -65,9 +92,8 @@ class WebhookTask(object):
time.sleep(retry_policy.get('minimum_delay', consts.MINIMUM_DELAY))
if self._post_request_success(subscriber, data, headers):
return
# Backoff Phase: Linear retry
# TODO(wanghao): Now we only support the linear function, we should
# support more in Queens.
# Now we support linear,arithmetic,
# exponential and geometric retry backoff function.
retry_function = retry_policy.get('retry_backoff_function', 'linear')
backoff_function = RETRY_BACKOFF_FUNCTION_MAP[retry_function]
for i in backoff_function(retry_policy.get('minimum_delay',
@ -75,8 +101,8 @@ class WebhookTask(object):
retry_policy.get('maximum_delay',
consts.MAXIMUM_DELAY),
consts.LINEAR_INTERVAL):
LOG.debug('Retry with retry_backoff_function, sleep: %s seconds',
i)
LOG.debug('Retry with function:%s, sleep: %s seconds',
retry_function, i)
time.sleep(i)
if self._post_request_success(subscriber, data, headers):
return

View File

@ -21,6 +21,7 @@ import mock
from zaqar.common import urls
from zaqar.notification import notifier
from zaqar.notification.tasks import webhook
from zaqar import tests as testing
@ -433,3 +434,16 @@ class NotifierTest(testing.TestBase):
@ddt.data(False, True)
def test_send_confirm_notification_with_email(self, is_unsub):
self._send_confirm_notification_with_email(is_unsubscribed=is_unsub)
def test_webhook_backoff_function(self):
expect = [10, 12, 14, 18, 22, 27, 33, 40, 49, 60]
sec = webhook._Exponential_function(10, 60, 5)
self.assertEqual(expect, sec)
expect = [20, 22, 25, 29, 33, 37, 42, 48, 54, 62, 70, 80]
sec = webhook._Geometric_function(20, 80, 5)
self.assertEqual(expect, sec)
expect = [30, 30, 32, 34, 37, 41, 46, 51, 57, 64, 72, 80, 90, 100]
sec = webhook._Arithmetic_function(30, 100, 5)
self.assertEqual(expect, sec)

View File

@ -21,8 +21,10 @@ from oslo_config import cfg
from oslo_utils import timeutils
import six
from zaqar.common import consts
from zaqar.i18n import _
MIN_MESSAGE_TTL = 60
MIN_CLAIM_TTL = 60
MIN_CLAIM_GRACE = 60
@ -249,11 +251,11 @@ class Validator(object):
if retry_value and not isinstance(retry_value, str):
msg = _('retry_backoff_function must be a string.')
raise ValidationFailed(msg)
# TODO(wanghao): Now we only support linear function.
# This will be removed after we support more functions.
if retry_value and retry_value != 'linear':
msg = _('retry_backoff_function only supports linear '
'now.')
# Now we support linear, arithmetic, exponential
# and geometric retry backoff function.
fun = {'linear', 'arithmetic', 'exponential', 'geometric'}
if retry_value and retry_value not in fun:
msg = _('invalid retry_backoff_function.')
raise ValidationFailed(msg)
elif key == 'ignore_subscription_override':
if retry_value and not isinstance(retry_value, bool):
@ -264,6 +266,16 @@ class Validator(object):
if retry_value and not isinstance(retry_value, int):
msg = _('Retry policy: %s must be a integer.') % key
raise ValidationFailed(msg)
min_delay = retry_policy.get('minimum_delay',
consts.MINIMUM_DELAY)
max_delay = retry_policy.get('maximum_delay',
consts.MAXIMUM_DELAY)
if max_delay < min_delay:
msg = _('minimum_delay must less than maximum_delay.')
raise ValidationFailed(msg)
if ((max_delay - min_delay) < 2*consts.LINEAR_INTERVAL):
msg = _('invalid minimum_delay and maximum_delay.')
raise ValidationFailed(msg)
def queue_patching(self, request, changes):
washed_changes = []