ad7a0eb293
This is more or less to get commit c63fd5a from oslo into the core projects which have several periodic tasks. Neutron has periodic tasks for L3, load balancing and metering agents to sync up state with the server and most don't have specific spacing values set which can lead to non-deterministic spacing of when the tasks run. Note that this does not include the gettextutils and log dependencies since there are not functional changes in those modules needed for the periodic_task changes synced in *and* more importantly, the changes to gettextutils and log require pervasive changes to neutron which should happen when neutron integrates with the oslo-i18n library for blueprint i18n--messages. Further note that this does not include jsonutils due to some issues introduced with a change for python 2.6 that impacts how strings are encoded with simplejson. The details for that issue are in bug 1314129. The jsonutils changes are not related to the periodic_task changes being synced in so the dependency is not functionally required. The LbaasAgentManager extends PeriodicTasks but wasn't calling the parent class init function, which was causing failures since commit 47c9d60 changed PeriodicTasks to init _periodic_last_run, so also fixed that here. Changes: c63fd5a Make unspecified periodic spaced tasks run on default interval f0dd798 Remove rendundant parentheses of cfg help strings fcf517d Update oslo log messages with translation domains 051b9f3 Refactor unnecessary arithmetic ops in periodic_task 674cdaf Refactor if logic in periodic_task b6b82c5 Use timestamp in periodic tasks 47c9d60 Don't share periodic_task instance data in a class attr 8b2b0b7 Use hacking import_exceptions for gettextutils._ Related-Bug: #1319232 Change-Id: Ib23e33326129be0dd952efde263f381d5aa2457f
184 lines
6.7 KiB
Python
184 lines
6.7 KiB
Python
#
|
|
# 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.
|
|
|
|
import time
|
|
|
|
from oslo.config import cfg
|
|
import six
|
|
|
|
from neutron.openstack.common.gettextutils import _, _LE, _LI
|
|
from neutron.openstack.common import log as logging
|
|
|
|
|
|
periodic_opts = [
|
|
cfg.BoolOpt('run_external_periodic_tasks',
|
|
default=True,
|
|
help='Some periodic tasks can be run in a separate process. '
|
|
'Should we run them here?'),
|
|
]
|
|
|
|
CONF = cfg.CONF
|
|
CONF.register_opts(periodic_opts)
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
DEFAULT_INTERVAL = 60.0
|
|
|
|
|
|
class InvalidPeriodicTaskArg(Exception):
|
|
message = _("Unexpected argument for periodic task creation: %(arg)s.")
|
|
|
|
|
|
def periodic_task(*args, **kwargs):
|
|
"""Decorator to indicate that a method is a periodic task.
|
|
|
|
This decorator can be used in two ways:
|
|
|
|
1. Without arguments '@periodic_task', this will be run on the default
|
|
interval of 60 seconds.
|
|
|
|
2. With arguments:
|
|
@periodic_task(spacing=N [, run_immediately=[True|False]])
|
|
this will be run on approximately every N seconds. If this number is
|
|
negative the periodic task will be disabled. If the run_immediately
|
|
argument is provided and has a value of 'True', the first run of the
|
|
task will be shortly after task scheduler starts. If
|
|
run_immediately is omitted or set to 'False', the first time the
|
|
task runs will be approximately N seconds after the task scheduler
|
|
starts.
|
|
"""
|
|
def decorator(f):
|
|
# Test for old style invocation
|
|
if 'ticks_between_runs' in kwargs:
|
|
raise InvalidPeriodicTaskArg(arg='ticks_between_runs')
|
|
|
|
# Control if run at all
|
|
f._periodic_task = True
|
|
f._periodic_external_ok = kwargs.pop('external_process_ok', False)
|
|
if f._periodic_external_ok and not CONF.run_external_periodic_tasks:
|
|
f._periodic_enabled = False
|
|
else:
|
|
f._periodic_enabled = kwargs.pop('enabled', True)
|
|
|
|
# Control frequency
|
|
f._periodic_spacing = kwargs.pop('spacing', 0)
|
|
f._periodic_immediate = kwargs.pop('run_immediately', False)
|
|
if f._periodic_immediate:
|
|
f._periodic_last_run = None
|
|
else:
|
|
f._periodic_last_run = time.time()
|
|
return f
|
|
|
|
# NOTE(sirp): The `if` is necessary to allow the decorator to be used with
|
|
# and without parents.
|
|
#
|
|
# In the 'with-parents' case (with kwargs present), this function needs to
|
|
# return a decorator function since the interpreter will invoke it like:
|
|
#
|
|
# periodic_task(*args, **kwargs)(f)
|
|
#
|
|
# In the 'without-parents' case, the original function will be passed
|
|
# in as the first argument, like:
|
|
#
|
|
# periodic_task(f)
|
|
if kwargs:
|
|
return decorator
|
|
else:
|
|
return decorator(args[0])
|
|
|
|
|
|
class _PeriodicTasksMeta(type):
|
|
def __init__(cls, names, bases, dict_):
|
|
"""Metaclass that allows us to collect decorated periodic tasks."""
|
|
super(_PeriodicTasksMeta, cls).__init__(names, bases, dict_)
|
|
|
|
# NOTE(sirp): if the attribute is not present then we must be the base
|
|
# class, so, go ahead an initialize it. If the attribute is present,
|
|
# then we're a subclass so make a copy of it so we don't step on our
|
|
# parent's toes.
|
|
try:
|
|
cls._periodic_tasks = cls._periodic_tasks[:]
|
|
except AttributeError:
|
|
cls._periodic_tasks = []
|
|
|
|
try:
|
|
cls._periodic_spacing = cls._periodic_spacing.copy()
|
|
except AttributeError:
|
|
cls._periodic_spacing = {}
|
|
|
|
for value in cls.__dict__.values():
|
|
if getattr(value, '_periodic_task', False):
|
|
task = value
|
|
name = task.__name__
|
|
|
|
if task._periodic_spacing < 0:
|
|
LOG.info(_LI('Skipping periodic task %(task)s because '
|
|
'its interval is negative'),
|
|
{'task': name})
|
|
continue
|
|
if not task._periodic_enabled:
|
|
LOG.info(_LI('Skipping periodic task %(task)s because '
|
|
'it is disabled'),
|
|
{'task': name})
|
|
continue
|
|
|
|
# A periodic spacing of zero indicates that this task should
|
|
# be run on the default interval to avoid running too
|
|
# frequently.
|
|
if task._periodic_spacing == 0:
|
|
task._periodic_spacing = DEFAULT_INTERVAL
|
|
|
|
cls._periodic_tasks.append((name, task))
|
|
cls._periodic_spacing[name] = task._periodic_spacing
|
|
|
|
|
|
@six.add_metaclass(_PeriodicTasksMeta)
|
|
class PeriodicTasks(object):
|
|
def __init__(self):
|
|
super(PeriodicTasks, self).__init__()
|
|
self._periodic_last_run = {}
|
|
for name, task in self._periodic_tasks:
|
|
self._periodic_last_run[name] = task._periodic_last_run
|
|
|
|
def run_periodic_tasks(self, context, raise_on_error=False):
|
|
"""Tasks to be run at a periodic interval."""
|
|
idle_for = DEFAULT_INTERVAL
|
|
for task_name, task in self._periodic_tasks:
|
|
full_task_name = '.'.join([self.__class__.__name__, task_name])
|
|
|
|
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
|
|
idle_for = min(idle_for, spacing)
|
|
if last_run is not None:
|
|
delta = last_run + spacing - time.time()
|
|
if delta > 0.2:
|
|
idle_for = min(idle_for, delta)
|
|
continue
|
|
|
|
LOG.debug("Running periodic task %(full_task_name)s",
|
|
{"full_task_name": full_task_name})
|
|
self._periodic_last_run[task_name] = time.time()
|
|
|
|
try:
|
|
task(self, context)
|
|
except Exception as e:
|
|
if raise_on_error:
|
|
raise
|
|
LOG.exception(_LE("Error during %(full_task_name)s: %(e)s"),
|
|
{"full_task_name": full_task_name, "e": e})
|
|
time.sleep(0)
|
|
|
|
return idle_for
|