diff --git a/etc/plugins.ini b/etc/plugins.ini deleted file mode 100644 index 49c660249d..0000000000 --- a/etc/plugins.ini +++ /dev/null @@ -1,3 +0,0 @@ -[PLUGIN] -# Quantum plugin provider module -provider = quantum.plugins.sample.SamplePlugin.FakePlugin diff --git a/etc/quantum.conf b/etc/quantum.conf index b19442865f..b96d0ef721 100644 --- a/etc/quantum.conf +++ b/etc/quantum.conf @@ -18,6 +18,9 @@ bind_port = 9696 # extensions are in there you don't need to specify them here api_extensions_path = +# Quantum plugin provider module +core_plugin = quantum.plugins.sample.SamplePlugin.FakePlugin + [composite:quantum] use = egg:Paste#urlmap /: quantumversions diff --git a/etc/quantum/plugins/ryu/ryu.ini b/etc/quantum/plugins/ryu/ryu.ini index 2d5a2c5d7c..a90e8466b9 100644 --- a/etc/quantum/plugins/ryu/ryu.ini +++ b/etc/quantum/plugins/ryu/ryu.ini @@ -5,12 +5,12 @@ sql_connection = sqlite:// [OVS] -integration-bridge = br-int +integration_bridge = br-int -# openflow-controller = : -# openflow-rest-api = : -openflow-controller = 127.0.0.1:6633 -openflow-rest-api = 127.0.0.1:8080 +# openflow_controller = : +# openflow_rest_api = : +openflow_controller = 127.0.0.1:6633 +openflow_rest_api = 127.0.0.1:8080 [AGENT] # Change to "sudo quantum-rootwrap" to limit commands that can be run diff --git a/quantum/api/__init__.py b/quantum/api/__init__.py index aa5a8791b1..813ff40ec4 100644 --- a/quantum/api/__init__.py +++ b/quantum/api/__init__.py @@ -42,23 +42,23 @@ class APIRouter(wsgi.Router): """ _version = None - def __init__(self, options=None): + def __init__(self): mapper = self._mapper() - self._setup_routes(mapper, options) + self._setup_routes(mapper) super(APIRouter, self).__init__(mapper) def _mapper(self): return routes.Mapper() - def _setup_routes(self, mapper, options): - self._setup_base_routes(mapper, options, self._version) + def _setup_routes(self, mapper): + self._setup_base_routes(mapper, self._version) - def _setup_base_routes(self, mapper, options, version): + def _setup_base_routes(self, mapper, version): """Routes common to all versions.""" # Loads the quantum plugin # Note(salvatore-orlando): Should the plugin be versioned # I don't think so - plugin = manager.QuantumManager.get_plugin(options) + plugin = manager.QuantumManager.get_plugin() uri_prefix = '/tenants/{tenant_id}/' attachment_path = ( diff --git a/quantum/api/v2/base.py b/quantum/api/v2/base.py index fbd92468c1..08148087b7 100644 --- a/quantum/api/v2/base.py +++ b/quantum/api/v2/base.py @@ -293,7 +293,7 @@ class Controller(object): return body -def create_resource(collection, resource, plugin, conf, params): +def create_resource(collection, resource, plugin, params): controller = Controller(plugin, collection, resource, params) # NOTE(jkoelker) To anyone wishing to add "proper" xml support diff --git a/quantum/api/v2/router.py b/quantum/api/v2/router.py index 04b2ff7f60..0b2d7a6f8c 100644 --- a/quantum/api/v2/router.py +++ b/quantum/api/v2/router.py @@ -24,6 +24,7 @@ import webob.exc from quantum import manager from quantum import wsgi from quantum.api.v2 import base +from quantum.openstack.common import cfg LOG = logging.getLogger(__name__) @@ -118,16 +119,14 @@ class APIRouter(wsgi.Router): @classmethod def factory(cls, global_config, **local_config): - return cls(global_config, **local_config) + return cls(**local_config) - def __init__(self, conf, **local_config): + def __init__(self, **local_config): mapper = routes_mapper.Mapper() - plugin_provider = manager.get_plugin_provider(conf) + plugin_provider = cfg.CONF.core_plugin + LOG.debug("Plugin location:%s", plugin_provider) plugin = manager.get_plugin(plugin_provider) - # NOTE(jkoelker) Merge local_conf into conf after the plugin - # is discovered - conf.update(local_config) col_kwargs = dict(collection_actions=COLLECTION_ACTIONS, member_actions=MEMBER_ACTIONS) @@ -137,8 +136,7 @@ class APIRouter(wsgi.Router): def _map_resource(collection, resource, params): controller = base.create_resource(collection, resource, - plugin, conf, - params) + plugin, params) mapper_kwargs = dict(controller=controller, requirements=REQUIREMENTS, **col_kwargs) diff --git a/quantum/common/config.py b/quantum/common/config.py index f426b4594a..1e608c013e 100644 --- a/quantum/common/config.py +++ b/quantum/common/config.py @@ -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 diff --git a/quantum/common/utils.py b/quantum/common/utils.py index 4a355bdf92..4ac2c328be 100644 --- a/quantum/common/utils.py +++ b/quantum/common/utils.py @@ -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 diff --git a/quantum/extensions/extensions.py b/quantum/extensions/extensions.py index 26aebc246e..1f5dc5d4ca 100644 --- a/quantum/extensions/extensions.py +++ b/quantum/extensions/extensions.py @@ -29,6 +29,7 @@ import webob.exc from quantum.common import exceptions import quantum.extensions from quantum.manager import QuantumManager +from quantum.openstack.common import cfg from quantum import wsgi @@ -217,12 +218,12 @@ class ExtensionController(wsgi.Controller): class ExtensionMiddleware(wsgi.Middleware): """Extensions middleware for WSGI.""" - def __init__(self, application, config_params, + def __init__(self, application, ext_mgr=None): self.ext_mgr = (ext_mgr or ExtensionManager( - get_extensions_path(config_params))) + get_extensions_path())) mapper = routes.Mapper() # extended resources @@ -339,10 +340,10 @@ class ExtensionMiddleware(wsgi.Middleware): def plugin_aware_extension_middleware_factory(global_config, **local_config): """Paste factory.""" def _factory(app): - extensions_path = get_extensions_path(global_config) + extensions_path = get_extensions_path() ext_mgr = PluginAwareExtensionManager(extensions_path, QuantumManager.get_plugin()) - return ExtensionMiddleware(app, global_config, ext_mgr=ext_mgr) + return ExtensionMiddleware(app, ext_mgr=ext_mgr) return _factory @@ -539,9 +540,9 @@ class ResourceExtension(object): # Returns the extention paths from a config entry and the __path__ # of quantum.extensions -def get_extensions_path(config=None): +def get_extensions_path(): paths = ':'.join(quantum.extensions.__path__) - if config: - paths = ':'.join([config.get('api_extensions_path', ''), paths]) + if cfg.CONF.api_extensions_path: + paths = ':'.join([cfg.CONF.api_extensions_path, paths]) return paths diff --git a/quantum/manager.py b/quantum/manager.py index 1f5f4f8f87..2d833c30e1 100644 --- a/quantum/manager.py +++ b/quantum/manager.py @@ -24,27 +24,15 @@ The caller should make sure that QuantumManager is a singleton. """ import logging -import os -from quantum.common import utils -from quantum.common.config import find_config_file from quantum.common.exceptions import ClassNotFound +from quantum.openstack.common import cfg from quantum.openstack.common import importutils LOG = logging.getLogger(__name__) -CONFIG_FILE = "plugins.ini" - - -def find_config(basepath): - for root, dirs, files in os.walk(basepath): - if CONFIG_FILE in files: - return os.path.join(root, CONFIG_FILE) - return None - - def get_plugin(plugin_provider): # If the plugin can't be found let them know gracefully try: @@ -58,16 +46,6 @@ def get_plugin(plugin_provider): return plugin_klass() -def get_plugin_provider(options, config_file=None): - if config_file: - config_file = [config_file] - - if not 'plugin_provider' in options: - cf = find_config_file(options, config_file, CONFIG_FILE) - options['plugin_provider'] = utils.get_plugin_from_config(cf) - return options['plugin_provider'] - - class QuantumManager(object): _instance = None @@ -81,12 +59,12 @@ class QuantumManager(object): # breaks tach monitoring. It has been removed # intentianally to allow v2 plugins to be monitored # for performance metrics. - plugin_provider = get_plugin_provider(options, config_file) + plugin_provider = cfg.CONF.core_plugin LOG.debug("Plugin location:%s", plugin_provider) self.plugin = get_plugin(plugin_provider) @classmethod - def get_plugin(cls, options=None, config_file=None): + def get_plugin(cls): if cls._instance is None: - cls._instance = cls(options, config_file) + cls._instance = cls() return cls._instance.plugin diff --git a/quantum/openstack/common/cfg.py b/quantum/openstack/common/cfg.py index 80345a6d2a..b1493a31c0 100644 --- a/quantum/openstack/common/cfg.py +++ b/quantum/openstack/common/cfg.py @@ -95,7 +95,7 @@ and --config-dir:: class ConfigOpts(object): - def __init__(self, ...): + def __call__(self, ...): opts = [ MultiStrOpt('config-file', @@ -233,6 +233,22 @@ log files: ... ] +This module also contains a global instance of the CommonConfigOpts class +in order to support a common usage pattern in OpenStack: + + from openstack.common import cfg + + opts = [ + cfg.StrOpt('bind_host' default='0.0.0.0'), + cfg.IntOpt('bind_port', default=9292), + ] + + CONF = cfg.CONF + CONF.register_opts(opts) + + def start(server, app): + server.start(app, CONF.bind_port, CONF.bind_host) + """ import collections @@ -478,7 +494,8 @@ class Opt(object): multi = False def __init__(self, name, dest=None, short=None, default=None, - metavar=None, help=None, secret=False, required=False): + metavar=None, help=None, secret=False, required=False, + deprecated_name=None): """Construct an Opt object. The only required parameter is the option's name. However, it is @@ -492,6 +509,7 @@ class Opt(object): :param help: an explanation of how the option is used :param secret: true iff the value should be obfuscated in log output :param required: true iff a value must be supplied for this option + :param deprecated_name: deprecated name option. Acts like an alias """ self.name = name if dest is None: @@ -504,6 +522,10 @@ class Opt(object): self.help = help self.secret = secret self.required = required + if deprecated_name is not None: + self.deprecated_name = deprecated_name.replace('-', '_') + else: + self.deprecated_name = None def _get_from_config_parser(self, cparser, section): """Retrieves the option value from a MultiConfigParser object. @@ -515,7 +537,13 @@ class Opt(object): :param cparser: a ConfigParser object :param section: a section name """ - return cparser.get(section, self.dest) + return self._cparser_get_with_deprecated(cparser, section) + + def _cparser_get_with_deprecated(self, cparser, section): + """If cannot find option as dest try deprecated_name alias.""" + if self.deprecated_name is not None: + return cparser.get(section, [self.dest, self.deprecated_name]) + return cparser.get(section, [self.dest]) def _add_to_cli(self, parser, group=None): """Makes the option available in the command line interface. @@ -530,9 +558,11 @@ class Opt(object): container = self._get_optparse_container(parser, group) kwargs = self._get_optparse_kwargs(group) prefix = self._get_optparse_prefix('', group) - self._add_to_optparse(container, self.name, self.short, kwargs, prefix) + self._add_to_optparse(container, self.name, self.short, kwargs, prefix, + self.deprecated_name) - def _add_to_optparse(self, container, name, short, kwargs, prefix=''): + def _add_to_optparse(self, container, name, short, kwargs, prefix='', + deprecated_name=None): """Add an option to an optparse parser or group. :param container: an optparse.OptionContainer object @@ -545,6 +575,8 @@ class Opt(object): args = ['--' + prefix + name] if short: args += ['-' + short] + if deprecated_name: + args += ['--' + prefix + deprecated_name] for a in args: if container.has_option(a): raise DuplicateOptError(a) @@ -577,7 +609,7 @@ class Opt(object): dest = group.name + '_' + dest kwargs.update({'dest': dest, 'metavar': self.metavar, - 'help': self.help}) + 'help': self.help, }) return kwargs def _get_optparse_prefix(self, prefix, group): @@ -627,7 +659,8 @@ class BoolOpt(Opt): return value - return [convert_bool(v) for v in cparser.get(section, self.dest)] + return [convert_bool(v) for v in + self._cparser_get_with_deprecated(cparser, section)] def _add_to_cli(self, parser, group=None): """Extends the base class method to add the --nooptname option.""" @@ -640,7 +673,8 @@ class BoolOpt(Opt): kwargs = self._get_optparse_kwargs(group, action='store_false') prefix = self._get_optparse_prefix('no', group) kwargs["help"] = "The inverse of --" + self.name - self._add_to_optparse(container, self.name, None, kwargs, prefix) + self._add_to_optparse(container, self.name, None, kwargs, prefix, + self.deprecated_name) def _get_optparse_kwargs(self, group, action='store_true', **kwargs): """Extends the base optparse keyword dict for boolean options.""" @@ -654,7 +688,8 @@ class IntOpt(Opt): def _get_from_config_parser(self, cparser, section): """Retrieve the opt value as a integer from ConfigParser.""" - return [int(v) for v in cparser.get(section, self.dest)] + return [int(v) for v in self._cparser_get_with_deprecated(cparser, + section)] def _get_optparse_kwargs(self, group, **kwargs): """Extends the base optparse keyword dict for integer options.""" @@ -668,7 +703,8 @@ class FloatOpt(Opt): def _get_from_config_parser(self, cparser, section): """Retrieve the opt value as a float from ConfigParser.""" - return [float(v) for v in cparser.get(section, self.dest)] + return [float(v) for v in + self._cparser_get_with_deprecated(cparser, section)] def _get_optparse_kwargs(self, group, **kwargs): """Extends the base optparse keyword dict for float options.""" @@ -685,7 +721,8 @@ class ListOpt(Opt): def _get_from_config_parser(self, cparser, section): """Retrieve the opt value as a list from ConfigParser.""" - return [v.split(',') for v in cparser.get(section, self.dest)] + return [v.split(',') for v in + self._cparser_get_with_deprecated(cparser, section)] def _get_optparse_kwargs(self, group, **kwargs): """Extends the base optparse keyword dict for list options.""" @@ -714,6 +751,13 @@ class MultiStrOpt(Opt): return super(MultiStrOpt, self)._get_optparse_kwargs(group, action='append') + def _cparser_get_with_deprecated(self, cparser, section): + """If cannot find option as dest try deprecated_name alias.""" + if self.deprecated_name is not None: + return cparser.get(section, [self.dest, self.deprecated_name], + multi=True) + return cparser.get(section, [self.dest], multi=True) + class OptGroup(object): @@ -766,6 +810,14 @@ class OptGroup(object): return True + def _unregister_opt(self, opt): + """Remove an opt from this group. + + :param opt: an Opt object + """ + if opt.dest in self._opts: + del self._opts[opt.dest] + def _get_optparse_group(self, parser): """Build an optparse.OptionGroup for this group.""" if self._optparse_group is None: @@ -773,6 +825,10 @@ class OptGroup(object): self.help) return self._optparse_group + def _clear(self): + """Clear this group's option parsing state.""" + self._optparse_group = None + class ParseError(iniparser.ParseError): def __init__(self, msg, lineno, line, filename): @@ -816,25 +872,38 @@ class ConfigParser(iniparser.BaseParser): class MultiConfigParser(object): def __init__(self): - self.sections = {} + self.parsed = [] def read(self, config_files): read_ok = [] for filename in config_files: - parser = ConfigParser(filename, self.sections) + sections = {} + parser = ConfigParser(filename, sections) try: parser.parse() except IOError: continue - + self.parsed.insert(0, sections) read_ok.append(filename) return read_ok - def get(self, section, name): - return self.sections[section][name] + def get(self, section, names, multi=False): + rvalue = [] + for sections in self.parsed: + if section not in sections: + continue + for name in names: + if name in sections[section]: + if multi: + rvalue = sections[section][name] + rvalue + else: + return sections[section][name] + if multi and rvalue != []: + return rvalue + raise KeyError class ConfigOpts(collections.Mapping): @@ -847,57 +916,41 @@ class ConfigOpts(collections.Mapping): the values of options. """ - def __init__(self, - project=None, - prog=None, - version=None, - usage=None, - default_config_files=None): - """Construct a ConfigOpts object. + def __init__(self): + """Construct a ConfigOpts object.""" + self._opts = {} # dict of dicts of (opt:, override:, default:) + self._groups = {} - Automatically registers the --config-file option with either a supplied - list of default config files, or a list from find_config_files(). + self._args = None + self._oparser = None + self._cparser = None + self._cli_values = {} + self.__cache = {} + self._config_opts = [] + self._disable_interspersed_args = False - :param project: the toplevel project name, used to locate config files - :param prog: the name of the program (defaults to sys.argv[0] basename) - :param version: the program version (for --version) - :param usage: a usage string (%prog will be expanded) - :param default_config_files: config files to use by default - """ + def _setup(self, project, prog, version, usage, default_config_files): + """Initialize a ConfigOpts object for option parsing.""" if prog is None: prog = os.path.basename(sys.argv[0]) if default_config_files is None: default_config_files = find_config_files(project, prog) - self.project = project - self.prog = prog - self.version = version - self.usage = usage - self.default_config_files = default_config_files + self._oparser = optparse.OptionParser(prog=prog, + version=version, + usage=usage) + if self._disable_interspersed_args: + self._oparser.disable_interspersed_args() - self._opts = {} # dict of dicts of (opt:, override:, default:) - self._groups = {} - - self._args = None - self._cli_values = {} - - self._oparser = optparse.OptionParser(prog=self.prog, - version=self.version, - usage=self.usage) - self._cparser = None - - self.__cache = {} - - opts = [ + self._config_opts = [ MultiStrOpt('config-file', - default=self.default_config_files, + default=default_config_files, metavar='PATH', help='Path to a config file to use. Multiple config ' 'files can be specified, with values in later ' 'files taking precedence. The default files ' - ' used are: %s' % - (self.default_config_files)), + ' used are: %s' % (default_config_files, )), StrOpt('config-dir', metavar='DIR', help='Path to a config directory to pull *.conf ' @@ -908,7 +961,13 @@ class ConfigOpts(collections.Mapping): 'hence over-ridden options in the directory take ' 'precedence.'), ] - self.register_cli_opts(opts) + self.register_cli_opts(self._config_opts) + + self.project = project + self.prog = prog + self.version = version + self.usage = usage + self.default_config_files = default_config_files def __clear_cache(f): @functools.wraps(f) @@ -919,7 +978,13 @@ class ConfigOpts(collections.Mapping): return __inner - def __call__(self, args=None): + def __call__(self, + args=None, + project=None, + prog=None, + version=None, + usage=None, + default_config_files=None): """Parse command line arguments and config files. Calling a ConfigOpts object causes the supplied command line arguments @@ -929,35 +994,34 @@ class ConfigOpts(collections.Mapping): The object may be called multiple times, each time causing the previous set of values to be overwritten. + Automatically registers the --config-file option with either a supplied + list of default config files, or a list from find_config_files(). + If the --config-dir option is set, any *.conf files from this directory are pulled in, after all the file(s) specified by the --config-file option. - :params args: command line arguments (defaults to sys.argv[1:]) + :param args: command line arguments (defaults to sys.argv[1:]) + :param project: the toplevel project name, used to locate config files + :param prog: the name of the program (defaults to sys.argv[0] basename) + :param version: the program version (for --version) + :param usage: a usage string (%prog will be expanded) + :param default_config_files: config files to use by default :returns: the list of arguments left over after parsing options :raises: SystemExit, ConfigFilesNotFoundError, ConfigFileParseError, - RequiredOptError + RequiredOptError, DuplicateOptError """ self.clear() - self._args = args + self._setup(project, prog, version, usage, default_config_files) - (values, args) = self._oparser.parse_args(self._args) + self._cli_values, leftovers = self._parse_cli_opts(args) - self._cli_values = vars(values) - - def _list_config_dir(): - return sorted(glob.glob(os.path.join(self.config_dir, '*.conf'))) - - from_file = list(self.config_file) - - from_dir = _list_config_dir() if self.config_dir else [] - - self._parse_config_files(from_file + from_dir) + self._parse_config_files() self._check_required_opts() - return args + return leftovers def __getattr__(self, name): """Look up an option value and perform string substitution. @@ -994,8 +1058,12 @@ class ConfigOpts(collections.Mapping): def clear(self): """Clear the state of the object to before it was called.""" self._args = None - self._cli_values = {} + self._cli_values.clear() + self._oparser = None self._cparser = None + self.unregister_opts(self._config_opts) + for group in self._groups.values(): + group._clear() @__clear_cache def register_opt(self, opt, group=None): @@ -1042,15 +1110,7 @@ class ConfigOpts(collections.Mapping): if self._args is not None: raise ArgsAlreadyParsedError("cannot register CLI option") - if not self.register_opt(opt, group, clear_cache=False): - return False - - if group is not None: - group = self._get_group(group, autocreate=True) - - opt._add_to_cli(self._oparser, group) - - return True + return self.register_opt(opt, group, clear_cache=False) @__clear_cache def register_cli_opts(self, opts, group=None): @@ -1071,6 +1131,28 @@ class ConfigOpts(collections.Mapping): self._groups[group.name] = copy.copy(group) + @__clear_cache + def unregister_opt(self, opt, group=None): + """Unregister an option. + + :param opt: an Opt object + :param group: an optional OptGroup object or group name + :raises: ArgsAlreadyParsedError, NoSuchGroupError + """ + if self._args is not None: + raise ArgsAlreadyParsedError("reset before unregistering options") + + if group is not None: + self._get_group(group)._unregister_opt(opt) + elif opt.dest in self._opts: + del self._opts[opt.dest] + + @__clear_cache + def unregister_opts(self, opts, group=None): + """Unregister multiple CLI option schemas at once.""" + for opt in opts: + self.unregister_opt(opt, group, clear_cache=False) + @__clear_cache def set_override(self, name, override, group=None): """Override an opt value. @@ -1101,16 +1183,24 @@ class ConfigOpts(collections.Mapping): opt_info = self._get_opt_info(name, group) opt_info['default'] = default + def _all_opt_infos(self): + """A generator function for iteration opt infos.""" + for info in self._opts.values(): + yield info, None + for group in self._groups.values(): + for info in group._opts.values(): + yield info, group + + def _all_opts(self): + """A generator function for iteration opts.""" + for info, group in self._all_opt_infos(): + yield info['opt'], group + def _unset_defaults_and_overrides(self): """Unset any default or override on all options.""" - def unset(opts): - for info in opts.values(): - info['default'] = None - info['override'] = None - - unset(self._opts) - for group in self._groups.values(): - unset(group._opts) + for info, group in self._all_opt_infos(): + info['default'] = None + info['override'] = None def disable_interspersed_args(self): """Set parsing to stop on the first non-option. @@ -1129,13 +1219,13 @@ class ConfigOpts(collections.Mapping): i.e. argument parsing is stopped at the first non-option argument. """ - self._oparser.disable_interspersed_args() + self._disable_interspersed_args = True def enable_interspersed_args(self): """Set parsing to not stop on the first non-option. This it the default behaviour.""" - self._oparser.enable_interspersed_args() + self._disable_interspersed_args = False def find_file(self, name): """Locate a file located alongside the config files. @@ -1272,7 +1362,7 @@ class ConfigOpts(collections.Mapping): def _substitute(self, value): """Perform string template substitution. - Substititue any template variables (e.g. $foo, ${bar}) in the supplied + Substitute any template variables (e.g. $foo, ${bar}) in the supplied string value(s) with opt values. :param value: the string value, or list of string values @@ -1329,11 +1419,17 @@ class ConfigOpts(collections.Mapping): return opts[opt_name] - def _parse_config_files(self, config_files): - """Parse the supplied configuration files. + def _parse_config_files(self): + """Parse the config files from --config-file and --config-dir. :raises: ConfigFilesNotFoundError, ConfigFileParseError """ + config_files = list(self.config_file) + + if self.config_dir: + config_dir_glob = os.path.join(self.config_dir, '*.conf') + config_files += sorted(glob.glob(config_dir_glob)) + self._cparser = MultiConfigParser() try: @@ -1345,8 +1441,12 @@ class ConfigOpts(collections.Mapping): not_read_ok = filter(lambda f: f not in read_ok, config_files) raise ConfigFilesNotFoundError(not_read_ok) - def _do_check_required_opts(self, opts, group=None): - for info in opts.values(): + def _check_required_opts(self): + """Check that all opts marked as required have values specified. + + :raises: RequiredOptError + """ + for info, group in self._all_opt_infos(): default, opt, override = [info[k] for k in sorted(info.keys())] if opt.required: @@ -1356,15 +1456,25 @@ class ConfigOpts(collections.Mapping): if self._get(opt.name, group) is None: raise RequiredOptError(opt.name, group) - def _check_required_opts(self): - """Check that all opts marked as required have values specified. + def _parse_cli_opts(self, args): + """Parse command line options. + + Initializes the command line option parser and parses the supplied + command line arguments. + + :param args: the command line arguments + :returns: a dict of parsed option values + :raises: SystemExit, DuplicateOptError - :raises: RequiredOptError """ - self._do_check_required_opts(self._opts) + self._args = args - for group in self._groups.values(): - self._do_check_required_opts(group._opts, group) + for opt, group in self._all_opts(): + opt._add_to_cli(self._oparser, group) + + values, leftovers = self._oparser.parse_args(args) + + return vars(values), leftovers class GroupAttr(collections.Mapping): @@ -1480,7 +1590,10 @@ class CommonConfigOpts(ConfigOpts): help='syslog facility to receive log lines') ] - def __init__(self, **kwargs): - super(CommonConfigOpts, self).__init__(**kwargs) + def __init__(self): + super(CommonConfigOpts, self).__init__() self.register_cli_opts(self.common_cli_opts) self.register_cli_opts(self.logging_cli_opts) + + +CONF = CommonConfigOpts() diff --git a/quantum/plugins/cisco/common/cisco_credentials.py b/quantum/plugins/cisco/common/cisco_credentials.py index 7c6302ae62..0e5eb0d3db 100644 --- a/quantum/plugins/cisco/common/cisco_credentials.py +++ b/quantum/plugins/cisco/common/cisco_credentials.py @@ -18,7 +18,7 @@ import logging as LOG -from quantum.common.config import find_config_file +from quantum.common.utils import find_config_file from quantum.plugins.cisco.common import cisco_configparser as confp from quantum.plugins.cisco.common import cisco_constants as const from quantum.plugins.cisco.common import cisco_exceptions as cexc @@ -28,7 +28,7 @@ from quantum.plugins.cisco.db import l2network_db as cdb LOG.basicConfig(level=LOG.WARN) LOG.getLogger(const.LOGGER_COMPONENT_NAME) -CREDENTIALS_FILE = find_config_file({'plugin': 'cisco'}, None, +CREDENTIALS_FILE = find_config_file({'plugin': 'cisco'}, "credentials.ini") TENANT = const.NETWORK_ADMIN diff --git a/quantum/plugins/cisco/l2network_plugin_configuration.py b/quantum/plugins/cisco/l2network_plugin_configuration.py index 59196156c1..fdd4191bc5 100644 --- a/quantum/plugins/cisco/l2network_plugin_configuration.py +++ b/quantum/plugins/cisco/l2network_plugin_configuration.py @@ -17,11 +17,11 @@ # @author: Sumit Naiksatam, Cisco Systems, Inc. # @author: Rohit Agarwalla, Cisco Systems, Inc. -from quantum.common.config import find_config_file +from quantum.common.utils import find_config_file from quantum.plugins.cisco.common import cisco_configparser as confp -CONF_FILE = find_config_file({'plugin': 'cisco'}, None, "l2network_plugin.ini") +CONF_FILE = find_config_file({'plugin': 'cisco'}, "l2network_plugin.ini") CONF_PARSER_OBJ = confp.CiscoConfigParser(CONF_FILE) @@ -43,7 +43,7 @@ MAX_NETWORKS = SECTION_CONF['max_networks'] SECTION_CONF = CONF_PARSER_OBJ['MODEL'] MODEL_CLASS = SECTION_CONF['model_class'] -CONF_FILE = find_config_file({'plugin': 'cisco'}, None, "cisco_plugins.ini") +CONF_FILE = find_config_file({'plugin': 'cisco'}, "cisco_plugins.ini") SECTION_CONF = CONF_PARSER_OBJ['SEGMENTATION'] MANAGER_CLASS = SECTION_CONF['manager_class'] @@ -55,7 +55,7 @@ CONF_PARSER_OBJ = confp.CiscoConfigParser(CONF_FILE) # Read the config for the device plugins PLUGINS = CONF_PARSER_OBJ.walk(CONF_PARSER_OBJ.dummy) -CONF_FILE = find_config_file({'plugin': 'cisco'}, None, "db_conn.ini") +CONF_FILE = find_config_file({'plugin': 'cisco'}, "db_conn.ini") CONF_PARSER_OBJ = confp.CiscoConfigParser(CONF_FILE) diff --git a/quantum/plugins/cisco/nexus/cisco_nexus_configuration.py b/quantum/plugins/cisco/nexus/cisco_nexus_configuration.py index 449924ed2f..790a351ab0 100644 --- a/quantum/plugins/cisco/nexus/cisco_nexus_configuration.py +++ b/quantum/plugins/cisco/nexus/cisco_nexus_configuration.py @@ -23,11 +23,11 @@ This module will export the configuration parameters from the nexus.ini file """ -from quantum.common.config import find_config_file +from quantum.common.utils import find_config_file from quantum.plugins.cisco.common import cisco_configparser as confp -CP = confp.CiscoConfigParser(find_config_file({'plugin': 'cisco'}, None, +CP = confp.CiscoConfigParser(find_config_file({'plugin': 'cisco'}, "nexus.ini")) SECTION = CP['SWITCH'] diff --git a/quantum/plugins/cisco/tests/unit/test_cisco_extension.py b/quantum/plugins/cisco/tests/unit/test_cisco_extension.py index f0d92496f7..94c2c7788a 100644 --- a/quantum/plugins/cisco/tests/unit/test_cisco_extension.py +++ b/quantum/plugins/cisco/tests/unit/test_cisco_extension.py @@ -52,7 +52,7 @@ from quantum import wsgi LOG = logging.getLogger('quantum.plugins.cisco.tests.test_cisco_extensions') -TEST_CONF_FILE = config.find_config_file({'plugin': 'cisco'}, None, +TEST_CONF_FILE = config.find_config_file({'plugin': 'cisco'}, 'quantum.conf.ciscoext') EXTENSIONS_PATH = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir, os.pardir, "extensions") @@ -1256,8 +1256,8 @@ def setup_extensions_middleware(extension_manager=None): PluginAwareExtensionManager(EXTENSIONS_PATH, L2Network())) options = {'config_file': TEST_CONF_FILE} - conf, app = config.load_paste_app('extensions_test_app', options, None) - return ExtensionMiddleware(app, conf, ext_mgr=extension_manager) + app = config.load_paste_app('extensions_test_app', options, None) + return ExtensionMiddleware(app, ext_mgr=extension_manager) def setup_extensions_test_app(extension_manager=None): diff --git a/quantum/plugins/cisco/ucs/cisco_ucs_configuration.py b/quantum/plugins/cisco/ucs/cisco_ucs_configuration.py index b065ddfc2a..ff512c7741 100644 --- a/quantum/plugins/cisco/ucs/cisco_ucs_configuration.py +++ b/quantum/plugins/cisco/ucs/cisco_ucs_configuration.py @@ -17,11 +17,11 @@ # @author: Sumit Naiksatam, Cisco Systems, Inc. # -from quantum.common.config import find_config_file +from quantum.common.utils import find_config_file from quantum.plugins.cisco.common import cisco_configparser as confp -CP = confp.CiscoConfigParser(find_config_file({'plugin': 'cisco'}, [], +CP = confp.CiscoConfigParser(find_config_file({'plugin': 'cisco'}, 'ucs.ini')) SECTION = CP['UCSM'] @@ -34,7 +34,7 @@ PROFILE_NAME_PREFIX = SECTION['profile_name_prefix'] SECTION = CP['DRIVER'] UCSM_DRIVER = SECTION['name'] -CP = confp.CiscoConfigParser(find_config_file({'plugin': 'cisco'}, [], +CP = confp.CiscoConfigParser(find_config_file({'plugin': 'cisco'}, 'ucs_inventory.ini')) INVENTORY = CP.walk(CP.dummy) diff --git a/quantum/plugins/cisco/ucs/cisco_ucs_inventory_configuration.py b/quantum/plugins/cisco/ucs/cisco_ucs_inventory_configuration.py index e8474f6728..8166233521 100644 --- a/quantum/plugins/cisco/ucs/cisco_ucs_inventory_configuration.py +++ b/quantum/plugins/cisco/ucs/cisco_ucs_inventory_configuration.py @@ -17,11 +17,11 @@ # @author: Sumit Naiksatam, Cisco Systems, Inc. # -from quantum.common.config import find_config_file +from quantum.common.utils import find_config_file from quantum.plugins.cisco.common import cisco_configparser as confp -CONF_FILE = find_config_file({'plugin': 'cisco'}, None, "ucs_inventory.ini") +CONF_FILE = find_config_file({'plugin': 'cisco'}, "ucs_inventory.ini") CP = confp.CiscoConfigParser(CONF_FILE) INVENTORY = CP.walk(CP.dummy) diff --git a/quantum/plugins/linuxbridge/common/config.py b/quantum/plugins/linuxbridge/common/config.py index c4f51f7bc0..85cc907bc1 100644 --- a/quantum/plugins/linuxbridge/common/config.py +++ b/quantum/plugins/linuxbridge/common/config.py @@ -41,8 +41,8 @@ agent_opts = [ def parse(config_file): - conf = cfg.ConfigOpts(default_config_files=[config_file]) - conf(args=[]) + conf = cfg.ConfigOpts() + conf(args=[], default_config_files=[config_file]) conf.register_opts(vlan_opts, "VLANS") conf.register_opts(database_opts, "DATABASE") conf.register_opts(bridge_opts, "LINUX_BRIDGE") diff --git a/quantum/plugins/linuxbridge/db/l2network_db.py b/quantum/plugins/linuxbridge/db/l2network_db.py index effb268feb..8160142a73 100644 --- a/quantum/plugins/linuxbridge/db/l2network_db.py +++ b/quantum/plugins/linuxbridge/db/l2network_db.py @@ -21,14 +21,14 @@ from sqlalchemy import func from sqlalchemy.orm import exc from quantum.common import exceptions as q_exc -from quantum.common.config import find_config_file +from quantum.common.utils import find_config_file import quantum.db.api as db from quantum.plugins.linuxbridge.common import exceptions as c_exc from quantum.plugins.linuxbridge.db import l2network_models from quantum.plugins.linuxbridge.common import config LOG = logging.getLogger(__name__) -CONF_FILE = find_config_file({'plugin': 'linuxbridge'}, None, +CONF_FILE = find_config_file({'plugin': 'linuxbridge'}, "linuxbridge_conf.ini") CONF = config.parse(CONF_FILE) diff --git a/quantum/plugins/openvswitch/common/config.py b/quantum/plugins/openvswitch/common/config.py index 16ff7d9cf6..c0f7680f63 100644 --- a/quantum/plugins/openvswitch/common/config.py +++ b/quantum/plugins/openvswitch/common/config.py @@ -38,8 +38,8 @@ agent_opts = [ def parse(config_file): - conf = cfg.ConfigOpts(default_config_files=[config_file]) - conf(args=[]) + conf = cfg.ConfigOpts() + conf(args=[], default_config_files=[config_file]) conf.register_opts(database_opts, "DATABASE") conf.register_opts(ovs_opts, "OVS") conf.register_opts(agent_opts, "AGENT") diff --git a/quantum/plugins/openvswitch/ovs_quantum_plugin.py b/quantum/plugins/openvswitch/ovs_quantum_plugin.py index 059451af4e..572a0c8fcd 100644 --- a/quantum/plugins/openvswitch/ovs_quantum_plugin.py +++ b/quantum/plugins/openvswitch/ovs_quantum_plugin.py @@ -23,7 +23,7 @@ import os from quantum.api.api_common import OperationalStatus from quantum.common import exceptions as q_exc -from quantum.common.config import find_config_file +from quantum.common.utils import find_config_file import quantum.db.api as db from quantum.plugins.openvswitch import ovs_db from quantum.plugins.openvswitch.common import config @@ -33,7 +33,7 @@ LOG = logging.getLogger("ovs_quantum_plugin") CONF_FILE = find_config_file({"plugin": "openvswitch"}, - None, "ovs_quantum_plugin.ini") + "ovs_quantum_plugin.ini") # Exception thrown if no more VLANs are available diff --git a/quantum/plugins/ryu/agent/ryu_quantum_agent.py b/quantum/plugins/ryu/agent/ryu_quantum_agent.py index 3fa8b507c7..74513e1894 100755 --- a/quantum/plugins/ryu/agent/ryu_quantum_agent.py +++ b/quantum/plugins/ryu/agent/ryu_quantum_agent.py @@ -20,7 +20,6 @@ # under the License. # @author: Isaku Yamahata -import ConfigParser import logging as LOG from optparse import OptionParser import shlex @@ -34,6 +33,7 @@ from ryu.app.client import OFPClient from sqlalchemy.ext.sqlsoup import SqlSoup from quantum.agent.linux import utils +from quantum.plugins.ryu.common import config OP_STATUS_UP = "UP" OP_STATUS_DOWN = "DOWN" @@ -286,18 +286,10 @@ def main(): sys.exit(1) config_file = args[0] - config = ConfigParser.ConfigParser() - try: - config.read(config_file) - except Exception, e: - LOG.error("Unable to parse config file \"%s\": %s", - config_file, str(e)) - - integ_br = config.get("OVS", "integration-bridge") - - root_helper = config.get("AGENT", "root_helper") - - options = {"sql_connection": config.get("DATABASE", "sql_connection")} + conf = config.parse(config_file) + integ_br = conf.OVS.integration_bridge + root_helper = conf.AGENT.root_helper + options = {"sql_connection": conf.DATABASE.sql_connection} db = SqlSoup(options["sql_connection"]) LOG.info("Connecting to database \"%s\" on %s", diff --git a/quantum/plugins/ryu/common/__init__.py b/quantum/plugins/ryu/common/__init__.py new file mode 100644 index 0000000000..e5f41adfee --- /dev/null +++ b/quantum/plugins/ryu/common/__init__.py @@ -0,0 +1,15 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. diff --git a/quantum/plugins/ryu/common/config.py b/quantum/plugins/ryu/common/config.py new file mode 100644 index 0000000000..871ef54e37 --- /dev/null +++ b/quantum/plugins/ryu/common/config.py @@ -0,0 +1,43 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 Red Hat, Inc. +# +# 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. + +from quantum.openstack.common import cfg + + +database_opts = [ + cfg.StrOpt('sql_connection', default='sqlite://'), + cfg.IntOpt('reconnect_interval', default=2), +] + +ovs_opts = [ + cfg.StrOpt('integration_bridge', default='br-int'), + cfg.StrOpt('openflow_controller', default='127.0.0.1:6633'), + cfg.StrOpt('openflow_rest_api', default='127.0.0.1:8080'), +] + +agent_opts = [ + cfg.IntOpt('polling_interval', default=2), + cfg.StrOpt('root_helper', default='sudo'), +] + + +def parse(config_file): + conf = cfg.ConfigOpts() + conf(args=[], default_config_files=[config_file]) + conf.register_opts(database_opts, "DATABASE") + conf.register_opts(ovs_opts, "OVS") + conf.register_opts(agent_opts, "AGENT") + return conf diff --git a/quantum/plugins/ryu/ovs_quantum_plugin_base.py b/quantum/plugins/ryu/ovs_quantum_plugin_base.py index 91841928fa..fd9a5dfde0 100644 --- a/quantum/plugins/ryu/ovs_quantum_plugin_base.py +++ b/quantum/plugins/ryu/ovs_quantum_plugin_base.py @@ -16,15 +16,14 @@ # under the License. # @author: Isaku Yamahata -import ConfigParser from abc import ABCMeta, abstractmethod import logging as LOG import os from quantum.api.api_common import OperationalStatus from quantum.common import exceptions as q_exc +from quantum.plugins.ryu.common import config import quantum.db.api as db -from quantum.manager import find_config from quantum.quantum_plugin_base import QuantumPluginBase @@ -55,24 +54,23 @@ class OVSQuantumPluginBase(QuantumPluginBase): """ def __init__(self, conf_file, mod_file, configfile=None): super(OVSQuantumPluginBase, self).__init__() - config = ConfigParser.ConfigParser() if configfile is None: - if conf_file and os.path.exists(conf_file): + if os.path.exists(conf_file): configfile = conf_file else: - configfile = ( - find_config(os.path.abspath(os.path.dirname(mod_file)))) + configfile = find_config(os.path.abspath( + os.path.dirname(__file__))) if configfile is None: raise Exception("Configuration file \"%s\" doesn't exist" % (configfile)) - LOG.debug("Using configuration file: %s", configfile) - config.read(configfile) - LOG.debug("Config: %s", config) - - options = {"sql_connection": config.get("DATABASE", "sql_connection")} + LOG.debug("Using configuration file: %s" % configfile) + conf = config.parse(configfile) + options = {"sql_connection": conf.DATABASE.sql_connection} + reconnect_interval = conf.DATABASE.reconnect_interval + options.update({"reconnect_interval": reconnect_interval}) db.configure_db(options) - self.config = config + self.conf = conf # Subclass must set self.driver to its own OVSQuantumPluginDriverBase self.driver = None diff --git a/quantum/plugins/ryu/ryu_quantum_plugin.py b/quantum/plugins/ryu/ryu_quantum_plugin.py index a08fb591ee..c6af60f3b4 100644 --- a/quantum/plugins/ryu/ryu_quantum_plugin.py +++ b/quantum/plugins/ryu/ryu_quantum_plugin.py @@ -19,7 +19,7 @@ from ryu.app import client from ryu.app import rest_nw_id -from quantum.common.config import find_config_file +from quantum.common.utils import find_config_file from quantum.common import exceptions as q_exc import quantum.db.api as db from quantum.plugins.ryu import ofp_service_type @@ -27,14 +27,14 @@ from quantum.plugins.ryu import ovs_quantum_plugin_base from quantum.plugins.ryu.db import api as db_api -CONF_FILE = find_config_file({"plugin": "ryu"}, None, "ryu.ini") +CONF_FILE = find_config_file({"plugin": "ryu"}, "ryu.ini") class OFPRyuDriver(ovs_quantum_plugin_base.OVSQuantumPluginDriverBase): - def __init__(self, config): + def __init__(self, conf): super(OFPRyuDriver, self).__init__() - ofp_con_host = config.get("OVS", "openflow-controller") - ofp_api_host = config.get("OVS", "openflow-rest-api") + ofp_con_host = conf.OVS.openflow_controller + ofp_api_host = conf.OVS.openflow_rest_api if ofp_con_host is None or ofp_api_host is None: raise q_exc.Invalid("invalid configuration. check ryu.ini") @@ -64,4 +64,4 @@ class OFPRyuDriver(ovs_quantum_plugin_base.OVSQuantumPluginDriverBase): class RyuQuantumPlugin(ovs_quantum_plugin_base.OVSQuantumPluginBase): def __init__(self, configfile=None): super(RyuQuantumPlugin, self).__init__(CONF_FILE, __file__, configfile) - self.driver = OFPRyuDriver(self.config) + self.driver = OFPRyuDriver(self.conf) diff --git a/quantum/plugins/ryu/tests/unit/fake_plugin.py b/quantum/plugins/ryu/tests/unit/fake_plugin.py index 55c4853ed2..3d521b4f77 100644 --- a/quantum/plugins/ryu/tests/unit/fake_plugin.py +++ b/quantum/plugins/ryu/tests/unit/fake_plugin.py @@ -21,6 +21,8 @@ from quantum.plugins.ryu import ovs_quantum_plugin_base class FakePluginDriver(ovs_quantum_plugin_base.OVSQuantumPluginDriverBase): def __init__(self, config): super(FakePluginDriver, self).__init__() + conf = config.parse(config) + self.conf = conf def create_network(self, net): pass @@ -32,4 +34,4 @@ class FakePluginDriver(ovs_quantum_plugin_base.OVSQuantumPluginDriverBase): class FakePlugin(ovs_quantum_plugin_base.OVSQuantumPluginBase): def __init__(self, configfile=None): super(FakePlugin, self).__init__(None, __file__, configfile) - self.driver = FakePluginDriver(self.config) + self.driver = FakePluginDriver(self.conf) diff --git a/quantum/plugins/ryu/tests/unit/test_ryu_driver.py b/quantum/plugins/ryu/tests/unit/test_ryu_driver.py index ad113e74be..c3a10a308c 100644 --- a/quantum/plugins/ryu/tests/unit/test_ryu_driver.py +++ b/quantum/plugins/ryu/tests/unit/test_ryu_driver.py @@ -18,16 +18,21 @@ import uuid import quantum.db.api as db +from quantum.common.utils import find_config_file +from quantum.plugins.ryu.common import config from quantum.plugins.ryu.tests.unit.basetest import BaseRyuTest from quantum.plugins.ryu.tests.unit import utils from quantum.plugins.ryu.tests.unit.utils import patch_fake_ryu_client +CONF_FILE = find_config_file({"plugin": "ryu"}, "ryu.ini") + + class RyuDriverTest(BaseRyuTest): """Class conisting of OFPRyuDriver unit tests""" def setUp(self): super(RyuDriverTest, self).setUp() - + self.conf = config.parse(CONF_FILE) # fake up ryu.app.client and ryu.app.rest_nw_id # With those, plugin can be tested without ryu installed self.module_patcher = patch_fake_ryu_client() @@ -65,7 +70,7 @@ class RyuDriverTest(BaseRyuTest): db.network_create('test', uuid0) from quantum.plugins.ryu import ryu_quantum_plugin - ryu_driver = ryu_quantum_plugin.OFPRyuDriver(self.config) + ryu_driver = ryu_quantum_plugin.OFPRyuDriver(self.conf) ryu_driver.create_network(net1) ryu_driver.delete_network(net1) self.mox.VerifyAll() diff --git a/quantum/policy.py b/quantum/policy.py index cc656456f0..702d1cc512 100644 --- a/quantum/policy.py +++ b/quantum/policy.py @@ -21,7 +21,7 @@ Policy engine for quantum. Largely copied from nova. import os.path -from quantum.common import config +from quantum.common.utils import find_config_file from quantum.common import exceptions from quantum.openstack.common import policy @@ -38,7 +38,7 @@ def reset(): def init(): global _POLICY_PATH if not _POLICY_PATH: - _POLICY_PATH = config.find_config_file({}, [], 'policy.json') + _POLICY_PATH = find_config_file({}, 'policy.json') if not _POLICY_PATH: raise exceptions.PolicyNotFound(path=FLAGS.policy_file) with open(_POLICY_PATH) as f: diff --git a/quantum/server/__init__.py b/quantum/server/__init__.py index c8820c1a99..2388944e39 100755 --- a/quantum/server/__init__.py +++ b/quantum/server/__init__.py @@ -25,28 +25,19 @@ import sys from quantum import service from quantum.common import config +from quantum.openstack.common import cfg from quantum.version import version_string -def create_options(parser): - """ - Sets up the CLI and config-file options that may be - parsed and program commands. - :param parser: The option parser - """ - config.add_common_options(parser) - config.add_log_options(parser) - - def main(): - oparser = optparse.OptionParser(version='%prog ' + version_string()) - create_options(oparser) - (options, args) = config.parse_options(oparser) - + # the configuration will be read into the cfg.CONF global data structure + config.parse(sys.argv) + if not cfg.CONF.config_file: + sys.exit("ERROR: Unable to find configuration file via the default" + " search paths (~/.quantum/, ~/, /etc/quantum/, /etc/) and" + " the '--config-file' option!") try: - quantum_service = service.serve_wsgi(service.QuantumApiService, - options=options, - args=args) + quantum_service = service.serve_wsgi(service.QuantumApiService) quantum_service.wait() except RuntimeError, e: sys.exit("ERROR: %s" % e) diff --git a/quantum/service.py b/quantum/service.py index ea2855c6c7..d6432ac15f 100644 --- a/quantum/service.py +++ b/quantum/service.py @@ -18,7 +18,7 @@ import logging from quantum.common import config -from quantum.common import exceptions as exception +from quantum.openstack.common import cfg from quantum import wsgi @@ -34,14 +34,12 @@ class WsgiService(object): """ - def __init__(self, app_name, conf_file, conf): + def __init__(self, app_name): self.app_name = app_name - self.conf_file = conf_file - self.conf = conf self.wsgi_app = None def start(self): - self.wsgi_app = _run_wsgi(self.app_name, self.conf, self.conf_file) + self.wsgi_app = _run_wsgi(self.app_name) def wait(self): self.wsgi_app.wait() @@ -51,13 +49,8 @@ class QuantumApiService(WsgiService): """Class for quantum-api service.""" @classmethod - def create(cls, conf=None, options=None, args=None): + def create(cls): app_name = "quantum" - if not conf: - conf_file, conf = config.load_paste_config(app_name, options, args) - if not conf: - message = (_('No paste configuration found for: %s'), app_name) - raise exception.Error(message) # Setup logging early, supplying both the CLI options and the # configuration mapping from the config file @@ -65,32 +58,24 @@ class QuantumApiService(WsgiService): # flags. Everything else must be set up in the conf file... # Log the options used when starting if we're in debug mode... - config.setup_logging(options, conf) - debug = (options.get('debug') or - config.get_option(conf, 'debug', type='bool', default=False)) - verbose = (options.get('verbose') or - config.get_option(conf, 'verbose', type='bool', - default=False)) - conf['debug'] = debug - conf['verbose'] = verbose + config.setup_logging(cfg.CONF) LOG.debug("*" * 80) LOG.debug("Configuration options gathered from config file:") - LOG.debug(conf_file) LOG.debug("================================================") - items = dict([(k, v) for k, v in conf.items() + items = dict([(k, v) for k, v in cfg.CONF.items() if k not in ('__file__', 'here')]) for key, value in sorted(items.items()): LOG.debug("%(key)-30s %(value)s" % {'key': key, 'value': value, }) LOG.debug("*" * 80) - service = cls(app_name, conf_file, conf) + service = cls(app_name) return service -def serve_wsgi(cls, conf=None, options=None, args=None): +def serve_wsgi(cls): try: - service = cls.create(conf, options, args) + service = cls.create() except Exception: logging.exception('in WsgiService.create()') raise @@ -100,15 +85,11 @@ def serve_wsgi(cls, conf=None, options=None, args=None): return service -def _run_wsgi(app_name, paste_conf, paste_config_file): - LOG.info(_('Using paste.deploy config at: %s'), paste_config_file) - conf, app = config.load_paste_app(app_name, - {'config_file': paste_config_file}, - None) +def _run_wsgi(app_name): + app = config.load_paste_app(app_name, "quantum.conf") if not app: - LOG.error(_('No known API applications configured in %s.'), - paste_config_file) + LOG.error(_('No known API applications configured.')) return server = wsgi.Server("Quantum") - server.start(app, int(paste_conf['bind_port']), paste_conf['bind_host']) + server.start(app, cfg.CONF.bind_port, cfg.CONF.bind_host) return server diff --git a/etc/quantum.conf.test b/quantum/tests/etc/quantum.conf.test similarity index 100% rename from etc/quantum.conf.test rename to quantum/tests/etc/quantum.conf.test diff --git a/quantum/tests/unit/_test_api.py b/quantum/tests/unit/_test_api.py index 46f77f2437..08b0ca2f10 100644 --- a/quantum/tests/unit/_test_api.py +++ b/quantum/tests/unit/_test_api.py @@ -21,19 +21,30 @@ import logging import unittest2 as unittest import mock +import os from quantum.api.api_common import APIFaultWrapper from quantum.api.networks import Controller from quantum.common.test_lib import test_config +from quantum.common import config from quantum.db import api as db from quantum.openstack.common import importutils import quantum.tests.unit.testlib_api as testlib +from quantum.openstack.common import cfg from quantum.wsgi import XMLDeserializer, JSONDeserializer LOG = logging.getLogger('quantum.tests.test_api') +ROOTDIR = os.path.dirname(os.path.dirname(__file__)) +ETCDIR = os.path.join(ROOTDIR, 'etc') + + +def etcdir(*p): + return os.path.join(ETCDIR, *p) + + NETS = "networks" PORTS = "ports" ATTS = "attachments" @@ -105,10 +116,13 @@ class AbstractAPITest(unittest.TestCase): self.assertEqual(put_attachment_res.status_int, expected_res_status) def setUp(self, api_router_klass, xml_metadata_dict): - options = {} - options['plugin_provider'] = test_config['plugin_name'] + # Create the default configurations + args = ['--config-file', etcdir('quantum.conf.test')] + config.parse(args=args) + # Update the plugin + cfg.CONF.set_override('core_plugin', test_config['plugin_name']) api_router_cls = importutils.import_class(api_router_klass) - self.api = api_router_cls(options) + self.api = api_router_cls() self.tenant_id = "test_tenant" self.network_name = "test_network" @@ -136,6 +150,7 @@ class AbstractAPITest(unittest.TestCase): """Clear the test environment""" # Remove database contents db.clear_db() + cfg.CONF.reset() class BaseAPIOperationsTest(AbstractAPITest): diff --git a/quantum/tests/unit/test_api_v2.py b/quantum/tests/unit/test_api_v2.py index 06e8c4148f..90f1082f6e 100644 --- a/quantum/tests/unit/test_api_v2.py +++ b/quantum/tests/unit/test_api_v2.py @@ -12,6 +12,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the spec +import os import logging import unittest import uuid @@ -26,6 +27,8 @@ from quantum.common import exceptions as q_exc from quantum.api.v2 import resource as wsgi_resource from quantum.api.v2 import router from quantum.api.v2 import views +from quantum.common import config +from quantum.openstack.common import cfg LOG = logging.getLogger(__name__) @@ -34,6 +37,13 @@ LOG = logging.getLogger(__name__) def _uuid(): return str(uuid.uuid4()) +ROOTDIR = os.path.dirname(os.path.dirname(__file__)) +ETCDIR = os.path.join(ROOTDIR, 'etc') + + +def etcdir(*p): + return os.path.join(ETCDIR, *p) + def _get_path(resource, id=None, fmt=None): path = '/%s' % resource @@ -123,16 +133,23 @@ class APIv2TestCase(unittest.TestCase): # will get around this. def setUp(self): plugin = 'quantum.quantum_plugin_base_v2.QuantumPluginBaseV2' + # Create the default configurations + args = ['--config-file', etcdir('quantum.conf.test')] + config.parse(args=args) + # Update the plugin + cfg.CONF.set_override('core_plugin', plugin) + self._plugin_patcher = mock.patch(plugin, autospec=True) self.plugin = self._plugin_patcher.start() - api = router.APIRouter({'plugin_provider': plugin}) + api = router.APIRouter() self.api = webtest.TestApp(api) def tearDown(self): self._plugin_patcher.stop() self.api = None self.plugin = None + cfg.CONF.reset() def test_verbose_attr(self): instance = self.plugin.return_value diff --git a/quantum/tests/unit/test_db_plugin.py b/quantum/tests/unit/test_db_plugin.py index 46a22f9530..d3e89d1d71 100644 --- a/quantum/tests/unit/test_db_plugin.py +++ b/quantum/tests/unit/test_db_plugin.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import os import logging import unittest import contextlib @@ -22,12 +23,21 @@ import quantum from quantum.api.v2.router import APIRouter from quantum.common import exceptions as q_exc from quantum.db import api as db +from quantum.common import config +from quantum.openstack.common import cfg from quantum.tests.unit.testlib_api import create_request from quantum.wsgi import Serializer, JSONDeserializer LOG = logging.getLogger(__name__) +ROOTDIR = os.path.dirname(os.path.dirname(__file__)) +ETCDIR = os.path.join(ROOTDIR, 'etc') + + +def etcdir(*p): + return os.path.join(ETCDIR, *p) + class QuantumDbPluginV2TestCase(unittest.TestCase): def setUp(self): @@ -46,7 +56,12 @@ class QuantumDbPluginV2TestCase(unittest.TestCase): } plugin = 'quantum.db.db_base_plugin_v2.QuantumDbPluginV2' - self.api = APIRouter({'plugin_provider': plugin}) + # Create the default configurations + args = ['--config-file', etcdir('quantum.conf.test')] + config.parse(args=args) + # Update the plugin + cfg.CONF.set_override('core_plugin', plugin) + self.api = APIRouter() def tearDown(self): super(QuantumDbPluginV2TestCase, self).tearDown() @@ -54,6 +69,7 @@ class QuantumDbPluginV2TestCase(unittest.TestCase): # doesn't like when the plugin changes ;) db._ENGINE = None db._MAKER = None + cfg.CONF.reset() def _req(self, method, resource, data=None, fmt='json', id=None): if id: diff --git a/quantum/tests/unit/test_extensions.py b/quantum/tests/unit/test_extensions.py index ecda049ea2..9c64d36cd7 100644 --- a/quantum/tests/unit/test_extensions.py +++ b/quantum/tests/unit/test_extensions.py @@ -14,6 +14,8 @@ # License for the specific language governing permissions and limitations # under the License. +import logging +import os import unittest import routes @@ -41,8 +43,15 @@ from quantum.tests.unit.extension_stubs import ( import quantum.tests.unit.extensions from quantum import wsgi +LOG = logging.getLogger('quantum.tests.test_extensions') + +ROOTDIR = os.path.dirname(os.path.dirname(__file__)) +ETCDIR = os.path.join(ROOTDIR, 'etc') + + +def etcdir(*p): + return os.path.join(ETCDIR, *p) -test_conf_file = config.find_config_file({}, None, "quantum.conf.test") extensions_path = ':'.join(quantum.tests.unit.extensions.__path__) @@ -455,8 +464,10 @@ def app_factory(global_conf, **local_conf): def setup_base_app(): - options = {'config_file': test_conf_file} - conf, app = config.load_paste_app('extensions_test_app', options, None) + config_file = 'quantum.conf.test' + args = ['--config-file', etcdir(config_file)] + config.parse(args=args) + app = config.load_paste_app('extensions_test_app', config_file) return app @@ -464,9 +475,11 @@ def setup_extensions_middleware(extension_manager=None): extension_manager = (extension_manager or PluginAwareExtensionManager(extensions_path, QuantumEchoPlugin())) - options = {'config_file': test_conf_file} - conf, app = config.load_paste_app('extensions_test_app', options, None) - return ExtensionMiddleware(app, conf, ext_mgr=extension_manager) + config_file = 'quantum.conf.test' + args = ['--config-file', etcdir(config_file)] + config.parse(args=args) + app = config.load_paste_app('extensions_test_app', config_file) + return ExtensionMiddleware(app, ext_mgr=extension_manager) def setup_extensions_test_app(extension_manager=None): diff --git a/setup.py b/setup.py index fe4f5fafdf..4d28542794 100644 --- a/setup.py +++ b/setup.py @@ -51,7 +51,7 @@ ryu_plugin_config_path = 'etc/quantum/plugins/ryu' DataFiles = [ (config_path, - ['etc/quantum.conf', 'etc/quantum.conf.test', 'etc/plugins.ini']), + ['etc/quantum.conf']), (init_path, ['etc/init.d/quantum-server']), (ovs_plugin_config_path, ['etc/quantum/plugins/openvswitch/ovs_quantum_plugin.ini']),