From 731166c9e543c9aee95700f6383a9758c81b131a Mon Sep 17 00:00:00 2001 From: Mark McLoughlin Date: Mon, 28 Nov 2011 14:38:35 +0000 Subject: [PATCH] Port nova.flags to cfg This modifies nova.flags to emulate gflags using the new cfg module instead of optparse. One side effect of this is that nova now has a --config-file argument which allows a .ini style config file to be used instead of a gflags style file. Downstream packagers may choose to switch to using this new config file by default, or stick with the gflags style file for now. We may, in time, choose to deprecate --flagfile. Obviously, this change is just a stepping stone towards having Nova use the cfg API throughout the codebase. Next steps might include: - Adding a DEFINE_opt() function and starting to convert all the option definitions to the cfg.Opt schema types - Passing a ConfigOpts instance around rather than referring to the global flags.FLAGS variable - Adding a default .ini style config file with the default values commented out and an explanation of each option. This could potentially be autogenerated from the option schemas in the code. - Making use of option groups to organize options - In time, deprecating --flagfile - In time, also deprecating most of the options as CLI options and only allowing them to be set via config files There are two hacks in the current code where we directly access the OptionParser instance which is technically just a private implementation detail of the ConfigOpts class: - We need to use optparse's disable_interspersed_args(). I think it's needed for nova-manage - We still need the gross hack for handling unknown CLI args. We should either make sure they are registered at startup, or just wait until we make them unavailable via the CLI before removing the hack. This would also allow us to remove the gross hack to allow CLI opts to be registered after the CLI args have been parsed. One final note - the cfg module doesn't have support yet for multistr opts where values are spread across the command line and config files. This isn't a regression as it still works fine with the CLI and --flagfile, and it should be straightforward to support later. Change-Id: I173b99ffd645b8ac5babd68e5c2ed521b98ec2ca --- nova/flags.py | 114 ++++++++++++++++++-------------------------------- 1 file changed, 41 insertions(+), 73 deletions(-) diff --git a/nova/flags.py b/nova/flags.py index dc4e648f..270190a7 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -19,22 +19,21 @@ """Command-line flag library. -Emulates gflags by wrapping optparse. +Emulates gflags by wrapping cfg.ConfigOpts. -The idea is to move to optparse eventually, and this wrapper is a +The idea is to move fully to cfg eventually, and this wrapper is a stepping stone. """ -import copy -import optparse import os import socket -import string import sys import gflags +from nova.common import cfg + class FlagValues(object): class Flag: @@ -69,76 +68,50 @@ class FlagValues(object): return filter(lambda i: i == a or i.startswith(a + "="), args)[0] def __init__(self): - self._parser = optparse.OptionParser() - self._parser.disable_interspersed_args() - self._multistring_defaults = {} + self._conf = cfg.ConfigOpts() + self._conf._oparser.disable_interspersed_args() + self._opts = {} self.Reset() - def _apply_multistring_defaults(self, values): - # - # This horrendous hack is to stop optparse appending - # values to the default value. See: - # http://bugs.python.org/issue5088 - # - for flag, default in self._multistring_defaults.items(): - if not getattr(values, flag): - setattr(values, flag, default) - return values - def _parse(self): - if not self._values is None: + if self._extra is not None: return args = gflags.FlagValues().ReadFlagsFromFiles(self._args) - values = extra = None - - # - # This horrendous hack is needed because optparse only - # shallow copies its defaults dict before parsing - # - defaults = copy.deepcopy(self._parser.defaults) + extra = None # # This horrendous hack allows us to stop optparse # exiting when it encounters an unknown option # - error_catcher = self.ErrorCatcher(self._parser.error) - self._parser.error = error_catcher.catch + error_catcher = self.ErrorCatcher(self._conf._oparser.error) + self._conf._oparser.error = error_catcher.catch try: while True: error_catcher.reset() - (values, extra) = self._parser.parse_args(args) + extra = self._conf(args) unknown = error_catcher.get_unknown_arg(args) if not unknown: break args.remove(unknown) - self._parser.defaults = defaults - defaults = copy.deepcopy(defaults) finally: - self._parser.error = error_catcher.orig_error - self._parser.defaults = defaults + self._conf._oparser.error = error_catcher.orig_error - values = self._apply_multistring_defaults(values) - - (self._values, self._extra) = (values, extra) + self._extra = extra def __call__(self, argv): + self.Reset() self._args = argv[1:] - self._values = None self._parse() return [argv[0]] + self._extra def __getattr__(self, name): self._parse() - val = getattr(self._values, name) - if type(val) is str: - tmpl = string.Template(val) - return tmpl.substitute(vars(self._values)) - return val + return getattr(self._conf, name) def get(self, name, default): value = getattr(self, name) @@ -149,11 +122,10 @@ class FlagValues(object): def __contains__(self, name): self._parse() - return hasattr(self._values, name) + return hasattr(self._conf, name) def _update_default(self, name, default): - self._parser.set_default(name, default) - self._values = None + self._conf.set_default(name, default) def __iter__(self): return self.FlagValuesDict().iterkeys() @@ -165,55 +137,51 @@ class FlagValues(object): return self.Flag(name, getattr(self, name), self._update_default) def Reset(self): + self._conf.reset() self._args = [] - self._values = None self._extra = None def ParseNewFlags(self): pass def FlagValuesDict(self): + self._parse() ret = {} - for opt in self._parser.option_list: - if opt.dest: - ret[opt.dest] = getattr(self, opt.dest) + for opt in self._opts.values(): + ret[opt.dest] = getattr(self, opt.dest) return ret - def _add_option(self, name, default, help, prefix='--', **kwargs): - prefixed_name = prefix + name - for opt in self._parser.option_list: - if prefixed_name == opt.get_opt_string(): - return - self._parser.add_option(prefixed_name, dest=name, - default=default, help=help, **kwargs) - self._values = None + def _add_option(self, opt): + if opt.dest in self._opts: + return + + self._opts[opt.dest] = opt + + try: + self._conf.register_cli_opts(self._opts.values()) + except cfg.ArgsAlreadyParsedError: + self._conf.reset() + self._conf.register_cli_opts(self._opts.values()) + self._extra = None def define_string(self, name, default, help): - self._add_option(name, default, help) + self._add_option(cfg.StrOpt(name, default=default, help=help)) def define_integer(self, name, default, help): - self._add_option(name, default, help, type='int') + self._add_option(cfg.IntOpt(name, default=default, help=help)) def define_float(self, name, default, help): - self._add_option(name, default, help, type='float') + self._add_option(cfg.FloatOpt(name, default=default, help=help)) def define_bool(self, name, default, help): - # - # FIXME(markmc): this doesn't support --boolflag=true/false/t/f/1/0 - # - self._add_option(name, default, help, action='store_true') - self._add_option(name, default, help, - prefix="--no", action='store_false') + self._add_option(cfg.BoolOpt(name, default=default, help=help)) def define_list(self, name, default, help): - def parse_list(option, opt, value, parser): - setattr(self._parser.values, name, value.split(',')) - self._add_option(name, default, help, type='string', - action='callback', callback=parse_list) + self._add_option(cfg.ListOpt(name, default=default, help=help)) def define_multistring(self, name, default, help): - self._add_option(name, [], help, action='append') - self._multistring_defaults[name] = default + self._add_option(cfg.MultiStrOpt(name, default=default, help=help)) + FLAGS = FlagValues()