From f09858e373754580c72fc623bb138c944fa2a934 Mon Sep 17 00:00:00 2001 From: Dirk Mueller Date: Mon, 28 Jan 2013 18:06:55 +0100 Subject: [PATCH] Merge from Oslo-incubator Change-Id: I2ef3bb9acdfb5bcae574d812e06fd689d0a7c890 --- doc/source/conf.py | 6 +- heat/openstack/common/cfg.py | 74 ++-------------- heat/openstack/common/log.py | 84 ++++++++++++++----- .../common/notifier/rpc_notifier2.py | 51 +++++++++++ heat/openstack/common/rpc/impl_zmq.py | 2 +- heat/openstack/common/setup.py | 48 +++++++++-- heat/openstack/common/timeutils.py | 18 ++++ heat/openstack/common/version.py | 20 ++++- 8 files changed, 203 insertions(+), 100 deletions(-) create mode 100644 heat/openstack/common/notifier/rpc_notifier2.py diff --git a/doc/source/conf.py b/doc/source/conf.py index 87a91e44..5ed01cf6 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -51,8 +51,10 @@ copyright = u'2012, Heat Developers' # |version| and |release|, also used in various other places throughout the # built documents. -from heat.version import version_info as heat_version -release = heat_version.version_string_with_vcs() +from heat import version as heat_version +# The full version, including alpha/beta/rc tags. +release = heat_version.version_string() +# The short X.Y version version = heat_version.canonical_version_string() # The language for content autogenerated by Sphinx. Refer to documentation diff --git a/heat/openstack/common/cfg.py b/heat/openstack/common/cfg.py index 07f99147..349a6ae2 100644 --- a/heat/openstack/common/cfg.py +++ b/heat/openstack/common/cfg.py @@ -217,7 +217,7 @@ log files:: ... ] -This module also contains a global instance of the CommonConfigOpts class +This module also contains a global instance of the ConfigOpts class in order to support a common usage pattern in OpenStack:: from heat.openstack.common import cfg @@ -863,7 +863,7 @@ class SubCommandOpt(Opt): description=self.description, help=self.help) - if not self.handler is None: + if self.handler is not None: self.handler(subparsers) @@ -1547,8 +1547,8 @@ class ConfigOpts(collections.Mapping): group = group_or_name if isinstance(group_or_name, OptGroup) else None group_name = group.name if group else group_or_name - if not group_name in self._groups: - if not group is None or not autocreate: + if group_name not in self._groups: + if group is not None or not autocreate: raise NoSuchGroupError(group_name) self.register_group(OptGroup(name=group_name)) @@ -1568,7 +1568,7 @@ class ConfigOpts(collections.Mapping): group = self._get_group(group) opts = group._opts - if not opt_name in opts: + if opt_name not in opts: raise NoSuchOptError(opt_name, group) return opts[opt_name] @@ -1606,7 +1606,7 @@ class ConfigOpts(collections.Mapping): opt = info['opt'] if opt.required: - if ('default' in info or 'override' in info): + if 'default' in info or 'override' in info: continue if self._get(opt.dest, group) is None: @@ -1728,64 +1728,4 @@ class ConfigOpts(collections.Mapping): return value -class CommonConfigOpts(ConfigOpts): - - DEFAULT_LOG_FORMAT = "%(asctime)s %(levelname)8s [%(name)s] %(message)s" - DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S" - - common_cli_opts = [ - BoolOpt('debug', - short='d', - default=False, - help='Print debugging output (set logging level to ' - 'DEBUG instead of default WARNING level).'), - BoolOpt('verbose', - short='v', - default=False, - help='Print more verbose output (set logging level to ' - 'INFO instead of default WARNING level).'), - ] - - logging_cli_opts = [ - StrOpt('log-config', - metavar='PATH', - help='If this option is specified, the logging configuration ' - 'file specified is used and overrides any other logging ' - 'options specified. Please see the Python logging module ' - 'documentation for details on logging configuration ' - 'files.'), - StrOpt('log-format', - default=DEFAULT_LOG_FORMAT, - metavar='FORMAT', - help='A logging.Formatter log message format string which may ' - 'use any of the available logging.LogRecord attributes. ' - 'Default: %(default)s'), - StrOpt('log-date-format', - default=DEFAULT_LOG_DATE_FORMAT, - metavar='DATE_FORMAT', - help='Format string for %%(asctime)s in log records. ' - 'Default: %(default)s'), - StrOpt('log-file', - metavar='PATH', - deprecated_name='logfile', - help='(Optional) Name of log file to output to. ' - 'If not set, logging will go to stdout.'), - StrOpt('log-dir', - deprecated_name='logdir', - help='(Optional) The directory to keep log files in ' - '(will be prepended to --log-file)'), - BoolOpt('use-syslog', - default=False, - help='Use syslog for logging.'), - StrOpt('syslog-log-facility', - default='LOG_USER', - help='syslog facility to receive log lines') - ] - - def __init__(self): - super(CommonConfigOpts, self).__init__() - self.register_cli_opts(self.common_cli_opts) - self.register_cli_opts(self.logging_cli_opts) - - -CONF = CommonConfigOpts() +CONF = ConfigOpts() diff --git a/heat/openstack/common/log.py b/heat/openstack/common/log.py index 50adedbb..9c2a9a0c 100644 --- a/heat/openstack/common/log.py +++ b/heat/openstack/common/log.py @@ -47,6 +47,67 @@ from heat.openstack.common import local from heat.openstack.common import notifier +_DEFAULT_LOG_FORMAT = "%(asctime)s %(levelname)8s [%(name)s] %(message)s" +_DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S" + +common_cli_opts = [ + cfg.BoolOpt('debug', + short='d', + default=False, + help='Print debugging output (set logging level to ' + 'DEBUG instead of default WARNING level).'), + cfg.BoolOpt('verbose', + short='v', + default=False, + help='Print more verbose output (set logging level to ' + 'INFO instead of default WARNING level).'), +] + +logging_cli_opts = [ + cfg.StrOpt('log-config', + metavar='PATH', + help='If this option is specified, the logging configuration ' + 'file specified is used and overrides any other logging ' + 'options specified. Please see the Python logging module ' + 'documentation for details on logging configuration ' + 'files.'), + cfg.StrOpt('log-format', + default=_DEFAULT_LOG_FORMAT, + metavar='FORMAT', + help='A logging.Formatter log message format string which may ' + 'use any of the available logging.LogRecord attributes. ' + 'Default: %(default)s'), + cfg.StrOpt('log-date-format', + default=_DEFAULT_LOG_DATE_FORMAT, + metavar='DATE_FORMAT', + help='Format string for %%(asctime)s in log records. ' + 'Default: %(default)s'), + cfg.StrOpt('log-file', + metavar='PATH', + deprecated_name='logfile', + help='(Optional) Name of log file to output to. ' + 'If not set, logging will go to stdout.'), + cfg.StrOpt('log-dir', + deprecated_name='logdir', + help='(Optional) The directory to keep log files in ' + '(will be prepended to --log-file)'), + cfg.BoolOpt('use-syslog', + default=False, + help='Use syslog for logging.'), + cfg.StrOpt('syslog-log-facility', + default='LOG_USER', + help='syslog facility to receive log lines') +] + +generic_log_opts = [ + cfg.BoolOpt('use_stderr', + default=True, + help='Log output to standard error'), + cfg.StrOpt('logfile_mode', + default='0644', + help='Default file mode used when creating log files'), +] + log_opts = [ cfg.StrOpt('logging_context_format_string', default='%(asctime)s.%(msecs)03d %(levelname)s %(name)s ' @@ -94,24 +155,9 @@ log_opts = [ 'format it like this'), ] - -generic_log_opts = [ - cfg.StrOpt('logdir', - default=None, - help='Log output to a per-service log file in named directory'), - cfg.StrOpt('logfile', - default=None, - help='Log output to a named file'), - cfg.BoolOpt('use_stderr', - default=True, - help='Log output to standard error'), - cfg.StrOpt('logfile_mode', - default='0644', - help='Default file mode used when creating log files'), -] - - CONF = cfg.CONF +CONF.register_cli_opts(common_cli_opts) +CONF.register_cli_opts(logging_cli_opts) CONF.register_opts(generic_log_opts) CONF.register_opts(log_opts) @@ -149,8 +195,8 @@ def _get_binary_name(): def _get_log_file_path(binary=None): - logfile = CONF.log_file or CONF.logfile - logdir = CONF.log_dir or CONF.logdir + logfile = CONF.log_file + logdir = CONF.log_dir if logfile and not logdir: return logfile diff --git a/heat/openstack/common/notifier/rpc_notifier2.py b/heat/openstack/common/notifier/rpc_notifier2.py new file mode 100644 index 00000000..e38663e9 --- /dev/null +++ b/heat/openstack/common/notifier/rpc_notifier2.py @@ -0,0 +1,51 @@ +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# 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. + +'''messaging based notification driver, with message envelopes''' + +from heat.openstack.common import cfg +from heat.openstack.common import context as req_context +from heat.openstack.common.gettextutils import _ +from heat.openstack.common import log as logging +from heat.openstack.common import rpc + +LOG = logging.getLogger(__name__) + +notification_topic_opt = cfg.ListOpt( + 'topics', default=['notifications', ], + help='AMQP topic(s) used for openstack notifications') + +opt_group = cfg.OptGroup(name='rpc_notifier2', + title='Options for rpc_notifier2') + +CONF = cfg.CONF +CONF.register_group(opt_group) +CONF.register_opt(notification_topic_opt, opt_group) + + +def notify(context, message): + """Sends a notification via RPC""" + if not context: + context = req_context.get_admin_context() + priority = message.get('priority', + CONF.default_notification_level) + priority = priority.lower() + for topic in CONF.rpc_notifier2.topics: + topic = '%s.%s' % (topic, priority) + try: + rpc.notify(context, topic, message, envelope=True) + except Exception: + LOG.exception(_("Could not send notification to %(topic)s. " + "Payload=%(message)s"), locals()) diff --git a/heat/openstack/common/rpc/impl_zmq.py b/heat/openstack/common/rpc/impl_zmq.py index b2c16bfc..1e10fbf0 100644 --- a/heat/openstack/common/rpc/impl_zmq.py +++ b/heat/openstack/common/rpc/impl_zmq.py @@ -449,7 +449,7 @@ class ZmqProxy(ZmqBaseReactor): else: sock_type = zmq.PUSH - if not topic in self.topic_proxy: + if topic not in self.topic_proxy: def publisher(waiter): LOG.info(_("Creating proxy for topic: %s"), topic) diff --git a/heat/openstack/common/setup.py b/heat/openstack/common/setup.py index 7267a6a2..fb187fff 100644 --- a/heat/openstack/common/setup.py +++ b/heat/openstack/common/setup.py @@ -108,13 +108,17 @@ def parse_dependency_links(requirements_files=['requirements.txt', return dependency_links -def _run_shell_command(cmd): +def _run_shell_command(cmd, throw_on_error=False): if os.name == 'nt': output = subprocess.Popen(["cmd.exe", "/C", cmd], - stdout=subprocess.PIPE) + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) else: output = subprocess.Popen(["/bin/sh", "-c", cmd], - stdout=subprocess.PIPE) + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + if output.returncode and throw_on_error: + raise Exception("%s returned %d" % cmd, output.returncode) out = output.communicate() if len(out) == 0: return None @@ -254,14 +258,39 @@ def get_cmdclass(): return cmdclass -def get_version_from_git(): +def _get_revno(): + """Return the number of commits since the most recent tag. + + We use git-describe to find this out, but if there are no + tags then we fall back to counting commits since the beginning + of time. + """ + describe = _run_shell_command("git describe --always") + if "-" in describe: + return describe.rsplit("-", 2)[-2] + + # no tags found + revlist = _run_shell_command("git rev-list --abbrev-commit HEAD") + return len(revlist.splitlines()) + + +def get_version_from_git(pre_version): """Return a version which is equal to the tag that's on the current revision if there is one, or tag plus number of additional revisions if the current revision has no tag.""" if os.path.isdir('.git'): - return _run_shell_command( - "git describe --always").replace('-', '.') + if pre_version: + try: + return _run_shell_command( + "git describe --exact-match", + throw_on_error=True).replace('-', '.') + except Exception: + sha = _run_shell_command("git log -n1 --pretty=format:%h") + return "%s.a%s.g%s" % (pre_version, _get_revno(), sha) + else: + return _run_shell_command( + "git describe --always").replace('-', '.') return None @@ -281,7 +310,7 @@ def get_version_from_pkg_info(package_name): return pkg_info.get('Version', None) -def get_version(package_name): +def get_version(package_name, pre_version=None): """Get the version of the project. First, try getting it from PKG-INFO, if it exists. If it does, that means we're in a distribution tarball or that install has happened. Otherwise, if there is no PKG-INFO file, pull the @@ -293,10 +322,13 @@ def get_version(package_name): to make a source tarball from a fork of our repo with additional tags in it that they understand and desire the results of doing that. """ + version = os.environ.get("OSLO_PACKAGE_VERSION", None) + if version: + return version version = get_version_from_pkg_info(package_name) if version: return version - version = get_version_from_git() + version = get_version_from_git(pre_version) if version: return version raise Exception("Versioning for this project requires either an sdist" diff --git a/heat/openstack/common/timeutils.py b/heat/openstack/common/timeutils.py index 0f346087..5a011e81 100644 --- a/heat/openstack/common/timeutils.py +++ b/heat/openstack/common/timeutils.py @@ -98,6 +98,11 @@ def utcnow(): return datetime.datetime.utcnow() +def iso8601_from_timestamp(timestamp): + """Returns a iso8601 formated date from timestamp""" + return isotime(datetime.datetime.utcfromtimestamp(timestamp)) + + utcnow.override_time = None @@ -162,3 +167,16 @@ def delta_seconds(before, after): except AttributeError: return ((delta.days * 24 * 3600) + delta.seconds + float(delta.microseconds) / (10 ** 6)) + + +def is_soon(dt, window): + """ + Determines if time is going to happen in the next window seconds. + + :params dt: the time + :params window: minimum seconds to remain to consider the time not soon + + :return: True if expiration is within the given duration + """ + soon = (utcnow() + datetime.timedelta(seconds=window)) + return normalize_time(dt) < soon diff --git a/heat/openstack/common/version.py b/heat/openstack/common/version.py index 8f7e1a90..3653ad0d 100644 --- a/heat/openstack/common/version.py +++ b/heat/openstack/common/version.py @@ -29,6 +29,7 @@ class VersionInfo(object): python-glanceclient """ self.package = package + self.release = None self.version = None self._cached_version = None @@ -39,18 +40,31 @@ class VersionInfo(object): provider = pkg_resources.get_provider(requirement) return provider.version - def version_string(self): + def release_string(self): """Return the full version of the package including suffixes indicating VCS status. """ + if self.release is None: + self.release = self._get_version_from_pkg_resources() + + return self.release + + def version_string(self): + """Return the short version minus any alpha/beta tags.""" if self.version is None: - self.version = self._get_version_from_pkg_resources() + parts = [] + for part in self.release_string().split('.'): + if part[0].isdigit(): + parts.append(part) + else: + break + self.version = ".".join(parts) return self.version # Compatibility functions canonical_version_string = version_string - version_string_with_vcs = version_string + version_string_with_vcs = release_string def cached_version_string(self, prefix=""): """Generate an object which will expand in a string context to -- 2.45.2