diff --git a/heat/common/timeutils.py b/heat/common/timeutils.py index 99b2dc3e9a..e31e9be04b 100644 --- a/heat/common/timeutils.py +++ b/heat/common/timeutils.py @@ -17,11 +17,31 @@ 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): diff --git a/heat/engine/scheduler.py b/heat/engine/scheduler.py index 225b04d12c..c6bba70f8b 100644 --- a/heat/engine/scheduler.py +++ b/heat/engine/scheduler.py @@ -14,7 +14,6 @@ import functools import itertools import sys -import time import types import eventlet @@ -26,13 +25,13 @@ from six import reraise as raise_ from heat.common.i18n import _ from heat.common.i18n import _LI +from heat.common import timeutils LOG = logging.getLogger(__name__) # Whether TaskRunner._sleep actually does an eventlet sleep when called. ENABLE_SLEEP = True -wallclock = time.time def task_description(task): @@ -68,15 +67,10 @@ class Timeout(BaseException): message = _('%s Timed out') % six.text_type(task_runner) super(Timeout, self).__init__(message) - # 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. - self._endtime = wallclock() + timeout + self._duration = timeutils.Duration(timeout) def expired(self): - return wallclock() > self._endtime + return self._duration.expired() def trigger(self, generator): """Trigger the timeout on a given generator.""" @@ -107,7 +101,7 @@ class Timeout(BaseException): def __lt__(self, other): if not isinstance(other, Timeout): return NotImplemented - return self._endtime < other._endtime + return self._duration.endtime() < other._duration.endtime() def __cmp__(self, other): return self < other diff --git a/heat/tests/test_scheduler.py b/heat/tests/test_scheduler.py index fe92957d04..9a87032aba 100644 --- a/heat/tests/test_scheduler.py +++ b/heat/tests/test_scheduler.py @@ -15,6 +15,7 @@ import contextlib import eventlet +from heat.common import timeutils from heat.engine import dependencies from heat.engine import scheduler from heat.tests import common @@ -647,16 +648,16 @@ class TaskTest(common.HeatTestCase): self.assertTrue(runner.step()) def test_timeout(self): - st = scheduler.wallclock() + st = timeutils.wallclock() def task(): while True: yield - self.m.StubOutWithMock(scheduler, 'wallclock') - scheduler.wallclock().AndReturn(st) - scheduler.wallclock().AndReturn(st + 0.5) - scheduler.wallclock().AndReturn(st + 1.5) + self.m.StubOutWithMock(timeutils, 'wallclock') + timeutils.wallclock().AndReturn(st) + timeutils.wallclock().AndReturn(st + 0.5) + timeutils.wallclock().AndReturn(st + 1.5) self.m.ReplayAll() @@ -667,7 +668,7 @@ class TaskTest(common.HeatTestCase): self.assertRaises(scheduler.Timeout, runner.step) def test_timeout_return(self): - st = scheduler.wallclock() + st = timeutils.wallclock() def task(): while True: @@ -676,10 +677,10 @@ class TaskTest(common.HeatTestCase): except scheduler.Timeout: return - self.m.StubOutWithMock(scheduler, 'wallclock') - scheduler.wallclock().AndReturn(st) - scheduler.wallclock().AndReturn(st + 0.5) - scheduler.wallclock().AndReturn(st + 1.5) + self.m.StubOutWithMock(timeutils, 'wallclock') + timeutils.wallclock().AndReturn(st) + timeutils.wallclock().AndReturn(st + 0.5) + timeutils.wallclock().AndReturn(st + 1.5) self.m.ReplayAll() @@ -691,7 +692,7 @@ class TaskTest(common.HeatTestCase): self.assertFalse(runner) def test_timeout_swallowed(self): - st = scheduler.wallclock() + st = timeutils.wallclock() def task(): while True: @@ -701,10 +702,10 @@ class TaskTest(common.HeatTestCase): yield self.fail('Task still running') - self.m.StubOutWithMock(scheduler, 'wallclock') - scheduler.wallclock().AndReturn(st) - scheduler.wallclock().AndReturn(st + 0.5) - scheduler.wallclock().AndReturn(st + 1.5) + self.m.StubOutWithMock(timeutils, 'wallclock') + timeutils.wallclock().AndReturn(st) + timeutils.wallclock().AndReturn(st + 0.5) + timeutils.wallclock().AndReturn(st + 1.5) self.m.ReplayAll() @@ -777,21 +778,21 @@ class TaskTest(common.HeatTestCase): self.assertTrue(runner.step()) def test_cancel_grace_period(self): - st = scheduler.wallclock() + st = timeutils.wallclock() task = DummyTask(5) self.m.StubOutWithMock(task, 'do_step') self.m.StubOutWithMock(scheduler.TaskRunner, '_sleep') - self.m.StubOutWithMock(scheduler, 'wallclock') + self.m.StubOutWithMock(timeutils, 'wallclock') task.do_step(1).AndReturn(None) task.do_step(2).AndReturn(None) - scheduler.wallclock().AndReturn(st) - scheduler.wallclock().AndReturn(st + 0.5) + timeutils.wallclock().AndReturn(st) + timeutils.wallclock().AndReturn(st + 0.5) task.do_step(3).AndReturn(None) - scheduler.wallclock().AndReturn(st + 1.0) + timeutils.wallclock().AndReturn(st + 1.0) task.do_step(4).AndReturn(None) - scheduler.wallclock().AndReturn(st + 1.5) + timeutils.wallclock().AndReturn(st + 1.5) self.m.ReplayAll() @@ -808,24 +809,24 @@ class TaskTest(common.HeatTestCase): self.assertTrue(runner.step()) def test_cancel_grace_period_before_timeout(self): - st = scheduler.wallclock() + st = timeutils.wallclock() task = DummyTask(5) self.m.StubOutWithMock(task, 'do_step') self.m.StubOutWithMock(scheduler.TaskRunner, '_sleep') - self.m.StubOutWithMock(scheduler, 'wallclock') + self.m.StubOutWithMock(timeutils, 'wallclock') - scheduler.wallclock().AndReturn(st) - scheduler.wallclock().AndReturn(st + 0.1) + timeutils.wallclock().AndReturn(st) + timeutils.wallclock().AndReturn(st + 0.1) task.do_step(1).AndReturn(None) - scheduler.wallclock().AndReturn(st + 0.2) + timeutils.wallclock().AndReturn(st + 0.2) task.do_step(2).AndReturn(None) - scheduler.wallclock().AndReturn(st + 0.2) - scheduler.wallclock().AndReturn(st + 0.5) + timeutils.wallclock().AndReturn(st + 0.2) + timeutils.wallclock().AndReturn(st + 0.5) task.do_step(3).AndReturn(None) - scheduler.wallclock().AndReturn(st + 1.0) + timeutils.wallclock().AndReturn(st + 1.0) task.do_step(4).AndReturn(None) - scheduler.wallclock().AndReturn(st + 1.5) + timeutils.wallclock().AndReturn(st + 1.5) self.m.ReplayAll() @@ -842,24 +843,24 @@ class TaskTest(common.HeatTestCase): self.assertTrue(runner.step()) def test_cancel_grace_period_after_timeout(self): - st = scheduler.wallclock() + st = timeutils.wallclock() task = DummyTask(5) self.m.StubOutWithMock(task, 'do_step') self.m.StubOutWithMock(scheduler.TaskRunner, '_sleep') - self.m.StubOutWithMock(scheduler, 'wallclock') + self.m.StubOutWithMock(timeutils, 'wallclock') - scheduler.wallclock().AndReturn(st) - scheduler.wallclock().AndReturn(st + 0.1) + timeutils.wallclock().AndReturn(st) + timeutils.wallclock().AndReturn(st + 0.1) task.do_step(1).AndReturn(None) - scheduler.wallclock().AndReturn(st + 0.2) + timeutils.wallclock().AndReturn(st + 0.2) task.do_step(2).AndReturn(None) - scheduler.wallclock().AndReturn(st + 0.2) - scheduler.wallclock().AndReturn(st + 0.5) + timeutils.wallclock().AndReturn(st + 0.2) + timeutils.wallclock().AndReturn(st + 0.5) task.do_step(3).AndReturn(None) - scheduler.wallclock().AndReturn(st + 1.0) + timeutils.wallclock().AndReturn(st + 1.0) task.do_step(4).AndReturn(None) - scheduler.wallclock().AndReturn(st + 1.5) + timeutils.wallclock().AndReturn(st + 1.5) self.m.ReplayAll() diff --git a/heat/tests/test_stack.py b/heat/tests/test_stack.py index b94e6fe05f..e52bc9154e 100644 --- a/heat/tests/test_stack.py +++ b/heat/tests/test_stack.py @@ -24,6 +24,7 @@ import six from heat.common import context from heat.common import exception from heat.common import template_format +from heat.common import timeutils from heat.db import api as db_api from heat.engine.clients.os import keystone from heat.engine.clients.os import nova @@ -954,7 +955,7 @@ class StackTest(common.HeatTestCase): def test_stack_create_timeout(self): self.m.StubOutWithMock(scheduler.DependencyTaskGroup, '__call__') - self.m.StubOutWithMock(scheduler, 'wallclock') + self.m.StubOutWithMock(timeutils, 'wallclock') stk = stack.Stack(self.ctx, 's', self.tmpl) @@ -963,10 +964,10 @@ class StackTest(common.HeatTestCase): yield start_time = time.time() - scheduler.wallclock().AndReturn(start_time) - scheduler.wallclock().AndReturn(start_time + 1) + timeutils.wallclock().AndReturn(start_time) + timeutils.wallclock().AndReturn(start_time + 1) scheduler.DependencyTaskGroup.__call__().AndReturn(dummy_task()) - scheduler.wallclock().AndReturn(start_time + stk.timeout_secs() + 1) + timeutils.wallclock().AndReturn(start_time + stk.timeout_secs() + 1) self.m.ReplayAll() diff --git a/heat/tests/test_stack_delete.py b/heat/tests/test_stack_delete.py index c22af1092c..a70741ab5e 100644 --- a/heat/tests/test_stack_delete.py +++ b/heat/tests/test_stack_delete.py @@ -22,6 +22,7 @@ import mock from heat.common import exception from heat.common import heat_keystoneclient as hkc from heat.common import template_format +from heat.common import timeutils from heat.engine.clients.os import keystone from heat.engine import scheduler from heat.engine import stack @@ -379,7 +380,7 @@ class StackTest(common.HeatTestCase): start_time = time.time() mock_tg = self.patchobject(scheduler.DependencyTaskGroup, '__call__', return_value=dummy_task()) - mock_wallclock = self.patchobject(scheduler, 'wallclock') + mock_wallclock = self.patchobject(timeutils, 'wallclock') mock_wallclock.side_effect = [ start_time, start_time + 1, diff --git a/heat/tests/test_timeutils.py b/heat/tests/test_timeutils.py index f00fcdd35f..567a502e8d 100644 --- a/heat/tests/test_timeutils.py +++ b/heat/tests/test_timeutils.py @@ -46,6 +46,21 @@ class ISO8601UtilityTest(common.HeatTestCase): self.assertRaises(ValueError, util.parse_isoduration, 'ABCDEFGH') +class DurationTest(common.HeatTestCase): + + def setUp(self): + super(DurationTest, self).setUp() + st = util.wallclock() + mock_clock = self.patchobject(util, 'wallclock') + mock_clock.side_effect = [st, st + 0.5] + + def test_duration_not_expired(self): + self.assertFalse(util.Duration(1.0).expired()) + + def test_duration_expired(self): + self.assertTrue(util.Duration(0.1).expired()) + + class RetryBackoffExponentialTest(common.HeatTestCase): scenarios = [(