Sync with latest oslo-incubator

Changes are as follows:
f29e865 3/17/15  Store ProcessLauncher signal handlers on class level
bf92010 2/19/15  Optimization of waiting subprocesses in ProcessLauncher
2aacb11 1/29/15  Change oslo.config to oslo_config
2fbf506 1/29/15  Remove oslo.log code and clean up versionutils API
0cc741a 1/26/15  switch to oslo_i18n
fbd77a7 1/21/15  Allow overriding name for periodic tasks
9896e0e 1/21/15  Separate add_periodic_task from the metaclass __init__
80c4751 1/5/15   Remove unnecessary import of eventlet
442fc22 12/30/14 Added graceful argument on Service.stop method
cf429e5 12/28/14 Remove extra white space in log message
5985b35 12/11/14 Prefer delayed %r formatting over explicit repr use

Closes-bug: #1434744
Change-Id: Icf0b52170b3457d570fd87fd2875a1e759b97579
This commit is contained in:
Davanum Srinivas 2015-03-20 16:07:13 -04:00 committed by Davanum Srinivas (dims)
parent fe80331cef
commit 0789d70056
9 changed files with 140 additions and 65 deletions

View File

@ -17,14 +17,14 @@ See http://docs.openstack.org/developer/oslo.i18n/usage.html
""" """
try: try:
import oslo.i18n import oslo_i18n
# NOTE(dhellmann): This reference to o-s-l-o will be replaced by the # NOTE(dhellmann): This reference to o-s-l-o will be replaced by the
# application name when this module is synced into the separate # application name when this module is synced into the separate
# repository. It is OK to have more than one translation function # repository. It is OK to have more than one translation function
# using the same domain, since there will still only be one message # using the same domain, since there will still only be one message
# catalog. # catalog.
_translators = oslo.i18n.TranslatorFactory(domain='magnum') _translators = oslo_i18n.TranslatorFactory(domain='magnum')
# The primary translation function using the well-known name "_" # The primary translation function using the well-known name "_"
_ = _translators.primary _ = _translators.primary
@ -40,6 +40,6 @@ try:
_LC = _translators.log_critical _LC = _translators.log_critical
except ImportError: except ImportError:
# NOTE(dims): Support for cases where a project wants to use # NOTE(dims): Support for cases where a project wants to use
# code from magnum-incubator, but is not ready to be internationalized # code from oslo-incubator, but is not ready to be internationalized
# (like tempest) # (like tempest)
_ = _LI = _LW = _LE = _LC = lambda x: x _ = _LI = _LW = _LE = _LC = lambda x: x

View File

