跳转到: 导航, 搜索

Oslo/Config

一个通用的配置选项处理模块

目标是在各个项目之间尽可能地重用通用基础设施。

此蓝图专门涉及处理的代码

  1. 命令行选项解析
  2. 通用的命令行选项
  3. 配置文件解析
  4. 选项值查找

并首先集中精力统一 Nova 和 Glance。

命令行选项解析

Nova 使用 gflags 库进行选项解析。命令行选项的定义分散在整个项目代码库中,例如


from nova import flags

FLAGS = flags.FLAGS
flags.DEFINE_bool('allow_admin_api',
    False,
    'When True, this API service will accept admin operations.')

if FLAGS.allow_admin_api:
    ...


该标志只有在定义它的模块加载后才被识别和解析。如果一个模块需要引用另一个模块中定义的标志,它会这样做:


FLAGS = flags.FLAGS
flags.DECLARE('num_iscsi_scan_tries', 'nova.volume.driver')

if tries >= FLAGS.num_iscsi_scan_tries:
    ...


Glance 对命令行选项的使用要少得多,更倾向于仅使用其配置文件来设置大多数选项。 也许,命令行选项主要用于在加载配置文件之前可能需要设置的选项?

Glance 使用 optparse 库来定义和解析这些选项,例如:


oparser = optparse.OptionParser(version='%%prog %s'
                                % version.version_string())
...
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")
...
parser.add_option_group(group)
...
(options, args) = parser.parse_args(cli_args)


return (vars(options), args)


最后一步将选项值对象上的属性转换为字典,因此可以通过例如以下方式访问每个选项:


if options.get('verbose'):
    ...


通用的命令行选项

Glance 的整个选项集是

  • --verbose, --debug:将日志级别设置为 INFO 或 DEBUG,否则设置为 WARNING
  • --config-file:一个 .ini 样式的配置文件
  • --log-config:python logging 配置
  • --log-date-format, --log-file, --log-dir, --use-syslog:其他日志配置,如果提供了日志配置文件则覆盖它
  • --use-syslog:记录到 syslog

Nova 有一个更大的选项集。与 Glance 共同的选项(大致)是

  • --verbose:将日志级别设置为 DEBUG,否则设置为 INFO
  • --flagfile:gflags 格式的配置文件
  • --use-syslog:记录到 syslog
  • --default_log_levels:各个模块的日志级别
  • --logging_context_format_string, --logging_debug_format_suffix, --logging_default_format_string, --logging_exception_prefix:各种格式化选项

很明显,一个通用的日志模块,具有与 Glance 当前选项类似的选项集,应该足以满足 Nova 的需求。对某些选项的支持将会丢失,但类似的功能仍然可以通过使用单独的日志配置文件来实现。

配置文件解析

Nova 使用 gflags 格式的配置文件,每行一个命令行选项。 大多数选项可能仅使用此配置文件设置,而不是直接在命令行上设置。

Glance 的配置文件是 PasteDeploy 配置文件,每个 WSGI 应用一个。 但是,对于 glance-scrubber 和 glance-cache-{cleaner,prefetcher,pruner},这些实际上并不是严格意义上的 WSGI 应用,而是由 PasteDeploy 从工厂加载的任意对象。

在直接使用 Glance 的直接方法之前,有一些事情值得考虑

  1. 对于非 WSGI 应用及其选项使用 PasteDeploy 似乎不太合适。 此外,PasteDeploy 使用 ConfigParser 而不是 SafeConfigParser 似乎导致了一些 问题。 因此,将 WSGI 应用配置放在单独的文件中(例如 glance-paste.conf)中,从其余的配置选项中分离出来,这些选项将使用 SafeConfigParser 进行解析,可能是一个更好的方法。
  2. 虽然 Glance 服务之间共享的配置值相对较少,但 Nova 服务之间共享的配置值相当多。 尽管,弄清楚 Nova 的哪些服务实际使用给定的选项并不容易。 这表明支持多个配置文件可能是有用的,例如 --config-file /etc/nova/nova-common.conf --config-file /etc/nova/nova-api.conf
  3. 最好在 /etc 目录中的配置文件中尽可能地保留默认值。 例如,使用 RPM 时,如果用户安装 Glance,设置 verbose = True,然后更新到 Glance 的新版本,旧配置文件将保留在原处,并且新版本将以 .rpmnew 后缀安装。 如果我们需要配置文件中存在任何给定值的合理默认值,那么在这种情况下可能会出现问题。 最佳实践是在代码中保留默认值,但也在配置文件中作为注释包含。

选项值查找

Glance 当前的方法涉及在各个地方传递 options 字典


class ImageCache(object):
    def __init__(self, options):
        self.options = options
        ...
    def prune(self):
        max_size = int(self.options.get('image_cache_max_size',
                                        DEFAULT_MAX_CACHE_SIZE))
        ...


如果需要,默认选项是在选项查找时使用 dict.get() 指定的。

Nova 使用全局标志值对象


from nova import flags

FLAGS = flags.FLAGS
flags.DECLARE('num_iscsi_scan_tries', 'nova.volume.driver')

if tries >= FLAGS.num_iscsi_scan_tries:
    ...


默认值在定义选项时指定。

全局变量不是理想的,应该避免,所以我们应该采用 Glance 的方法,即传递选项。 但是,Nova 可能会保留一个全局值集,直到代码库可以完全适应为止。

