nova 配置文件机制——FLAGS

看网上的一篇文档,说这是个“神奇”的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模块的主要作用有两个:一个是对配置文件进行解析,一个是对命令行参数进行解析。

相应的,有两个较为核心的类:MultiConfigParserOptionParser,前者是对配置文件进行解析的,这个类是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,并且对不同的选项类型分别派生出了StrOptBoolOpt

2)对OptionGroup进行了封装,即OptGroup,里面维护了一个OptionGroup对象和一个_opts字典,用来存放Opt对象。

3ConfigOpts类封装了OptGroupOpt类,以及一个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中。

一般一个模块都有它自己需要的一些参数,这些参数别的模块用不到,所以采用了这种缓存的技术,实现了动态的存取参数。

一般一个模块都会在它开始的地方,把它需要的参数用FLAGSregister_*()方法注册到_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)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值