@ -19,19 +19,18 @@ from __future__ import print_function
import copy import copy
import errno import errno
import gc import gc
import logging
import os import os
import pprint import pprint
import socket import socket
import sys import sys
import traceback import traceback
import eventlet
import eventlet.backdoor import eventlet.backdoor
import greenlet import greenlet
from oslo_config import cfg from oslo_config import cfg
from magnum.openstack.common._i18n import _LI from magnum.openstack.common._i18n import _LI
from magnum.openstack.common import log as logging
help_for_backdoor_port = ( help_for_backdoor_port = (
"Acceptable values are 0, <port>, and <start>:<end>, where 0 results " "Acceptable values are 0, <port>, and <start>:<end>, where 0 results "
@ -51,7 +50,7 @@ LOG = logging.getLogger(__name__)
def list_opts(): def list_opts():
"""Entry point for oslo.config-generator. """Entry point for oslo-config-generator.
""" """
return [(None, copy.deepcopy(eventlet_backdoor_opts))] return [(None, copy.deepcopy(eventlet_backdoor_opts))]

View File

@ -15,6 +15,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import logging
import sys import sys
import time import time
@ -22,7 +23,6 @@ from eventlet import event
from eventlet import greenthread from eventlet import greenthread
from magnum.openstack.common._i18n import _LE, _LW from magnum.openstack.common._i18n import _LE, _LW
from magnum.openstack.common import log as logging
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -84,9 +84,9 @@ class FixedIntervalLoopingCall(LoopingCallBase):
break break
delay = end - start - interval delay = end - start - interval
if delay > 0: if delay > 0:
LOG.warn(_LW('task %(func_name)s run outlasted ' LOG.warn(_LW('task %(func_name)r run outlasted '
'interval by %(delay).2f sec'), 'interval by %(delay).2f sec'),
{'func_name': repr(self.f), 'delay': delay}) {'func_name': self.f, 'delay': delay})
greenthread.sleep(-delay if delay < 0 else 0) greenthread.sleep(-delay if delay < 0 else 0)
except LoopingCallDone as e: except LoopingCallDone as e:
self.stop() self.stop()
@ -127,9 +127,9 @@ class DynamicLoopingCall(LoopingCallBase):
if periodic_interval_max is not None: if periodic_interval_max is not None:
idle = min(idle, periodic_interval_max) idle = min(idle, periodic_interval_max)
LOG.debug('Dynamic looping call %(func_name)s sleeping ' LOG.debug('Dynamic looping call %(func_name)r sleeping '
'for %(idle).02f seconds', 'for %(idle).02f seconds',
{'func_name': repr(self.f), 'idle': idle}) {'func_name': self.f, 'idle': idle})
greenthread.sleep(idle) greenthread.sleep(idle)
except LoopingCallDone as e: except LoopingCallDone as e:
self.stop() self.stop()

View File

@ -12,6 +12,7 @@
# under the License. # under the License.
import copy import copy
import logging
import random import random
import time import time
@ -19,7 +20,6 @@ from oslo_config import cfg
import six import six
from magnum.openstack.common._i18n import _, _LE, _LI from magnum.openstack.common._i18n import _, _LE, _LI
from magnum.openstack.common import log as logging
periodic_opts = [ periodic_opts = [
@ -38,7 +38,7 @@ DEFAULT_INTERVAL = 60.0
def list_opts(): def list_opts():
"""Entry point for oslo.config-generator.""" """Entry point for oslo-config-generator."""
return [(None, copy.deepcopy(periodic_opts))] return [(None, copy.deepcopy(periodic_opts))]
@ -55,14 +55,15 @@ def periodic_task(*args, **kwargs):
interval of 60 seconds. interval of 60 seconds.
2. With arguments: 2. With arguments:
@periodic_task(spacing=N [, run_immediately=[True|False]]) @periodic_task(spacing=N [, run_immediately=[True|False]]
[, name=[None|"string"])
this will be run on approximately every N seconds. If this number is this will be run on approximately every N seconds. If this number is
negative the periodic task will be disabled. If the run_immediately 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 argument is provided and has a value of 'True', the first run of the
task will be shortly after task scheduler starts. If task will be shortly after task scheduler starts. If
run_immediately is omitted or set to 'False', the first time the run_immediately is omitted or set to 'False', the first time the
task runs will be approximately N seconds after the task scheduler task runs will be approximately N seconds after the task scheduler
starts. starts. If name is not provided, __name__ of function is used.
""" """
def decorator(f): def decorator(f):
# Test for old style invocation # Test for old style invocation
@ -76,6 +77,7 @@ def periodic_task(*args, **kwargs):
f._periodic_enabled = False f._periodic_enabled = False
else: else:
f._periodic_enabled = kwargs.pop('enabled', True) f._periodic_enabled = kwargs.pop('enabled', True)
f._periodic_name = kwargs.pop('name', f.__name__)
# Control frequency # Control frequency
f._periodic_spacing = kwargs.pop('spacing', 0) f._periodic_spacing = kwargs.pop('spacing', 0)
@ -105,6 +107,36 @@ def periodic_task(*args, **kwargs):
class _PeriodicTasksMeta(type): class _PeriodicTasksMeta(type):
def _add_periodic_task(cls, task):
"""Add a periodic task to the list of periodic tasks.
The task should already be decorated by @periodic_task.
:return: whether task was actually enabled
"""
name = task._periodic_name
if task._periodic_spacing < 0:
LOG.info(_LI('Skipping periodic task %(task)s because '
'its interval is negative'),
{'task': name})
return False
if not task._periodic_enabled:
LOG.info(_LI('Skipping periodic task %(task)s because '
'it is disabled'),
{'task': name})
return False
# 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
return True
def __init__(cls, names, bases, dict_): def __init__(cls, names, bases, dict_):
"""Metaclass that allows us to collect decorated periodic tasks.""" """Metaclass that allows us to collect decorated periodic tasks."""
super(_PeriodicTasksMeta, cls).__init__(names, bases, dict_) super(_PeriodicTasksMeta, cls).__init__(names, bases, dict_)
@ -125,28 +157,7 @@ class _PeriodicTasksMeta(type):
for value in cls.__dict__.values(): for value in cls.__dict__.values():
if getattr(value, '_periodic_task', False): if getattr(value, '_periodic_task', False):
task = value cls._add_periodic_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
def _nearest_boundary(last_run, spacing): def _nearest_boundary(last_run, spacing):
@ -178,6 +189,15 @@ class PeriodicTasks(object):
for name, task in self._periodic_tasks: for name, task in self._periodic_tasks:
self._periodic_last_run[name] = task._periodic_last_run self._periodic_last_run[name] = task._periodic_last_run
def add_periodic_task(self, task):
"""Add a periodic task to the list of periodic tasks.
The task should already be decorated by @periodic_task.
"""
if self.__class__._add_periodic_task(task):
self._periodic_last_run[task._periodic_name] = (
task._periodic_last_run)
def run_periodic_tasks(self, context, raise_on_error=False): def run_periodic_tasks(self, context, raise_on_error=False):
"""Tasks to be run at a periodic interval.""" """Tasks to be run at a periodic interval."""
idle_for = DEFAULT_INTERVAL idle_for = DEFAULT_INTERVAL

View File

@ -18,7 +18,7 @@
"""Generic Node base class for all workers that run on hosts.""" """Generic Node base class for all workers that run on hosts."""
import errno import errno
import logging as std_logging import logging
import os import os
import random import random
import signal import signal
@ -39,7 +39,6 @@ from oslo_config import cfg
from magnum.openstack.common import eventlet_backdoor from magnum.openstack.common import eventlet_backdoor
from magnum.openstack.common._i18n import _LE, _LI, _LW from magnum.openstack.common._i18n import _LE, _LI, _LW
from magnum.openstack.common import log as logging
from magnum.openstack.common import systemd from magnum.openstack.common import systemd
from magnum.openstack.common import threadgroup from magnum.openstack.common import threadgroup
@ -163,7 +162,7 @@ class ServiceLauncher(Launcher):
signo = 0 signo = 0
LOG.debug('Full set of CONF:') LOG.debug('Full set of CONF:')
CONF.log_opt_values(LOG, std_logging.DEBUG) CONF.log_opt_values(LOG, logging.DEBUG)
try: try:
if ready_callback: if ready_callback:
@ -200,22 +199,26 @@ class ServiceWrapper(object):
class ProcessLauncher(object): class ProcessLauncher(object):
def __init__(self, wait_interval=0.01): _signal_handlers_set = set()
"""Constructor.
@classmethod
def _handle_class_signals(cls, *args, **kwargs):
for handler in cls._signal_handlers_set:
handler(*args, **kwargs)
def __init__(self):
"""Constructor."""
:param wait_interval: The interval to sleep for between checks
of child process exit.
"""
self.children = {} self.children = {}
self.sigcaught = None self.sigcaught = None
self.running = True self.running = True
self.wait_interval = wait_interval
rfd, self.writepipe = os.pipe() rfd, self.writepipe = os.pipe()
self.readpipe = eventlet.greenio.GreenPipe(rfd, 'r') self.readpipe = eventlet.greenio.GreenPipe(rfd, 'r')
self.handle_signal() self.handle_signal()
def handle_signal(self): def handle_signal(self):
_set_signals_handler(self._handle_signal) self._signal_handlers_set.add(self._handle_signal)
_set_signals_handler(self._handle_class_signals)
def _handle_signal(self, signo, frame): def _handle_signal(self, signo, frame):
self.sigcaught = signo self.sigcaught = signo
@ -334,8 +337,8 @@ class ProcessLauncher(object):
def _wait_child(self): def _wait_child(self):
try: try:
# Don't block if no child processes have exited # Block while any of child processes have exited
pid, status = os.waitpid(0, os.WNOHANG) pid, status = os.waitpid(0, 0)
if not pid: if not pid:
return None return None
except OSError as exc: except OSError as exc:
@ -364,10 +367,6 @@ class ProcessLauncher(object):
while self.running: while self.running:
wrap = self._wait_child() wrap = self._wait_child()
if not wrap: if not wrap:
# Yield to other threads if no children have exited
# Sleep for a short time to avoid excessive CPU usage
# (see bug #1095346)
eventlet.greenthread.sleep(self.wait_interval)
continue continue
while self.running and len(wrap.children) < wrap.workers: while self.running and len(wrap.children) < wrap.workers:
self._start_child(wrap) self._start_child(wrap)
@ -377,7 +376,7 @@ class ProcessLauncher(object):
systemd.notify_once() systemd.notify_once()
LOG.debug('Full set of CONF:') LOG.debug('Full set of CONF:')
CONF.log_opt_values(LOG, std_logging.DEBUG) CONF.log_opt_values(LOG, logging.DEBUG)
try: try:
while True: while True:
@ -397,7 +396,7 @@ class ProcessLauncher(object):
self.running = True self.running = True
self.sigcaught = None self.sigcaught = None
except eventlet.greenlet.GreenletExit: except eventlet.greenlet.GreenletExit:
LOG.info(_LI("Wait called after thread killed. Cleaning up.")) LOG.info(_LI("Wait called after thread killed. Cleaning up."))
self.stop() self.stop()
@ -434,8 +433,8 @@ class Service(object):
def start(self): def start(self):
pass pass
def stop(self): def stop(self, graceful=False):
self.tg.stop() self.tg.stop(graceful)
self.tg.wait() self.tg.wait()
# Signal that service cleanup is done: # Signal that service cleanup is done:
if not self._done.ready(): if not self._done.ready():

View File

@ -16,12 +16,11 @@
Helper module for systemd service readiness notification. Helper module for systemd service readiness notification.
""" """
import logging
import os import os
import socket import socket
import sys import sys
from magnum.openstack.common import log as logging
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)

View File

@ -11,12 +11,12 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import logging
import threading import threading
import eventlet import eventlet
from eventlet import greenpool from eventlet import greenpool
from magnum.openstack.common import log as logging
from magnum.openstack.common import loopingcall from magnum.openstack.common import loopingcall

View File

@ -17,17 +17,33 @@
Helpers for comparing version strings. Helpers for comparing version strings.
""" """
import copy
import functools import functools
import inspect import inspect
import logging
from oslo_config import cfg
import pkg_resources import pkg_resources
import six import six
from magnum.openstack.common._i18n import _ from magnum.openstack.common._i18n import _
from magnum.openstack.common import log as logging
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
CONF = cfg.CONF
deprecated_opts = [
cfg.BoolOpt('fatal_deprecations',
default=False,
help='Enables or disables fatal status of deprecations.'),
]
def list_opts():
"""Entry point for oslo.config-generator.
"""
return [(None, copy.deepcopy(deprecated_opts))]
class deprecated(object): class deprecated(object):
@ -74,6 +90,7 @@ class deprecated(object):
ICEHOUSE = 'I' ICEHOUSE = 'I'
JUNO = 'J' JUNO = 'J'
KILO = 'K' KILO = 'K'
LIBERTY = 'L'
_RELEASES = { _RELEASES = {
# NOTE(morganfainberg): Bexar is used for unit test purposes, it is # NOTE(morganfainberg): Bexar is used for unit test purposes, it is
@ -85,6 +102,7 @@ class deprecated(object):
'I': 'Icehouse', 'I': 'Icehouse',
'J': 'Juno', 'J': 'Juno',
'K': 'Kilo', 'K': 'Kilo',
'L': 'Liberty',
} }
_deprecated_msg_with_alternative = _( _deprecated_msg_with_alternative = _(
@ -127,7 +145,7 @@ class deprecated(object):
@six.wraps(func_or_cls) @six.wraps(func_or_cls)
def wrapped(*args, **kwargs): def wrapped(*args, **kwargs):
LOG.deprecated(msg, details) report_deprecated_feature(LOG, msg, details)
return func_or_cls(*args, **kwargs) return func_or_cls(*args, **kwargs)
return wrapped return wrapped
elif inspect.isclass(func_or_cls): elif inspect.isclass(func_or_cls):
@ -136,10 +154,10 @@ class deprecated(object):
# TODO(tsufiev): change `functools` module to `six` as # TODO(tsufiev): change `functools` module to `six` as
# soon as six 1.7.4 (with fix for passing `assigned` # soon as six 1.7.4 (with fix for passing `assigned`
# argument to underlying `functools.wraps`) is released # argument to underlying `functools.wraps`) is released
# and added to the magnum-incubator requrements # and added to the oslo-incubator requrements
@functools.wraps(orig_init, assigned=('__name__', '__doc__')) @functools.wraps(orig_init, assigned=('__name__', '__doc__'))
def new_init(self, *args, **kwargs): def new_init(self, *args, **kwargs):
LOG.deprecated(msg, details) report_deprecated_feature(LOG, msg, details)
orig_init(self, *args, **kwargs) orig_init(self, *args, **kwargs)
func_or_cls.__init__ = new_init func_or_cls.__init__ = new_init
return func_or_cls return func_or_cls
@ -201,3 +219,44 @@ def is_compatible(requested_version, current_version, same_major=True):
return False return False
return current_parts >= requested_parts return current_parts >= requested_parts
# Track the messages we have sent already. See
# report_deprecated_feature().
_deprecated_messages_sent = {}
def report_deprecated_feature(logger, msg, *args, **kwargs):
"""Call this function when a deprecated feature is used.
If the system is configured for fatal deprecations then the message
is logged at the 'critical' level and :class:`DeprecatedConfig` will
be raised.
Otherwise, the message will be logged (once) at the 'warn' level.
:raises: :class:`DeprecatedConfig` if the system is configured for
fatal deprecations.
"""
stdmsg = _("Deprecated: %s") % msg
CONF.register_opts(deprecated_opts)
if CONF.fatal_deprecations:
logger.critical(stdmsg, *args, **kwargs)
raise DeprecatedConfig(msg=stdmsg)
# Using a list because a tuple with dict can't be stored in a set.
sent_args = _deprecated_messages_sent.setdefault(msg, list())
if args in sent_args:
# Already logged this message, so don't log it again.
return
sent_args.append(args)
logger.warn(stdmsg, *args, **kwargs)
class DeprecatedConfig(Exception):
message = _("Fatal call to deprecated config: %(msg)s")
def __init__(self, msg):
super(Exception, self).__init__(self.message % dict(msg=msg))

View File

@ -2,7 +2,6 @@
# The list of modules to copy from oslo-incubator.git # The list of modules to copy from oslo-incubator.git
module=eventlet_backdoor module=eventlet_backdoor
module=log
module=loopingcall module=loopingcall
module=periodic_task module=periodic_task
module=service module=service