但是,Nova 将选项及其默认值一起以结构化方式定义的方法似乎值得。

需求

需求

  • 一种为每个配置选项定义模式的方法 - 它的名称、类型、可选组、默认值和描述


common_opts = [
    cfg.StrOpt('bind_host',
               default='0.0.0.0',
               help='IP address to listen on'),
    cfg.IntOpt('bind_port',
               default=9292,
               help='Port number to listen on')
]


  • 配置选项类型 - 字符串、整数、浮点数、布尔值、列表、多字符串


enabled_apis_opt = \
    cfg.ListOpt('enabled_apis',
                default=['ec2', 'osapi'],
                help='List of APIs to enable by default')


DEFAULT_EXTENSIONS = [
    'nova.api.openstack.contrib.standard_extensions'
]
osapi_extension_opt = \
    cfg.MultiStrOpt('osapi_extension',
                    default=DEFAULT_EXTENSIONS)


  • 配置选项模式在运行时注册到配置管理器,但在引用选项之前


class ExtensionManager(object):

    enabled_apis_opt = cfg.ListOpt(...)

    def __init__(self, conf):
        self.conf = conf
        self.conf.register_opt(enabled_apis_opt)
        ...

    def _load_extensions(self):
        for ext_factory in self.conf.osapi_extension:
            ....


  • 每个配置选项模式都应定义在使用的模块或类中


opts = ...

def add_common_opts(conf):
    conf.register_opts(opts)

def get_bind_host(conf):
    return conf.bind_host

def get_bind_port(conf):
    return conf.bind_port


  • 配置选项可以选择作为命令行选项提供;这些必须在解析命令行之前注册到配置管理器(用于验证和 --help)


cli_opts = [
    cfg.BoolOpt('verbose',
                short='v',
                default=False,
                help='Print more verbose output'),
    cfg.BoolOpt('debug',
                short='d',
                default=False,
                help='Print debugging output'),
]

def add_common_opts(conf):
    conf.register_cli_opts(cli_opts)


  • 配置管理器默认定义一个 CLI 选项,--config-file


class ConfigOpts(object):

    config_file_opt = \
        MultiStrOpt('config-file',
                    ...

    def __init__(self, ...):
        ...
        self.register_cli_opt(self.config_file_opt)


  • 选项值从任何提供的配置文件中解析,使用 SafeConfigParser。 如果没有指定,则使用默认集,例如 ['glance-api.conf', 'glance.conf']


glance-api.conf:
  [DEFAULT]
  bind_port = 9292


glance.conf:
  [DEFAULT]
  bind_host = 0.0.0.0


  • 通过调用配置管理器来启动 CLI 参数和配置文件的解析,例如:


conf = ConfigOpts()
conf(sys.argv[1:])
if conf.verbose:
    ...


  • 选项可以注册到组中


rabbit_group = cfg.OptionGroup(name='rabbit', title='RabbitMQ options')

rabbit_host_opt = \
    cfg.StrOpt('host',
               default='localhost',
               help='IP/hostname to listen on'),
rabbit_port_opt = \
    cfg.IntOpt('port',
               default=5672,
               help='Port number to listen on')
rabbit_ssl_opt = \
    cfg.BoolOpt('use_ssl',
                default=False,
                help='Whether to support SSL connections')

def register_rabbit_opts(conf):
    conf.register_group(rabbit_group)
    # options can be registered under a group in any of these ways:
    conf.register_opt(rabbit_host_opt)
    conf.register_opt(rabbit_port_opt, group='rabbit')
    conf.register_opt(rabbit_ssl_opt, group=rabbit_group)


  • 如果未指定组,则选项属于配置文件的 'DEFAULT' 部分


glance-api.conf:
  [DEFAULT]
  bind_port = 9292
  ...

  [rabbit]
  host = localhost
  port = 5672
  use_ssl = False
  userid = guest
  password = guest
  virtual_host = /


  • 组中的命令行选项会自动以组名称为前缀,例如:


--rabbit-host localhost --rabbit-use-ssl False


 - Option values in the default group are referenced as attributes/properties on the config manager object; groups are also attributes on the config manager, with attributes for each of the options associated with the group


server.start(app, conf.bind_port, conf.bind_host, conf)


self.connection = kombu.connection.BrokerConnection(
    hostname=conf.rabbit.host,
    port=conf.rabbit.port,
    ...)


  • 字符串选项值可以使用模板


opts = [
    cfg.StrOpt('state_path',
               default=os.path.join(os.path.dirname(__file__), '../'),
               help='Top-level directory for maintaining nova state'),
    cfg.StrOpt('sqlite_db',
               default='nova.sqlite',
               help='file name for sqlite'),
    cfg.StrOpt('sql_connection',
               default='sqlite:///$state_path/$sqlite_db',
               help='connection string for sql database'),
]


  • CommonConfigOpts 配置管理器类允许自动注册一组通用的配置选项


common_opts = [
    cfg.BoolOpt('verbose', ...),
    cfg.BoolOpt('debug', ...),
]

logging_opts = [
    cfg.StrOpt('log-config', ...),
    cfg.StrOpt('log-date-format', ...),
    ...
]

def CommonConfigOpts(object):

    def __init__(self):
        ...
        self.register_cli_opts(common_opts)
        self.register_cli_opts(logging_opts)