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