看网上的一篇文档,说这是个“神奇”的FLAGS,可以分布式地定义参数,即每个模块只需定义自己需要的,通过”import”引入其它模块定义的参数。下面 来看一下它是怎么实现这种“神奇”的。
nova整个项目统一采用flags模块处理可由用户定义的参数,包括配置文件、通过命令行传入的参数等。支持flags的是cfg这个模块,位于nova.openstack.common包中。首先来看一下关于cfg这个模块的类图:
【类图介绍】
Opts:所有参数的描述类,主要属性包括:
name:参数名称 --verbose
dest:参数在字典内对应的key
short: 参数的缩写 -v
default: 参数的默认值
metavar: 参数的帮助信息 --help 输出
help: 帮助信息
StrOpts,IntOpts...: 参数值类型对应的Opts子类。其中:
ListOpts:对应['ec2', 'osapi_compute']
MultiStrOpt:对应如果同一字符串变量str重复赋值多次,则转换成list。
例如: str=001
str=002 ==> {str:[001,002]}
OptGroup:对应不同[section]的opts
ConfigParser: 配置文件的parser类,实际parser方法:iniparser.BaseParser.parse(f)
MultiConfigParser: 解析配置文件的实现类。
OptionParser: 解析cli参数的实现类,主要使用python的optparse组件。
ConfigOpts:包含以上的类对象,是解析配置文件,用户自定义,cli参数的统一接口类。
CommonConfigOpts 对外接口,将全局变量和opt的逻辑操作分离。
由上可知,使用Opts及其子类来描述参数信息,使用ConfigOpts及*Parser类来配置参数,使用CommonConfigOpts来做对外接口。
【优势】
1. 实现config和flag不必集中在全局py中定义、维护。
a 方法:ConfigOpts::import_opt(self,name,module_str,group)
思路:导入指定模块自定义的opts,能够实现不同module之间opt的依赖关系,而且由于不存在共享的global opts,opts的导入、维护对于其他模块无影响。默认的opt是cfg.CONF。
b 方法:ConfigOpts::register_opt(self, opt, group=None)
思路:
方法中self._opts[opt.dest] = {'opt': opt},通过opt对象中的name来做其dest(self.dest = self.name.replace('-', '_')from opt class)。
示例:
***logging_cli_opts = [
StrOpt('log-config',
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.'),...]
***self.register_cli_opts(self.logging_cli_opts)
将StrOpt的对象以dest:log_config保存在self._opts中。
c 总结:此为在多数module中经常看到cfg.CONF,opts字典定义,register_cli_opts()的来源。
2. cfg.CONF作为全局opt字典,通过ConfigOpts的__cache做opt遍历的缓存,并通过@__clear_cache,做缓存的释放。
a 方法:ConfigOpts:: __clear_cache(f)
思路:__cache字典中存放opt的键值对,每次在获取opt的值时,先查询__cache字典,如果没有,则查询conf文件,然后加到__cache字典。
代码:
def _get(self, name, group=None):
if isinstance(group, OptGroup):
key = (group.name, name)
else:
key = (group, name)
try:
return self.__cache[key] # 查询__cache字典
except KeyError:
value = self._substitute(self._do_get(name, group))
self.__cache[key] = value
return value
b 总结:通过维护conf文件中的所有配置项的子集__cache,提高查询性能和内存使用率。
【其它】
1. 定义Error的handler类。
2. 定义配置文件相关的方法,例如:
a _get_config_dirs:获取对应project的配置文件目录。规则:
If a project is specified, following directories are returned::
~/.${project}/
~/
/etc/${project}/
/etc/
Otherwise, these directories::
~/
/etc/
b find_config_files: 默认在配置文件目录查找*.conf文件
我觉得cfg模块的主要作用有两个:一个是对配置文件进行解析,一个是对命令行参数进行解析。
相应的,有两个较为核心的类:MultiConfigParser和OptionParser,前者是对配置文件进行解析的,这个类是nova自己 写的,后者是对命令行进行解析的,用的是python库中的类。这两个类最终整合到ConfigOpts类中,然后又派生出了 CommonConfigOpts类,就是通过这个类和外面进行交互的。
类图只是把主要的类以及主要的方法和变量罗列出来,还有其它很多类这里就没有详细列举了,先举一个简单的例子,来看一下最核心的东西,其它的都是在围绕这个核心进行了加工处理:
1. 命令行解析
核心的代码可举例如下:
from optparse import OptionParser
[...]
parser =OptionParser(usage=“%prog [-f] [-q]“, version=“%prog 1.0″)
parser.add_option(“-f”,“–file”, dest=“filename”,
help=“write report to FILE”, metavar=“FILE”)
parser.add_option(“-q”,“–quiet”,
action=“store_false”, dest=“verbose”, default=True,
help=“don’t print status messages to stdout”)
group =OptionGroup(parser,“Dangerous Options”,
“Caution: use these options at your own risk. “
“It is believed that some of them bite.”)
group.add_option(“-g”, action=“store_true”, help=“Group option.”)
parser.add_option_group(group)
(options, args)= parser.parse_args()
OptionParser这个类,就是对一个命令的解析,包括这个命令的各个选项,这些选项如何处理。如果对这个地方不懂的,可以去python标准库中找optparse这个模块。 在cfg中,进行了如下封装:
(1)把选项(option)封装成了一个类Opt,并且对不同的选项类型分别派生出了StrOpt,BoolOpt等
(2)对OptionGroup进行了封装,即OptGroup,里面维护了一个OptionGroup对象和一个_opts字典,用来存放Opt对象。
(3)ConfigOpts类封装了OptGroup和Opt类,以及一个OptionParser类,这样这三个主角就融合在一起了,并且提供了register_*()方法,供外部调用,来向OptionParser对象添加选项(option)或组(group)。
2. 配置文件解析
至于对配置文件的解析,则主要是MultiConfigParser类和ConfigParser类,两者是聚合的关 系,MultiConfigParser提供了read()和get()方法,前者是解析配置文件,后者是读取配置文件中的信息,最终也聚合在了 ConfigOpts中。核心代码:
#对所有配置文件取出每一区域中每一项的值,放到parsed列表中
class MultiConfigParser(object):
def __init__(self):
self.parsed =[]
def read(self, config_files):
read_ok =[]
for filename in config_files:
sections ={}#相对于一个文件中的sections
parser =ConfigParser(filename, sections)#操作级别是一个文件中的所有区域
try:
parser.parse()
except IOError:
continue
self.parsed.insert(0, sections)
read_ok.append(filename)
return read_ok
#要从所有的配置文件所生成的sections中找到section中的names的值,并返回,返回值的类型是一个列表。但多数情况下,这个列表中的元素只有一个。
def get(self, section, names, multi=False):
rvalue =[]
for sections in self.parsed:
if section notin 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
3. FLAGS如何“神奇”
FLAGS神奇的地方在于ConfigOpts中的__cache变量,它是一个字典类型的缓存区,里面存放的是所定义的参数组成的键值对,在外部通过FLAGS取参数值的时候,会调用ConfigOpts中的_get()方法,来取得参数值,该方法如下:
def _get(self, name, group=None):
if isinstance(group, OptGroup):
key =(group.name, name)
else:
key =(group, name)
# 如果这个key在__cache中存在,则返回其对应的值,
# 如果不存在这个key,那么在__cache中就新生成一个key,并为其赋值
try:
return self.__cache[key]
except KeyError:
value = self._substitute(self._do_get(name, group))
self.__cache[key]= value
return value
可以看到如果这个key在__cache中,那么就直接返回,如果不存在这个key,那么就调用_do_get()方法从_opts或_group中查找这个key所对应的值,找到之后,将这个键值对放入到__cache中。
一般一个模块都有它自己需要的一些参数,这些参数别的模块用不到,所以采用了这种缓存的技术,实现了动态的存取参数。
一般一个模块都会在它开始的地方,把它需要的参数用FLAGS的register_*()方法注册到_opts字典中,而这些retister_*()方法都带着一个修饰器:@__clear_cache(),如:
@__clear_cache
def register_opts(self, opts, group=None):
“”"Register multiple option schemas at once.”"”
for opt in opts:
self.register_opt(opt, group, clear_cache=False)
这个修饰器的作用就是清空__cache字典,即把之前模块存放到这个__cache中的值都清空掉,然后就可以添加自己的参数到缓存中,这样既增加了程序的可读性,又提高了查找的效率。
4. 使用unittest进行测试
这里主要测试一下cfg模块的配置文件解析功能,在/etc/nova/目录下,添加一个nova.conf配置文件,通过注册几个参数,然后再取得参数的值,代码如下:
import sys
from nova.openstack.common import cfg
import unittest
global_opts =[
cfg.StrOpt('aws_access_key_id',
default='admin',
help='AWS Access ID'),
cfg.IntOpt('glance_port',
default=9292,
help='default glance port'),
cfg.BoolOpt('api_rate_limit',
default=True,
help='whether to rate limit the api'),
cfg.ListOpt('enabled_apis',
default=['ec2','osapi_compute','osapi_volume','metadata'],
help=‘a list of APIs to enable by default’),
cfg.MultiStrOpt(‘osapi_compute_extension’,
default=[
'nova.api.openstack.compute.contrib.standard_extensions'
],
help=‘osapi compute extension to load’),
]
mycfg=cfg.CONF
class TestCfg(unittest.TestCase):
def setup(self):
pass
def test_find_config_files(self):
config_files=cfg.find_config_files(‘nova’)
self.assertEqual(config_files,['/etc/nova/nova.conf'])
def test_register_opts(self):
mycfg.register_opts(global_opts)
aws_access_key_id=mycfg.__getattr__(‘aws_access_key_id’)
self.assertEqual(aws_access_key_id,‘admin’)
def test_cparser(self):
mycfg([''],project=‘nova’)
print mycfg._cparser.parsed
rabbit_host=mycfg._cparser.get(‘DEFAULT’,['--rabbit_host'])
self.assertEqual(rabbit_host,['10.10.10.2'])
suite = unittest.TestLoader().loadTestsFromTestCase(TestCfg)
unittest.TextTestRunner(verbosity=2).run(suite)