oslo_config模块是openstack用来解析命令行选项和读取配置文件的一个库。该库在OpenStack中使用是非常广泛的,应该是所有的组件都用到了它。所以在学习OpenStack代码之前,非常有必要了解一下它的使用方法和运行原理。

使用方法

首先先了解一下这个模块的使用方法。要用这个模块来解析命令行选项或者配置文件前,需要将命令行选项和配置文件选项声明并注册到模块实例中去,模块实例再从命令行或者配置文件中读取对应的值。其中需要注意的是如果需要解析命令行选项则应该将所有的选项都注册进实例,因为无法提前预知用户会使用什么选项。

一个简单的通用流程应该是下面这样:
oslo_config_process.png

graph LR
A(声明需要用到的配置项/命令行选项)-->B(注册配置项/命令行选项)
B --> C(解析文件/命令行)

下面就按照这个流程分别讲述一下解析命令行选项和配置文件的使用。

1. 解析CLI选项参数

#!/bin/python
# -*- coding: utf-8 -*-
from oslo_config import cfg
import sys

# 声明参数选项
cli_opts = [
    cfg.StrOpt('ip_address',
               default='88.88.88.88',
               help='just ip address'),
]

# 注册参数选项
cfg.CONF.register_cli_opts(cli_opts)

# 解析参数选项
cfg.CONF(args=sys.argv[1:])

# 读取解析后的参数选项
for name, values in cfg.CONF.items():
    print "%s : %s" % (name, values)

将以上代码保存至文件parameter.py并通过命令行执行。
首先不传入对应的参数:

# python parameter.py
config_file : []
config_dir : None
ip_address : 88.88.88.88

可以看到打印出来的ip_address显示的是声明的时候指定的默认值。
下面传入对应的参数:

# python parameter.py --ip_address 1.1.1.1
config_file : []
config_dir : None
ip_address : 1.1.1.1

可以看到显示的参数值就是命令行中指定的值。
config_file和config_dir是模块中默认会自动注册的两个项目,用来指定模块读取的配置文件的存放路径,既然注册了,自然也可以通过命令参数传入。

# touch /tmp/example.conf
# python parameter.py --ip_address 1.1.1.1 --config-file /tmp/example.conf
config_file : ['/tmp/example.conf']
config_dir : None
ip_address : 1.1.1.1

2. 解析配置文件

2.1 解析默认分组配置项

首先我们需要先创建一个测试用的配置文件,例如我将它创建为/etc/test/oslo_config/example.conf,内容如下:

[DEFAULT]
app_name = myapp
debug = true
extern = message


[database]
ip = 11.11.11.11
port = 8118

一样的流程,先声明需要读取的配置项,注册配置项,解析配置文件,如以下代码所示:

#!/bin/python
# -*- coding: utf-8 -*-
from oslo_config import cfg
import sys

# 声明配置项
conf_opts = [
    cfg.StrOpt('app_name',
               default='unknown_app',
               help='app name'),
    cfg.BoolOpt('debug',
                default=False,
                help='open debug log')
]

# 注册配置项
cfg.CONF.register_opts(conf_opts)

# 解析配置文件
cfg.CONF(default_config_files=['/etc/test/oslo_config/example.conf'])

# 读取解析后的配置
for name, values in cfg.CONF.items():
    print "%s : %s" % (name, values)

将代码保存至文件file.py并执行

# python file.py 
debug : True
config_dir : None
config_file : ['/etc/test/oslo_config/example.conf']
app_name : myapp

与参数解析不同的是,在解析完配置文件后,我们还能继续声明并注册需要的配置项,依然能够正常读取这些后来注册的配置项的值。例如在上面代码的基础上继续读取extern配置项:

#!/bin/python
# -*- coding: utf-8 -*-
from oslo_config import cfg
import sys

# 声明配置项
conf_opts = [
    cfg.StrOpt('app_name',
               default='unknown_app',
               help='app name'),
    cfg.BoolOpt('debug',
                default=False,
                help='open debug log')
]

