diff --git a/nova/manager.py b/nova/manager.py index cc7d464cacc1..d6a62c509fe6 100644 --- a/nova/manager.py +++ b/nova/manager.py @@ -53,8 +53,7 @@ This module provides Manager, a base class for managers. """ -import time - +import datetime import eventlet from oslo.config import cfg @@ -63,6 +62,7 @@ from nova import exception from nova.openstack.common import log as logging from nova.openstack.common.plugin import pluginmanager from nova.openstack.common.rpc import dispatcher as rpc_dispatcher +from nova.openstack.common import timeutils from nova.scheduler import rpcapi as scheduler_rpcapi @@ -114,10 +114,11 @@ def periodic_task(*args, **kwargs): # Control frequency f._periodic_spacing = kwargs.pop('spacing', 0) - if kwargs.pop('run_immediately', False): + f._periodic_immediate = kwargs.pop('run_immediately', False) + if f._periodic_immediate: f._periodic_last_run = None else: - f._periodic_last_run = time.time() + f._periodic_last_run = timeutils.utcnow() return f # NOTE(sirp): The `if` is necessary to allow the decorator to be used with @@ -220,22 +221,22 @@ class Manager(base.Base): for task_name, task in self._periodic_tasks: full_task_name = '.'.join([self.__class__.__name__, task_name]) + now = timeutils.utcnow() + spacing = self._periodic_spacing[task_name] + last_run = self._periodic_last_run[task_name] + # If a periodic task is _nearly_ due, then we'll run it early - if self._periodic_spacing[task_name] is None: - wait = 0 - elif self._periodic_last_run[task_name] is None: - wait = 0 - else: - due = (self._periodic_last_run[task_name] + - self._periodic_spacing[task_name]) - wait = max(0, due - time.time()) - if wait > 0.2: - if wait < idle_for: - idle_for = wait + if spacing is not None and last_run is not None: + due = last_run + datetime.timedelta(seconds=spacing) + if not timeutils.is_soon(due, 0.2): + idle_for = min(idle_for, timeutils.delta_seconds(now, due)) continue + if spacing is not None: + idle_for = min(idle_for, spacing) + LOG.debug(_("Running periodic task %(full_task_name)s"), locals()) - self._periodic_last_run[task_name] = time.time() + self._periodic_last_run[task_name] = timeutils.utcnow() try: task(self, context) @@ -244,10 +245,6 @@ class Manager(base.Base): raise LOG.exception(_("Error during %(full_task_name)s: %(e)s"), locals()) - - if (not self._periodic_spacing[task_name] is None and - self._periodic_spacing[task_name] < idle_for): - idle_for = self._periodic_spacing[task_name] eventlet.sleep(0) return idle_for diff --git a/nova/tests/test_periodic_tasks.py b/nova/tests/test_periodic_tasks.py index 2539332a99bb..a4f594b3a710 100644 --- a/nova/tests/test_periodic_tasks.py +++ b/nova/tests/test_periodic_tasks.py @@ -15,11 +15,12 @@ # License for the specific language governing permissions and limitations # under the License. -import time +import datetime from testtools import matchers from nova import manager +from nova.openstack.common import timeutils from nova import test @@ -79,17 +80,74 @@ class Manager(test.TestCase): self.assertAlmostEqual(60, idle, 1) def test_periodic_tasks_idle_calculation(self): + fake_time = datetime.datetime(3000, 1, 1) + timeutils.set_time_override(fake_time) + class Manager(manager.Manager): @manager.periodic_task(spacing=10) - def bar(self): + def bar(self, context): return 'bar' m = Manager() + + # Ensure initial values are correct + self.assertEqual(1, len(m._periodic_tasks)) + task_name, task = m._periodic_tasks[0] + + # Test task values + self.assertEqual('bar', task_name) + self.assertEqual(10, task._periodic_spacing) + self.assertEqual(True, task._periodic_enabled) + self.assertEqual(False, task._periodic_external_ok) + self.assertEqual(False, task._periodic_immediate) + self.assertNotEqual(None, task._periodic_last_run) + + # Test the manager's representation of those values + self.assertEqual(10, m._periodic_spacing[task_name]) + self.assertNotEqual(None, m._periodic_last_run[task_name]) + + timeutils.advance_time_delta(datetime.timedelta(seconds=5)) m.periodic_tasks(None) - time.sleep(0.1) + + timeutils.advance_time_delta(datetime.timedelta(seconds=5)) idle = m.periodic_tasks(None) - self.assertThat(idle, matchers.GreaterThan(9.7)) - self.assertThat(idle, matchers.LessThan(9.9)) + self.assertAlmostEqual(10, idle, 1) + + def test_periodic_tasks_immediate_runs_now(self): + fake_time = datetime.datetime(3000, 1, 1) + timeutils.set_time_override(fake_time) + + class Manager(manager.Manager): + @manager.periodic_task(spacing=10, run_immediately=True) + def bar(self, context): + return 'bar' + + m = Manager() + + # Ensure initial values are correct + self.assertEqual(1, len(m._periodic_tasks)) + task_name, task = m._periodic_tasks[0] + + # Test task values + self.assertEqual('bar', task_name) + self.assertEqual(10, task._periodic_spacing) + self.assertEqual(True, task._periodic_enabled) + self.assertEqual(False, task._periodic_external_ok) + self.assertEqual(True, task._periodic_immediate) + self.assertEqual(None, task._periodic_last_run) + + # Test the manager's representation of those values + self.assertEqual(10, m._periodic_spacing[task_name]) + self.assertEqual(None, m._periodic_last_run[task_name]) + + idle = m.periodic_tasks(None) + self.assertEqual(datetime.datetime(3000, 1, 1, 0, 0), + m._periodic_last_run[task_name]) + self.assertAlmostEqual(10, idle, 1) + + timeutils.advance_time_delta(datetime.timedelta(seconds=5)) + idle = m.periodic_tasks(None) + self.assertAlmostEqual(5, idle, 1) def test_periodic_tasks_disabled(self): class Manager(manager.Manager):