The cfg Module

Configuration options may be set on the command line or in config files.

The schema for each option is defined using the Opt class or its sub-classes, for example:

from oslo_config import cfg
from oslo_config import types

PortType = types.Integer(1, 65535)

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

Option Types

Options can have arbitrary types via the type parameter to the Opt constructor. The type parameter is a callable object that takes a string and either returns a value of that particular type or raises ValueError if the value can not be converted.

For convenience, there are predefined option subclasses in oslo_config.cfg that set the option type as in the following table:

Type Option
oslo_config.types.String oslo_config.cfg.StrOpt
oslo_config.types.String oslo_config.cfg.SubCommandOpt
oslo_config.types.Boolean oslo_config.cfg.BoolOpt
oslo_config.types.Integer oslo_config.cfg.IntOpt
oslo_config.types.Float oslo_config.cfg.FloatOpt
oslo_config.types.Port oslo_config.cfg.PortOpt
oslo_config.types.List oslo_config.cfg.ListOpt
oslo_config.types.Dict oslo_config.cfg.DictOpt
oslo_config.types.IPAddress oslo_config.cfg.IPOpt
oslo_config.types.Hostname oslo_config.cfg.HostnameOpt
oslo_config.types.HostAddress oslo_config.cfg.HostAddressOpt
oslo_config.types.URI oslo_config.cfg.URIOpt

For oslo_config.cfg.MultiOpt the item_type parameter defines the type of the values. For convenience, oslo_config.cfg.MultiStrOpt is MultiOpt with the item_type parameter set to oslo_config.types.MultiString.

The following example defines options using the convenience classes:

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

DEFAULT_EXTENSIONS = [
    'nova.api.openstack.compute.contrib.standard_extensions'
]
osapi_compute_extension_opt = cfg.MultiStrOpt('osapi_compute_extension',
                                              default=DEFAULT_EXTENSIONS)

Registering Options

Option schemas are registered with the config manager at runtime, but before the option is referenced:

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_compute_extension:
            ....

A common usage pattern is for each option schema to be defined in the module or class which uses the option:

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

An option may optionally be made available via the command line. Such options must be registered with the config manager before the command line is parsed (for the purposes of –help and CLI arg validation):

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)

Loading Config Files

The config manager has two CLI options defined by default, –config-file and –config-dir:

class ConfigOpts(object):

    def __call__(self, ...):

        opts = [
            MultiStrOpt('config-file',
                    ...),
            StrOpt('config-dir',
                   ...),
        ]

        self.register_cli_opts(opts)

Option values are parsed from any supplied config files using oslo_config.iniparser. If none are specified, a default set is used for example glance-api.conf and glance-common.conf:

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

glance-common.conf:
  [DEFAULT]
  bind_host = 0.0.0.0

Lines in a configuration file should not start with whitespace. A configuration file also supports comments, which must start with ‘#’ or ‘;’. Option values in config files and those on the command line are parsed in order. The same option (includes deprecated option name and current option name) can appear many times, in config files or on the command line. Later values always override earlier ones.

The order of configuration files inside the same configuration directory is defined by the alphabetic sorting order of their file names.

The parsing of CLI args and config files is initiated by invoking the config manager for example:

conf = cfg.ConfigOpts()
conf.register_opt(cfg.BoolOpt('verbose', ...))
conf(sys.argv[1:])
if conf.verbose:
    ...

Option Groups

Options can be registered as belonging to a group:

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

rabbit_host_opt = cfg.StrOpt('host',
                             default='localhost',
                             help='IP/hostname to listen on.'),
rabbit_port_opt = cfg.PortOpt('port',
                              default=5672,
                              help='Port number to listen on.')

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

If no group attributes are required other than the group name, the group need not be explicitly registered for example:

def register_rabbit_opts(conf):
    # The group will automatically be created, equivalent calling::
    #   conf.register_group(OptGroup(name='rabbit'))
    conf.register_opt(rabbit_port_opt, group='rabbit')

If no group is specified, options belong to the ‘DEFAULT’ section of config files:

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

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

Command-line options in a group are automatically prefixed with the group name:

--rabbit-host localhost --rabbit-port 9999

Dynamic Groups

Groups can be registered dynamically by application code. This introduces a challenge for the sample generator, discovery mechanisms, and validation tools, since they do not know in advance the names of all of the groups. The dynamic_group_owner parameter to the constructor specifies the full name of an option registered in another group that controls repeated instances of a dynamic group. This option is usually a MultiStrOpt.

For example, Cinder supports multiple storage backend devices and services. To configure Cinder to communicate with multiple backends, the enabled_backends option is set to the list of names of backends. Each backend group includes the options for communicating with that device or service.

Driver Groups

Groups can have dynamic sets of options, usually based on a driver that has unique requirements. This works at runtime because the code registers options before it uses them, but it introduces a challenge for the sample generator, discovery mechanisms, and validation tools because they do not know in advance the correct options for a group.