# 注册配置项
cfg.CONF.register_opts(conf_opts)

# 解析配置文件
cfg.CONF(default_config_files=['/etc/test/oslo_config/example.conf'])

# 读取解析后的配置
for name, values in cfg.CONF.items():
    print "%s : %s" % (name, values)

# 追加extern配置项
add_opts = [
    cfg.StrOpt('extern',
               default='unknown',
               help='extern message'),
]
cfg.CONF.register_opts(add_opts)

# 读取解析后的配置
print "========== after add extern =========="
for name, values in cfg.CONF.items():
    print "%s : %s" % (name, values)

执行结果如下所示:

# python file.py 
debug : True
config_dir : None
config_file : ['/etc/test/oslo_config/example.conf']
app_name : myapp
========== after add extern ==========
debug : True
config_dir : None
extern : message    # extern被正确读取 !!
config_file : ['/etc/test/oslo_config/example.conf']
app_name : myapp

2.2 解析指定配置组配置项

我们看到上面的配置文件中还含有一个配置组:database,那么应该如何读取配置组中的配置项呢?其实流程大致相同,只不过多了两步:声明组和注册组。
示例代码如下:

#!/bin/python
# -*- coding: utf-8 -*-
from oslo_config import cfg
import sys

# 声明组
database_group = cfg.OptGroup(name='database', title='database options')

# 声明配置项
database_opts = [
    cfg.StrOpt('ip',
               default='0.0.0.0',
               help='database ip address'),

    cfg.IntOpt('port',
               default='999',
               help='database port'),
]

# 注册配置组
cfg.CONF.register_group(database_group)

# 注册配置项
cfg.CONF.register_opts(database_opts, group=database_group)

# 解析配置文件
cfg.CONF(default_config_files=['/etc/test/oslo_config/example.conf'])

# 读取解析后的配置
for name, values in cfg.CONF.database.items():
    print "%s : %s" % (name, values)

运行结果如下:

# python file.py 
ip : 11.11.11.11
port : 8118

2.3 结合使用参数解析和配置文件解析

接下来就进阶提升一下,将命令行选项和配置文件两者结合起来使用,其实这也是OpenStack目前使用的方式。如何结合起来呢?就是利用config-file和config-dir这两个自动被声明和注册的选项。上面解析配置文件的示例代码中我们将配置文件的路径放在了default_config_files这个参数中,现在改为命令行选项传入:

#!/bin/python
# -*- coding: utf-8 -*-
from oslo_config import cfg
import sys

# 声明配置项
conf_opts = [
    cfg.StrOpt('app_name',
               default='unknown_app',
               help='app name'),
    cfg.BoolOpt('debug',
                default=False,
                help='open debug log')
]

# 注册配置项
cfg.CONF.register_opts(conf_opts)

# 解析配置文件
cfg.CONF()

# 读取解析后的配置
for name, values in cfg.CONF.items():
    print "%s : %s" % (name, values)

代码基本不变,不过cfg.CONF()中不再传入default_config_files。
然后我们按照下面的方法执行:

python file.py  --config-file /etc/test/oslo_config/example.conf
debug : True
config_dir : None
config_file : ['/etc/test/oslo_config/example.conf']
app_name : myapp

你没看错,我们一样读取出了指定配置文件中的配置项。当然OpenStack中不会仅仅只传入config-file这一个参数,举个例子,像neutron-server启动时传入的参数如下:

/usr/bin/neutron-server --config-file /usr/share/neutron/neutron-dist.conf \
                        --config-dir /usr/share/neutron/server \
                        --config-file /etc/neutron/neutron.conf \
                        --config-file /etc/neutron/plugin.ini \
                        --config-dir /etc/neutron/conf.d/common \
                        --config-dir /etc/neutron/conf.d/neutron-server \
                        --log-file /var/log/neutron/server.log

传入的参数就多了很多。