Added config tools
Change-Id: I604a510984bf4f071bb9aef80be3635e12b6739c
This commit is contained in:
		
							
								
								
									
										0
									
								
								cloudkitty/openstack/common/config/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								cloudkitty/openstack/common/config/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										313
									
								
								cloudkitty/openstack/common/config/generator.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										313
									
								
								cloudkitty/openstack/common/config/generator.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,313 @@
 | 
				
			|||||||
 | 
					# Copyright 2012 SINA Corporation
 | 
				
			||||||
 | 
					# Copyright 2014 Cisco Systems, 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.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""Extracts OpenStack config option info from module(s)."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from __future__ import print_function
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import argparse
 | 
				
			||||||
 | 
					import imp
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					import socket
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import textwrap
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from oslo.config import cfg
 | 
				
			||||||
 | 
					import six
 | 
				
			||||||
 | 
					import stevedore.named
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from cloudkitty.openstack.common import gettextutils
 | 
				
			||||||
 | 
					from cloudkitty.openstack.common import importutils
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					gettextutils.install('cloudkitty')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					STROPT = "StrOpt"
 | 
				
			||||||
 | 
					BOOLOPT = "BoolOpt"
 | 
				
			||||||
 | 
					INTOPT = "IntOpt"
 | 
				
			||||||
 | 
					FLOATOPT = "FloatOpt"
 | 
				
			||||||
 | 
					LISTOPT = "ListOpt"
 | 
				
			||||||
 | 
					DICTOPT = "DictOpt"
 | 
				
			||||||
 | 
					MULTISTROPT = "MultiStrOpt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					OPT_TYPES = {
 | 
				
			||||||
 | 
					    STROPT: 'string value',
 | 
				
			||||||
 | 
					    BOOLOPT: 'boolean value',
 | 
				
			||||||
 | 
					    INTOPT: 'integer value',
 | 
				
			||||||
 | 
					    FLOATOPT: 'floating point value',
 | 
				
			||||||
 | 
					    LISTOPT: 'list value',
 | 
				
			||||||
 | 
					    DICTOPT: 'dict value',
 | 
				
			||||||
 | 
					    MULTISTROPT: 'multi valued',
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					OPTION_REGEX = re.compile(r"(%s)" % "|".join([STROPT, BOOLOPT, INTOPT,
 | 
				
			||||||
 | 
					                                              FLOATOPT, LISTOPT, DICTOPT,
 | 
				
			||||||
 | 
					                                              MULTISTROPT]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PY_EXT = ".py"
 | 
				
			||||||
 | 
					BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__),
 | 
				
			||||||
 | 
					                                       "../../../../"))
 | 
				
			||||||
 | 
					WORDWRAP_WIDTH = 60
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def raise_extension_exception(extmanager, ep, err):
 | 
				
			||||||
 | 
					    raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def generate(argv):
 | 
				
			||||||
 | 
					    parser = argparse.ArgumentParser(
 | 
				
			||||||
 | 
					        description='generate sample configuration file',
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    parser.add_argument('-m', dest='modules', action='append')
 | 
				
			||||||
 | 
					    parser.add_argument('-l', dest='libraries', action='append')
 | 
				
			||||||
 | 
					    parser.add_argument('srcfiles', nargs='*')
 | 
				
			||||||
 | 
					    parsed_args = parser.parse_args(argv)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mods_by_pkg = dict()
 | 
				
			||||||
 | 
					    for filepath in parsed_args.srcfiles:
 | 
				
			||||||
 | 
					        pkg_name = filepath.split(os.sep)[1]
 | 
				
			||||||
 | 
					        mod_str = '.'.join(['.'.join(filepath.split(os.sep)[:-1]),
 | 
				
			||||||
 | 
					                            os.path.basename(filepath).split('.')[0]])
 | 
				
			||||||
 | 
					        mods_by_pkg.setdefault(pkg_name, list()).append(mod_str)
 | 
				
			||||||
 | 
					    # NOTE(lzyeval): place top level modules before packages
 | 
				
			||||||
 | 
					    pkg_names = sorted(pkg for pkg in mods_by_pkg if pkg.endswith(PY_EXT))
 | 
				
			||||||
 | 
					    ext_names = sorted(pkg for pkg in mods_by_pkg if pkg not in pkg_names)
 | 
				
			||||||
 | 
					    pkg_names.extend(ext_names)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # opts_by_group is a mapping of group name to an options list
 | 
				
			||||||
 | 
					    # The options list is a list of (module, options) tuples
 | 
				
			||||||
 | 
					    opts_by_group = {'DEFAULT': []}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if parsed_args.modules:
 | 
				
			||||||
 | 
					        for module_name in parsed_args.modules:
 | 
				
			||||||
 | 
					            module = _import_module(module_name)
 | 
				
			||||||
 | 
					            if module:
 | 
				
			||||||
 | 
					                for group, opts in _list_opts(module):
 | 
				
			||||||
 | 
					                    opts_by_group.setdefault(group, []).append((module_name,
 | 
				
			||||||
 | 
					                                                                opts))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Look for entry points defined in libraries (or applications) for
 | 
				
			||||||
 | 
					    # option discovery, and include their return values in the output.
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # Each entry point should be a function returning an iterable
 | 
				
			||||||
 | 
					    # of pairs with the group name (or None for the default group)
 | 
				
			||||||
 | 
					    # and the list of Opt instances for that group.
 | 
				
			||||||
 | 
					    if parsed_args.libraries:
 | 
				
			||||||
 | 
					        loader = stevedore.named.NamedExtensionManager(
 | 
				
			||||||
 | 
					            'oslo.config.opts',
 | 
				
			||||||
 | 
					            names=list(set(parsed_args.libraries)),
 | 
				
			||||||
 | 
					            invoke_on_load=False,
 | 
				
			||||||
 | 
					            on_load_failure_callback=raise_extension_exception
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        for ext in loader:
 | 
				
			||||||
 | 
					            for group, opts in ext.plugin():
 | 
				
			||||||
 | 
					                opt_list = opts_by_group.setdefault(group or 'DEFAULT', [])
 | 
				
			||||||
 | 
					                opt_list.append((ext.name, opts))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for pkg_name in pkg_names:
 | 
				
			||||||
 | 
					        mods = mods_by_pkg.get(pkg_name)
 | 
				
			||||||
 | 
					        mods.sort()
 | 
				
			||||||
 | 
					        for mod_str in mods:
 | 
				
			||||||
 | 
					            if mod_str.endswith('.__init__'):
 | 
				
			||||||
 | 
					                mod_str = mod_str[:mod_str.rfind(".")]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            mod_obj = _import_module(mod_str)
 | 
				
			||||||
 | 
					            if not mod_obj:
 | 
				
			||||||
 | 
					                raise RuntimeError("Unable to import module %s" % mod_str)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for group, opts in _list_opts(mod_obj):
 | 
				
			||||||
 | 
					                opts_by_group.setdefault(group, []).append((mod_str, opts))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    print_group_opts('DEFAULT', opts_by_group.pop('DEFAULT', []))
 | 
				
			||||||
 | 
					    for group in sorted(opts_by_group.keys()):
 | 
				
			||||||
 | 
					        print_group_opts(group, opts_by_group[group])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _import_module(mod_str):
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        if mod_str.startswith('bin.'):
 | 
				
			||||||
 | 
					            imp.load_source(mod_str[4:], os.path.join('bin', mod_str[4:]))
 | 
				
			||||||
 | 
					            return sys.modules[mod_str[4:]]
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return importutils.import_module(mod_str)
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        sys.stderr.write("Error importing module %s: %s\n" % (mod_str, str(e)))
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _is_in_group(opt, group):
 | 
				
			||||||
 | 
					    """Check if opt is in group."""
 | 
				
			||||||
 | 
					    for value in group._opts.values():
 | 
				
			||||||
 | 
					        # NOTE(llu): Temporary workaround for bug #1262148, wait until
 | 
				
			||||||
 | 
					        # newly released oslo.config support '==' operator.
 | 
				
			||||||
 | 
					        if not(value['opt'] != opt):
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					    return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _guess_groups(opt):
 | 
				
			||||||
 | 
					    # is it in the DEFAULT group?
 | 
				
			||||||
 | 
					    if _is_in_group(opt, cfg.CONF):
 | 
				
			||||||
 | 
					        return 'DEFAULT'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # what other groups is it in?
 | 
				
			||||||
 | 
					    for value in cfg.CONF.values():
 | 
				
			||||||
 | 
					        if isinstance(value, cfg.CONF.GroupAttr):
 | 
				
			||||||
 | 
					            if _is_in_group(opt, value._group):
 | 
				
			||||||
 | 
					                return value._group.name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    raise RuntimeError(
 | 
				
			||||||
 | 
					        "Unable to find group for option %s, "
 | 
				
			||||||
 | 
					        "maybe it's defined twice in the same group?"
 | 
				
			||||||
 | 
					        % opt.name
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _list_opts(obj):
 | 
				
			||||||
 | 
					    def is_opt(o):
 | 
				
			||||||
 | 
					        return (isinstance(o, cfg.Opt) and
 | 
				
			||||||
 | 
					                not isinstance(o, cfg.SubCommandOpt))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    opts = list()
 | 
				
			||||||
 | 
					    for attr_str in dir(obj):
 | 
				
			||||||
 | 
					        attr_obj = getattr(obj, attr_str)
 | 
				
			||||||
 | 
					        if is_opt(attr_obj):
 | 
				
			||||||
 | 
					            opts.append(attr_obj)
 | 
				
			||||||
 | 
					        elif (isinstance(attr_obj, list) and
 | 
				
			||||||
 | 
					              all(map(lambda x: is_opt(x), attr_obj))):
 | 
				
			||||||
 | 
					            opts.extend(attr_obj)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ret = {}
 | 
				
			||||||
 | 
					    for opt in opts:
 | 
				
			||||||
 | 
					        ret.setdefault(_guess_groups(opt), []).append(opt)
 | 
				
			||||||
 | 
					    return ret.items()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def print_group_opts(group, opts_by_module):
 | 
				
			||||||
 | 
					    print("[%s]" % group)
 | 
				
			||||||
 | 
					    print('')
 | 
				
			||||||
 | 
					    for mod, opts in opts_by_module:
 | 
				
			||||||
 | 
					        print('#')
 | 
				
			||||||
 | 
					        print('# Options defined in %s' % mod)
 | 
				
			||||||
 | 
					        print('#')
 | 
				
			||||||
 | 
					        print('')
 | 
				
			||||||
 | 
					        for opt in opts:
 | 
				
			||||||
 | 
					            _print_opt(opt)
 | 
				
			||||||
 | 
					        print('')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _get_my_ip():
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
 | 
				
			||||||
 | 
					        csock.connect(('8.8.8.8', 80))
 | 
				
			||||||
 | 
					        (addr, port) = csock.getsockname()
 | 
				
			||||||
 | 
					        csock.close()
 | 
				
			||||||
 | 
					        return addr
 | 
				
			||||||
 | 
					    except socket.error:
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _sanitize_default(name, value):
 | 
				
			||||||
 | 
					    """Set up a reasonably sensible default for pybasedir, my_ip and host."""
 | 
				
			||||||
 | 
					    hostname = socket.gethostname()
 | 
				
			||||||
 | 
					    fqdn = socket.getfqdn()
 | 
				
			||||||
 | 
					    if value.startswith(sys.prefix):
 | 
				
			||||||
 | 
					        # NOTE(jd) Don't use os.path.join, because it is likely to think the
 | 
				
			||||||
 | 
					        # second part is an absolute pathname and therefore drop the first
 | 
				
			||||||
 | 
					        # part.
 | 
				
			||||||
 | 
					        value = os.path.normpath("/usr/" + value[len(sys.prefix):])
 | 
				
			||||||
 | 
					    elif value.startswith(BASEDIR):
 | 
				
			||||||
 | 
					        return value.replace(BASEDIR, '/usr/lib/python/site-packages')
 | 
				
			||||||
 | 
					    elif BASEDIR in value:
 | 
				
			||||||
 | 
					        return value.replace(BASEDIR, '')
 | 
				
			||||||
 | 
					    elif value == _get_my_ip():
 | 
				
			||||||
 | 
					        return '10.0.0.1'
 | 
				
			||||||
 | 
					    elif value in (hostname, fqdn):
 | 
				
			||||||
 | 
					        if 'host' in name:
 | 
				
			||||||
 | 
					            return 'cloudkitty'
 | 
				
			||||||
 | 
					    elif value.endswith(hostname):
 | 
				
			||||||
 | 
					        return value.replace(hostname, 'cloudkitty')
 | 
				
			||||||
 | 
					    elif value.endswith(fqdn):
 | 
				
			||||||
 | 
					        return value.replace(fqdn, 'cloudkitty')
 | 
				
			||||||
 | 
					    elif value.strip() != value:
 | 
				
			||||||
 | 
					        return '"%s"' % value
 | 
				
			||||||
 | 
					    return value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _print_opt(opt):
 | 
				
			||||||
 | 
					    opt_name, opt_default, opt_help = opt.dest, opt.default, opt.help
 | 
				
			||||||
 | 
					    if not opt_help:
 | 
				
			||||||
 | 
					        sys.stderr.write('WARNING: "%s" is missing help string.\n' % opt_name)
 | 
				
			||||||
 | 
					        opt_help = ""
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        opt_type = OPTION_REGEX.search(str(type(opt))).group(0)
 | 
				
			||||||
 | 
					    except (ValueError, AttributeError) as err:
 | 
				
			||||||
 | 
					        sys.stderr.write("%s\n" % str(err))
 | 
				
			||||||
 | 
					        sys.exit(1)
 | 
				
			||||||
 | 
					    opt_help = u'%s (%s)' % (opt_help,
 | 
				
			||||||
 | 
					                             OPT_TYPES[opt_type])
 | 
				
			||||||
 | 
					    print('#', "\n# ".join(textwrap.wrap(opt_help, WORDWRAP_WIDTH)))
 | 
				
			||||||
 | 
					    if opt.deprecated_opts:
 | 
				
			||||||
 | 
					        for deprecated_opt in opt.deprecated_opts:
 | 
				
			||||||
 | 
					            if deprecated_opt.name:
 | 
				
			||||||
 | 
					                deprecated_group = (deprecated_opt.group if
 | 
				
			||||||
 | 
					                                    deprecated_opt.group else "DEFAULT")
 | 
				
			||||||
 | 
					                print('# Deprecated group/name - [%s]/%s' %
 | 
				
			||||||
 | 
					                      (deprecated_group,
 | 
				
			||||||
 | 
					                       deprecated_opt.name))
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        if opt_default is None:
 | 
				
			||||||
 | 
					            print('#%s=<None>' % opt_name)
 | 
				
			||||||
 | 
					        elif opt_type == STROPT:
 | 
				
			||||||
 | 
					            assert(isinstance(opt_default, six.string_types))
 | 
				
			||||||
 | 
					            print('#%s=%s' % (opt_name, _sanitize_default(opt_name,
 | 
				
			||||||
 | 
					                                                          opt_default)))
 | 
				
			||||||
 | 
					        elif opt_type == BOOLOPT:
 | 
				
			||||||
 | 
					            assert(isinstance(opt_default, bool))
 | 
				
			||||||
 | 
					            print('#%s=%s' % (opt_name, str(opt_default).lower()))
 | 
				
			||||||
 | 
					        elif opt_type == INTOPT:
 | 
				
			||||||
 | 
					            assert(isinstance(opt_default, int) and
 | 
				
			||||||
 | 
					                   not isinstance(opt_default, bool))
 | 
				
			||||||
 | 
					            print('#%s=%s' % (opt_name, opt_default))
 | 
				
			||||||
 | 
					        elif opt_type == FLOATOPT:
 | 
				
			||||||
 | 
					            assert(isinstance(opt_default, float))
 | 
				
			||||||
 | 
					            print('#%s=%s' % (opt_name, opt_default))
 | 
				
			||||||
 | 
					        elif opt_type == LISTOPT:
 | 
				
			||||||
 | 
					            assert(isinstance(opt_default, list))
 | 
				
			||||||
 | 
					            print('#%s=%s' % (opt_name, ','.join(opt_default)))
 | 
				
			||||||
 | 
					        elif opt_type == DICTOPT:
 | 
				
			||||||
 | 
					            assert(isinstance(opt_default, dict))
 | 
				
			||||||
 | 
					            opt_default_strlist = [str(key) + ':' + str(value)
 | 
				
			||||||
 | 
					                                   for (key, value) in opt_default.items()]
 | 
				
			||||||
 | 
					            print('#%s=%s' % (opt_name, ','.join(opt_default_strlist)))
 | 
				
			||||||
 | 
					        elif opt_type == MULTISTROPT:
 | 
				
			||||||
 | 
					            assert(isinstance(opt_default, list))
 | 
				
			||||||
 | 
					            if not opt_default:
 | 
				
			||||||
 | 
					                opt_default = ['']
 | 
				
			||||||
 | 
					            for default in opt_default:
 | 
				
			||||||
 | 
					                print('#%s=%s' % (opt_name, default))
 | 
				
			||||||
 | 
					        print('')
 | 
				
			||||||
 | 
					    except Exception:
 | 
				
			||||||
 | 
					        sys.stderr.write('Error in option "%s"\n' % opt_name)
 | 
				
			||||||
 | 
					        sys.exit(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def main():
 | 
				
			||||||
 | 
					    generate(sys.argv[1:])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == '__main__':
 | 
				
			||||||
 | 
					    main()
 | 
				
			||||||
							
								
								
									
										498
									
								
								cloudkitty/openstack/common/gettextutils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										498
									
								
								cloudkitty/openstack/common/gettextutils.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,498 @@
 | 
				
			|||||||
 | 
					# Copyright 2012 Red Hat, Inc.
 | 
				
			||||||
 | 
					# Copyright 2013 IBM Corp.
 | 
				
			||||||
 | 
					# 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 cloudkitty.openstack.common.gettextutils import _
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import copy
 | 
				
			||||||
 | 
					import functools
 | 
				
			||||||
 | 
					import gettext
 | 
				
			||||||
 | 
					import locale
 | 
				
			||||||
 | 
					from logging import handlers
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from babel import localedata
 | 
				
			||||||
 | 
					import six
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_AVAILABLE_LANGUAGES = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# FIXME(dhellmann): Remove this when moving to oslo.i18n.
 | 
				
			||||||
 | 
					USE_LAZY = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TranslatorFactory(object):
 | 
				
			||||||
 | 
					    """Create translator functions
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, domain, lazy=False, localedir=None):
 | 
				
			||||||
 | 
					        """Establish a set of translation functions for the domain.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param domain: Name of translation domain,
 | 
				
			||||||
 | 
					                       specifying a message catalog.
 | 
				
			||||||
 | 
					        :type domain: str
 | 
				
			||||||
 | 
					        :param lazy: Delays translation until a message is emitted.
 | 
				
			||||||
 | 
					                     Defaults to False.
 | 
				
			||||||
 | 
					        :type lazy: Boolean
 | 
				
			||||||
 | 
					        :param localedir: Directory with translation catalogs.
 | 
				
			||||||
 | 
					        :type localedir: str
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.domain = domain
 | 
				
			||||||
 | 
					        self.lazy = lazy
 | 
				
			||||||
 | 
					        if localedir is None:
 | 
				
			||||||
 | 
					            localedir = os.environ.get(domain.upper() + '_LOCALEDIR')
 | 
				
			||||||
 | 
					        self.localedir = localedir
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _make_translation_func(self, domain=None):
 | 
				
			||||||
 | 
					        """Return a new translation function ready for use.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Takes into account whether or not lazy translation is being
 | 
				
			||||||
 | 
					        done.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        The domain can be specified to override the default from the
 | 
				
			||||||
 | 
					        factory, but the localedir from the factory is always used
 | 
				
			||||||
 | 
					        because we assume the log-level translation catalogs are
 | 
				
			||||||
 | 
					        installed in the same directory as the main application
 | 
				
			||||||
 | 
					        catalog.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if domain is None:
 | 
				
			||||||
 | 
					            domain = self.domain
 | 
				
			||||||
 | 
					        if self.lazy:
 | 
				
			||||||
 | 
					            return functools.partial(Message, domain=domain)
 | 
				
			||||||
 | 
					        t = gettext.translation(
 | 
				
			||||||
 | 
					            domain,
 | 
				
			||||||
 | 
					            localedir=self.localedir,
 | 
				
			||||||
 | 
					            fallback=True,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        if six.PY3:
 | 
				
			||||||
 | 
					            return t.gettext
 | 
				
			||||||
 | 
					        return t.ugettext
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def primary(self):
 | 
				
			||||||
 | 
					        "The default translation function."
 | 
				
			||||||
 | 
					        return self._make_translation_func()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _make_log_translation_func(self, level):
 | 
				
			||||||
 | 
					        return self._make_translation_func(self.domain + '-log-' + level)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def log_info(self):
 | 
				
			||||||
 | 
					        "Translate info-level log messages."
 | 
				
			||||||
 | 
					        return self._make_log_translation_func('info')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def log_warning(self):
 | 
				
			||||||
 | 
					        "Translate warning-level log messages."
 | 
				
			||||||
 | 
					        return self._make_log_translation_func('warning')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def log_error(self):
 | 
				
			||||||
 | 
					        "Translate error-level log messages."
 | 
				
			||||||
 | 
					        return self._make_log_translation_func('error')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def log_critical(self):
 | 
				
			||||||
 | 
					        "Translate critical-level log messages."
 | 
				
			||||||
 | 
					        return self._make_log_translation_func('critical')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# NOTE(dhellmann): When this module moves out of the incubator into
 | 
				
			||||||
 | 
					# oslo.i18n, these global variables can be moved to an integration
 | 
				
			||||||
 | 
					# module within each application.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Create the global translation functions.
 | 
				
			||||||
 | 
					_translators = TranslatorFactory('cloudkitty')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# The primary translation function using the well-known name "_"
 | 
				
			||||||
 | 
					_ = _translators.primary
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Translators for log levels.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# The abbreviated names are meant to reflect the usual use of a short
 | 
				
			||||||
 | 
					# name like '_'. The "L" is for "log" and the other letter comes from
 | 
				
			||||||
 | 
					# the level.
 | 
				
			||||||
 | 
					_LI = _translators.log_info
 | 
				
			||||||
 | 
					_LW = _translators.log_warning
 | 
				
			||||||
 | 
					_LE = _translators.log_error
 | 
				
			||||||
 | 
					_LC = _translators.log_critical
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# NOTE(dhellmann): End of globals that will move to the application's
 | 
				
			||||||
 | 
					# integration module.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def enable_lazy():
 | 
				
			||||||
 | 
					    """Convenience function for configuring _() to use lazy gettext
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Call this at the start of execution to enable the gettextutils._
 | 
				
			||||||
 | 
					    function to use lazy gettext functionality. This is useful if
 | 
				
			||||||
 | 
					    your project is importing _ directly instead of using the
 | 
				
			||||||
 | 
					    gettextutils.install() way of importing the _ function.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    # FIXME(dhellmann): This function will be removed in oslo.i18n,
 | 
				
			||||||
 | 
					    # because the TranslatorFactory makes it superfluous.
 | 
				
			||||||
 | 
					    global _, _LI, _LW, _LE, _LC, USE_LAZY
 | 
				
			||||||
 | 
					    tf = TranslatorFactory('cloudkitty', lazy=True)
 | 
				
			||||||
 | 
					    _ = tf.primary
 | 
				
			||||||
 | 
					    _LI = tf.log_info
 | 
				
			||||||
 | 
					    _LW = tf.log_warning
 | 
				
			||||||
 | 
					    _LE = tf.log_error
 | 
				
			||||||
 | 
					    _LC = tf.log_critical
 | 
				
			||||||
 | 
					    USE_LAZY = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def install(domain, lazy=False):
 | 
				
			||||||
 | 
					    """Install a _() function using the given translation domain.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Given a translation domain, install a _() function using gettext's
 | 
				
			||||||
 | 
					    install() function.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The main difference from gettext.install() is that we allow
 | 
				
			||||||
 | 
					    overriding the default localedir (e.g. /usr/share/locale) using
 | 
				
			||||||
 | 
					    a translation-domain-specific environment variable (e.g.
 | 
				
			||||||
 | 
					    NOVA_LOCALEDIR).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param domain: the translation domain
 | 
				
			||||||
 | 
					    :param lazy: indicates whether or not to install the lazy _() function.
 | 
				
			||||||
 | 
					                 The lazy _() introduces a way to do deferred translation
 | 
				
			||||||
 | 
					                 of messages by installing a _ that builds Message objects,
 | 
				
			||||||
 | 
					                 instead of strings, which can then be lazily translated into
 | 
				
			||||||
 | 
					                 any available locale.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    if lazy:
 | 
				
			||||||
 | 
					        from six import moves
 | 
				
			||||||
 | 
					        tf = TranslatorFactory(domain, lazy=True)
 | 
				
			||||||
 | 
					        moves.builtins.__dict__['_'] = tf.primary
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        localedir = '%s_LOCALEDIR' % domain.upper()
 | 
				
			||||||
 | 
					        if six.PY3:
 | 
				
			||||||
 | 
					            gettext.install(domain,
 | 
				
			||||||
 | 
					                            localedir=os.environ.get(localedir))
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            gettext.install(domain,
 | 
				
			||||||
 | 
					                            localedir=os.environ.get(localedir),
 | 
				
			||||||
 | 
					                            unicode=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Message(six.text_type):
 | 
				
			||||||
 | 
					    """A Message object is a unicode object that can be translated.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Translation of Message is done explicitly using the translate() method.
 | 
				
			||||||
 | 
					    For all non-translation intents and purposes, a Message is simply unicode,
 | 
				
			||||||
 | 
					    and can be treated as such.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __new__(cls, msgid, msgtext=None, params=None,
 | 
				
			||||||
 | 
					                domain='cloudkitty', *args):
 | 
				
			||||||
 | 
					        """Create a new Message object.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        In order for translation to work gettext requires a message ID, this
 | 
				
			||||||
 | 
					        msgid will be used as the base unicode text. It is also possible
 | 
				
			||||||
 | 
					        for the msgid and the base unicode text to be different by passing
 | 
				
			||||||
 | 
					        the msgtext parameter.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        # If the base msgtext is not given, we use the default translation
 | 
				
			||||||
 | 
					        # of the msgid (which is in English) just in case the system locale is
 | 
				
			||||||
 | 
					        # not English, so that the base text will be in that locale by default.
 | 
				
			||||||
 | 
					        if not msgtext:
 | 
				
			||||||
 | 
					            msgtext = Message._translate_msgid(msgid, domain)
 | 
				
			||||||
 | 
					        # We want to initialize the parent unicode with the actual object that
 | 
				
			||||||
 | 
					        # would have been plain unicode if 'Message' was not enabled.
 | 
				
			||||||
 | 
					        msg = super(Message, cls).__new__(cls, msgtext)
 | 
				
			||||||
 | 
					        msg.msgid = msgid
 | 
				
			||||||
 | 
					        msg.domain = domain
 | 
				
			||||||
 | 
					        msg.params = params
 | 
				
			||||||
 | 
					        return msg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def translate(self, desired_locale=None):
 | 
				
			||||||
 | 
					        """Translate this message to the desired locale.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param desired_locale: The desired locale to translate the message to,
 | 
				
			||||||
 | 
					                               if no locale is provided the message will be
 | 
				
			||||||
 | 
					                               translated to the system's default locale.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :returns: the translated message in unicode
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        translated_message = Message._translate_msgid(self.msgid,
 | 
				
			||||||
 | 
					                                                      self.domain,
 | 
				
			||||||
 | 
					                                                      desired_locale)
 | 
				
			||||||
 | 
					        if self.params is None:
 | 
				
			||||||
 | 
					            # No need for more translation
 | 
				
			||||||
 | 
					            return translated_message
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # This Message object may have been formatted with one or more
 | 
				
			||||||
 | 
					        # Message objects as substitution arguments, given either as a single
 | 
				
			||||||
 | 
					        # argument, part of a tuple, or as one or more values in a dictionary.
 | 
				
			||||||
 | 
					        # When translating this Message we need to translate those Messages too
 | 
				
			||||||
 | 
					        translated_params = _translate_args(self.params, desired_locale)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        translated_message = translated_message % translated_params
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return translated_message
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def _translate_msgid(msgid, domain, desired_locale=None):
 | 
				
			||||||
 | 
					        if not desired_locale:
 | 
				
			||||||
 | 
					            system_locale = locale.getdefaultlocale()
 | 
				
			||||||
 | 
					            # If the system locale is not available to the runtime use English
 | 
				
			||||||
 | 
					            if not system_locale[0]:
 | 
				
			||||||
 | 
					                desired_locale = 'en_US'
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                desired_locale = system_locale[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        locale_dir = os.environ.get(domain.upper() + '_LOCALEDIR')
 | 
				
			||||||
 | 
					        lang = gettext.translation(domain,
 | 
				
			||||||
 | 
					                                   localedir=locale_dir,
 | 
				
			||||||
 | 
					                                   languages=[desired_locale],
 | 
				
			||||||
 | 
					                                   fallback=True)
 | 
				
			||||||
 | 
					        if six.PY3:
 | 
				
			||||||
 | 
					            translator = lang.gettext
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            translator = lang.ugettext
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        translated_message = translator(msgid)
 | 
				
			||||||
 | 
					        return translated_message
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __mod__(self, other):
 | 
				
			||||||
 | 
					        # When we mod a Message we want the actual operation to be performed
 | 
				
			||||||
 | 
					        # by the parent class (i.e. unicode()), the only thing  we do here is
 | 
				
			||||||
 | 
					        # save the original msgid and the parameters in case of a translation
 | 
				
			||||||
 | 
					        params = self._sanitize_mod_params(other)
 | 
				
			||||||
 | 
					        unicode_mod = super(Message, self).__mod__(params)
 | 
				
			||||||
 | 
					        modded = Message(self.msgid,
 | 
				
			||||||
 | 
					                         msgtext=unicode_mod,
 | 
				
			||||||
 | 
					                         params=params,
 | 
				
			||||||
 | 
					                         domain=self.domain)
 | 
				
			||||||
 | 
					        return modded
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _sanitize_mod_params(self, other):
 | 
				
			||||||
 | 
					        """Sanitize the object being modded with this Message.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        - Add support for modding 'None' so translation supports it
 | 
				
			||||||
 | 
					        - Trim the modded object, which can be a large dictionary, to only
 | 
				
			||||||
 | 
					        those keys that would actually be used in a translation
 | 
				
			||||||
 | 
					        - Snapshot the object being modded, in case the message is
 | 
				
			||||||
 | 
					        translated, it will be used as it was when the Message was created
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if other is None:
 | 
				
			||||||
 | 
					            params = (other,)
 | 
				
			||||||
 | 
					        elif isinstance(other, dict):
 | 
				
			||||||
 | 
					            # Merge the dictionaries
 | 
				
			||||||
 | 
					            # Copy each item in case one does not support deep copy.
 | 
				
			||||||
 | 
					            params = {}
 | 
				
			||||||
 | 
					            if isinstance(self.params, dict):
 | 
				
			||||||
 | 
					                for key, val in self.params.items():
 | 
				
			||||||
 | 
					                    params[key] = self._copy_param(val)
 | 
				
			||||||
 | 
					            for key, val in other.items():
 | 
				
			||||||
 | 
					                params[key] = self._copy_param(val)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            params = self._copy_param(other)
 | 
				
			||||||
 | 
					        return params
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _copy_param(self, param):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            return copy.deepcopy(param)
 | 
				
			||||||
 | 
					        except Exception:
 | 
				
			||||||
 | 
					            # Fallback to casting to unicode this will handle the
 | 
				
			||||||
 | 
					            # python code-like objects that can't be deep-copied
 | 
				
			||||||
 | 
					            return six.text_type(param)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __add__(self, other):
 | 
				
			||||||
 | 
					        msg = _('Message objects do not support addition.')
 | 
				
			||||||
 | 
					        raise TypeError(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __radd__(self, other):
 | 
				
			||||||
 | 
					        return self.__add__(other)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if six.PY2:
 | 
				
			||||||
 | 
					        def __str__(self):
 | 
				
			||||||
 | 
					            # NOTE(luisg): Logging in python 2.6 tries to str() log records,
 | 
				
			||||||
 | 
					            # and it expects specifically a UnicodeError in order to proceed.
 | 
				
			||||||
 | 
					            msg = _('Message objects do not support str() because they may '
 | 
				
			||||||
 | 
					                    'contain non-ascii characters. '
 | 
				
			||||||
 | 
					                    'Please use unicode() or translate() instead.')
 | 
				
			||||||
 | 
					            raise UnicodeError(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_available_languages(domain):
 | 
				
			||||||
 | 
					    """Lists the available languages for the given translation domain.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param domain: the domain to get languages for
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    if domain in _AVAILABLE_LANGUAGES:
 | 
				
			||||||
 | 
					        return copy.copy(_AVAILABLE_LANGUAGES[domain])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    localedir = '%s_LOCALEDIR' % domain.upper()
 | 
				
			||||||
 | 
					    find = lambda x: gettext.find(domain,
 | 
				
			||||||
 | 
					                                  localedir=os.environ.get(localedir),
 | 
				
			||||||
 | 
					                                  languages=[x])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # NOTE(mrodden): en_US should always be available (and first in case
 | 
				
			||||||
 | 
					    # order matters) since our in-line message strings are en_US
 | 
				
			||||||
 | 
					    language_list = ['en_US']
 | 
				
			||||||
 | 
					    # NOTE(luisg): Babel <1.0 used a function called list(), which was
 | 
				
			||||||
 | 
					    # renamed to locale_identifiers() in >=1.0, the requirements master list
 | 
				
			||||||
 | 
					    # requires >=0.9.6, uncapped, so defensively work with both. We can remove
 | 
				
			||||||
 | 
					    # this check when the master list updates to >=1.0, and update all projects
 | 
				
			||||||
 | 
					    list_identifiers = (getattr(localedata, 'list', None) or
 | 
				
			||||||
 | 
					                        getattr(localedata, 'locale_identifiers'))
 | 
				
			||||||
 | 
					    locale_identifiers = list_identifiers()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for i in locale_identifiers:
 | 
				
			||||||
 | 
					        if find(i) is not None:
 | 
				
			||||||
 | 
					            language_list.append(i)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # NOTE(luisg): Babel>=1.0,<1.3 has a bug where some OpenStack supported
 | 
				
			||||||
 | 
					    # locales (e.g. 'zh_CN', and 'zh_TW') aren't supported even though they
 | 
				
			||||||
 | 
					    # are perfectly legitimate locales:
 | 
				
			||||||
 | 
					    #     https://github.com/mitsuhiko/babel/issues/37
 | 
				
			||||||
 | 
					    # In Babel 1.3 they fixed the bug and they support these locales, but
 | 
				
			||||||
 | 
					    # they are still not explicitly "listed" by locale_identifiers().
 | 
				
			||||||
 | 
					    # That is  why we add the locales here explicitly if necessary so that
 | 
				
			||||||
 | 
					    # they are listed as supported.
 | 
				
			||||||
 | 
					    aliases = {'zh': 'zh_CN',
 | 
				
			||||||
 | 
					               'zh_Hant_HK': 'zh_HK',
 | 
				
			||||||
 | 
					               'zh_Hant': 'zh_TW',
 | 
				
			||||||
 | 
					               'fil': 'tl_PH'}
 | 
				
			||||||
 | 
					    for (locale_, alias) in six.iteritems(aliases):
 | 
				
			||||||
 | 
					        if locale_ in language_list and alias not in language_list:
 | 
				
			||||||
 | 
					            language_list.append(alias)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _AVAILABLE_LANGUAGES[domain] = language_list
 | 
				
			||||||
 | 
					    return copy.copy(language_list)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def translate(obj, desired_locale=None):
 | 
				
			||||||
 | 
					    """Gets the translated unicode representation of the given object.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    If the object is not translatable it is returned as-is.
 | 
				
			||||||
 | 
					    If the locale is None the object is translated to the system locale.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param obj: the object to translate
 | 
				
			||||||
 | 
					    :param desired_locale: the locale to translate the message to, if None the
 | 
				
			||||||
 | 
					                           default system locale will be used
 | 
				
			||||||
 | 
					    :returns: the translated object in unicode, or the original object if
 | 
				
			||||||
 | 
					              it could not be translated
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    message = obj
 | 
				
			||||||
 | 
					    if not isinstance(message, Message):
 | 
				
			||||||
 | 
					        # If the object to translate is not already translatable,
 | 
				
			||||||
 | 
					        # let's first get its unicode representation
 | 
				
			||||||
 | 
					        message = six.text_type(obj)
 | 
				
			||||||
 | 
					    if isinstance(message, Message):
 | 
				
			||||||
 | 
					        # Even after unicoding() we still need to check if we are
 | 
				
			||||||
 | 
					        # running with translatable unicode before translating
 | 
				
			||||||
 | 
					        return message.translate(desired_locale)
 | 
				
			||||||
 | 
					    return obj
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _translate_args(args, desired_locale=None):
 | 
				
			||||||
 | 
					    """Translates all the translatable elements of the given arguments object.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    This method is used for translating the translatable values in method
 | 
				
			||||||
 | 
					    arguments which include values of tuples or dictionaries.
 | 
				
			||||||
 | 
					    If the object is not a tuple or a dictionary the object itself is
 | 
				
			||||||
 | 
					    translated if it is translatable.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    If the locale is None the object is translated to the system locale.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param args: the args to translate
 | 
				
			||||||
 | 
					    :param desired_locale: the locale to translate the args to, if None the
 | 
				
			||||||
 | 
					                           default system locale will be used
 | 
				
			||||||
 | 
					    :returns: a new args object with the translated contents of the original
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    if isinstance(args, tuple):
 | 
				
			||||||
 | 
					        return tuple(translate(v, desired_locale) for v in args)
 | 
				
			||||||
 | 
					    if isinstance(args, dict):
 | 
				
			||||||
 | 
					        translated_dict = {}
 | 
				
			||||||
 | 
					        for (k, v) in six.iteritems(args):
 | 
				
			||||||
 | 
					            translated_v = translate(v, desired_locale)
 | 
				
			||||||
 | 
					            translated_dict[k] = translated_v
 | 
				
			||||||
 | 
					        return translated_dict
 | 
				
			||||||
 | 
					    return translate(args, desired_locale)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TranslationHandler(handlers.MemoryHandler):
 | 
				
			||||||
 | 
					    """Handler that translates records before logging them.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The TranslationHandler takes a locale and a target logging.Handler object
 | 
				
			||||||
 | 
					    to forward LogRecord objects to after translating them. This handler
 | 
				
			||||||
 | 
					    depends on Message objects being logged, instead of regular strings.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The handler can be configured declaratively in the logging.conf as follows:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [handlers]
 | 
				
			||||||
 | 
					        keys = translatedlog, translator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [handler_translatedlog]
 | 
				
			||||||
 | 
					        class = handlers.WatchedFileHandler
 | 
				
			||||||
 | 
					        args = ('/var/log/api-localized.log',)
 | 
				
			||||||
 | 
					        formatter = context
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [handler_translator]
 | 
				
			||||||
 | 
					        class = openstack.common.log.TranslationHandler
 | 
				
			||||||
 | 
					        target = translatedlog
 | 
				
			||||||
 | 
					        args = ('zh_CN',)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    If the specified locale is not available in the system, the handler will
 | 
				
			||||||
 | 
					    log in the default locale.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, locale=None, target=None):
 | 
				
			||||||
 | 
					        """Initialize a TranslationHandler
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param locale: locale to use for translating messages
 | 
				
			||||||
 | 
					        :param target: logging.Handler object to forward
 | 
				
			||||||
 | 
					                       LogRecord objects to after translation
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        # NOTE(luisg): In order to allow this handler to be a wrapper for
 | 
				
			||||||
 | 
					        # other handlers, such as a FileHandler, and still be able to
 | 
				
			||||||
 | 
					        # configure it using logging.conf, this handler has to extend
 | 
				
			||||||
 | 
					        # MemoryHandler because only the MemoryHandlers' logging.conf
 | 
				
			||||||
 | 
					        # parsing is implemented such that it accepts a target handler.
 | 
				
			||||||
 | 
					        handlers.MemoryHandler.__init__(self, capacity=0, target=target)
 | 
				
			||||||
 | 
					        self.locale = locale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def setFormatter(self, fmt):
 | 
				
			||||||
 | 
					        self.target.setFormatter(fmt)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def emit(self, record):
 | 
				
			||||||
 | 
					        # We save the message from the original record to restore it
 | 
				
			||||||
 | 
					        # after translation, so other handlers are not affected by this
 | 
				
			||||||
 | 
					        original_msg = record.msg
 | 
				
			||||||
 | 
					        original_args = record.args
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self._translate_and_log_record(record)
 | 
				
			||||||
 | 
					        finally:
 | 
				
			||||||
 | 
					            record.msg = original_msg
 | 
				
			||||||
 | 
					            record.args = original_args
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _translate_and_log_record(self, record):
 | 
				
			||||||
 | 
					        record.msg = translate(record.msg, self.locale)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # In addition to translating the message, we also need to translate
 | 
				
			||||||
 | 
					        # arguments that were passed to the log method that were not part
 | 
				
			||||||
 | 
					        # of the main message e.g., log.info(_('Some message %s'), this_one))
 | 
				
			||||||
 | 
					        record.args = _translate_args(record.args, self.locale)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.target.emit(record)
 | 
				
			||||||
							
								
								
									
										186
									
								
								cloudkitty/openstack/common/jsonutils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								cloudkitty/openstack/common/jsonutils.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,186 @@
 | 
				
			|||||||
 | 
					# Copyright 2010 United States Government as represented by the
 | 
				
			||||||
 | 
					# Administrator of the National Aeronautics and Space Administration.
 | 
				
			||||||
 | 
					# Copyright 2011 Justin Santa Barbara
 | 
				
			||||||
 | 
					# 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					JSON related utilities.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This module provides a few things:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    1) A handy function for getting an object down to something that can be
 | 
				
			||||||
 | 
					    JSON serialized.  See to_primitive().
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    2) Wrappers around loads() and dumps().  The dumps() wrapper will
 | 
				
			||||||
 | 
					    automatically use to_primitive() for you if needed.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    3) This sets up anyjson to use the loads() and dumps() wrappers if anyjson
 | 
				
			||||||
 | 
					    is available.
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import codecs
 | 
				
			||||||
 | 
					import datetime
 | 
				
			||||||
 | 
					import functools
 | 
				
			||||||
 | 
					import inspect
 | 
				
			||||||
 | 
					import itertools
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if sys.version_info < (2, 7):
 | 
				
			||||||
 | 
					    # On Python <= 2.6, json module is not C boosted, so try to use
 | 
				
			||||||
 | 
					    # simplejson module if available
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        import simplejson as json
 | 
				
			||||||
 | 
					    except ImportError:
 | 
				
			||||||
 | 
					        import json
 | 
				
			||||||
 | 
					else:
 | 
				
			||||||
 | 
					    import json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import six
 | 
				
			||||||
 | 
					import six.moves.xmlrpc_client as xmlrpclib
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from cloudkitty.openstack.common import gettextutils
 | 
				
			||||||
 | 
					from cloudkitty.openstack.common import importutils
 | 
				
			||||||
 | 
					from cloudkitty.openstack.common import strutils
 | 
				
			||||||
 | 
					from cloudkitty.openstack.common import timeutils
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					netaddr = importutils.try_import("netaddr")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_nasty_type_tests = [inspect.ismodule, inspect.isclass, inspect.ismethod,
 | 
				
			||||||
 | 
					                     inspect.isfunction, inspect.isgeneratorfunction,
 | 
				
			||||||
 | 
					                     inspect.isgenerator, inspect.istraceback, inspect.isframe,
 | 
				
			||||||
 | 
					                     inspect.iscode, inspect.isbuiltin, inspect.isroutine,
 | 
				
			||||||
 | 
					                     inspect.isabstract]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_simple_types = (six.string_types + six.integer_types
 | 
				
			||||||
 | 
					                 + (type(None), bool, float))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def to_primitive(value, convert_instances=False, convert_datetime=True,
 | 
				
			||||||
 | 
					                 level=0, max_depth=3):
 | 
				
			||||||
 | 
					    """Convert a complex object into primitives.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Handy for JSON serialization. We can optionally handle instances,
 | 
				
			||||||
 | 
					    but since this is a recursive function, we could have cyclical
 | 
				
			||||||
 | 
					    data structures.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    To handle cyclical data structures we could track the actual objects
 | 
				
			||||||
 | 
					    visited in a set, but not all objects are hashable. Instead we just
 | 
				
			||||||
 | 
					    track the depth of the object inspections and don't go too deep.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Therefore, convert_instances=True is lossy ... be aware.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    # handle obvious types first - order of basic types determined by running
 | 
				
			||||||
 | 
					    # full tests on nova project, resulting in the following counts:
 | 
				
			||||||
 | 
					    # 572754 <type 'NoneType'>
 | 
				
			||||||
 | 
					    # 460353 <type 'int'>
 | 
				
			||||||
 | 
					    # 379632 <type 'unicode'>
 | 
				
			||||||
 | 
					    # 274610 <type 'str'>
 | 
				
			||||||
 | 
					    # 199918 <type 'dict'>
 | 
				
			||||||
 | 
					    # 114200 <type 'datetime.datetime'>
 | 
				
			||||||
 | 
					    #  51817 <type 'bool'>
 | 
				
			||||||
 | 
					    #  26164 <type 'list'>
 | 
				
			||||||
 | 
					    #   6491 <type 'float'>
 | 
				
			||||||
 | 
					    #    283 <type 'tuple'>
 | 
				
			||||||
 | 
					    #     19 <type 'long'>
 | 
				
			||||||
 | 
					    if isinstance(value, _simple_types):
 | 
				
			||||||
 | 
					        return value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if isinstance(value, datetime.datetime):
 | 
				
			||||||
 | 
					        if convert_datetime:
 | 
				
			||||||
 | 
					            return timeutils.strtime(value)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # value of itertools.count doesn't get caught by nasty_type_tests
 | 
				
			||||||
 | 
					    # and results in infinite loop when list(value) is called.
 | 
				
			||||||
 | 
					    if type(value) == itertools.count:
 | 
				
			||||||
 | 
					        return six.text_type(value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # FIXME(vish): Workaround for LP bug 852095. Without this workaround,
 | 
				
			||||||
 | 
					    #              tests that raise an exception in a mocked method that
 | 
				
			||||||
 | 
					    #              has a @wrap_exception with a notifier will fail. If
 | 
				
			||||||
 | 
					    #              we up the dependency to 0.5.4 (when it is released) we
 | 
				
			||||||
 | 
					    #              can remove this workaround.
 | 
				
			||||||
 | 
					    if getattr(value, '__module__', None) == 'mox':
 | 
				
			||||||
 | 
					        return 'mock'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if level > max_depth:
 | 
				
			||||||
 | 
					        return '?'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # The try block may not be necessary after the class check above,
 | 
				
			||||||
 | 
					    # but just in case ...
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        recursive = functools.partial(to_primitive,
 | 
				
			||||||
 | 
					                                      convert_instances=convert_instances,
 | 
				
			||||||
 | 
					                                      convert_datetime=convert_datetime,
 | 
				
			||||||
 | 
					                                      level=level,
 | 
				
			||||||
 | 
					                                      max_depth=max_depth)
 | 
				
			||||||
 | 
					        if isinstance(value, dict):
 | 
				
			||||||
 | 
					            return dict((k, recursive(v)) for k, v in six.iteritems(value))
 | 
				
			||||||
 | 
					        elif isinstance(value, (list, tuple)):
 | 
				
			||||||
 | 
					            return [recursive(lv) for lv in value]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # It's not clear why xmlrpclib created their own DateTime type, but
 | 
				
			||||||
 | 
					        # for our purposes, make it a datetime type which is explicitly
 | 
				
			||||||
 | 
					        # handled
 | 
				
			||||||
 | 
					        if isinstance(value, xmlrpclib.DateTime):
 | 
				
			||||||
 | 
					            value = datetime.datetime(*tuple(value.timetuple())[:6])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if convert_datetime and isinstance(value, datetime.datetime):
 | 
				
			||||||
 | 
					            return timeutils.strtime(value)
 | 
				
			||||||
 | 
					        elif isinstance(value, gettextutils.Message):
 | 
				
			||||||
 | 
					            return value.data
 | 
				
			||||||
 | 
					        elif hasattr(value, 'iteritems'):
 | 
				
			||||||
 | 
					            return recursive(dict(value.iteritems()), level=level + 1)
 | 
				
			||||||
 | 
					        elif hasattr(value, '__iter__'):
 | 
				
			||||||
 | 
					            return recursive(list(value))
 | 
				
			||||||
 | 
					        elif convert_instances and hasattr(value, '__dict__'):
 | 
				
			||||||
 | 
					            # Likely an instance of something. Watch for cycles.
 | 
				
			||||||
 | 
					            # Ignore class member vars.
 | 
				
			||||||
 | 
					            return recursive(value.__dict__, level=level + 1)
 | 
				
			||||||
 | 
					        elif netaddr and isinstance(value, netaddr.IPAddress):
 | 
				
			||||||
 | 
					            return six.text_type(value)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            if any(test(value) for test in _nasty_type_tests):
 | 
				
			||||||
 | 
					                return six.text_type(value)
 | 
				
			||||||
 | 
					            return value
 | 
				
			||||||
 | 
					    except TypeError:
 | 
				
			||||||
 | 
					        # Class objects are tricky since they may define something like
 | 
				
			||||||
 | 
					        # __iter__ defined but it isn't callable as list().
 | 
				
			||||||
 | 
					        return six.text_type(value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def dumps(value, default=to_primitive, **kwargs):
 | 
				
			||||||
 | 
					    return json.dumps(value, default=default, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def loads(s, encoding='utf-8', **kwargs):
 | 
				
			||||||
 | 
					    return json.loads(strutils.safe_decode(s, encoding), **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def load(fp, encoding='utf-8', **kwargs):
 | 
				
			||||||
 | 
					    return json.load(codecs.getreader(encoding)(fp), **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					try:
 | 
				
			||||||
 | 
					    import anyjson
 | 
				
			||||||
 | 
					except ImportError:
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					else:
 | 
				
			||||||
 | 
					    anyjson._modules.append((__name__, 'dumps', TypeError,
 | 
				
			||||||
 | 
					                                       'loads', ValueError, 'load'))
 | 
				
			||||||
 | 
					    anyjson.force_implementation(__name__)
 | 
				
			||||||
							
								
								
									
										45
									
								
								cloudkitty/openstack/common/local.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								cloudkitty/openstack/common/local.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					# Copyright 2011 OpenStack Foundation.
 | 
				
			||||||
 | 
					# 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""Local storage of variables using weak references"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import threading
 | 
				
			||||||
 | 
					import weakref
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class WeakLocal(threading.local):
 | 
				
			||||||
 | 
					    def __getattribute__(self, attr):
 | 
				
			||||||
 | 
					        rval = super(WeakLocal, self).__getattribute__(attr)
 | 
				
			||||||
 | 
					        if rval:
 | 
				
			||||||
 | 
					            # NOTE(mikal): this bit is confusing. What is stored is a weak
 | 
				
			||||||
 | 
					            # reference, not the value itself. We therefore need to lookup
 | 
				
			||||||
 | 
					            # the weak reference and return the inner value here.
 | 
				
			||||||
 | 
					            rval = rval()
 | 
				
			||||||
 | 
					        return rval
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __setattr__(self, attr, value):
 | 
				
			||||||
 | 
					        value = weakref.ref(value)
 | 
				
			||||||
 | 
					        return super(WeakLocal, self).__setattr__(attr, value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# NOTE(mikal): the name "store" should be deprecated in the future
 | 
				
			||||||
 | 
					store = WeakLocal()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# A "weak" store uses weak references and allows an object to fall out of scope
 | 
				
			||||||
 | 
					# when it falls out of scope in the code that uses the thread local storage. A
 | 
				
			||||||
 | 
					# "strong" store will hold a reference to the object so that it never falls out
 | 
				
			||||||
 | 
					# of scope.
 | 
				
			||||||
 | 
					weak_store = WeakLocal()
 | 
				
			||||||
 | 
					strong_store = threading.local()
 | 
				
			||||||
							
								
								
									
										732
									
								
								cloudkitty/openstack/common/log.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										732
									
								
								cloudkitty/openstack/common/log.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,732 @@
 | 
				
			|||||||
 | 
					# Copyright 2011 OpenStack Foundation.
 | 
				
			||||||
 | 
					# Copyright 2010 United States Government as represented by the
 | 
				
			||||||
 | 
					# Administrator of the National Aeronautics and Space Administration.
 | 
				
			||||||
 | 
					# 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""OpenStack logging handler.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This module adds to logging functionality by adding the option to specify
 | 
				
			||||||
 | 
					a context object when calling the various log methods.  If the context object
 | 
				
			||||||
 | 
					is not specified, default formatting is used. Additionally, an instance uuid
 | 
				
			||||||
 | 
					may be passed as part of the log message, which is intended to make it easier
 | 
				
			||||||
 | 
					for admins to find messages related to a specific instance.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					It also allows setting of formatting information through conf.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import inspect
 | 
				
			||||||
 | 
					import itertools
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					import logging.handlers
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import traceback
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from oslo.config import cfg
 | 
				
			||||||
 | 
					import six
 | 
				
			||||||
 | 
					from six import moves
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from cloudkitty.openstack.common.gettextutils import _
 | 
				
			||||||
 | 
					from cloudkitty.openstack.common import importutils
 | 
				
			||||||
 | 
					from cloudkitty.openstack.common import jsonutils
 | 
				
			||||||
 | 
					from cloudkitty.openstack.common import local
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_SANITIZE_KEYS = ['adminPass', 'admin_pass', 'password', 'admin_password']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# NOTE(ldbragst): Let's build a list of regex objects using the list of
 | 
				
			||||||
 | 
					# _SANITIZE_KEYS we already have. This way, we only have to add the new key
 | 
				
			||||||
 | 
					# to the list of _SANITIZE_KEYS and we can generate regular expressions
 | 
				
			||||||
 | 
					# for XML and JSON automatically.
 | 
				
			||||||
 | 
					_SANITIZE_PATTERNS = []
 | 
				
			||||||
 | 
					_FORMAT_PATTERNS = [r'(%(key)s\s*[=]\s*[\"\']).*?([\"\'])',
 | 
				
			||||||
 | 
					                    r'(<%(key)s>).*?(</%(key)s>)',
 | 
				
			||||||
 | 
					                    r'([\"\']%(key)s[\"\']\s*:\s*[\"\']).*?([\"\'])',
 | 
				
			||||||
 | 
					                    r'([\'"].*?%(key)s[\'"]\s*:\s*u?[\'"]).*?([\'"])',
 | 
				
			||||||
 | 
					                    r'([\'"].*?%(key)s[\'"]\s*,\s*\'--?[A-z]+\'\s*,\s*u?[\'"])'
 | 
				
			||||||
 | 
					                    '.*?([\'"])',
 | 
				
			||||||
 | 
					                    r'(%(key)s\s*--?[A-z]+\s*).*?([\s])']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					for key in _SANITIZE_KEYS:
 | 
				
			||||||
 | 
					    for pattern in _FORMAT_PATTERNS:
 | 
				
			||||||
 | 
					        reg_ex = re.compile(pattern % {'key': key}, re.DOTALL)
 | 
				
			||||||
 | 
					        _SANITIZE_PATTERNS.append(reg_ex)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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-append',
 | 
				
			||||||
 | 
					               metavar='PATH',
 | 
				
			||||||
 | 
					               deprecated_name='log-config',
 | 
				
			||||||
 | 
					               help='The name of a logging configuration file. This file '
 | 
				
			||||||
 | 
					                    'is appended to any existing logging configuration '
 | 
				
			||||||
 | 
					                    'files. For details about logging configuration files, '
 | 
				
			||||||
 | 
					                    'see the Python logging module documentation.'),
 | 
				
			||||||
 | 
					    cfg.StrOpt('log-format',
 | 
				
			||||||
 | 
					               metavar='FORMAT',
 | 
				
			||||||
 | 
					               help='DEPRECATED. '
 | 
				
			||||||
 | 
					                    'A logging.Formatter log message format string which may '
 | 
				
			||||||
 | 
					                    'use any of the available logging.LogRecord attributes. '
 | 
				
			||||||
 | 
					                    'This option is deprecated.  Please use '
 | 
				
			||||||
 | 
					                    'logging_context_format_string and '
 | 
				
			||||||
 | 
					                    'logging_default_format_string instead.'),
 | 
				
			||||||
 | 
					    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 no default is set, logging will go to stdout.'),
 | 
				
			||||||
 | 
					    cfg.StrOpt('log-dir',
 | 
				
			||||||
 | 
					               deprecated_name='logdir',
 | 
				
			||||||
 | 
					               help='(Optional) The base directory used for relative '
 | 
				
			||||||
 | 
					                    '--log-file paths.'),
 | 
				
			||||||
 | 
					    cfg.BoolOpt('use-syslog',
 | 
				
			||||||
 | 
					                default=False,
 | 
				
			||||||
 | 
					                help='Use syslog for logging. '
 | 
				
			||||||
 | 
					                     'Existing syslog format is DEPRECATED during I, '
 | 
				
			||||||
 | 
					                     'and will change in J to honor RFC5424.'),
 | 
				
			||||||
 | 
					    cfg.BoolOpt('use-syslog-rfc-format',
 | 
				
			||||||
 | 
					                # TODO(bogdando) remove or use True after existing
 | 
				
			||||||
 | 
					                #    syslog format deprecation in J
 | 
				
			||||||
 | 
					                default=False,
 | 
				
			||||||
 | 
					                help='(Optional) Enables or disables syslog rfc5424 format '
 | 
				
			||||||
 | 
					                     'for logging. If enabled, prefixes the MSG part of the '
 | 
				
			||||||
 | 
					                     'syslog message with APP-NAME (RFC5424). The '
 | 
				
			||||||
 | 
					                     'format without the APP-NAME is deprecated in I, '
 | 
				
			||||||
 | 
					                     'and will be removed in J.'),
 | 
				
			||||||
 | 
					    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.')
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					log_opts = [
 | 
				
			||||||
 | 
					    cfg.StrOpt('logging_context_format_string',
 | 
				
			||||||
 | 
					               default='%(asctime)s.%(msecs)03d %(process)d %(levelname)s '
 | 
				
			||||||
 | 
					                       '%(name)s [%(request_id)s %(user_identity)s] '
 | 
				
			||||||
 | 
					                       '%(instance)s%(message)s',
 | 
				
			||||||
 | 
					               help='Format string to use for log messages with context.'),
 | 
				
			||||||
 | 
					    cfg.StrOpt('logging_default_format_string',
 | 
				
			||||||
 | 
					               default='%(asctime)s.%(msecs)03d %(process)d %(levelname)s '
 | 
				
			||||||
 | 
					                       '%(name)s [-] %(instance)s%(message)s',
 | 
				
			||||||
 | 
					               help='Format string to use for log messages without context.'),
 | 
				
			||||||
 | 
					    cfg.StrOpt('logging_debug_format_suffix',
 | 
				
			||||||
 | 
					               default='%(funcName)s %(pathname)s:%(lineno)d',
 | 
				
			||||||
 | 
					               help='Data to append to log format when level is DEBUG.'),
 | 
				
			||||||
 | 
					    cfg.StrOpt('logging_exception_prefix',
 | 
				
			||||||
 | 
					               default='%(asctime)s.%(msecs)03d %(process)d TRACE %(name)s '
 | 
				
			||||||
 | 
					               '%(instance)s',
 | 
				
			||||||
 | 
					               help='Prefix each line of exception output with this format.'),
 | 
				
			||||||
 | 
					    cfg.ListOpt('default_log_levels',
 | 
				
			||||||
 | 
					                default=[
 | 
				
			||||||
 | 
					                    'amqp=WARN',
 | 
				
			||||||
 | 
					                    'amqplib=WARN',
 | 
				
			||||||
 | 
					                    'boto=WARN',
 | 
				
			||||||
 | 
					                    'qpid=WARN',
 | 
				
			||||||
 | 
					                    'sqlalchemy=WARN',
 | 
				
			||||||
 | 
					                    'suds=INFO',
 | 
				
			||||||
 | 
					                    'oslo.messaging=INFO',
 | 
				
			||||||
 | 
					                    'iso8601=WARN',
 | 
				
			||||||
 | 
					                    'requests.packages.urllib3.connectionpool=WARN'
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                help='List of logger=LEVEL pairs.'),
 | 
				
			||||||
 | 
					    cfg.BoolOpt('publish_errors',
 | 
				
			||||||
 | 
					                default=False,
 | 
				
			||||||
 | 
					                help='Enables or disables publication of error events.'),
 | 
				
			||||||
 | 
					    cfg.BoolOpt('fatal_deprecations',
 | 
				
			||||||
 | 
					                default=False,
 | 
				
			||||||
 | 
					                help='Enables or disables fatal status of deprecations.'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # NOTE(mikal): there are two options here because sometimes we are handed
 | 
				
			||||||
 | 
					    # a full instance (and could include more information), and other times we
 | 
				
			||||||
 | 
					    # are just handed a UUID for the instance.
 | 
				
			||||||
 | 
					    cfg.StrOpt('instance_format',
 | 
				
			||||||
 | 
					               default='[instance: %(uuid)s] ',
 | 
				
			||||||
 | 
					               help='The format for an instance that is passed with the log '
 | 
				
			||||||
 | 
					                    'message. '),
 | 
				
			||||||
 | 
					    cfg.StrOpt('instance_uuid_format',
 | 
				
			||||||
 | 
					               default='[instance: %(uuid)s] ',
 | 
				
			||||||
 | 
					               help='The format for an instance UUID that is passed with the '
 | 
				
			||||||
 | 
					                    'log message. '),
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# our new audit level
 | 
				
			||||||
 | 
					# NOTE(jkoelker) Since we synthesized an audit level, make the logging
 | 
				
			||||||
 | 
					#                module aware of it so it acts like other levels.
 | 
				
			||||||
 | 
					logging.AUDIT = logging.INFO + 1
 | 
				
			||||||
 | 
					logging.addLevelName(logging.AUDIT, 'AUDIT')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					try:
 | 
				
			||||||
 | 
					    NullHandler = logging.NullHandler
 | 
				
			||||||
 | 
					except AttributeError:  # NOTE(jkoelker) NullHandler added in Python 2.7
 | 
				
			||||||
 | 
					    class NullHandler(logging.Handler):
 | 
				
			||||||
 | 
					        def handle(self, record):
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def emit(self, record):
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def createLock(self):
 | 
				
			||||||
 | 
					            self.lock = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _dictify_context(context):
 | 
				
			||||||
 | 
					    if context is None:
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					    if not isinstance(context, dict) and getattr(context, 'to_dict', None):
 | 
				
			||||||
 | 
					        context = context.to_dict()
 | 
				
			||||||
 | 
					    return context
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _get_binary_name():
 | 
				
			||||||
 | 
					    return os.path.basename(inspect.stack()[-1][1])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _get_log_file_path(binary=None):
 | 
				
			||||||
 | 
					    logfile = CONF.log_file
 | 
				
			||||||
 | 
					    logdir = CONF.log_dir
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if logfile and not logdir:
 | 
				
			||||||
 | 
					        return logfile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if logfile and logdir:
 | 
				
			||||||
 | 
					        return os.path.join(logdir, logfile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if logdir:
 | 
				
			||||||
 | 
					        binary = binary or _get_binary_name()
 | 
				
			||||||
 | 
					        return '%s.log' % (os.path.join(logdir, binary),)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def mask_password(message, secret="***"):
 | 
				
			||||||
 | 
					    """Replace password with 'secret' in message.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param message: The string which includes security information.
 | 
				
			||||||
 | 
					    :param secret: value with which to replace passwords.
 | 
				
			||||||
 | 
					    :returns: The unicode value of message with the password fields masked.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    For example:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    >>> mask_password("'adminPass' : 'aaaaa'")
 | 
				
			||||||
 | 
					    "'adminPass' : '***'"
 | 
				
			||||||
 | 
					    >>> mask_password("'admin_pass' : 'aaaaa'")
 | 
				
			||||||
 | 
					    "'admin_pass' : '***'"
 | 
				
			||||||
 | 
					    >>> mask_password('"password" : "aaaaa"')
 | 
				
			||||||
 | 
					    '"password" : "***"'
 | 
				
			||||||
 | 
					    >>> mask_password("'original_password' : 'aaaaa'")
 | 
				
			||||||
 | 
					    "'original_password' : '***'"
 | 
				
			||||||
 | 
					    >>> mask_password("u'original_password' :   u'aaaaa'")
 | 
				
			||||||
 | 
					    "u'original_password' :   u'***'"
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    message = six.text_type(message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # NOTE(ldbragst): Check to see if anything in message contains any key
 | 
				
			||||||
 | 
					    # specified in _SANITIZE_KEYS, if not then just return the message since
 | 
				
			||||||
 | 
					    # we don't have to mask any passwords.
 | 
				
			||||||
 | 
					    if not any(key in message for key in _SANITIZE_KEYS):
 | 
				
			||||||
 | 
					        return message
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    secret = r'\g<1>' + secret + r'\g<2>'
 | 
				
			||||||
 | 
					    for pattern in _SANITIZE_PATTERNS:
 | 
				
			||||||
 | 
					        message = re.sub(pattern, secret, message)
 | 
				
			||||||
 | 
					    return message
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BaseLoggerAdapter(logging.LoggerAdapter):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def audit(self, msg, *args, **kwargs):
 | 
				
			||||||
 | 
					        self.log(logging.AUDIT, msg, *args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LazyAdapter(BaseLoggerAdapter):
 | 
				
			||||||
 | 
					    def __init__(self, name='unknown', version='unknown'):
 | 
				
			||||||
 | 
					        self._logger = None
 | 
				
			||||||
 | 
					        self.extra = {}
 | 
				
			||||||
 | 
					        self.name = name
 | 
				
			||||||
 | 
					        self.version = version
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def logger(self):
 | 
				
			||||||
 | 
					        if not self._logger:
 | 
				
			||||||
 | 
					            self._logger = getLogger(self.name, self.version)
 | 
				
			||||||
 | 
					        return self._logger
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ContextAdapter(BaseLoggerAdapter):
 | 
				
			||||||
 | 
					    warn = logging.LoggerAdapter.warning
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, logger, project_name, version_string):
 | 
				
			||||||
 | 
					        self.logger = logger
 | 
				
			||||||
 | 
					        self.project = project_name
 | 
				
			||||||
 | 
					        self.version = version_string
 | 
				
			||||||
 | 
					        self._deprecated_messages_sent = dict()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def handlers(self):
 | 
				
			||||||
 | 
					        return self.logger.handlers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def deprecated(self, msg, *args, **kwargs):
 | 
				
			||||||
 | 
					        """Call this method 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
 | 
				
			||||||
 | 
					        if CONF.fatal_deprecations:
 | 
				
			||||||
 | 
					            self.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 = self._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)
 | 
				
			||||||
 | 
					        self.warn(stdmsg, *args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def process(self, msg, kwargs):
 | 
				
			||||||
 | 
					        # NOTE(mrodden): catch any Message/other object and
 | 
				
			||||||
 | 
					        #                coerce to unicode before they can get
 | 
				
			||||||
 | 
					        #                to the python logging and possibly
 | 
				
			||||||
 | 
					        #                cause string encoding trouble
 | 
				
			||||||
 | 
					        if not isinstance(msg, six.string_types):
 | 
				
			||||||
 | 
					            msg = six.text_type(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if 'extra' not in kwargs:
 | 
				
			||||||
 | 
					            kwargs['extra'] = {}
 | 
				
			||||||
 | 
					        extra = kwargs['extra']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        context = kwargs.pop('context', None)
 | 
				
			||||||
 | 
					        if not context:
 | 
				
			||||||
 | 
					            context = getattr(local.store, 'context', None)
 | 
				
			||||||
 | 
					        if context:
 | 
				
			||||||
 | 
					            extra.update(_dictify_context(context))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        instance = kwargs.pop('instance', None)
 | 
				
			||||||
 | 
					        instance_uuid = (extra.get('instance_uuid') or
 | 
				
			||||||
 | 
					                         kwargs.pop('instance_uuid', None))
 | 
				
			||||||
 | 
					        instance_extra = ''
 | 
				
			||||||
 | 
					        if instance:
 | 
				
			||||||
 | 
					            instance_extra = CONF.instance_format % instance
 | 
				
			||||||
 | 
					        elif instance_uuid:
 | 
				
			||||||
 | 
					            instance_extra = (CONF.instance_uuid_format
 | 
				
			||||||
 | 
					                              % {'uuid': instance_uuid})
 | 
				
			||||||
 | 
					        extra['instance'] = instance_extra
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        extra.setdefault('user_identity', kwargs.pop('user_identity', None))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        extra['project'] = self.project
 | 
				
			||||||
 | 
					        extra['version'] = self.version
 | 
				
			||||||
 | 
					        extra['extra'] = extra.copy()
 | 
				
			||||||
 | 
					        return msg, kwargs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class JSONFormatter(logging.Formatter):
 | 
				
			||||||
 | 
					    def __init__(self, fmt=None, datefmt=None):
 | 
				
			||||||
 | 
					        # NOTE(jkoelker) we ignore the fmt argument, but its still there
 | 
				
			||||||
 | 
					        #                since logging.config.fileConfig passes it.
 | 
				
			||||||
 | 
					        self.datefmt = datefmt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def formatException(self, ei, strip_newlines=True):
 | 
				
			||||||
 | 
					        lines = traceback.format_exception(*ei)
 | 
				
			||||||
 | 
					        if strip_newlines:
 | 
				
			||||||
 | 
					            lines = [moves.filter(
 | 
				
			||||||
 | 
					                lambda x: x,
 | 
				
			||||||
 | 
					                line.rstrip().splitlines()) for line in lines]
 | 
				
			||||||
 | 
					            lines = list(itertools.chain(*lines))
 | 
				
			||||||
 | 
					        return lines
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def format(self, record):
 | 
				
			||||||
 | 
					        message = {'message': record.getMessage(),
 | 
				
			||||||
 | 
					                   'asctime': self.formatTime(record, self.datefmt),
 | 
				
			||||||
 | 
					                   'name': record.name,
 | 
				
			||||||
 | 
					                   'msg': record.msg,
 | 
				
			||||||
 | 
					                   'args': record.args,
 | 
				
			||||||
 | 
					                   'levelname': record.levelname,
 | 
				
			||||||
 | 
					                   'levelno': record.levelno,
 | 
				
			||||||
 | 
					                   'pathname': record.pathname,
 | 
				
			||||||
 | 
					                   'filename': record.filename,
 | 
				
			||||||
 | 
					                   'module': record.module,
 | 
				
			||||||
 | 
					                   'lineno': record.lineno,
 | 
				
			||||||
 | 
					                   'funcname': record.funcName,
 | 
				
			||||||
 | 
					                   'created': record.created,
 | 
				
			||||||
 | 
					                   'msecs': record.msecs,
 | 
				
			||||||
 | 
					                   'relative_created': record.relativeCreated,
 | 
				
			||||||
 | 
					                   'thread': record.thread,
 | 
				
			||||||
 | 
					                   'thread_name': record.threadName,
 | 
				
			||||||
 | 
					                   'process_name': record.processName,
 | 
				
			||||||
 | 
					                   'process': record.process,
 | 
				
			||||||
 | 
					                   'traceback': None}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if hasattr(record, 'extra'):
 | 
				
			||||||
 | 
					            message['extra'] = record.extra
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if record.exc_info:
 | 
				
			||||||
 | 
					            message['traceback'] = self.formatException(record.exc_info)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return jsonutils.dumps(message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _create_logging_excepthook(product_name):
 | 
				
			||||||
 | 
					    def logging_excepthook(exc_type, value, tb):
 | 
				
			||||||
 | 
					        extra = {'exc_info': (exc_type, value, tb)}
 | 
				
			||||||
 | 
					        getLogger(product_name).critical(
 | 
				
			||||||
 | 
					            "".join(traceback.format_exception_only(exc_type, value)),
 | 
				
			||||||
 | 
					            **extra)
 | 
				
			||||||
 | 
					    return logging_excepthook
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LogConfigError(Exception):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    message = _('Error loading logging config %(log_config)s: %(err_msg)s')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, log_config, err_msg):
 | 
				
			||||||
 | 
					        self.log_config = log_config
 | 
				
			||||||
 | 
					        self.err_msg = err_msg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __str__(self):
 | 
				
			||||||
 | 
					        return self.message % dict(log_config=self.log_config,
 | 
				
			||||||
 | 
					                                   err_msg=self.err_msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _load_log_config(log_config_append):
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        logging.config.fileConfig(log_config_append,
 | 
				
			||||||
 | 
					                                  disable_existing_loggers=False)
 | 
				
			||||||
 | 
					    except moves.configparser.Error as exc:
 | 
				
			||||||
 | 
					        raise LogConfigError(log_config_append, six.text_type(exc))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def setup(product_name, version='unknown'):
 | 
				
			||||||
 | 
					    """Setup logging."""
 | 
				
			||||||
 | 
					    if CONF.log_config_append:
 | 
				
			||||||
 | 
					        _load_log_config(CONF.log_config_append)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        _setup_logging_from_conf(product_name, version)
 | 
				
			||||||
 | 
					    sys.excepthook = _create_logging_excepthook(product_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def set_defaults(logging_context_format_string):
 | 
				
			||||||
 | 
					    cfg.set_defaults(
 | 
				
			||||||
 | 
					        log_opts, logging_context_format_string=logging_context_format_string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _find_facility_from_conf():
 | 
				
			||||||
 | 
					    facility_names = logging.handlers.SysLogHandler.facility_names
 | 
				
			||||||
 | 
					    facility = getattr(logging.handlers.SysLogHandler,
 | 
				
			||||||
 | 
					                       CONF.syslog_log_facility,
 | 
				
			||||||
 | 
					                       None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if facility is None and CONF.syslog_log_facility in facility_names:
 | 
				
			||||||
 | 
					        facility = facility_names.get(CONF.syslog_log_facility)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if facility is None:
 | 
				
			||||||
 | 
					        valid_facilities = facility_names.keys()
 | 
				
			||||||
 | 
					        consts = ['LOG_AUTH', 'LOG_AUTHPRIV', 'LOG_CRON', 'LOG_DAEMON',
 | 
				
			||||||
 | 
					                  'LOG_FTP', 'LOG_KERN', 'LOG_LPR', 'LOG_MAIL', 'LOG_NEWS',
 | 
				
			||||||
 | 
					                  'LOG_AUTH', 'LOG_SYSLOG', 'LOG_USER', 'LOG_UUCP',
 | 
				
			||||||
 | 
					                  'LOG_LOCAL0', 'LOG_LOCAL1', 'LOG_LOCAL2', 'LOG_LOCAL3',
 | 
				
			||||||
 | 
					                  'LOG_LOCAL4', 'LOG_LOCAL5', 'LOG_LOCAL6', 'LOG_LOCAL7']
 | 
				
			||||||
 | 
					        valid_facilities.extend(consts)
 | 
				
			||||||
 | 
					        raise TypeError(_('syslog facility must be one of: %s') %
 | 
				
			||||||
 | 
					                        ', '.join("'%s'" % fac
 | 
				
			||||||
 | 
					                                  for fac in valid_facilities))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return facility
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RFCSysLogHandler(logging.handlers.SysLogHandler):
 | 
				
			||||||
 | 
					    def __init__(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        self.binary_name = _get_binary_name()
 | 
				
			||||||
 | 
					        # Do not use super() unless type(logging.handlers.SysLogHandler)
 | 
				
			||||||
 | 
					        #  is 'type' (Python 2.7).
 | 
				
			||||||
 | 
					        # Use old style calls, if the type is 'classobj' (Python 2.6)
 | 
				
			||||||
 | 
					        logging.handlers.SysLogHandler.__init__(self, *args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def format(self, record):
 | 
				
			||||||
 | 
					        # Do not use super() unless type(logging.handlers.SysLogHandler)
 | 
				
			||||||
 | 
					        #  is 'type' (Python 2.7).
 | 
				
			||||||
 | 
					        # Use old style calls, if the type is 'classobj' (Python 2.6)
 | 
				
			||||||
 | 
					        msg = logging.handlers.SysLogHandler.format(self, record)
 | 
				
			||||||
 | 
					        msg = self.binary_name + ' ' + msg
 | 
				
			||||||
 | 
					        return msg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _setup_logging_from_conf(project, version):
 | 
				
			||||||
 | 
					    log_root = getLogger(None).logger
 | 
				
			||||||
 | 
					    for handler in log_root.handlers:
 | 
				
			||||||
 | 
					        log_root.removeHandler(handler)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if CONF.use_syslog:
 | 
				
			||||||
 | 
					        facility = _find_facility_from_conf()
 | 
				
			||||||
 | 
					        # TODO(bogdando) use the format provided by RFCSysLogHandler
 | 
				
			||||||
 | 
					        #   after existing syslog format deprecation in J
 | 
				
			||||||
 | 
					        if CONF.use_syslog_rfc_format:
 | 
				
			||||||
 | 
					            syslog = RFCSysLogHandler(address='/dev/log',
 | 
				
			||||||
 | 
					                                      facility=facility)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            syslog = logging.handlers.SysLogHandler(address='/dev/log',
 | 
				
			||||||
 | 
					                                                    facility=facility)
 | 
				
			||||||
 | 
					        log_root.addHandler(syslog)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    logpath = _get_log_file_path()
 | 
				
			||||||
 | 
					    if logpath:
 | 
				
			||||||
 | 
					        filelog = logging.handlers.WatchedFileHandler(logpath)
 | 
				
			||||||
 | 
					        log_root.addHandler(filelog)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if CONF.use_stderr:
 | 
				
			||||||
 | 
					        streamlog = ColorHandler()
 | 
				
			||||||
 | 
					        log_root.addHandler(streamlog)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    elif not logpath:
 | 
				
			||||||
 | 
					        # pass sys.stdout as a positional argument
 | 
				
			||||||
 | 
					        # python2.6 calls the argument strm, in 2.7 it's stream
 | 
				
			||||||
 | 
					        streamlog = logging.StreamHandler(sys.stdout)
 | 
				
			||||||
 | 
					        log_root.addHandler(streamlog)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if CONF.publish_errors:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            handler = importutils.import_object(
 | 
				
			||||||
 | 
					                "cloudkitty.openstack.common.log_handler.PublishErrorsHandler",
 | 
				
			||||||
 | 
					                logging.ERROR)
 | 
				
			||||||
 | 
					        except ImportError:
 | 
				
			||||||
 | 
					            handler = importutils.import_object(
 | 
				
			||||||
 | 
					                "oslo.messaging.notify.log_handler.PublishErrorsHandler",
 | 
				
			||||||
 | 
					                logging.ERROR)
 | 
				
			||||||
 | 
					        log_root.addHandler(handler)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    datefmt = CONF.log_date_format
 | 
				
			||||||
 | 
					    for handler in log_root.handlers:
 | 
				
			||||||
 | 
					        # NOTE(alaski): CONF.log_format overrides everything currently.  This
 | 
				
			||||||
 | 
					        # should be deprecated in favor of context aware formatting.
 | 
				
			||||||
 | 
					        if CONF.log_format:
 | 
				
			||||||
 | 
					            handler.setFormatter(logging.Formatter(fmt=CONF.log_format,
 | 
				
			||||||
 | 
					                                                   datefmt=datefmt))
 | 
				
			||||||
 | 
					            log_root.info('Deprecated: log_format is now deprecated and will '
 | 
				
			||||||
 | 
					                          'be removed in the next release')
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            handler.setFormatter(ContextFormatter(project=project,
 | 
				
			||||||
 | 
					                                                  version=version,
 | 
				
			||||||
 | 
					                                                  datefmt=datefmt))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if CONF.debug:
 | 
				
			||||||
 | 
					        log_root.setLevel(logging.DEBUG)
 | 
				
			||||||
 | 
					    elif CONF.verbose:
 | 
				
			||||||
 | 
					        log_root.setLevel(logging.INFO)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        log_root.setLevel(logging.WARNING)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for pair in CONF.default_log_levels:
 | 
				
			||||||
 | 
					        mod, _sep, level_name = pair.partition('=')
 | 
				
			||||||
 | 
					        logger = logging.getLogger(mod)
 | 
				
			||||||
 | 
					        # NOTE(AAzza) in python2.6 Logger.setLevel doesn't convert string name
 | 
				
			||||||
 | 
					        # to integer code.
 | 
				
			||||||
 | 
					        if sys.version_info < (2, 7):
 | 
				
			||||||
 | 
					            level = logging.getLevelName(level_name)
 | 
				
			||||||
 | 
					            logger.setLevel(level)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            logger.setLevel(level_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_loggers = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def getLogger(name='unknown', version='unknown'):
 | 
				
			||||||
 | 
					    if name not in _loggers:
 | 
				
			||||||
 | 
					        _loggers[name] = ContextAdapter(logging.getLogger(name),
 | 
				
			||||||
 | 
					                                        name,
 | 
				
			||||||
 | 
					                                        version)
 | 
				
			||||||
 | 
					    return _loggers[name]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def getLazyLogger(name='unknown', version='unknown'):
 | 
				
			||||||
 | 
					    """Returns lazy logger.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Creates a pass-through logger that does not create the real logger
 | 
				
			||||||
 | 
					    until it is really needed and delegates all calls to the real logger
 | 
				
			||||||
 | 
					    once it is created.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    return LazyAdapter(name, version)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class WritableLogger(object):
 | 
				
			||||||
 | 
					    """A thin wrapper that responds to `write` and logs."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, logger, level=logging.INFO):
 | 
				
			||||||
 | 
					        self.logger = logger
 | 
				
			||||||
 | 
					        self.level = level
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def write(self, msg):
 | 
				
			||||||
 | 
					        self.logger.log(self.level, msg.rstrip())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ContextFormatter(logging.Formatter):
 | 
				
			||||||
 | 
					    """A context.RequestContext aware formatter configured through flags.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The flags used to set format strings are: logging_context_format_string
 | 
				
			||||||
 | 
					    and logging_default_format_string.  You can also specify
 | 
				
			||||||
 | 
					    logging_debug_format_suffix to append extra formatting if the log level is
 | 
				
			||||||
 | 
					    debug.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    For information about what variables are available for the formatter see:
 | 
				
			||||||
 | 
					    http://docs.python.org/library/logging.html#formatter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    If available, uses the context value stored in TLS - local.store.context
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        """Initialize ContextFormatter instance
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Takes additional keyword arguments which can be used in the message
 | 
				
			||||||
 | 
					        format string.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :keyword project: project name
 | 
				
			||||||
 | 
					        :type project: string
 | 
				
			||||||
 | 
					        :keyword version: project version
 | 
				
			||||||
 | 
					        :type version: string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.project = kwargs.pop('project', 'unknown')
 | 
				
			||||||
 | 
					        self.version = kwargs.pop('version', 'unknown')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        logging.Formatter.__init__(self, *args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def format(self, record):
 | 
				
			||||||
 | 
					        """Uses contextstring if request_id is set, otherwise default."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # store project info
 | 
				
			||||||
 | 
					        record.project = self.project
 | 
				
			||||||
 | 
					        record.version = self.version
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # store request info
 | 
				
			||||||
 | 
					        context = getattr(local.store, 'context', None)
 | 
				
			||||||
 | 
					        if context:
 | 
				
			||||||
 | 
					            d = _dictify_context(context)
 | 
				
			||||||
 | 
					            for k, v in d.items():
 | 
				
			||||||
 | 
					                setattr(record, k, v)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # NOTE(sdague): default the fancier formatting params
 | 
				
			||||||
 | 
					        # to an empty string so we don't throw an exception if
 | 
				
			||||||
 | 
					        # they get used
 | 
				
			||||||
 | 
					        for key in ('instance', 'color', 'user_identity'):
 | 
				
			||||||
 | 
					            if key not in record.__dict__:
 | 
				
			||||||
 | 
					                record.__dict__[key] = ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if record.__dict__.get('request_id'):
 | 
				
			||||||
 | 
					            fmt = CONF.logging_context_format_string
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            fmt = CONF.logging_default_format_string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (record.levelno == logging.DEBUG and
 | 
				
			||||||
 | 
					                CONF.logging_debug_format_suffix):
 | 
				
			||||||
 | 
					            fmt += " " + CONF.logging_debug_format_suffix
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if sys.version_info < (3, 2):
 | 
				
			||||||
 | 
					            self._fmt = fmt
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self._style = logging.PercentStyle(fmt)
 | 
				
			||||||
 | 
					            self._fmt = self._style._fmt
 | 
				
			||||||
 | 
					        # Cache this on the record, Logger will respect our formatted copy
 | 
				
			||||||
 | 
					        if record.exc_info:
 | 
				
			||||||
 | 
					            record.exc_text = self.formatException(record.exc_info, record)
 | 
				
			||||||
 | 
					        return logging.Formatter.format(self, record)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def formatException(self, exc_info, record=None):
 | 
				
			||||||
 | 
					        """Format exception output with CONF.logging_exception_prefix."""
 | 
				
			||||||
 | 
					        if not record:
 | 
				
			||||||
 | 
					            return logging.Formatter.formatException(self, exc_info)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        stringbuffer = moves.StringIO()
 | 
				
			||||||
 | 
					        traceback.print_exception(exc_info[0], exc_info[1], exc_info[2],
 | 
				
			||||||
 | 
					                                  None, stringbuffer)
 | 
				
			||||||
 | 
					        lines = stringbuffer.getvalue().split('\n')
 | 
				
			||||||
 | 
					        stringbuffer.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if CONF.logging_exception_prefix.find('%(asctime)') != -1:
 | 
				
			||||||
 | 
					            record.asctime = self.formatTime(record, self.datefmt)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        formatted_lines = []
 | 
				
			||||||
 | 
					        for line in lines:
 | 
				
			||||||
 | 
					            pl = CONF.logging_exception_prefix % record.__dict__
 | 
				
			||||||
 | 
					            fl = '%s%s' % (pl, line)
 | 
				
			||||||
 | 
					            formatted_lines.append(fl)
 | 
				
			||||||
 | 
					        return '\n'.join(formatted_lines)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ColorHandler(logging.StreamHandler):
 | 
				
			||||||
 | 
					    LEVEL_COLORS = {
 | 
				
			||||||
 | 
					        logging.DEBUG: '\033[00;32m',  # GREEN
 | 
				
			||||||
 | 
					        logging.INFO: '\033[00;36m',  # CYAN
 | 
				
			||||||
 | 
					        logging.AUDIT: '\033[01;36m',  # BOLD CYAN
 | 
				
			||||||
 | 
					        logging.WARN: '\033[01;33m',  # BOLD YELLOW
 | 
				
			||||||
 | 
					        logging.ERROR: '\033[01;31m',  # BOLD RED
 | 
				
			||||||
 | 
					        logging.CRITICAL: '\033[01;31m',  # BOLD RED
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def format(self, record):
 | 
				
			||||||
 | 
					        record.color = self.LEVEL_COLORS[record.levelno]
 | 
				
			||||||
 | 
					        return logging.StreamHandler.format(self, record)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DeprecatedConfig(Exception):
 | 
				
			||||||
 | 
					    message = _("Fatal call to deprecated config: %(msg)s")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, msg):
 | 
				
			||||||
 | 
					        super(Exception, self).__init__(self.message % dict(msg=msg))
 | 
				
			||||||
							
								
								
									
										239
									
								
								cloudkitty/openstack/common/strutils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										239
									
								
								cloudkitty/openstack/common/strutils.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,239 @@
 | 
				
			|||||||
 | 
					# Copyright 2011 OpenStack Foundation.
 | 
				
			||||||
 | 
					# 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					System-level utilities and helper functions.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import math
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import unicodedata
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import six
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from cloudkitty.openstack.common.gettextutils import _
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					UNIT_PREFIX_EXPONENT = {
 | 
				
			||||||
 | 
					    'k': 1,
 | 
				
			||||||
 | 
					    'K': 1,
 | 
				
			||||||
 | 
					    'Ki': 1,
 | 
				
			||||||
 | 
					    'M': 2,
 | 
				
			||||||
 | 
					    'Mi': 2,
 | 
				
			||||||
 | 
					    'G': 3,
 | 
				
			||||||
 | 
					    'Gi': 3,
 | 
				
			||||||
 | 
					    'T': 4,
 | 
				
			||||||
 | 
					    'Ti': 4,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					UNIT_SYSTEM_INFO = {
 | 
				
			||||||
 | 
					    'IEC': (1024, re.compile(r'(^[-+]?\d*\.?\d+)([KMGT]i?)?(b|bit|B)$')),
 | 
				
			||||||
 | 
					    'SI': (1000, re.compile(r'(^[-+]?\d*\.?\d+)([kMGT])?(b|bit|B)$')),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes')
 | 
				
			||||||
 | 
					FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SLUGIFY_STRIP_RE = re.compile(r"[^\w\s-]")
 | 
				
			||||||
 | 
					SLUGIFY_HYPHENATE_RE = re.compile(r"[-\s]+")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def int_from_bool_as_string(subject):
 | 
				
			||||||
 | 
					    """Interpret a string as a boolean and return either 1 or 0.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Any string value in:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ('True', 'true', 'On', 'on', '1')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    is interpreted as a boolean True.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Useful for JSON-decoded stuff and config file parsing
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    return bool_from_string(subject) and 1 or 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def bool_from_string(subject, strict=False, default=False):
 | 
				
			||||||
 | 
					    """Interpret a string as a boolean.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    A case-insensitive match is performed such that strings matching 't',
 | 
				
			||||||
 | 
					    'true', 'on', 'y', 'yes', or '1' are considered True and, when
 | 
				
			||||||
 | 
					    `strict=False`, anything else returns the value specified by 'default'.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Useful for JSON-decoded stuff and config file parsing.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    If `strict=True`, unrecognized values, including None, will raise a
 | 
				
			||||||
 | 
					    ValueError which is useful when parsing values passed in from an API call.
 | 
				
			||||||
 | 
					    Strings yielding False are 'f', 'false', 'off', 'n', 'no', or '0'.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    if not isinstance(subject, six.string_types):
 | 
				
			||||||
 | 
					        subject = six.text_type(subject)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    lowered = subject.strip().lower()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if lowered in TRUE_STRINGS:
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					    elif lowered in FALSE_STRINGS:
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					    elif strict:
 | 
				
			||||||
 | 
					        acceptable = ', '.join(
 | 
				
			||||||
 | 
					            "'%s'" % s for s in sorted(TRUE_STRINGS + FALSE_STRINGS))
 | 
				
			||||||
 | 
					        msg = _("Unrecognized value '%(val)s', acceptable values are:"
 | 
				
			||||||
 | 
					                " %(acceptable)s") % {'val': subject,
 | 
				
			||||||
 | 
					                                      'acceptable': acceptable}
 | 
				
			||||||
 | 
					        raise ValueError(msg)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        return default
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def safe_decode(text, incoming=None, errors='strict'):
 | 
				
			||||||
 | 
					    """Decodes incoming text/bytes string using `incoming` if they're not
 | 
				
			||||||
 | 
					       already unicode.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param incoming: Text's current encoding
 | 
				
			||||||
 | 
					    :param errors: Errors handling policy. See here for valid
 | 
				
			||||||
 | 
					        values http://docs.python.org/2/library/codecs.html
 | 
				
			||||||
 | 
					    :returns: text or a unicode `incoming` encoded
 | 
				
			||||||
 | 
					                representation of it.
 | 
				
			||||||
 | 
					    :raises TypeError: If text is not an instance of str
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    if not isinstance(text, (six.string_types, six.binary_type)):
 | 
				
			||||||
 | 
					        raise TypeError("%s can't be decoded" % type(text))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if isinstance(text, six.text_type):
 | 
				
			||||||
 | 
					        return text
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if not incoming:
 | 
				
			||||||
 | 
					        incoming = (sys.stdin.encoding or
 | 
				
			||||||
 | 
					                    sys.getdefaultencoding())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        return text.decode(incoming, errors)
 | 
				
			||||||
 | 
					    except UnicodeDecodeError:
 | 
				
			||||||
 | 
					        # Note(flaper87) If we get here, it means that
 | 
				
			||||||
 | 
					        # sys.stdin.encoding / sys.getdefaultencoding
 | 
				
			||||||
 | 
					        # didn't return a suitable encoding to decode
 | 
				
			||||||
 | 
					        # text. This happens mostly when global LANG
 | 
				
			||||||
 | 
					        # var is not set correctly and there's no
 | 
				
			||||||
 | 
					        # default encoding. In this case, most likely
 | 
				
			||||||
 | 
					        # python will use ASCII or ANSI encoders as
 | 
				
			||||||
 | 
					        # default encodings but they won't be capable
 | 
				
			||||||
 | 
					        # of decoding non-ASCII characters.
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        # Also, UTF-8 is being used since it's an ASCII
 | 
				
			||||||
 | 
					        # extension.
 | 
				
			||||||
 | 
					        return text.decode('utf-8', errors)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def safe_encode(text, incoming=None,
 | 
				
			||||||
 | 
					                encoding='utf-8', errors='strict'):
 | 
				
			||||||
 | 
					    """Encodes incoming text/bytes string using `encoding`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    If incoming is not specified, text is expected to be encoded with
 | 
				
			||||||
 | 
					    current python's default encoding. (`sys.getdefaultencoding`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param incoming: Text's current encoding
 | 
				
			||||||
 | 
					    :param encoding: Expected encoding for text (Default UTF-8)
 | 
				
			||||||
 | 
					    :param errors: Errors handling policy. See here for valid
 | 
				
			||||||
 | 
					        values http://docs.python.org/2/library/codecs.html
 | 
				
			||||||
 | 
					    :returns: text or a bytestring `encoding` encoded
 | 
				
			||||||
 | 
					                representation of it.
 | 
				
			||||||
 | 
					    :raises TypeError: If text is not an instance of str
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    if not isinstance(text, (six.string_types, six.binary_type)):
 | 
				
			||||||
 | 
					        raise TypeError("%s can't be encoded" % type(text))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if not incoming:
 | 
				
			||||||
 | 
					        incoming = (sys.stdin.encoding or
 | 
				
			||||||
 | 
					                    sys.getdefaultencoding())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if isinstance(text, six.text_type):
 | 
				
			||||||
 | 
					        return text.encode(encoding, errors)
 | 
				
			||||||
 | 
					    elif text and encoding != incoming:
 | 
				
			||||||
 | 
					        # Decode text before encoding it with `encoding`
 | 
				
			||||||
 | 
					        text = safe_decode(text, incoming, errors)
 | 
				
			||||||
 | 
					        return text.encode(encoding, errors)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        return text
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def string_to_bytes(text, unit_system='IEC', return_int=False):
 | 
				
			||||||
 | 
					    """Converts a string into an float representation of bytes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The units supported for IEC ::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Kb(it), Kib(it), Mb(it), Mib(it), Gb(it), Gib(it), Tb(it), Tib(it)
 | 
				
			||||||
 | 
					        KB, KiB, MB, MiB, GB, GiB, TB, TiB
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The units supported for SI ::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        kb(it), Mb(it), Gb(it), Tb(it)
 | 
				
			||||||
 | 
					        kB, MB, GB, TB
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Note that the SI unit system does not support capital letter 'K'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param text: String input for bytes size conversion.
 | 
				
			||||||
 | 
					    :param unit_system: Unit system for byte size conversion.
 | 
				
			||||||
 | 
					    :param return_int: If True, returns integer representation of text
 | 
				
			||||||
 | 
					                       in bytes. (default: decimal)
 | 
				
			||||||
 | 
					    :returns: Numerical representation of text in bytes.
 | 
				
			||||||
 | 
					    :raises ValueError: If text has an invalid value.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        base, reg_ex = UNIT_SYSTEM_INFO[unit_system]
 | 
				
			||||||
 | 
					    except KeyError:
 | 
				
			||||||
 | 
					        msg = _('Invalid unit system: "%s"') % unit_system
 | 
				
			||||||
 | 
					        raise ValueError(msg)
 | 
				
			||||||
 | 
					    match = reg_ex.match(text)
 | 
				
			||||||
 | 
					    if match:
 | 
				
			||||||
 | 
					        magnitude = float(match.group(1))
 | 
				
			||||||
 | 
					        unit_prefix = match.group(2)
 | 
				
			||||||
 | 
					        if match.group(3) in ['b', 'bit']:
 | 
				
			||||||
 | 
					            magnitude /= 8
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        msg = _('Invalid string format: %s') % text
 | 
				
			||||||
 | 
					        raise ValueError(msg)
 | 
				
			||||||
 | 
					    if not unit_prefix:
 | 
				
			||||||
 | 
					        res = magnitude
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        res = magnitude * pow(base, UNIT_PREFIX_EXPONENT[unit_prefix])
 | 
				
			||||||
 | 
					    if return_int:
 | 
				
			||||||
 | 
					        return int(math.ceil(res))
 | 
				
			||||||
 | 
					    return res
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def to_slug(value, incoming=None, errors="strict"):
 | 
				
			||||||
 | 
					    """Normalize string.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Convert to lowercase, remove non-word characters, and convert spaces
 | 
				
			||||||
 | 
					    to hyphens.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Inspired by Django's `slugify` filter.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param value: Text to slugify
 | 
				
			||||||
 | 
					    :param incoming: Text's current encoding
 | 
				
			||||||
 | 
					    :param errors: Errors handling policy. See here for valid
 | 
				
			||||||
 | 
					        values http://docs.python.org/2/library/codecs.html
 | 
				
			||||||
 | 
					    :returns: slugified unicode representation of `value`
 | 
				
			||||||
 | 
					    :raises TypeError: If text is not an instance of str
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    value = safe_decode(value, incoming, errors)
 | 
				
			||||||
 | 
					    # NOTE(aababilov): no need to use safe_(encode|decode) here:
 | 
				
			||||||
 | 
					    # encodings are always "ascii", error handling is always "ignore"
 | 
				
			||||||
 | 
					    # and types are always known (first: unicode; second: str)
 | 
				
			||||||
 | 
					    value = unicodedata.normalize("NFKD", value).encode(
 | 
				
			||||||
 | 
					        "ascii", "ignore").decode("ascii")
 | 
				
			||||||
 | 
					    value = SLUGIFY_STRIP_RE.sub("", value).strip().lower()
 | 
				
			||||||
 | 
					    return SLUGIFY_HYPHENATE_RE.sub("-", value)
 | 
				
			||||||
							
								
								
									
										210
									
								
								cloudkitty/openstack/common/timeutils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										210
									
								
								cloudkitty/openstack/common/timeutils.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,210 @@
 | 
				
			|||||||
 | 
					# Copyright 2011 OpenStack Foundation.
 | 
				
			||||||
 | 
					# 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					Time related utilities and helper functions.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import calendar
 | 
				
			||||||
 | 
					import datetime
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import iso8601
 | 
				
			||||||
 | 
					import six
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# ISO 8601 extended time format with microseconds
 | 
				
			||||||
 | 
					_ISO8601_TIME_FORMAT_SUBSECOND = '%Y-%m-%dT%H:%M:%S.%f'
 | 
				
			||||||
 | 
					_ISO8601_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S'
 | 
				
			||||||
 | 
					PERFECT_TIME_FORMAT = _ISO8601_TIME_FORMAT_SUBSECOND
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def isotime(at=None, subsecond=False):
 | 
				
			||||||
 | 
					    """Stringify time in ISO 8601 format."""
 | 
				
			||||||
 | 
					    if not at:
 | 
				
			||||||
 | 
					        at = utcnow()
 | 
				
			||||||
 | 
					    st = at.strftime(_ISO8601_TIME_FORMAT
 | 
				
			||||||
 | 
					                     if not subsecond
 | 
				
			||||||
 | 
					                     else _ISO8601_TIME_FORMAT_SUBSECOND)
 | 
				
			||||||
 | 
					    tz = at.tzinfo.tzname(None) if at.tzinfo else 'UTC'
 | 
				
			||||||
 | 
					    st += ('Z' if tz == 'UTC' else tz)
 | 
				
			||||||
 | 
					    return st
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def parse_isotime(timestr):
 | 
				
			||||||
 | 
					    """Parse time from ISO 8601 format."""
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        return iso8601.parse_date(timestr)
 | 
				
			||||||
 | 
					    except iso8601.ParseError as e:
 | 
				
			||||||
 | 
					        raise ValueError(six.text_type(e))
 | 
				
			||||||
 | 
					    except TypeError as e:
 | 
				
			||||||
 | 
					        raise ValueError(six.text_type(e))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 naive object."""
 | 
				
			||||||
 | 
					    offset = timestamp.utcoffset()
 | 
				
			||||||
 | 
					    if offset is None:
 | 
				
			||||||
 | 
					        return timestamp
 | 
				
			||||||
 | 
					    return timestamp.replace(tzinfo=None) - offset
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def is_older_than(before, seconds):
 | 
				
			||||||
 | 
					    """Return True if before is older than seconds."""
 | 
				
			||||||
 | 
					    if isinstance(before, six.string_types):
 | 
				
			||||||
 | 
					        before = parse_strtime(before).replace(tzinfo=None)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        before = before.replace(tzinfo=None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return utcnow() - before > datetime.timedelta(seconds=seconds)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def is_newer_than(after, seconds):
 | 
				
			||||||
 | 
					    """Return True if after is newer than seconds."""
 | 
				
			||||||
 | 
					    if isinstance(after, six.string_types):
 | 
				
			||||||
 | 
					        after = parse_strtime(after).replace(tzinfo=None)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        after = after.replace(tzinfo=None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return after - utcnow() > datetime.timedelta(seconds=seconds)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def utcnow_ts():
 | 
				
			||||||
 | 
					    """Timestamp version of our utcnow function."""
 | 
				
			||||||
 | 
					    if utcnow.override_time is None:
 | 
				
			||||||
 | 
					        # NOTE(kgriffs): This is several times faster
 | 
				
			||||||
 | 
					        # than going through calendar.timegm(...)
 | 
				
			||||||
 | 
					        return int(time.time())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return calendar.timegm(utcnow().timetuple())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def utcnow():
 | 
				
			||||||
 | 
					    """Overridable version of utils.utcnow."""
 | 
				
			||||||
 | 
					    if utcnow.override_time:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            return utcnow.override_time.pop(0)
 | 
				
			||||||
 | 
					        except AttributeError:
 | 
				
			||||||
 | 
					            return utcnow.override_time
 | 
				
			||||||
 | 
					    return datetime.datetime.utcnow()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def iso8601_from_timestamp(timestamp):
 | 
				
			||||||
 | 
					    """Returns an iso8601 formatted date from timestamp."""
 | 
				
			||||||
 | 
					    return isotime(datetime.datetime.utcfromtimestamp(timestamp))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					utcnow.override_time = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def set_time_override(override_time=None):
 | 
				
			||||||
 | 
					    """Overrides utils.utcnow.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Make it return a constant time or a list thereof, one at a time.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param override_time: datetime instance or list thereof. If not
 | 
				
			||||||
 | 
					                          given, defaults to the current UTC time.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    utcnow.override_time = override_time or datetime.datetime.utcnow()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def advance_time_delta(timedelta):
 | 
				
			||||||
 | 
					    """Advance overridden time using a datetime.timedelta."""
 | 
				
			||||||
 | 
					    assert utcnow.override_time is not None
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        for dt in utcnow.override_time:
 | 
				
			||||||
 | 
					            dt += timedelta
 | 
				
			||||||
 | 
					    except TypeError:
 | 
				
			||||||
 | 
					        utcnow.override_time += timedelta
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def advance_time_seconds(seconds):
 | 
				
			||||||
 | 
					    """Advance overridden time by seconds."""
 | 
				
			||||||
 | 
					    advance_time_delta(datetime.timedelta(0, seconds))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def clear_time_override():
 | 
				
			||||||
 | 
					    """Remove the overridden time."""
 | 
				
			||||||
 | 
					    utcnow.override_time = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def marshall_now(now=None):
 | 
				
			||||||
 | 
					    """Make an rpc-safe datetime with microseconds.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Note: tzinfo is stripped, but not required for relative times.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    if not now:
 | 
				
			||||||
 | 
					        now = utcnow()
 | 
				
			||||||
 | 
					    return dict(day=now.day, month=now.month, year=now.year, hour=now.hour,
 | 
				
			||||||
 | 
					                minute=now.minute, second=now.second,
 | 
				
			||||||
 | 
					                microsecond=now.microsecond)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def unmarshall_time(tyme):
 | 
				
			||||||
 | 
					    """Unmarshall a datetime dict."""
 | 
				
			||||||
 | 
					    return datetime.datetime(day=tyme['day'],
 | 
				
			||||||
 | 
					                             month=tyme['month'],
 | 
				
			||||||
 | 
					                             year=tyme['year'],
 | 
				
			||||||
 | 
					                             hour=tyme['hour'],
 | 
				
			||||||
 | 
					                             minute=tyme['minute'],
 | 
				
			||||||
 | 
					                             second=tyme['second'],
 | 
				
			||||||
 | 
					                             microsecond=tyme['microsecond'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def delta_seconds(before, after):
 | 
				
			||||||
 | 
					    """Return the difference between two timing objects.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Compute the difference in seconds between two date, time, or
 | 
				
			||||||
 | 
					    datetime objects (as a float, to microsecond resolution).
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    delta = after - before
 | 
				
			||||||
 | 
					    return total_seconds(delta)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def total_seconds(delta):
 | 
				
			||||||
 | 
					    """Return the total seconds of datetime.timedelta object.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Compute total seconds of datetime.timedelta, datetime.timedelta
 | 
				
			||||||
 | 
					    doesn't have method total_seconds in Python2.6, calculate it manually.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        return delta.total_seconds()
 | 
				
			||||||
 | 
					    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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param dt: the time
 | 
				
			||||||
 | 
					    :param 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
 | 
				
			||||||
							
								
								
									
										177
									
								
								etc/cloudkitty.conf.sample
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								etc/cloudkitty.conf.sample
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,177 @@
 | 
				
			|||||||
 | 
					[DEFAULT]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Options defined in cloudkitty.openstack.common.log
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Print debugging output (set logging level to DEBUG instead
 | 
				
			||||||
 | 
					# of default WARNING level). (boolean value)
 | 
				
			||||||
 | 
					#debug=false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Print more verbose output (set logging level to INFO instead
 | 
				
			||||||
 | 
					# of default WARNING level). (boolean value)
 | 
				
			||||||
 | 
					#verbose=false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Log output to standard error. (boolean value)
 | 
				
			||||||
 | 
					#use_stderr=true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Format string to use for log messages with context. (string
 | 
				
			||||||
 | 
					# value)
 | 
				
			||||||
 | 
					#logging_context_format_string=%(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s [%(request_id)s %(user_identity)s] %(instance)s%(message)s
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Format string to use for log messages without context.
 | 
				
			||||||
 | 
					# (string value)
 | 
				
			||||||
 | 
					#logging_default_format_string=%(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s [-] %(instance)s%(message)s
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Data to append to log format when level is DEBUG. (string
 | 
				
			||||||
 | 
					# value)
 | 
				
			||||||
 | 
					#logging_debug_format_suffix=%(funcName)s %(pathname)s:%(lineno)d
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Prefix each line of exception output with this format.
 | 
				
			||||||
 | 
					# (string value)
 | 
				
			||||||
 | 
					#logging_exception_prefix=%(asctime)s.%(msecs)03d %(process)d TRACE %(name)s %(instance)s
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# List of logger=LEVEL pairs. (list value)
 | 
				
			||||||
 | 
					#default_log_levels=amqp=WARN,amqplib=WARN,boto=WARN,qpid=WARN,sqlalchemy=WARN,suds=INFO,oslo.messaging=INFO,iso8601=WARN,requests.packages.urllib3.connectionpool=WARN
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Enables or disables publication of error events. (boolean
 | 
				
			||||||
 | 
					# value)
 | 
				
			||||||
 | 
					#publish_errors=false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Enables or disables fatal status of deprecations. (boolean
 | 
				
			||||||
 | 
					# value)
 | 
				
			||||||
 | 
					#fatal_deprecations=false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# The format for an instance that is passed with the log
 | 
				
			||||||
 | 
					# message.  (string value)
 | 
				
			||||||
 | 
					#instance_format="[instance: %(uuid)s] "
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# The format for an instance UUID that is passed with the log
 | 
				
			||||||
 | 
					# message.  (string value)
 | 
				
			||||||
 | 
					#instance_uuid_format="[instance: %(uuid)s] "
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# The name of a logging configuration file. This file is
 | 
				
			||||||
 | 
					# appended to any existing logging configuration files. For
 | 
				
			||||||
 | 
					# details about logging configuration files, see the Python
 | 
				
			||||||
 | 
					# logging module documentation. (string value)
 | 
				
			||||||
 | 
					# Deprecated group/name - [DEFAULT]/log_config
 | 
				
			||||||
 | 
					#log_config_append=<None>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# DEPRECATED. A logging.Formatter log message format string
 | 
				
			||||||
 | 
					# which may use any of the available logging.LogRecord
 | 
				
			||||||
 | 
					# attributes. This option is deprecated.  Please use
 | 
				
			||||||
 | 
					# logging_context_format_string and
 | 
				
			||||||
 | 
					# logging_default_format_string instead. (string value)
 | 
				
			||||||
 | 
					#log_format=<None>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Format string for %%(asctime)s in log records. Default:
 | 
				
			||||||
 | 
					# %(default)s . (string value)
 | 
				
			||||||
 | 
					#log_date_format=%Y-%m-%d %H:%M:%S
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# (Optional) Name of log file to output to. If no default is
 | 
				
			||||||
 | 
					# set, logging will go to stdout. (string value)
 | 
				
			||||||
 | 
					# Deprecated group/name - [DEFAULT]/logfile
 | 
				
			||||||
 | 
					#log_file=<None>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# (Optional) The base directory used for relative --log-file
 | 
				
			||||||
 | 
					# paths. (string value)
 | 
				
			||||||
 | 
					# Deprecated group/name - [DEFAULT]/logdir
 | 
				
			||||||
 | 
					#log_dir=<None>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Use syslog for logging. Existing syslog format is DEPRECATED
 | 
				
			||||||
 | 
					# during I, and will change in J to honor RFC5424. (boolean
 | 
				
			||||||
 | 
					# value)
 | 
				
			||||||
 | 
					#use_syslog=false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# (Optional) Enables or disables syslog rfc5424 format for
 | 
				
			||||||
 | 
					# logging. If enabled, prefixes the MSG part of the syslog
 | 
				
			||||||
 | 
					# message with APP-NAME (RFC5424). The format without the APP-
 | 
				
			||||||
 | 
					# NAME is deprecated in I, and will be removed in J. (boolean
 | 
				
			||||||
 | 
					# value)
 | 
				
			||||||
 | 
					#use_syslog_rfc_format=false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Syslog facility to receive log lines. (string value)
 | 
				
			||||||
 | 
					#syslog_log_facility=LOG_USER
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[auth]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Options defined in cloudkitty.config
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# OpenStack username. (string value)
 | 
				
			||||||
 | 
					#username=
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# OpenStack password. (string value)
 | 
				
			||||||
 | 
					#password=
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# OpenStack tenant. (string value)
 | 
				
			||||||
 | 
					#tenant=
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# OpenStack region. (string value)
 | 
				
			||||||
 | 
					#region=
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# OpenStack auth URL. (string value)
 | 
				
			||||||
 | 
					#url=
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[billing]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Options defined in cloudkitty.config
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Billing pipeline modules. (list value)
 | 
				
			||||||
 | 
					#pipeline=cloudkitty.billing.hash.BasicHashMap,cloudkitty.billing.noop.Noop
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[collect]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Options defined in cloudkitty.config
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Data collector. (string value)
 | 
				
			||||||
 | 
					#collector=cloudkitty.collector.ceilometer.CeilometerCollector
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Number of samples to collect per call. (integer value)
 | 
				
			||||||
 | 
					#window=1800
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Billing period in seconds. (integer value)
 | 
				
			||||||
 | 
					#period=3600
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Services to monitor. (list value)
 | 
				
			||||||
 | 
					#services=compute
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[output]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Options defined in cloudkitty.config
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Backend for the output manager. (string value)
 | 
				
			||||||
 | 
					#backend=cloudkitty.backend.file.FileBackend
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Storage directory for the file output backend. (string
 | 
				
			||||||
 | 
					# value)
 | 
				
			||||||
 | 
					#basepath=/var/lib/cloudkitty/states/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Output pipeline (list value)
 | 
				
			||||||
 | 
					#pipeline=cloudkitty.writer.osrf.OSRFBackend
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[state]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Options defined in cloudkitty.config
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Backend for the state manager. (string value)
 | 
				
			||||||
 | 
					#backend=cloudkitty.backend.file.FileBackend
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Storage directory for the file state backend. (string value)
 | 
				
			||||||
 | 
					#basepath=/var/lib/cloudkitty/states/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1,4 +1,10 @@
 | 
				
			|||||||
[DEFAULT]
 | 
					[DEFAULT]
 | 
				
			||||||
base=cloudkitty
 | 
					base=cloudkitty
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module=config
 | 
				
			||||||
module=importutils
 | 
					module=importutils
 | 
				
			||||||
 | 
					module=jsonutils
 | 
				
			||||||
 | 
					module=log
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					script=tools/config/check_uptodate.sh
 | 
				
			||||||
 | 
					script=tools/config/generate_sample.sh
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										29
									
								
								tools/config/check_uptodate.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										29
									
								
								tools/config/check_uptodate.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PROJECT_NAME=${PROJECT_NAME:-cloudkitty}
 | 
				
			||||||
 | 
					CFGFILE_NAME=${PROJECT_NAME}.conf.sample
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [ -e etc/${PROJECT_NAME}/${CFGFILE_NAME} ]; then
 | 
				
			||||||
 | 
					    CFGFILE=etc/${PROJECT_NAME}/${CFGFILE_NAME}
 | 
				
			||||||
 | 
					elif [ -e etc/${CFGFILE_NAME} ]; then
 | 
				
			||||||
 | 
					    CFGFILE=etc/${CFGFILE_NAME}
 | 
				
			||||||
 | 
					else
 | 
				
			||||||
 | 
					    echo "${0##*/}: can not find config file"
 | 
				
			||||||
 | 
					    exit 1
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TEMPDIR=`mktemp -d /tmp/${PROJECT_NAME}.XXXXXX`
 | 
				
			||||||
 | 
					trap "rm -rf $TEMPDIR" EXIT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tools/config/generate_sample.sh -b ./ -p ${PROJECT_NAME} -o ${TEMPDIR}
 | 
				
			||||||
 | 
					if [ $? != 0 ]
 | 
				
			||||||
 | 
					then
 | 
				
			||||||
 | 
					    exit 1
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if ! diff -u ${TEMPDIR}/${CFGFILE_NAME} ${CFGFILE}
 | 
				
			||||||
 | 
					then
 | 
				
			||||||
 | 
					   echo "${0##*/}: ${PROJECT_NAME}.conf.sample is not up to date."
 | 
				
			||||||
 | 
					   echo "${0##*/}: Please run ${0%%${0##*/}}generate_sample.sh."
 | 
				
			||||||
 | 
					   exit 1
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
							
								
								
									
										138
									
								
								tools/config/generate_sample.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										138
									
								
								tools/config/generate_sample.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,138 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Generate sample configuration for your project.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Aside from the command line flags, it also respects a config file which
 | 
				
			||||||
 | 
					# should be named oslo.config.generator.rc and be placed in the same directory.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# You can then export the following variables:
 | 
				
			||||||
 | 
					# CLOUDKITTY_CONFIG_GENERATOR_EXTRA_MODULES: list of modules to interrogate for options.
 | 
				
			||||||
 | 
					# CLOUDKITTY_CONFIG_GENERATOR_EXTRA_LIBRARIES: list of libraries to discover.
 | 
				
			||||||
 | 
					# CLOUDKITTY_CONFIG_GENERATOR_EXCLUDED_FILES: list of files to remove from automatic listing.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					print_hint() {
 | 
				
			||||||
 | 
					    echo "Try \`${0##*/} --help' for more information." >&2
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PARSED_OPTIONS=$(getopt -n "${0##*/}" -o hb:p:m:l:o: \
 | 
				
			||||||
 | 
					                 --long help,base-dir:,package-name:,output-dir:,module:,library: -- "$@")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [ $? != 0 ] ; then print_hint ; exit 1 ; fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					eval set -- "$PARSED_OPTIONS"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					while true; do
 | 
				
			||||||
 | 
					    case "$1" in
 | 
				
			||||||
 | 
					        -h|--help)
 | 
				
			||||||
 | 
					            echo "${0##*/} [options]"
 | 
				
			||||||
 | 
					            echo ""
 | 
				
			||||||
 | 
					            echo "options:"
 | 
				
			||||||
 | 
					            echo "-h, --help                show brief help"
 | 
				
			||||||
 | 
					            echo "-b, --base-dir=DIR        project base directory"
 | 
				
			||||||
 | 
					            echo "-p, --package-name=NAME   project package name"
 | 
				
			||||||
 | 
					            echo "-o, --output-dir=DIR      file output directory"
 | 
				
			||||||
 | 
					            echo "-m, --module=MOD          extra python module to interrogate for options"
 | 
				
			||||||
 | 
					            echo "-l, --library=LIB         extra library that registers options for discovery"
 | 
				
			||||||
 | 
					            exit 0
 | 
				
			||||||
 | 
					            ;;
 | 
				
			||||||
 | 
					        -b|--base-dir)
 | 
				
			||||||
 | 
					            shift
 | 
				
			||||||
 | 
					            BASEDIR=`echo $1 | sed -e 's/\/*$//g'`
 | 
				
			||||||
 | 
					            shift
 | 
				
			||||||
 | 
					            ;;
 | 
				
			||||||
 | 
					        -p|--package-name)
 | 
				
			||||||
 | 
					            shift
 | 
				
			||||||
 | 
					            PACKAGENAME=`echo $1`
 | 
				
			||||||
 | 
					            shift
 | 
				
			||||||
 | 
					            ;;
 | 
				
			||||||
 | 
					        -o|--output-dir)
 | 
				
			||||||
 | 
					            shift
 | 
				
			||||||
 | 
					            OUTPUTDIR=`echo $1 | sed -e 's/\/*$//g'`
 | 
				
			||||||
 | 
					            shift
 | 
				
			||||||
 | 
					            ;;
 | 
				
			||||||
 | 
					        -m|--module)
 | 
				
			||||||
 | 
					            shift
 | 
				
			||||||
 | 
					            MODULES="$MODULES -m $1"
 | 
				
			||||||
 | 
					            shift
 | 
				
			||||||
 | 
					            ;;
 | 
				
			||||||
 | 
					        -l|--library)
 | 
				
			||||||
 | 
					            shift
 | 
				
			||||||
 | 
					            LIBRARIES="$LIBRARIES -l $1"
 | 
				
			||||||
 | 
					            shift
 | 
				
			||||||
 | 
					            ;;
 | 
				
			||||||
 | 
					        --)
 | 
				
			||||||
 | 
					            break
 | 
				
			||||||
 | 
					            ;;
 | 
				
			||||||
 | 
					    esac
 | 
				
			||||||
 | 
					done
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					BASEDIR=${BASEDIR:-`pwd`}
 | 
				
			||||||
 | 
					if ! [ -d $BASEDIR ]
 | 
				
			||||||
 | 
					then
 | 
				
			||||||
 | 
					    echo "${0##*/}: missing project base directory" >&2 ; print_hint ; exit 1
 | 
				
			||||||
 | 
					elif [[ $BASEDIR != /* ]]
 | 
				
			||||||
 | 
					then
 | 
				
			||||||
 | 
					    BASEDIR=$(cd "$BASEDIR" && pwd)
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PACKAGENAME=${PACKAGENAME:-$(python setup.py --name)}
 | 
				
			||||||
 | 
					TARGETDIR=$BASEDIR/$PACKAGENAME
 | 
				
			||||||
 | 
					if ! [ -d $TARGETDIR ]
 | 
				
			||||||
 | 
					then
 | 
				
			||||||
 | 
					    echo "${0##*/}: invalid project package name" >&2 ; print_hint ; exit 1
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					OUTPUTDIR=${OUTPUTDIR:-$BASEDIR/etc}
 | 
				
			||||||
 | 
					# NOTE(bnemec): Some projects put their sample config in etc/,
 | 
				
			||||||
 | 
					#               some in etc/$PACKAGENAME/
 | 
				
			||||||
 | 
					if [ -d $OUTPUTDIR/$PACKAGENAME ]
 | 
				
			||||||
 | 
					then
 | 
				
			||||||
 | 
					    OUTPUTDIR=$OUTPUTDIR/$PACKAGENAME
 | 
				
			||||||
 | 
					elif ! [ -d $OUTPUTDIR ]
 | 
				
			||||||
 | 
					then
 | 
				
			||||||
 | 
					    echo "${0##*/}: cannot access \`$OUTPUTDIR': No such file or directory" >&2
 | 
				
			||||||
 | 
					    exit 1
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					BASEDIRESC=`echo $BASEDIR | sed -e 's/\//\\\\\//g'`
 | 
				
			||||||
 | 
					find $TARGETDIR -type f -name "*.pyc" -delete
 | 
				
			||||||
 | 
					FILES=$(find $TARGETDIR -type f -name "*.py" ! -path "*/tests/*" \
 | 
				
			||||||
 | 
					        -exec grep -l "Opt(" {} + | sed -e "s/^$BASEDIRESC\///g" | sort -u)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RC_FILE="`dirname $0`/oslo.config.generator.rc"
 | 
				
			||||||
 | 
					if test -r "$RC_FILE"
 | 
				
			||||||
 | 
					then
 | 
				
			||||||
 | 
					    source "$RC_FILE"
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					for filename in ${CLOUDKITTY_CONFIG_GENERATOR_EXCLUDED_FILES}; do
 | 
				
			||||||
 | 
					    FILES="${FILES[@]/$filename/}"
 | 
				
			||||||
 | 
					done
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					for mod in ${CLOUDKITTY_CONFIG_GENERATOR_EXTRA_MODULES}; do
 | 
				
			||||||
 | 
					    MODULES="$MODULES -m $mod"
 | 
				
			||||||
 | 
					done
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					for lib in ${CLOUDKITTY_CONFIG_GENERATOR_EXTRA_LIBRARIES}; do
 | 
				
			||||||
 | 
					    LIBRARIES="$LIBRARIES -l $lib"
 | 
				
			||||||
 | 
					done
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export EVENTLET_NO_GREENDNS=yes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					OS_VARS=$(set | sed -n '/^OS_/s/=[^=]*$//gp' | xargs)
 | 
				
			||||||
 | 
					[ "$OS_VARS" ] && eval "unset \$OS_VARS"
 | 
				
			||||||
 | 
					DEFAULT_MODULEPATH=cloudkitty.openstack.common.config.generator
 | 
				
			||||||
 | 
					MODULEPATH=${MODULEPATH:-$DEFAULT_MODULEPATH}
 | 
				
			||||||
 | 
					OUTPUTFILE=$OUTPUTDIR/$PACKAGENAME.conf.sample
 | 
				
			||||||
 | 
					python -m $MODULEPATH $MODULES $LIBRARIES $FILES > $OUTPUTFILE
 | 
				
			||||||
 | 
					if [ $? != 0 ]
 | 
				
			||||||
 | 
					then
 | 
				
			||||||
 | 
					    echo "Can not generate $OUTPUTFILE"
 | 
				
			||||||
 | 
					    exit 1
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Hook to allow projects to append custom config file snippets
 | 
				
			||||||
 | 
					CONCAT_FILES=$(ls $BASEDIR/tools/config/*.conf.sample 2>/dev/null)
 | 
				
			||||||
 | 
					for CONCAT_FILE in $CONCAT_FILES; do
 | 
				
			||||||
 | 
					    cat $CONCAT_FILE >> $OUTPUTFILE
 | 
				
			||||||
 | 
					done
 | 
				
			||||||
							
								
								
									
										6
									
								
								tox.ini
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								tox.ini
									
									
									
									
									
								
							@@ -19,9 +19,13 @@ downloadcache = ~/cache/pip
 | 
				
			|||||||
[testenv:pep8]
 | 
					[testenv:pep8]
 | 
				
			||||||
commands =
 | 
					commands =
 | 
				
			||||||
  flake8 {posargs} cloudkitty
 | 
					  flake8 {posargs} cloudkitty
 | 
				
			||||||
 | 
					  {toxinidir}/tools/config/check_uptodate.sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[testenv:docs]
 | 
				
			||||||
 | 
					commands = python setup.py build_sphinx
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[testenv:venv]
 | 
					[testenv:venv]
 | 
				
			||||||
commands = {posargs}
 | 
					commands = {posargs}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[flake8]
 | 
					[flake8]
 | 
				
			||||||
exclude = .git,.venv,.tox,dist,doc,*egg,build,./cloudkitty/openstack/common
 | 
					exclude = .git,.venv,.tox,dist,doc,*egg,build,.ropeproject,./cloudkitty/openstack/common,*/alembic/versions/*
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user