• nova读取配置文件流程



          在我们安装nova的过程中,设置它的配置文件/etc/nova/nova.conf是必不可少的一步。配置好nova.conf文件,nova-compute、nova-network等服务才有可能正常启动。当然在修改nova.conf文件后,必须重启nova的所有服务,新的配置才能生效。
          其实我们的配置文件不一定非要存放为/etc/nova/nova.conf,也可以设置成任意目录下的任意文件。nova获取配置文件的方式有两种:
    1、通过命令行 选项 --config-file 指定 任意文件为配置文件(当然前提是有读写权限)
    例如:  /usr/bin/python /usr/bin/nova-compute--config-file=/etc/nova/nova.conf--config-file=/etc/nova/nova-compute.conf
          通过--config-file选项,可以指定任意多个配置文件,上例给nova-compute服务指定了两个配置文件。此时,在这两个文件设置的配置项,都是有效的。但如果这两个文件有相同的配置项,而且这个配置项是不允许有多个值的,那会以哪个为有效指呢,还是会出现错误?
    2、如果在服务启动时,没有通过--config-file指定配置文件,那么nova会在几个特定的目录按照先后顺序下寻找配置文件。在每个服务启动时,会按照以下顺序寻找配置文件:
    [~/.nova/, ~/, /etc/nova/,/etc/]
    例如,启动nova-compute时,它会在这些目录下分别寻找nova.conf和nova-compute.conf,如果~/.nova/nova.conf存在,那么就不会再寻找其它目录下的nova.conf了。对于nova-compute.conf文件也是一样。

    nova配置文件内容格式如下:
    [DEFAULT]
    dhcpbridge_flagfile=/etc/nova/nova.conf
    dhcpbridge=/usr/bin/nova-dhcpbridge
    libvirt_use_virtio_for_bridges=True
    connection_type=libvirt
    ...…
    分析

        下文用于分析nova组件是如何读取命令行和文件配置,从分析流程中我们可以知道:
    1)nova是如何寻找配置文件,配置文件存放在哪个目录下才能被它读取
    2)配置文件的编辑应该遵守什么样的格式,才能被nova正常读取
    3)当我们需要在配置文件中添加一个我们自己的选项时,我们应该在哪些地方进行如何修改,才能被nova获取到我们写入配置文件的值

    nova有多个服务:nova-cert、nova-consoleauth、nova-manage、nova-novncproxy、nova-scheduler、 nova-api、nova-compute、nova-dhcpbridge、nova-network、nova-rootwrap。各个服务在启动时,读取配置文件的流程都是一样的。下面针对nova-compute的配置读取过程进行分析,其他的服务相同。

    compute启动脚本
    /usr/bin/nova-compute文件:
      42 if __name__ == '__main__':
      43     flags.parse_args(sys.argv)
      44     logging.setup('nova')
      45     utils.monkey_patch()
      46     server = service.Service.create(binary='nova-compute')
      47     service.serve(server)
      48     service.wait()

    第42行调用flags.parse_args(sys.argv)函数,该函数定义于nova/flags.py。它调用CommonConfigOpts实例进行命令行参数和配置文件的读取:
    nova/flags.py:
      36 FLAGS = cfg.CONF
      37 
      38 
      39 def parse_args(argv,default_config_files=None):
      40     FLAGS.disable_interspersed_args()
      41     return argv[:1] + FLAGS(argv[1:],
      42                             project='nova',
      43                             default_config_files=default_config_files)

    继续查看cfg.CONF
    /nova/openstack/common/cfg.py
    1654  CONF =CommonConfigOpts()

        由上面代码可知,nova-compute后面的命令行参数直接调用CommonConfigOpts对象处理,并设project=‘nova’,默认配置文件为空。 CommonConfigOpts乃何许类也,它都干些什么呢?
          CommonConfigOpts是ConfigOpts的子类,而在ConfigOpts中定义__call__函数,所以CommonConfigOpts对象可以像函数一样,被直接调用。为了能够理解这两个类是干什么的,我们需要先了解另外几个类:

    optparse.OptionParser
    optparser库中的OptionParser,用来解析通过命令行指定的参数,该库目前不再更新,将会被argparser代替。但openstack使用该库来解析,所以做个简单介绍:

    from optparse import OptionParser

    parser = OptionParser()
    parser.add_option("-f", "--file", dest="filename",#dest用来明确指定解析参数后对应的属性名,
                                                                                      #如果没有指定则为file
                      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")

    (options, args) = parser.parse_args(['-f', 'test.txt', 'ddd','ffff'])
    该脚本执行过后,options.filename=test.txt、options.verbose=True。args=[ 'ddd','ffff'],它是用于返回不能被OptionParser识别的参数。

    optparse.OptionGroup
    当参数较多时,可利用OptionGroup对其进行分类,这样在输出help信息时,能够分类整齐些,所以每一个OptionGroup都必须与一个OptionParser关联(多对一的关系)。在使用上,与OptionParser差不多。如下:
    group = OptionGroup(parser, "Dangerous Options",#其中parser为上面定义的类
                        "Caution: use these options at your own risk.  "
                        "It is believed that some of them bite.")
    group.add_option("-g", action="store_true", help="Groupoption.")
    parser.add_option_group(group)

    (options,args) = parser.parse_args(['-f', 'test.txt', 'ddd','ffff'])
    该option和args的值与上例中结果一样。

    openstack.common.cfg.Opt:
            self.name = name   #name对应命令行参数名和配置文件中的配置项名
              self.dest = dest #解析命令行参数时生成属性的名,通过该名可获取命令行参数值,如上例filename
            self.short = short  #命令行参数的简写,如上例中f
            self.default = default  #该选项的默认值
            self.metavar = metavar  #用于在显示帮助信息时用的,如FILE
            self.help = help
            self.secret = secret     #bool类型,指定该选项是否为保密信息,这样在打印日志时会用特殊字符代替
            self.required = required#是否为必填项,在读取配置文件后,会依据该值进行检查
            self.deprecated_name = None  #该选项的备用名,可用作命令行参数名和配置项名

    ConfigParser(iniparser.BaseParser):
            self.filename = filename
            self.sections = sections

        def parse(self):
            with open(self.filename) as f:
                returnsuper(ConfigParser, self).parse(f)

    对于每一个配置文件,都会生成一个ConfigParser对象。该类调用BaseParser.parse()函数,将文件内容解析成keyvalue对,定义如下:
      def parse(self, lineiter):
            key = None
            value = []

            for line in lineiter:
                self.lineno += 1

                line =line.rstrip()
                if notline:
                    # Blank line, ends multi-linevalues
                    if key:
                        key, value = self._assignment(key, value)
                    continue
                elifline[0] in (' ', ' '):
                    # Continuation of previousassignment,这里可以知道,为什么配置项不能以空格开始了
                    if key is None:
                        self.error_unexpected_continuation(line)
                    else:
                        value.append(line.lstrip())
                    continue

                ifkey:
                    # Flush previous assignment,if any
                    key, value =self._assignment(key, value)

                if line[0]== '[':
                    # Section start
                    section =self._get_section(line)
                    if section:
                        self.new_section(section)
                elifline[0] in '#;':     #这里可以看出配置项的key和value可以通过#或;进行分隔
                    self.comment(line[1:].lstrip())
                else:
                    key, value =self._split_key_value(line)
                    if not key:
                        return self.error_empty_key(line)
            if key:#用作处理最后一个key value对
                # Flushprevious assignment, if any
                self._assignment(key, value)

    从上面的代码可知,ConfigParser读取配置文件,将每个配置项以{key:value}储存在sections中{‘section1’:{‘key11’:value}}。在配件文件中,每个配置项必须处于一个section之中,即在一个通过[section]定义的行之下。否则会报parse_exc异常。这样,一个配置文件中所有配置项都被读到了ConfigParser的sections中。

    classMultiConfigParser(object):
        def__init__(self):
            self.parsed = []

        def read(self,config_files):
            read_ok = []

            for filename in config_files:
                sections ={}
                parser =ConfigParser(filename, sections)

                try:
                    parser.parse()
                exceptIOError:
                    continue
                self.parsed.insert(0, sections)
                read_ok.append(filename)

            return read_ok

        def get(self, section,names, multi=False):
            rvalue = []
            for sections in self.parsed:
                if sectionnot in sections:
                    continue
                for namein names:
                    if name insections[section]:
                        if multi:
                            rvalue =sections[section][name] + rvalue
                        else:
                            returnsections[section][name]
            if multi and rvalue != []:
                returnrvalue
            raise KeyError
    该类的read函数,为每个配置文件初始化一个ConfigPaser对象,并将该对象解析过后的sections存放到parsed只中,这样每个配置文件就对应了parsed列表中的一项,所有配置文件的配置项都存放在parsed成员变量中。
          这里需要注意一点,在往parsed中添加sections时,使用 parsed.insert(0,sections)。排在后面的配置文件,解析后会放在parsed的最前面。当调用get()获取配置项的时,如果该配置项不为多选项(multi),那么只会去parsed前面sections中的值。也就是说如果一个配置项在多个配置文件中被重复定义,那么只会读取最后一个配置文件中的值作为使用值。


          好了,在了解了上面几个类之后,我们可以查看ConfigOpts和CommonConfigOpts类了。ConfigOpts的成员变量如下:
    ConfigOpts(collections.Mapping):
    /nova/openstack/common/cfg.py 
        def__init__(self):
            """Construct a ConfigOpts object."""
            self._opts = {}   # dict ofdicts of (opt:, override:, default:)
            self._groups = {}

            self._args = None
            self._oparser = None  #命令行参数解释器,即一个OptionParser对象
            self._cparser = None  #配置文件解释器,即一个MultiConfigParser对象
            self._cli_values = {}     #程序启动时,通过_oparser解释的命令行参数值
            self.__cache = {}         #缓存
            self._config_opts = []  
            self._disable_interspersed_args = False

       def __call__(self,
                  args=None,
                  project=None,
                  prog=None,
                  version=None,
                  usage=None,
                 default_config_files=None):

          self.clear()

          self._setup(project, prog, version, usage,default_config_files)

          self._cli_values, leftovers =self._parse_cli_opts(args)

          self._parse_config_files()

          self._check_required_opts()

           returnleftovers


    clear()函数:
    清空opts、groups、cache等成员变量,对于刚初始化的ConfigOpts对象,基本什么也没干。

    _setup函数:
          def_setup(self, project, prog, version, usage,default_config_files):
            """Initialize a ConfigOpts object for optionparsing."""
            if prog is None:
                prog =os.path.basename(sys.argv[0])

            if default_config_files is None:
                #在['~/.nova/', '~/', '/etc/nova/','/etc/']寻找配置文件nova.conf和nova-compute.conf
                default_config_files = find_config_files(project, prog)

            self._oparser =optparse.OptionParser(prog=prog,
                                                  version=version,
                                                  usage=usage)
            if self._disable_interspersed_args:
                self._oparser.disable_interspersed_args()

            self._config_opts = [
                MultiStrOpt('config-file',
                            default=default_config_files,
                            metavar='PATH',
                            help='Pathto a config file to use. Multiple config '
                                  'files canbe specified, with values in later '
                                  'filestaking precedence. The default files '
                                  ' used are:%s' % (default_config_files, )),
                StrOpt('config-dir',
                        metavar='DIR',
                        help='Path to a config directory to pull *.conf'
                            'filesfrom. This file set is sorted, so as to '
                            'provide apredictable parse order if individual '
                            'optionsare over-ridden. The set is parsed after '
                            'thefile(s), if any, specified via --config-file, '
                            'henceover-ridden options in the directory take '
                            'precedence.'),
            ]
            #注册命令行参数config-file和config-dir,这样就可以通过命令行指定该配置项了。
            self.register_cli_opts(self._config_opts)

            self.project = project
            self.prog = prog
            self.version = version
            self.usage = usage
            self.default_config_files =default_config_files
    1、寻找配置文件,如果__call__的参数default_config_files=None。那么它将按一下顺序~/.nova/,~/, /etc/nova/,/etc/搜索配置文件nova.conf和nova-compute.conf,对于每个配置文件只要在一个目录下搜索到,剩余的目录就不会被搜索。并将default_config_files赋值为搜索的结果,
    2、用命令行参数config-file、config-dir初始化命令行解释器:
          self._oparser =optparse.OptionParser(prog=prog, version=version,usage=usage)
    这样就能解析通过命令行传递参数config-file、config-dir的值。

    _parse_cli_opts函数
    用于解析通过命令行传过来的参数:它先调用Opt._add_to_cli将所有的opts全都注册到self._oparser中。再调用self._oparser.parse_args(args)解析所有的命令函数。然后将解析结果存放到self._cli_values中。
    此时命令行参数已经解析完成,然后解析配置文件中的选项。

    _parse_config_files函数:
            """Parse the config files from --config-file and--config-dir.

            :raises: ConfigFilesNotFoundError ,ConfigFileParseError
            """
            config_files = list(self.config_file)
         
            if self.config_dir:  #如果指定了config_dir,那么该目录下所有匹配*.conf的文件,都会被当作配置文件
                config_dir_glob = os.path.join(self.config_dir, '*.conf')
                config_files += sorted(glob.glob(config_dir_glob))

            config_files = [_fixpath(p) for p inconfig_files]

            self._cparser = MultiConfigParser()

            try:
                read_ok =self._cparser.read(config_files)
            except iniparser.ParseError as pe:
                raiseConfigFileParseError(pe.filename, str(pe))
             
            if read_ok != config_files:
                not_read_ok = filter(lambda f: f not in read_ok,config_files)
                raiseConfigFilesNotFoundError (not_read_ok)

    在这个函数中,self._cparser被初始化:
    self._cparser =MultiConfigParser()
    如果通过命令行参数--config-file指定配置文件,和--config-dir指定配置目录。那这些文件全都会通过
    read_ok =self._cparser.read(config_files)
    解析后的选项和值都被存放到列表self._cparser.parsed中,其中每一元素对应一个配置文件,元素结构是:
    sections[section][key]=value

    _check_required_opts函数:
    根据每个opt的requred属性,判断是否所有必设置选项是否被设置,如果没有,则报异常。

    classCommonConfigOpts(ConfigOpts):
    CommonConfigOpts只是重新定义了 构造函数,在初始化对象时,注册了几个Opt对象:-d-v--log-config--log-format--log-date-format--log-file--log-dir--use-syslog--syslog-log-facility。这样就可以通过命令行参数对这些配置项进行设置。
    到此,命令行参数和配置文件参数都已解析完成,所有的命令行参数和配置文件内容都保存到FLAGS中。


    获取配置项的值
      def _do_get(self, name, group=None):
            if group is None and name in self._groups:
                returnself.GroupAttr(self, self._get_group(name))

            #首先要获取该对象的注册信息,所以使用前必须先注册
            info = self._get_opt_info(name, group)
            opt = info['opt']

            if 'override' in info:
                returninfo['override']

            values = []
            if self._cparser is not None:
                section =group.name if group is not None else 'DEFAULT'
                try:
                    value =opt._get_from_config_parser(self._cparser, section)
                exceptKeyError:
                    pass
                exceptValueError as ve:
                    raiseConfigFileValueError(str(ve))
                else:
                    if not opt.multi:
                        # No need to continue since the last valuewins
                        return value[-1]
                    values.extend(value)

            name = name if group is None else group.name +'_' + name
            value = self._cli_values.get(name)
            if value is not None:
                if notopt.multi:
                    return value

                returnvalue + values

            if values:
                returnvalues

            if 'default' in info:
                returninfo['default']  

          returnopt.default
    从该流程中,可以看出。获取一个配置项的值,首先得注册该配置项。获取该配置项的值的优先级如下:
    info['override'],配置文件,命令行参数、info['default']、Opt['default']

    注册一个配置项流程如下:
    compute_opts = [
        cfg.StrOpt('instances_path',
                    default='$state_path/instances',
                    help='where instances arestored on disk'),
        cfg.IntOpt('live_migration_retry_count',
                    default=30,
                    help="Number of 1 secondretries needed in live_migration"),
    ]
    FLAGS = flags.FLAGS
    FLAGS.register_opts(compute_opts)
    然后就可以如下进行访问了
    retry = FLAGS. live_migration_retry_count

    StrOpt、IntOpt、BoolOpt、FloatOpt、ListOpt是Opt的子类,它们主要是重定义了获取值和注册OptionParser的函数,以IntOpt为例:
    class IntOpt(Opt):

        """Int opt values areconverted to integers using the int() builtin."""

        def_get_from_config_parser(self, cparser, section):
            """Retrieve the opt value as a integer fromConfigParser."""
            return [int(v) for v inself._cparser_get_with_deprecated(cparser,
                    section)]

        def_get_optparse_kwargs(self, group, **kwargs):
            """Extends the base optparse keyword dict forinteger options."""
            return super(IntOpt,
                          self)._get_optparse_kwargs(group, type='int',**kwargs)

    这样,特定类型的Opt在获取配置文件中的值和通过命令行参数获得的值,都能够自动转换成相应的类型,方便使用。

  • 相关阅读:
    MyBatis-XML和注解
    spring-Servlet/Tomcat/Spring
    spring-过滤器与拦截器
    spring-spring task原理及使用
    spring-事务源码解析-todo
    架构设计-SOA架构和微服务架构的区别
    架构设计-微服务架构初步学习
    Python 中 OS 模块获取文件/目录路径方法
    python 中 pymysql 模块链接 MySQL 数据库,进行数据验证及封装
    Python 接口测试的步骤、特点、关注点、get和post 区别
  • 原文地址:https://www.cnblogs.com/james1207/p/3366121.html
Copyright © 2020-2023  润新知