Revision 79d22269 lib/utils/__init__.py
b/lib/utils/__init__.py | ||
---|---|---|
63 | 63 |
from ganeti import compat |
64 | 64 |
|
65 | 65 |
from ganeti.utils.algo import * # pylint: disable-msg=W0401 |
66 |
from ganeti.utils.retry import * # pylint: disable-msg=W0401 |
|
66 | 67 |
|
67 | 68 |
_locksheld = [] |
68 | 69 |
_re_shell_unquoted = re.compile('^[-.,=:/_+@A-Za-z0-9]+$') |
... | ... | |
3353 | 3354 |
return value |
3354 | 3355 |
|
3355 | 3356 |
|
3356 |
class RetryTimeout(Exception): |
|
3357 |
"""Retry loop timed out. |
|
3358 |
|
|
3359 |
Any arguments which was passed by the retried function to RetryAgain will be |
|
3360 |
preserved in RetryTimeout, if it is raised. If such argument was an exception |
|
3361 |
the RaiseInner helper method will reraise it. |
|
3362 |
|
|
3363 |
""" |
|
3364 |
def RaiseInner(self): |
|
3365 |
if self.args and isinstance(self.args[0], Exception): |
|
3366 |
raise self.args[0] |
|
3367 |
else: |
|
3368 |
raise RetryTimeout(*self.args) |
|
3369 |
|
|
3370 |
|
|
3371 |
class RetryAgain(Exception): |
|
3372 |
"""Retry again. |
|
3373 |
|
|
3374 |
Any arguments passed to RetryAgain will be preserved, if a timeout occurs, as |
|
3375 |
arguments to RetryTimeout. If an exception is passed, the RaiseInner() method |
|
3376 |
of the RetryTimeout() method can be used to reraise it. |
|
3377 |
|
|
3378 |
""" |
|
3379 |
|
|
3380 |
|
|
3381 |
class _RetryDelayCalculator(object): |
|
3382 |
"""Calculator for increasing delays. |
|
3383 |
|
|
3384 |
""" |
|
3385 |
__slots__ = [ |
|
3386 |
"_factor", |
|
3387 |
"_limit", |
|
3388 |
"_next", |
|
3389 |
"_start", |
|
3390 |
] |
|
3391 |
|
|
3392 |
def __init__(self, start, factor, limit): |
|
3393 |
"""Initializes this class. |
|
3394 |
|
|
3395 |
@type start: float |
|
3396 |
@param start: Initial delay |
|
3397 |
@type factor: float |
|
3398 |
@param factor: Factor for delay increase |
|
3399 |
@type limit: float or None |
|
3400 |
@param limit: Upper limit for delay or None for no limit |
|
3401 |
|
|
3402 |
""" |
|
3403 |
assert start > 0.0 |
|
3404 |
assert factor >= 1.0 |
|
3405 |
assert limit is None or limit >= 0.0 |
|
3406 |
|
|
3407 |
self._start = start |
|
3408 |
self._factor = factor |
|
3409 |
self._limit = limit |
|
3410 |
|
|
3411 |
self._next = start |
|
3412 |
|
|
3413 |
def __call__(self): |
|
3414 |
"""Returns current delay and calculates the next one. |
|
3415 |
|
|
3416 |
""" |
|
3417 |
current = self._next |
|
3418 |
|
|
3419 |
# Update for next run |
|
3420 |
if self._limit is None or self._next < self._limit: |
|
3421 |
self._next = min(self._limit, self._next * self._factor) |
|
3422 |
|
|
3423 |
return current |
|
3424 |
|
|
3425 |
|
|
3426 |
#: Special delay to specify whole remaining timeout |
|
3427 |
RETRY_REMAINING_TIME = object() |
|
3428 |
|
|
3429 |
|
|
3430 |
def Retry(fn, delay, timeout, args=None, wait_fn=time.sleep, |
|
3431 |
_time_fn=time.time): |
|
3432 |
"""Call a function repeatedly until it succeeds. |
|
3433 |
|
|
3434 |
The function C{fn} is called repeatedly until it doesn't throw L{RetryAgain} |
|
3435 |
anymore. Between calls a delay, specified by C{delay}, is inserted. After a |
|
3436 |
total of C{timeout} seconds, this function throws L{RetryTimeout}. |
|
3437 |
|
|
3438 |
C{delay} can be one of the following: |
|
3439 |
- callable returning the delay length as a float |
|
3440 |
- Tuple of (start, factor, limit) |
|
3441 |
- L{RETRY_REMAINING_TIME} to sleep until the timeout expires (this is |
|
3442 |
useful when overriding L{wait_fn} to wait for an external event) |
|
3443 |
- A static delay as a number (int or float) |
|
3444 |
|
|
3445 |
@type fn: callable |
|
3446 |
@param fn: Function to be called |
|
3447 |
@param delay: Either a callable (returning the delay), a tuple of (start, |
|
3448 |
factor, limit) (see L{_RetryDelayCalculator}), |
|
3449 |
L{RETRY_REMAINING_TIME} or a number (int or float) |
|
3450 |
@type timeout: float |
|
3451 |
@param timeout: Total timeout |
|
3452 |
@type wait_fn: callable |
|
3453 |
@param wait_fn: Waiting function |
|
3454 |
@return: Return value of function |
|
3455 |
|
|
3456 |
""" |
|
3457 |
assert callable(fn) |
|
3458 |
assert callable(wait_fn) |
|
3459 |
assert callable(_time_fn) |
|
3460 |
|
|
3461 |
if args is None: |
|
3462 |
args = [] |
|
3463 |
|
|
3464 |
end_time = _time_fn() + timeout |
|
3465 |
|
|
3466 |
if callable(delay): |
|
3467 |
# External function to calculate delay |
|
3468 |
calc_delay = delay |
|
3469 |
|
|
3470 |
elif isinstance(delay, (tuple, list)): |
|
3471 |
# Increasing delay with optional upper boundary |
|
3472 |
(start, factor, limit) = delay |
|
3473 |
calc_delay = _RetryDelayCalculator(start, factor, limit) |
|
3474 |
|
|
3475 |
elif delay is RETRY_REMAINING_TIME: |
|
3476 |
# Always use the remaining time |
|
3477 |
calc_delay = None |
|
3478 |
|
|
3479 |
else: |
|
3480 |
# Static delay |
|
3481 |
calc_delay = lambda: delay |
|
3482 |
|
|
3483 |
assert calc_delay is None or callable(calc_delay) |
|
3484 |
|
|
3485 |
while True: |
|
3486 |
retry_args = [] |
|
3487 |
try: |
|
3488 |
# pylint: disable-msg=W0142 |
|
3489 |
return fn(*args) |
|
3490 |
except RetryAgain, err: |
|
3491 |
retry_args = err.args |
|
3492 |
except RetryTimeout: |
|
3493 |
raise errors.ProgrammerError("Nested retry loop detected that didn't" |
|
3494 |
" handle RetryTimeout") |
|
3495 |
|
|
3496 |
remaining_time = end_time - _time_fn() |
|
3497 |
|
|
3498 |
if remaining_time < 0.0: |
|
3499 |
# pylint: disable-msg=W0142 |
|
3500 |
raise RetryTimeout(*retry_args) |
|
3501 |
|
|
3502 |
assert remaining_time >= 0.0 |
|
3503 |
|
|
3504 |
if calc_delay is None: |
|
3505 |
wait_fn(remaining_time) |
|
3506 |
else: |
|
3507 |
current_delay = calc_delay() |
|
3508 |
if current_delay > 0.0: |
|
3509 |
wait_fn(current_delay) |
|
3510 |
|
|
3511 |
|
|
3512 | 3357 |
def GetClosedTempfile(*args, **kwargs): |
3513 | 3358 |
"""Creates a temporary file and returns its path. |
3514 | 3359 |
|
Also available in: Unified diff