# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Utilities for handling ISO 8601 duration format. """ import random import re import time from heat.common.i18n import _ iso_duration_re = re.compile('PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?$') wallclock = time.time class Duration(object): ''' Note that we don't attempt to handle leap seconds or large clock jumps here. The latter are assumed to be rare and the former negligible in the context of the timeout. Time zone adjustments, Daylight Savings and the like *are* handled. PEP 418 adds a proper monotonic clock, but only in Python 3.3. ''' def __init__(self, timeout=0): self._endtime = wallclock() + timeout def expired(self): return wallclock() > self._endtime def endtime(self): return self._endtime def parse_isoduration(duration): """ Convert duration in ISO 8601 format to second(s). Year, Month, Week, and Day designators are not supported. Example: 'PT12H30M5S' """ result = iso_duration_re.match(duration) if not result: raise ValueError(_('Only ISO 8601 duration format of the form ' 'PT#H#M#S is supported.')) t = 0 t += (3600 * int(result.group(1))) if result.group(1) else 0 t += (60 * int(result.group(2))) if result.group(2) else 0 t += int(result.group(3)) if result.group(3) else 0 return t def retry_backoff_delay(attempt, scale_factor=1.0, jitter_max=0.0): """ Calculate an exponential backoff delay with jitter. Delay is calculated as 2^attempt + (uniform random from [0,1) * jitter_max) :param attempt: The count of the current retry attempt :param scale_factor: Multiplier to scale the exponential delay by :param jitter_max: Maximum of random seconds to add to the delay :returns: Seconds since epoch to wait until """ exp = float(2 ** attempt) * float(scale_factor) if jitter_max == 0.0: return exp return exp + random.random() * jitter_max