Implements the blueprint use-common-cfg for the quantum service.
More specifically uses global CONF for the quantum.conf file.
Added support for the RYU plugin (similar to ovs and lb,
which use non-global conf for plugins)
patch 27: clean up find_config_file
patch 28: for config file use old paths (plugin unit tests)
this hopefully will be replaced when we move to common
config file
patch 30: rebase and merge (utils.py and policy.py)
Change-Id: Ic0bf5bdd44f24a557240f7afe4e070dee448c63c
This commit is contained in:
@@ -20,325 +20,100 @@ Routines for configuring Quantum
|
||||
"""
|
||||
|
||||
import logging
|
||||
import logging.config
|
||||
import logging.handlers
|
||||
import optparse
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
|
||||
from paste import deploy
|
||||
|
||||
from quantum.common import flags
|
||||
from quantum.openstack.common import cfg
|
||||
from quantum.version import version_string
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
DEFAULT_LOG_FORMAT = "%(asctime)s %(levelname)8s [%(name)s] %(message)s"
|
||||
DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
bind_opts = [
|
||||
cfg.StrOpt('bind_host', default='0.0.0.0'),
|
||||
cfg.IntOpt('bind_port', default=9696),
|
||||
cfg.StrOpt('api_extensions_path', default=""),
|
||||
cfg.StrOpt('core_plugin',
|
||||
default='quantum.plugins.sample.SamplePlugin.FakePlugin'),
|
||||
]
|
||||
|
||||
# Register the configuration options
|
||||
cfg.CONF.register_opts(bind_opts)
|
||||
|
||||
|
||||
def parse_options(parser, cli_args=None):
|
||||
"""
|
||||
Returns the parsed CLI options, command to run and its arguments, merged
|
||||
with any same-named options found in a configuration file.
|
||||
|
||||
The function returns a tuple of (options, args), where options is a
|
||||
mapping of option key/str(value) pairs, and args is the set of arguments
|
||||
(not options) supplied on the command-line.
|
||||
|
||||
The reason that the option values are returned as strings only is that
|
||||
ConfigParser and paste.deploy only accept string values...
|
||||
|
||||
:param parser: The option parser
|
||||
:param cli_args: (Optional) Set of arguments to process. If not present,
|
||||
sys.argv[1:] is used.
|
||||
:retval tuple of (options, args)
|
||||
"""
|
||||
|
||||
(options, args) = parser.parse_args(cli_args)
|
||||
|
||||
return (vars(options), args)
|
||||
def parse(args):
|
||||
cfg.CONF(args=args, project='quantum',
|
||||
version='%%prog %s' % version_string())
|
||||
|
||||
|
||||
def add_common_options(parser):
|
||||
"""
|
||||
Given a supplied optparse.OptionParser, adds an OptionGroup that
|
||||
represents all common configuration options.
|
||||
|
||||
:param parser: optparse.OptionParser
|
||||
"""
|
||||
help_text = "The following configuration options are common to "\
|
||||
"all quantum programs."
|
||||
|
||||
group = optparse.OptionGroup(parser, "Common Options", help_text)
|
||||
group.add_option('-v', '--verbose', default=False, dest="verbose",
|
||||
action="store_true",
|
||||
help="Print more verbose output")
|
||||
group.add_option('-d', '--debug', default=False, dest="debug",
|
||||
action="store_true",
|
||||
help="Print debugging output")
|
||||
group.add_option('--config-file', default=None, metavar="PATH",
|
||||
help="Path to the config file to use. When not specified "
|
||||
"(the default), we generally look at the first "
|
||||
"argument specified to be a config file, and if "
|
||||
"that is also missing, we search standard "
|
||||
"directories for a config file.")
|
||||
parser.add_option_group(group)
|
||||
|
||||
|
||||
def add_log_options(parser):
|
||||
"""
|
||||
Given a supplied optparse.OptionParser, adds an OptionGroup that
|
||||
represents all the configuration options around logging.
|
||||
|
||||
:param parser: optparse.OptionParser
|
||||
"""
|
||||
help_text = "The following configuration options are specific to logging "\
|
||||
"functionality for this program."
|
||||
|
||||
group = optparse.OptionGroup(parser, "Logging Options", help_text)
|
||||
group.add_option('--log-config', default=None, metavar="PATH",
|
||||
help="If this option is specified, the logging "
|
||||
"configuration file specified is used and overrides "
|
||||
"any other logging options specified. Please see "
|
||||
"the Python logging module documentation for "
|
||||
"details on logging configuration files.")
|
||||
group.add_option('--log-date-format', metavar="FORMAT",
|
||||
default=DEFAULT_LOG_DATE_FORMAT,
|
||||
help="Format string for %(asctime)s in log records. "
|
||||
"Default: %default")
|
||||
group.add_option('--use-syslog', default=False,
|
||||
action="store_true",
|
||||
help="Output logs to syslog.")
|
||||
group.add_option('--log-file', default=None, metavar="PATH",
|
||||
help="(Optional) Name of log file to output to. "
|
||||
"If not set, logging will go to stdout.")
|
||||
group.add_option("--log-dir", default=None,
|
||||
help="(Optional) The directory to keep log files in "
|
||||
"(will be prepended to --logfile)")
|
||||
parser.add_option_group(group)
|
||||
|
||||
|
||||
def setup_logging(options, conf):
|
||||
def setup_logging(conf):
|
||||
"""
|
||||
Sets up the logging options for a log with supplied name
|
||||
|
||||
:param options: Mapping of typed option key/values
|
||||
:param conf: Mapping of untyped key/values from config file
|
||||
:param conf: a cfg.ConfOpts object
|
||||
"""
|
||||
|
||||
if options.get('log_config', None):
|
||||
if conf.log_config:
|
||||
# Use a logging configuration file for all settings...
|
||||
if os.path.exists(options['log_config']):
|
||||
logging.config.fileConfig(options['log_config'])
|
||||
if os.path.exists(conf.log_config):
|
||||
logging.config.fileConfig(conf.log_config)
|
||||
return
|
||||
else:
|
||||
raise RuntimeError("Unable to locate specified logging "
|
||||
"config file: %s" % options['log_config'])
|
||||
"config file: %s" % conf.log_config)
|
||||
|
||||
# If either the CLI option or the conf value
|
||||
# is True, we set to True
|
||||
debug = (options.get('debug') or
|
||||
get_option(conf, 'debug', type='bool', default=False))
|
||||
verbose = (options.get('verbose') or
|
||||
get_option(conf, 'verbose', type='bool', default=False))
|
||||
root_logger = logging.root
|
||||
if debug:
|
||||
if conf.debug:
|
||||
root_logger.setLevel(logging.DEBUG)
|
||||
elif verbose:
|
||||
elif conf.verbose:
|
||||
root_logger.setLevel(logging.INFO)
|
||||
else:
|
||||
root_logger.setLevel(logging.WARNING)
|
||||
|
||||
# Set log configuration from options...
|
||||
# Note that we use a hard-coded log format in the options
|
||||
# because of Paste.Deploy bug #379
|
||||
# http://trac.pythonpaste.org/pythonpaste/ticket/379
|
||||
log_format = options.get('log_format', DEFAULT_LOG_FORMAT)
|
||||
log_date_format = options.get('log_date_format', DEFAULT_LOG_DATE_FORMAT)
|
||||
formatter = logging.Formatter(log_format, log_date_format)
|
||||
formatter = logging.Formatter(conf.log_format, conf.log_date_format)
|
||||
|
||||
syslog = options.get('use_syslog')
|
||||
if not syslog:
|
||||
syslog = conf.get('use_syslog')
|
||||
|
||||
if syslog:
|
||||
SysLogHandler = logging.handlers.SysLogHandler
|
||||
if conf.use_syslog:
|
||||
try:
|
||||
handler = SysLogHandler(address='/dev/log',
|
||||
facility=SysLogHandler.LOG_SYSLOG)
|
||||
except socket.error:
|
||||
handler = SysLogHandler(address='/var/run/syslog',
|
||||
facility=SysLogHandler.LOG_SYSLOG)
|
||||
handler.setFormatter(formatter)
|
||||
root_logger.addHandler(handler)
|
||||
facility = getattr(logging.handlers.SysLogHandler,
|
||||
conf.syslog_log_facility)
|
||||
except AttributeError:
|
||||
raise ValueError(_("Invalid syslog facility"))
|
||||
|
||||
logfile = options.get('log_file')
|
||||
if not logfile:
|
||||
logfile = conf.get('log_file')
|
||||
|
||||
if logfile:
|
||||
logdir = options.get('log_dir')
|
||||
if not logdir:
|
||||
logdir = conf.get('log_dir')
|
||||
if logdir:
|
||||
logfile = os.path.join(logdir, logfile)
|
||||
logfile = logging.FileHandler(logfile)
|
||||
logfile.setFormatter(formatter)
|
||||
logfile.setFormatter(formatter)
|
||||
root_logger.addHandler(logfile)
|
||||
handler = logging.handlers.SysLogHandler(address='/dev/log',
|
||||
facility=facility)
|
||||
elif conf.log_file:
|
||||
logfile = conf.log_file
|
||||
if conf.log_dir:
|
||||
logfile = os.path.join(conf.log_dir, logfile)
|
||||
handler = logging.handlers.WatchedFileHandler(logfile)
|
||||
else:
|
||||
handler = logging.StreamHandler(sys.stdout)
|
||||
handler.setFormatter(formatter)
|
||||
root_logger.addHandler(handler)
|
||||
|
||||
handler.setFormatter(formatter)
|
||||
root_logger.addHandler(handler)
|
||||
|
||||
|
||||
def find_config_file(options, args, config_file='quantum.conf'):
|
||||
"""
|
||||
Return the first config file found.
|
||||
|
||||
We search for the paste config file in the following order:
|
||||
* If --config-file option is used, use that
|
||||
* If args[0] is a file, use that
|
||||
* Search for the configuration file in standard directories:
|
||||
* .
|
||||
* ~.quantum/
|
||||
* ~
|
||||
* $FLAGS.state_path/etc/quantum
|
||||
* $FLAGS.state_path/etc
|
||||
|
||||
:retval Full path to config file, or None if no config file found
|
||||
"""
|
||||
|
||||
fix_path = lambda p: os.path.abspath(os.path.expanduser(p))
|
||||
if options.get('config_file'):
|
||||
if os.path.exists(options['config_file']):
|
||||
return fix_path(options['config_file'])
|
||||
elif args:
|
||||
if os.path.exists(args[0]):
|
||||
return fix_path(args[0])
|
||||
|
||||
dir_to_common = os.path.dirname(os.path.abspath(__file__))
|
||||
root = os.path.join(dir_to_common, '..', '..', '..', '..')
|
||||
# Handle standard directory search for the config file
|
||||
config_file_dirs = [fix_path(os.path.join(os.getcwd(), 'etc')),
|
||||
fix_path(os.path.join('~', '.quantum-venv', 'etc',
|
||||
'quantum')),
|
||||
fix_path('~'),
|
||||
os.path.join(FLAGS.state_path, 'etc'),
|
||||
os.path.join(FLAGS.state_path, 'etc', 'quantum'),
|
||||
fix_path(os.path.join('~', '.local',
|
||||
'etc', 'quantum')),
|
||||
'/usr/etc/quantum',
|
||||
'/usr/local/etc/quantum',
|
||||
'/etc/quantum/',
|
||||
'/etc']
|
||||
|
||||
if 'plugin' in options:
|
||||
config_file_dirs = [os.path.join(x, 'quantum',
|
||||
'plugins', options['plugin'])
|
||||
for x in config_file_dirs]
|
||||
|
||||
if os.path.exists(os.path.join(root, 'plugins')):
|
||||
plugins = [fix_path(os.path.join(root, 'plugins', p, 'etc'))
|
||||
for p in os.listdir(os.path.join(root, 'plugins'))]
|
||||
plugins = [p for p in plugins if os.path.isdir(p)]
|
||||
config_file_dirs.extend(plugins)
|
||||
|
||||
for cfg_dir in config_file_dirs:
|
||||
cfg_file = os.path.join(cfg_dir, config_file)
|
||||
if os.path.exists(cfg_file):
|
||||
return cfg_file
|
||||
|
||||
|
||||
def load_paste_config(app_name, options, args):
|
||||
"""
|
||||
Looks for a config file to use for an app and returns the
|
||||
config file path and a configuration mapping from a paste config file.
|
||||
|
||||
We search for the paste config file in the following order:
|
||||
* If --config-file option is used, use that
|
||||
* If args[0] is a file, use that
|
||||
* Search for quantum.conf in standard directories:
|
||||
* .
|
||||
* ~.quantum/
|
||||
* ~
|
||||
* /etc/quantum
|
||||
* /etc
|
||||
|
||||
:param app_name: Name of the application to load config for, or None.
|
||||
None signifies to only load the [DEFAULT] section of
|
||||
the config file.
|
||||
:param options: Set of typed options returned from parse_options()
|
||||
:param args: Command line arguments from argv[1:]
|
||||
:retval Tuple of (conf_file, conf)
|
||||
|
||||
:raises RuntimeError when config file cannot be located or there was a
|
||||
problem loading the configuration file.
|
||||
"""
|
||||
conf_file = find_config_file(options, args)
|
||||
if not conf_file:
|
||||
raise RuntimeError("Unable to locate any configuration file. "
|
||||
"Cannot load application %s" % app_name)
|
||||
try:
|
||||
conf = deploy.appconfig("config:%s" % conf_file, name=app_name)
|
||||
return conf_file, conf
|
||||
except Exception, e:
|
||||
raise RuntimeError("Error trying to load config %s: %s"
|
||||
% (conf_file, e))
|
||||
|
||||
|
||||
def load_paste_app(app_name, options, args):
|
||||
def load_paste_app(app_name, config_file):
|
||||
"""
|
||||
Builds and returns a WSGI app from a paste config file.
|
||||
|
||||
We search for the paste config file in the following order:
|
||||
* If --config-file option is used, use that
|
||||
* If args[0] is a file, use that
|
||||
* Search for quantum.conf in standard directories:
|
||||
* .
|
||||
* ~.quantum/
|
||||
* ~
|
||||
* /etc/quantum
|
||||
* /etc
|
||||
|
||||
:param app_name: Name of the application to load
|
||||
:param options: Set of typed options returned from parse_options()
|
||||
:param args: Command line arguments from argv[1:]
|
||||
|
||||
:param config_file: name of the configuration file
|
||||
:raises RuntimeError when config file cannot be located or application
|
||||
cannot be loaded from config file
|
||||
"""
|
||||
conf_file, conf = load_paste_config(app_name, options, args)
|
||||
|
||||
config_path = os.path.abspath(cfg.CONF.find_file(config_file))
|
||||
LOG.info("Config paste file: %s", config_path)
|
||||
|
||||
try:
|
||||
app = deploy.loadapp("config:%s" % conf_file, name=app_name)
|
||||
app = deploy.loadapp("config:%s" % config_path, name=app_name)
|
||||
except (LookupError, ImportError):
|
||||
msg = ("Unable to load %(app_name)s from "
|
||||
"configuration file %(conf_file)s.") % locals()
|
||||
"configuration file %(config_path)s.") % locals()
|
||||
LOG.exception(msg)
|
||||
raise RuntimeError(msg)
|
||||
return conf, app
|
||||
|
||||
|
||||
def get_option(options, option, **kwargs):
|
||||
if option in options:
|
||||
value = options[option]
|
||||
type_ = kwargs.get('type', 'str')
|
||||
if type_ == 'bool':
|
||||
if hasattr(value, 'lower'):
|
||||
return value.lower() == 'true'
|
||||
else:
|
||||
return value
|
||||
elif type_ == 'int':
|
||||
return int(value)
|
||||
elif type_ == 'float':
|
||||
return float(value)
|
||||
else:
|
||||
return value
|
||||
elif 'default' in kwargs:
|
||||
return kwargs['default']
|
||||
else:
|
||||
raise KeyError("option '%s' not found" % option)
|
||||
return app
|
||||
|
||||
@@ -21,7 +21,8 @@
|
||||
"""Utilities and helper functions."""
|
||||
|
||||
|
||||
import ConfigParser
|
||||
import datetime
|
||||
import inspect
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
@@ -75,12 +76,6 @@ def execute(cmd, process_input=None, addl_env=None, check_exit_code=True):
|
||||
return result
|
||||
|
||||
|
||||
def get_plugin_from_config(file="config.ini"):
|
||||
Config = ConfigParser.ConfigParser()
|
||||
Config.read(file)
|
||||
return Config.get("PLUGIN", "provider")
|
||||
|
||||
|
||||
class LazyPluggable(object):
|
||||
"""A pluggable backend loaded lazily based on some value."""
|
||||
|
||||
@@ -110,3 +105,51 @@ class LazyPluggable(object):
|
||||
def __getattr__(self, key):
|
||||
backend = self.__get_backend()
|
||||
return getattr(backend, key)
|
||||
|
||||
|
||||
def find_config_file(options, config_file):
|
||||
"""
|
||||
Return the first config file found.
|
||||
|
||||
We search for the paste config file in the following order:
|
||||
* If --config-file option is used, use that
|
||||
* Search for the configuration files via common cfg directories
|
||||
:retval Full path to config file, or None if no config file found
|
||||
"""
|
||||
fix_path = lambda p: os.path.abspath(os.path.expanduser(p))
|
||||
if options.get('config_file'):
|
||||
if os.path.exists(options['config_file']):
|
||||
return fix_path(options['config_file'])
|
||||
|
||||
dir_to_common = os.path.dirname(os.path.abspath(__file__))
|
||||
root = os.path.join(dir_to_common, '..', '..', '..', '..')
|
||||
# Handle standard directory search for the config file
|
||||
config_file_dirs = [fix_path(os.path.join(os.getcwd(), 'etc')),
|
||||
fix_path(os.path.join('~', '.quantum-venv', 'etc',
|
||||
'quantum')),
|
||||
fix_path('~'),
|
||||
os.path.join(FLAGS.state_path, 'etc'),
|
||||
os.path.join(FLAGS.state_path, 'etc', 'quantum'),
|
||||
fix_path(os.path.join('~', '.local',
|
||||
'etc', 'quantum')),
|
||||
'/usr/etc/quantum',
|
||||
'/usr/local/etc/quantum',
|
||||
'/etc/quantum/',
|
||||
'/etc']
|
||||
|
||||
if 'plugin' in options:
|
||||
config_file_dirs = [
|
||||
os.path.join(x, 'quantum', 'plugins', options['plugin'])
|
||||
for x in config_file_dirs
|
||||
]
|
||||
|
||||
if os.path.exists(os.path.join(root, 'plugins')):
|
||||
plugins = [fix_path(os.path.join(root, 'plugins', p, 'etc'))
|
||||
for p in os.listdir(os.path.join(root, 'plugins'))]
|
||||
plugins = [p for p in plugins if os.path.isdir(p)]
|
||||
config_file_dirs.extend(plugins)
|
||||
|
||||
for cfg_dir in config_file_dirs:
|
||||
cfg_file = os.path.join(cfg_dir, config_file)
|
||||
if os.path.exists(cfg_file):
|
||||
return cfg_file
|
||||
|
||||
Reference in New Issue
Block a user