To address this issue, the driver option for a group can be named using the driver_option parameter. Each driver option should define its own discovery entry point namespace to return the set of options for that driver, named using the prefix "oslo.config.opts." followed by the driver option name.

In the Cinder case described above, a volume_backend_name option is part of the static definition of the group, so driver_option should be set to "volume_backend_name". And plugins should be registered under "oslo.config.opts.volume_backend_name" using the same names as the main plugin registered with "oslo.config.opts". The drivers residing within the Cinder code base have an entry point named "cinder" registered.

Accessing Option Values In Your Code

Option values in the default group are referenced as attributes/properties on the config manager; 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,
    ...)

Option Value Interpolation

Option values may reference other values using PEP 292 string substitution:

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.'),
]

Note

Interpolation can be avoided by using $$.

Note

You can use . to delimit option from other groups, e.g. ${mygroup.myoption}.

Special Handling Instructions

Options may be declared as required so that an error is raised if the user does not supply a value for the option:

opts = [
    cfg.StrOpt('service_name', required=True),
    cfg.StrOpt('image_id', required=True),
    ...
]

Options may be declared as secret so that their values are not leaked into log files:

opts = [
   cfg.StrOpt('s3_store_access_key', secret=True),
   cfg.StrOpt('s3_store_secret_key', secret=True),
   ...
]

Dictionary Options

If you need end users to specify a dictionary of key/value pairs, then you can use the DictOpt:

opts = [
    cfg.DictOpt('foo',
                default={})
]

The end users can then specify the option foo in their configuration file as shown below:

[DEFAULT]
foo = k1:v1,k2:v2

Global ConfigOpts

This module also contains a global instance of the ConfigOpts class in order to support a common usage pattern in OpenStack:

from oslo_config import cfg

opts = [
    cfg.StrOpt('bind_host', default='0.0.0.0'),
    cfg.PortOpt('bind_port', default=9292),
]

CONF = cfg.CONF
CONF.register_opts(opts)

def start(server, app):
    server.start(app, CONF.bind_port, CONF.bind_host)

Positional Command Line Arguments

Positional command line arguments are supported via a ‘positional’ Opt constructor argument:

>>> conf = cfg.ConfigOpts()
>>> conf.register_cli_opt(cfg.MultiStrOpt('bar', positional=True))
True
>>> conf(['a', 'b'])
>>> conf.bar
['a', 'b']

Sub-Parsers

It is also possible to use argparse “sub-parsers” to parse additional command line arguments using the SubCommandOpt class:

>>> def add_parsers(subparsers):
...     list_action = subparsers.add_parser('list')
...     list_action.add_argument('id')
...
>>> conf = cfg.ConfigOpts()
>>> conf.register_cli_opt(cfg.SubCommandOpt('action', handler=add_parsers))
True
>>> conf(args=['list', '10'])
>>> conf.action.name, conf.action.id
('list', '10')

Advanced Option

Use if you need to label an option as advanced in sample files, indicating the option is not normally used by the majority of users and might have a significant effect on stability and/or performance:

from oslo_config import cfg

opts = [
    cfg.StrOpt('option1', default='default_value',
                advanced=True, help='This is help '
                'text.'),
    cfg.PortOpt('option2', default='default_value',
                 help='This is help text.'),
]

CONF = cfg.CONF
CONF.register_opts(opts)

This will result in the option being pushed to the bottom of the namespace and labeled as advanced in the sample files, with a notation about possible effects:

[DEFAULT]
...
# This is help text. (string value)
# option2 = default_value
...
<pushed to bottom of section>
...
# This is help text. (string value)
# Advanced Option: intended for advanced users and not used
# by the majority of users, and might have a significant
# effect on stability and/or performance.
# option1 = default_value

Option Deprecation

If you want to rename some options, move them to another group or remove completely, you may change their declarations using deprecated_name, deprecated_group and deprecated_for_removal parameters to the Opt constructor:

from oslo_config import cfg

conf = cfg.ConfigOpts()

opt_1 = cfg.StrOpt('opt_1', default='foo', deprecated_name='opt1')
opt_2 = cfg.StrOpt('opt_2', default='spam', deprecated_group='DEFAULT')
opt_3 = cfg.BoolOpt('opt_3', default=False, deprecated_for_removal=True)

conf.register_opt(opt_1, group='group_1')
conf.register_opt(opt_2, group='group_2')
conf.register_opt(opt_3)

conf(['--config-file', 'config.conf'])

assert conf.group_1.opt_1 == 'bar'
assert conf.group_2.opt_2 == 'eggs'
assert conf.opt_3

Assuming that the file config.conf has the following content:

[group_1]
opt1 = bar

[DEFAULT]
opt_2 = eggs
opt_3 = True

the script will succeed, but will log three respective warnings about the given deprecated options.

There are also deprecated_reason and deprecated_since parameters for specifying some additional information about a deprecation.

All the mentioned parameters can be mixed together in any combinations.