# -*- coding: utf-8 -*- """\ © Copyright. All rights reserved. """ from __future__ import unicode_literals from datetime import datetime, timedelta from functools import wraps import logging LOG = logging.getLogger(__name__) class Retry: """Ops decorated with @retry can return a Retry instance to request retry with the interval and timeout as specified in the decorator. See also the RETRY sentinel if there is no intermediate_result. """ def __init__(self, intermediate_result=None): if not (intermediate_result is None or isinstance(intermediate_result, dict)): raise TypeError('Retry intermediate_result must be dictionary ' 'but got %s' % type(intermediate_result)) self.intermediate_result = intermediate_result def __str__(self): return 'Retry ' + str(self.__dict__) RETRY = Retry() # A Retry that can be returned from ops decorated with @retry # when there is no intermediate_result. def retry(interval=2, timeout=60): """Op decorator that returns a retry request to Nydus when the op returns a Retry instance. Op will be retried by Nydus after `interval` seconds. Returns failure at the specified timeout (seconds). """ def deco(original): @wraps(original) def wrapper(*args, **kw): intermediate_result = kw.get('intermediate_result', None) now = datetime.utcnow() timeout_abs = (intermediate_result and intermediate_result.get('timeout') or now + timedelta(seconds=timeout)) if now >= timeout_abs: msg = 'Timeout waiting for {} ({}s)'.format(getattr(original, '__name__', '?'), timeout) LOG.error(msg) return False, msg result = original(*args, **kw) if isinstance(result, Retry): ir = result.intermediate_result or {} ir.setdefault('timeout', timeout_abs) return False, ir, interval return result return wrapper return deco