From 5d5d8ba5dd388b5952a4fe4f614409ba07d3bd9d Mon Sep 17 00:00:00 2001 From: Steven Dake Date: Fri, 13 Jul 2012 15:30:03 -0700 Subject: [PATCH] Update openstack-common in prep for pulling in common.rpc Change-Id: Ib3444d97967c807cb96175ce23d4b670a028e9a7 Signed-off-by: Steven Dake --- heat/openstack/common/__init__.py | 14 -- heat/openstack/common/cfg.py | 108 ++++++++---- heat/openstack/common/exception.py | 1 + heat/openstack/common/gettextutils.py | 33 ++++ heat/openstack/common/importutils.py | 17 +- heat/openstack/common/iniparser.py | 5 +- heat/openstack/common/setup.py | 233 ++++++++++++++++++++++---- heat/openstack/common/timeutils.py | 36 ++++ heat/openstack/common/utils.py | 11 +- openstack-common.conf | 2 +- setup.py | 2 - 11 files changed, 366 insertions(+), 96 deletions(-) create mode 100644 heat/openstack/common/gettextutils.py diff --git a/heat/openstack/common/__init__.py b/heat/openstack/common/__init__.py index e8e4035941..e69de29bb2 100644 --- a/heat/openstack/common/__init__.py +++ b/heat/openstack/common/__init__.py @@ -1,14 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# -# 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. diff --git a/heat/openstack/common/cfg.py b/heat/openstack/common/cfg.py index b68c19b076..6785766cb4 100644 --- a/heat/openstack/common/cfg.py +++ b/heat/openstack/common/cfg.py @@ -42,8 +42,8 @@ Options can be strings, integers, floats, booleans, lists or 'multi strings':: osapi_compute_extension_opt = cfg.MultiStrOpt('osapi_compute_extension', default=DEFAULT_EXTENSIONS) -Option schemas are registered with with the config manager at runtime, but -before the option is referenced:: +Option schemas are registered with the config manager at runtime, but before +the option is referenced:: class ExtensionManager(object): @@ -391,7 +391,7 @@ def _get_config_dirs(project=None): fix_path('~'), os.path.join('/etc', project) if project else None, '/etc' - ] + ] return filter(bool, cfg_dirs) @@ -494,7 +494,8 @@ class Opt(object): multi = False def __init__(self, name, dest=None, short=None, default=None, - metavar=None, help=None, secret=False, required=False): + metavar=None, help=None, secret=False, required=False, + deprecated_name=None): """Construct an Opt object. The only required parameter is the option's name. However, it is @@ -508,6 +509,7 @@ class Opt(object): :param help: an explanation of how the option is used :param secret: true iff the value should be obfuscated in log output :param required: true iff a value must be supplied for this option + :param deprecated_name: deprecated name option. Acts like an alias """ self.name = name if dest is None: @@ -520,6 +522,10 @@ class Opt(object): self.help = help self.secret = secret self.required = required + if deprecated_name is not None: + self.deprecated_name = deprecated_name.replace('-', '_') + else: + self.deprecated_name = None def _get_from_config_parser(self, cparser, section): """Retrieves the option value from a MultiConfigParser object. @@ -531,7 +537,13 @@ class Opt(object): :param cparser: a ConfigParser object :param section: a section name """ - return cparser.get(section, self.dest) + return self._cparser_get_with_deprecated(cparser, section) + + def _cparser_get_with_deprecated(self, cparser, section): + """If cannot find option as dest try deprecated_name alias.""" + if self.deprecated_name is not None: + return cparser.get(section, [self.dest, self.deprecated_name]) + return cparser.get(section, [self.dest]) def _add_to_cli(self, parser, group=None): """Makes the option available in the command line interface. @@ -546,9 +558,11 @@ class Opt(object): container = self._get_optparse_container(parser, group) kwargs = self._get_optparse_kwargs(group) prefix = self._get_optparse_prefix('', group) - self._add_to_optparse(container, self.name, self.short, kwargs, prefix) + self._add_to_optparse(container, self.name, self.short, kwargs, prefix, + self.deprecated_name) - def _add_to_optparse(self, container, name, short, kwargs, prefix=''): + def _add_to_optparse(self, container, name, short, kwargs, prefix='', + deprecated_name=None): """Add an option to an optparse parser or group. :param container: an optparse.OptionContainer object @@ -561,6 +575,8 @@ class Opt(object): args = ['--' + prefix + name] if short: args += ['-' + short] + if deprecated_name: + args += ['--' + prefix + deprecated_name] for a in args: if container.has_option(a): raise DuplicateOptError(a) @@ -591,11 +607,9 @@ class Opt(object): dest = self.dest if group is not None: dest = group.name + '_' + dest - kwargs.update({ - 'dest': dest, - 'metavar': self.metavar, - 'help': self.help, - }) + kwargs.update({'dest': dest, + 'metavar': self.metavar, + 'help': self.help, }) return kwargs def _get_optparse_prefix(self, prefix, group): @@ -645,7 +659,8 @@ class BoolOpt(Opt): return value - return [convert_bool(v) for v in cparser.get(section, self.dest)] + return [convert_bool(v) for v in + self._cparser_get_with_deprecated(cparser, section)] def _add_to_cli(self, parser, group=None): """Extends the base class method to add the --nooptname option.""" @@ -658,7 +673,8 @@ class BoolOpt(Opt): kwargs = self._get_optparse_kwargs(group, action='store_false') prefix = self._get_optparse_prefix('no', group) kwargs["help"] = "The inverse of --" + self.name - self._add_to_optparse(container, self.name, None, kwargs, prefix) + self._add_to_optparse(container, self.name, None, kwargs, prefix, + self.deprecated_name) def _get_optparse_kwargs(self, group, action='store_true', **kwargs): """Extends the base optparse keyword dict for boolean options.""" @@ -672,7 +688,8 @@ class IntOpt(Opt): def _get_from_config_parser(self, cparser, section): """Retrieve the opt value as a integer from ConfigParser.""" - return [int(v) for v in cparser.get(section, self.dest)] + return [int(v) for v in self._cparser_get_with_deprecated(cparser, + section)] def _get_optparse_kwargs(self, group, **kwargs): """Extends the base optparse keyword dict for integer options.""" @@ -686,7 +703,8 @@ class FloatOpt(Opt): def _get_from_config_parser(self, cparser, section): """Retrieve the opt value as a float from ConfigParser.""" - return [float(v) for v in cparser.get(section, self.dest)] + return [float(v) for v in + self._cparser_get_with_deprecated(cparser, section)] def _get_optparse_kwargs(self, group, **kwargs): """Extends the base optparse keyword dict for float options.""" @@ -703,7 +721,8 @@ class ListOpt(Opt): def _get_from_config_parser(self, cparser, section): """Retrieve the opt value as a list from ConfigParser.""" - return [v.split(',') for v in cparser.get(section, self.dest)] + return [v.split(',') for v in + self._cparser_get_with_deprecated(cparser, section)] def _get_optparse_kwargs(self, group, **kwargs): """Extends the base optparse keyword dict for list options.""" @@ -732,6 +751,13 @@ class MultiStrOpt(Opt): return super(MultiStrOpt, self)._get_optparse_kwargs(group, action='append') + def _cparser_get_with_deprecated(self, cparser, section): + """If cannot find option as dest try deprecated_name alias.""" + if self.deprecated_name is not None: + return cparser.get(section, [self.dest, self.deprecated_name], + multi=True) + return cparser.get(section, [self.dest], multi=True) + class OptGroup(object): @@ -846,25 +872,38 @@ class ConfigParser(iniparser.BaseParser): class MultiConfigParser(object): def __init__(self): - self.sections = {} + self.parsed = [] def read(self, config_files): read_ok = [] for filename in config_files: - parser = ConfigParser(filename, self.sections) + sections = {} + parser = ConfigParser(filename, sections) try: parser.parse() except IOError: continue - + self.parsed.insert(0, sections) read_ok.append(filename) return read_ok - def get(self, section, name): - return self.sections[section][name] + def get(self, section, names, multi=False): + rvalue = [] + for sections in self.parsed: + if section not in sections: + continue + for name in names: + if name in sections[section]: + if multi: + rvalue = sections[section][name] + rvalue + else: + return sections[section][name] + if multi and rvalue != []: + return rvalue + raise KeyError class ConfigOpts(collections.Mapping): @@ -905,13 +944,13 @@ class ConfigOpts(collections.Mapping): self._oparser.disable_interspersed_args() self._config_opts = [ - MultiStrOpt('config-file', - default=default_config_files, - metavar='PATH', - help='Path to a config file to use. Multiple config ' - 'files can be specified, with values in later ' - 'files taking precedence. The default files ' - ' used are: %s' % (default_config_files, )), + MultiStrOpt('config-file', + default=default_config_files, + metavar='PATH', + help='Path to a config file to use. Multiple config ' + 'files can be specified, with values in later ' + 'files taking precedence. The default files ' + ' used are: %s' % (default_config_files, )), StrOpt('config-dir', metavar='DIR', help='Path to a config directory to pull *.conf ' @@ -921,7 +960,7 @@ class ConfigOpts(collections.Mapping): 'the file(s), if any, specified via --config-file, ' 'hence over-ridden options in the directory take ' 'precedence.'), - ] + ] self.register_cli_opts(self._config_opts) self.project = project @@ -1323,7 +1362,7 @@ class ConfigOpts(collections.Mapping): def _substitute(self, value): """Perform string template substitution. - Substititue any template variables (e.g. $foo, ${bar}) in the supplied + Substitute any template variables (e.g. $foo, ${bar}) in the supplied string value(s) with opt values. :param value: the string value, or list of string values @@ -1411,8 +1450,7 @@ class ConfigOpts(collections.Mapping): default, opt, override = [info[k] for k in sorted(info.keys())] if opt.required: - if (default is not None or - override is not None): + if (default is not None or override is not None): continue if self._get(opt.name, group) is None: @@ -1516,7 +1554,7 @@ class CommonConfigOpts(ConfigOpts): short='v', default=False, help='Print more verbose output'), - ] + ] logging_cli_opts = [ StrOpt('log-config', @@ -1550,7 +1588,7 @@ class CommonConfigOpts(ConfigOpts): StrOpt('syslog-log-facility', default='LOG_USER', help='syslog facility to receive log lines') - ] + ] def __init__(self): super(CommonConfigOpts, self).__init__() diff --git a/heat/openstack/common/exception.py b/heat/openstack/common/exception.py index ba32da550b..e5da94b949 100644 --- a/heat/openstack/common/exception.py +++ b/heat/openstack/common/exception.py @@ -19,6 +19,7 @@ Exceptions common to OpenStack projects """ +import itertools import logging diff --git a/heat/openstack/common/gettextutils.py b/heat/openstack/common/gettextutils.py new file mode 100644 index 0000000000..235350cc49 --- /dev/null +++ b/heat/openstack/common/gettextutils.py @@ -0,0 +1,33 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 Red Hat, Inc. +# 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. + +""" +gettext for openstack-common modules. + +Usual usage in an openstack.common module: + + from openstack.common.gettextutils import _ +""" + +import gettext + + +t = gettext.translation('openstack-common', 'locale', fallback=True) + + +def _(msg): + return t.ugettext(msg) diff --git a/heat/openstack/common/importutils.py b/heat/openstack/common/importutils.py index 7654af5b95..2fbb0291a0 100644 --- a/heat/openstack/common/importutils.py +++ b/heat/openstack/common/importutils.py @@ -20,6 +20,7 @@ Import related utilities and helper functions. """ import sys +import traceback def import_class(import_str): @@ -30,7 +31,8 @@ def import_class(import_str): return getattr(sys.modules[mod_str], class_str) except (ImportError, ValueError, AttributeError), exc: raise ImportError('Class %s cannot be found (%s)' % - (class_str, str(exc))) + (class_str, + traceback.format_exception(*sys.exc_info()))) def import_object(import_str, *args, **kwargs): @@ -38,6 +40,19 @@ def import_object(import_str, *args, **kwargs): return import_class(import_str)(*args, **kwargs) +def import_object_ns(name_space, import_str, *args, **kwargs): + """ + Import a class and return an instance of it, first by trying + to find the class in a default namespace, then failing back to + a full path if not found in the default namespace. + """ + import_value = "%s.%s" % (name_space, import_str) + try: + return import_class(import_value)(*args, **kwargs) + except ImportError: + return import_class(import_str)(*args, **kwargs) + + def import_module(import_str): """Import a module.""" __import__(import_str) diff --git a/heat/openstack/common/iniparser.py b/heat/openstack/common/iniparser.py index 53ca023343..e91eea5380 100644 --- a/heat/openstack/common/iniparser.py +++ b/heat/openstack/common/iniparser.py @@ -52,7 +52,10 @@ class BaseParser(object): else: key, value = line[:colon], line[colon + 1:] - return key.strip(), [value.strip()] + value = value.strip() + if value[0] == value[-1] and value[0] == "\"" or value[0] == "'": + value = value[1:-1] + return key.strip(), [value] def parse(self, lineiter): key = None diff --git a/heat/openstack/common/setup.py b/heat/openstack/common/setup.py index 79b5a62bca..59f255d0c0 100644 --- a/heat/openstack/common/setup.py +++ b/heat/openstack/common/setup.py @@ -19,9 +19,11 @@ Utilities with minimum-depends for use in setup.py """ +import datetime import os import re import subprocess +import sys from setuptools.command import sdist @@ -76,6 +78,10 @@ def parse_requirements(requirements_files=['requirements.txt', # -f lines are for index locations, and don't get used here elif re.match(r'\s*-f\s+', line): pass + # argparse is part of the standard library starting with 2.7 + # adding it to the requirements list screws distro installs + elif line == 'argparse' and sys.version_info >= (2, 7): + pass else: requirements.append(line) @@ -113,38 +119,75 @@ def write_requirements(): def _run_shell_command(cmd): output = subprocess.Popen(["/bin/sh", "-c", cmd], stdout=subprocess.PIPE) - return output.communicate()[0].strip() + out = output.communicate() + if len(out) == 0: + return None + if len(out[0].strip()) == 0: + return None + return out[0].strip() -def write_vcsversion(location): - """Produce a vcsversion dict that mimics the old one produced by bzr. - """ - if os.path.isdir('.git'): - branch_nick_cmd = 'git branch | grep -Ei "\* (.*)" | cut -f2 -d" "' - branch_nick = _run_shell_command(branch_nick_cmd) - revid_cmd = "git rev-parse HEAD" - revid = _run_shell_command(revid_cmd).split()[0] - revno_cmd = "git log --oneline | wc -l" - revno = _run_shell_command(revno_cmd) - with open(location, 'w') as version_file: - version_file.write(""" -# This file is automatically generated by setup.py, So don't edit it. :) -version_info = { - 'branch_nick': '%s', - 'revision_id': '%s', - 'revno': %s -} -""" % (branch_nick, revid, revno)) +def _get_git_next_version_suffix(branch_name): + datestamp = datetime.datetime.now().strftime('%Y%m%d') + if branch_name == 'milestone-proposed': + revno_prefix = "r" + else: + revno_prefix = "" + _run_shell_command("git fetch origin +refs/meta/*:refs/remotes/meta/*") + milestone_cmd = "git show meta/openstack/release:%s" % branch_name + milestonever = _run_shell_command(milestone_cmd) + if not milestonever: + milestonever = "" + post_version = _get_git_post_version() + # post version should look like: + # 0.1.1.4.gcc9e28a + # where the bit after the last . is the short sha, and the bit between + # the last and second to last is the revno count + (revno, sha) = post_version.split(".")[-2:] + first_half = "%(milestonever)s~%(datestamp)s" % locals() + second_half = "%(revno_prefix)s%(revno)s.%(sha)s" % locals() + return ".".join((first_half, second_half)) + + +def _get_git_current_tag(): + return _run_shell_command("git tag --contains HEAD") + + +def _get_git_tag_info(): + return _run_shell_command("git describe --tags") + + +def _get_git_post_version(): + current_tag = _get_git_current_tag() + if current_tag is not None: + return current_tag + else: + tag_info = _get_git_tag_info() + if tag_info is None: + base_version = "0.0" + cmd = "git --no-pager log --oneline" + out = _run_shell_command(cmd) + revno = len(out.split("\n")) + sha = _run_shell_command("git describe --always") + else: + tag_infos = tag_info.split("-") + base_version = "-".join(tag_infos[:-2]) + (revno, sha) = tag_infos[-2:] + return "%s.%s.%s" % (base_version, revno, sha) def write_git_changelog(): """Write a changelog based on the git changelog.""" - if os.path.isdir('.git'): - git_log_cmd = 'git log --stat' - changelog = _run_shell_command(git_log_cmd) - mailmap = parse_mailmap() - with open("ChangeLog", "w") as changelog_file: - changelog_file.write(canonicalize_emails(changelog, mailmap)) + new_changelog = 'ChangeLog' + if not os.getenv('SKIP_WRITE_GIT_CHANGELOG'): + if os.path.isdir('.git'): + git_log_cmd = 'git log --stat' + changelog = _run_shell_command(git_log_cmd) + mailmap = parse_mailmap() + with open(new_changelog, "w") as changelog_file: + changelog_file.write(canonicalize_emails(changelog, mailmap)) + else: + open(new_changelog, 'w').close() def generate_authors(): @@ -152,17 +195,49 @@ def generate_authors(): jenkins_email = 'jenkins@review.openstack.org' old_authors = 'AUTHORS.in' new_authors = 'AUTHORS' - if os.path.isdir('.git'): - # don't include jenkins email address in AUTHORS file - git_log_cmd = ("git log --format='%aN <%aE>' | sort -u | " - "grep -v " + jenkins_email) - changelog = _run_shell_command(git_log_cmd) - mailmap = parse_mailmap() - with open(new_authors, 'w') as new_authors_fh: - new_authors_fh.write(canonicalize_emails(changelog, mailmap)) - if os.path.exists(old_authors): - with open(old_authors, "r") as old_authors_fh: - new_authors_fh.write('\n' + old_authors_fh.read()) + if not os.getenv('SKIP_GENERATE_AUTHORS'): + if os.path.isdir('.git'): + # don't include jenkins email address in AUTHORS file + git_log_cmd = ("git log --format='%aN <%aE>' | sort -u | " + "grep -v " + jenkins_email) + changelog = _run_shell_command(git_log_cmd) + mailmap = parse_mailmap() + with open(new_authors, 'w') as new_authors_fh: + new_authors_fh.write(canonicalize_emails(changelog, mailmap)) + if os.path.exists(old_authors): + with open(old_authors, "r") as old_authors_fh: + new_authors_fh.write('\n' + old_authors_fh.read()) + else: + open(new_authors, 'w').close() + + +_rst_template = """%(heading)s +%(underline)s + +.. automodule:: %(module)s + :members: + :undoc-members: + :show-inheritance: +""" + + +def read_versioninfo(project): + """Read the versioninfo file. If it doesn't exist, we're in a github + zipball, and there's really no way to know what version we really + are, but that should be ok, because the utility of that should be + just about nil if this code path is in use in the first place.""" + versioninfo_path = os.path.join(project, 'versioninfo') + if os.path.exists(versioninfo_path): + with open(versioninfo_path, 'r') as vinfo: + version = vinfo.read().strip() + else: + version = "0.0.0" + return version + + +def write_versioninfo(project, version): + """Write a simple file containing the version of the package.""" + open(os.path.join(project, 'versioninfo'), 'w').write("%s\n" % version) def get_cmdclass(): @@ -170,6 +245,12 @@ def get_cmdclass(): cmdclass = dict() + def _find_modules(arg, dirname, files): + for filename in files: + if filename.endswith('.py') and filename != '__init__.py': + arg["%s.%s" % (dirname.replace('/', '.'), + filename[:-3])] = True + class LocalSDist(sdist.sdist): """Builds the ChangeLog and Authors files from VC first.""" @@ -188,13 +269,91 @@ def get_cmdclass(): from sphinx.setup_command import BuildDoc class LocalBuildDoc(BuildDoc): + def generate_autoindex(self): + print "**Autodocumenting from %s" % os.path.abspath(os.curdir) + modules = {} + option_dict = self.distribution.get_option_dict('build_sphinx') + source_dir = os.path.join(option_dict['source_dir'][1], 'api') + if not os.path.exists(source_dir): + os.makedirs(source_dir) + for pkg in self.distribution.packages: + if '.' not in pkg: + os.path.walk(pkg, _find_modules, modules) + module_list = modules.keys() + module_list.sort() + autoindex_filename = os.path.join(source_dir, 'autoindex.rst') + with open(autoindex_filename, 'w') as autoindex: + autoindex.write(""".. toctree:: + :maxdepth: 1 + +""") + for module in module_list: + output_filename = os.path.join(source_dir, + "%s.rst" % module) + heading = "The :mod:`%s` Module" % module + underline = "=" * len(heading) + values = dict(module=module, heading=heading, + underline=underline) + + print "Generating %s" % output_filename + with open(output_filename, 'w') as output_file: + output_file.write(_rst_template % values) + autoindex.write(" %s.rst\n" % module) + def run(self): + if not os.getenv('SPHINX_DEBUG'): + self.generate_autoindex() + for builder in ['html', 'man']: self.builder = builder self.finalize_options() + self.project = self.distribution.get_name() + self.version = self.distribution.get_version() + self.release = self.distribution.get_version() BuildDoc.run(self) cmdclass['build_sphinx'] = LocalBuildDoc except ImportError: pass return cmdclass + + +def get_git_branchname(): + for branch in _run_shell_command("git branch --color=never").split("\n"): + if branch.startswith('*'): + _branch_name = branch.split()[1].strip() + if _branch_name == "(no": + _branch_name = "no-branch" + return _branch_name + + +def get_pre_version(projectname, base_version): + """Return a version which is leading up to a version that will + be released in the future.""" + if os.path.isdir('.git'): + current_tag = _get_git_current_tag() + if current_tag is not None: + version = current_tag + else: + branch_name = os.getenv('BRANCHNAME', + os.getenv('GERRIT_REFNAME', + get_git_branchname())) + version_suffix = _get_git_next_version_suffix(branch_name) + version = "%s~%s" % (base_version, version_suffix) + write_versioninfo(projectname, version) + return version + else: + version = read_versioninfo(projectname) + return version + + +def get_post_version(projectname): + """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'): + version = _get_git_post_version() + write_versioninfo(projectname, version) + return version + return read_versioninfo(projectname) diff --git a/heat/openstack/common/timeutils.py b/heat/openstack/common/timeutils.py index 345f315fbe..5eeaf70aa4 100644 --- a/heat/openstack/common/timeutils.py +++ b/heat/openstack/common/timeutils.py @@ -19,12 +19,15 @@ Time related utilities and helper functions. """ +import calendar import datetime +import time import iso8601 TIME_FORMAT = "%Y-%m-%dT%H:%M:%S" +PERFECT_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%f" def isotime(at=None): @@ -47,12 +50,34 @@ def parse_isotime(timestr): raise ValueError(e.message) +def strtime(at=None, fmt=PERFECT_TIME_FORMAT): + """Returns formatted utcnow.""" + if not at: + at = utcnow() + return at.strftime(fmt) + + +def parse_strtime(timestr, fmt=PERFECT_TIME_FORMAT): + """Turn a formatted time back into a datetime.""" + return datetime.datetime.strptime(timestr, fmt) + + def normalize_time(timestamp): """Normalize time in arbitrary timezone to UTC""" offset = timestamp.utcoffset() return timestamp.replace(tzinfo=None) - offset if offset else timestamp +def is_older_than(before, seconds): + """Return True if before is older than seconds.""" + return utcnow() - before > datetime.timedelta(seconds=seconds) + + +def utcnow_ts(): + """Timestamp version of our utcnow function.""" + return calendar.timegm(utcnow().timetuple()) + + def utcnow(): """Overridable version of utils.utcnow.""" if utcnow.override_time: @@ -68,6 +93,17 @@ def set_time_override(override_time=datetime.datetime.utcnow()): utcnow.override_time = override_time +def advance_time_delta(timedelta): + """Advance overriden time using a datetime.timedelta.""" + assert(not utcnow.override_time is None) + utcnow.override_time += timedelta + + +def advance_time_seconds(seconds): + """Advance overriden time by seconds.""" + advance_time_delta(datetime.timedelta(0, seconds)) + + def clear_time_override(): """Remove the overridden time.""" utcnow.override_time = None diff --git a/heat/openstack/common/utils.py b/heat/openstack/common/utils.py index 7e5f061100..5d0099e925 100644 --- a/heat/openstack/common/utils.py +++ b/heat/openstack/common/utils.py @@ -28,6 +28,7 @@ from eventlet import greenthread from eventlet.green import subprocess from heat.openstack.common import exception +from heat.openstack.common.gettextutils import _ LOG = logging.getLogger(__name__) @@ -118,13 +119,13 @@ def execute(*cmd, **kwargs): LOG.debug(_('Result was %s') % _returncode) if (isinstance(check_exit_code, int) and not isinstance(check_exit_code, bool) and - _returncode != check_exit_code): + _returncode != check_exit_code): (stdout, stderr) = result raise exception.ProcessExecutionError( - exit_code=_returncode, - stdout=stdout, - stderr=stderr, - cmd=' '.join(cmd)) + exit_code=_returncode, + stdout=stdout, + stderr=stderr, + cmd=' '.join(cmd)) return result except exception.ProcessExecutionError: if not attempts: diff --git a/openstack-common.conf b/openstack-common.conf index 7a059c8865..d61ff0b602 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -1,7 +1,7 @@ [DEFAULT] # The list of modules to copy from openstack-common -modules=cfg,local,iniparser,utils,exception,timeutils,importutils,setup +modules=gettextutils,cfg,local,iniparser,utils,exception,timeutils,importutils,setup # The base module to hold the copy of openstack.common base=heat diff --git a/setup.py b/setup.py index 53df1d5506..6cafbb08b7 100755 --- a/setup.py +++ b/setup.py @@ -21,8 +21,6 @@ import setuptools from heat.openstack.common import setup -setup.write_vcsversion('heat/vcsversion.py') - # import this after write_vcsversion because version imports vcsversion from heat import version