天天看點

Openstack Oslo.config 學習(一)

轉載:http://www.choudan.net/2013/11/27/OpenStack-Oslo.config-%E5%AD%A6%E4%B9%A0(%E4%B8%80).html

OpenStack的項目貌似越來越多了,在Grizzly版之前,每個項目都得實作一套處理配置檔案的代碼。在每個項目的源碼中基本上都可以找到

openstack/common/cfg.py,iniparser.py

檔案,當然,這些不同項目之間的cfg.py等檔案很大可能是copy-and-paste分分鐘來搞定。這種情況肯定無法被大神忍受,最終,社群決定改變這一切,提出了Oslo項目。Oslo項目的宗旨是提供一系列OpenStack Projects共享的基礎庫,可以從wiki的原話中了解到。

To produce a set of python libraries containing code shared by OpenStack projects. The APIs provided by these libraries should be high quality, stable, consistent, documented and generally applicable.

社群顯然已經無法忍受在不同項目中大量重複的代碼了。Oslo 項目提供了一系列的庫,我們接觸的最多的為oslo.config這個庫,要來處理程式指令行參數和配置檔案。當然還有其他的,例如,pbr(Python Build Reasonableness)與setuptools相關的庫,hacking,用來處理編碼風格的庫,還有oslo.messageing等等。這些庫有的是在oslo這個namespace下,有的是完全獨立的。Oslo開發者是這樣考慮的,如果這個庫存在被廣泛使用的潛質的話,則不将其放在oslo命名空間下。

Oslo項目包含的庫較多,我們将目光聚集到接觸最多的oslo.config這個庫上,這個庫應該是所有OpenStack項目中重用最多的。

oslo.config

在了解oslo.confg的使用和實作之前,我們需要知道,這個庫是用來解決什麼樣的問題。在有了此問題答案的基礎上,然後沿着怎樣來使用這個庫和這個庫到底是如何實作的路線來分析。

前面我們介紹了,OpenStack在G版之前的幾乎每個項目都得拷貝一份cfg.py,iniparser.py兩個檔案放到

openstack/common/

目錄下,這兩個檔案主要緻力于解決讀取配置檔案和解析指令行參數的問題。在實際使用OpenStack的過程中,我們啟動一個服務,例如nova-api或者glance-api,往往都是這樣的形式:

/usr/bin/nova-api –config-file=/etc/nova/nova.conf –log-file=/var/log/nova/api.log

從這個啟動指令來看,我們需要能夠正确的處理指令行參數,還有配置檔案。細心觀察會發現,不同的服務,nova-api,glance-api等等,都會有一些共同的指令行參數,如上面的–config-file,–log-file等等,然後每個服務還有自己專屬的指令行參數。對于配置檔案,可能存在多個,例如,nova項目存在多個服務,nova-api,nova-compute等等。那麼這些nova services之間會存在大量共同的配置,對此,Oslo建議如果支援多個配置檔案的話,那麼就很給力了,像這個形式:

--config-file=/etc/nova/nova-commmon.conf --config-file=/etc/nova/nova-api.conf

。對配置檔案格式的支援,目前主要是ini風格的檔案。除了解析配置選項之外,另一個問題是,快速通路到這些配置選項的值。

是以,olso.config wiki上貼出了oslo.config需要解決的幾點問題:

  • command line option parsing
  • common command line options
  • configuration file parsing
  • option value lookup

在wiki中還提到一種場景,建議最好将一些options的預設值寫在code裡面,同時也在config file中作為注釋表明。這應該就是在config中看到的很多被注釋掉的配置,在代碼中同樣可以看到這些預設值。

oslo.config使用

oslo.config庫隻有兩個檔案,cfg.py和iniparser.py,oslo.config的使用方法在cfg.py檔案中已經給出不是一般詳細的注釋。

options

options即所謂的配置選項,可以通過指令行和配置檔案設定,它一般的形式如下:

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

上面的一般形式,指定了options的名字,預設值和幫助資訊,還可以看出,不同的配置選項屬于不同的類型。StrOpt,IntOpt。除此之外,Options還支援floats,booleans,list,dict,multi strings。

這些options在被引用之前,必須先在運作期通過config manager注冊該options,即使用前得先注冊。例如下的情況:

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

我們若要使用osapi_compute_extension選項,則需要先通過self.conf.register_opt(enabled_apis_opt)完成option的注冊。

前面我們提到options可以在啟動服務的指令行中啟動,這些選項在被程式解析之前,必須先通過config manager注冊。這樣的好處,我們可以實作常用的help參數,并且确認指令行參數的正确性。指令行的注冊略微不同前面提到的注冊方式,調用的是特定的函數,conf.register_cli_opts(cli_opts)。

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)
           
config file

前面我們提到oslo.config支援的是ini風格的配置檔案,該檔案将所有的配置選項進行了分組,即所謂的section或者group,這兩個單詞是同一個概念,沒有指定section的,則會分到default組。下面給出了一個ini風格的配置檔案例子:

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

在config manager中,會預設的指定兩個值,即

--config-file --config-dir

,config manager會在沒有顯示指定這兩個參數的情況下去預設的檔案夾中查找預設的檔案。例如

~/.${project}, ~/, /etc/${project},/etc/

這幾個目錄下查找配置檔案,如果程式是nova,則會查找預設路徑下的nova.conf檔案。

在代碼中的注釋指出,

Option values in config files override those on the command line.

 即config files中的選項值會覆寫指令行中的選項值。這貌似與潛意識中的相反呀,英文是原話。

補充:2013-11-28,經過自己的測試和對源碼的閱讀,應該是Option values specified on command lines override those in config files,具體參考下一篇的分析。

 多個配置檔案會按順序來解析,後面檔案中的選項會覆寫前面出現過的選項。

option group

在配置檔案中,我們已經看到很多配置選項已經被我們主動的進行了一個分組的劃分,沒有歸屬的選項則扔到了default組。同樣,在代碼中options可以顯示的注冊某個組中。注冊的方式有兩種,直接指定group,或者指定group的name,參考下面代碼:

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.IntOpt('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')
           

我們需要先定義一個group,指定group的name和title屬性,也得将group注冊,最後可通過兩種方式将options注冊到該組中。

若一個group僅隻有name屬性,那麼我們可以不用顯示的注冊group,例如下面的代碼:

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')
           
option values

若要引用某個option的值,則直接通過通路config manager屬性的方式即可。例如,通路default組或者其他的組,可以通過如下的方式:

conf.bind_port  conf.rabbit.port
           

同時,option值還可以通過PEP 292 string substitution(pep 292描述了字元串替換的方式)再引用其他的option的值,具體看下面的例子,在sql_connection值中,我們引用了其他的option的值。

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

還有在某些情況下,我們需要在日志檔案中隐藏關鍵option的值,可以在建立該option時,添加secret參數,設定為True。

config manager

我們已經多次提到config manager了,要使用options,得先将options注冊到config manager中,通路option的值,直接通路config manager的屬性,config manager對options進行了統一的管理,其實config manager是一個全局的對象,重載了__call__方法,還有__getattr__方法。最為關鍵的,全局就隻有這麼一個執行個體,在cfg.py的注釋尾,再給出了一個完整的例子,首先擷取全局的這個執行個體,然後注冊options,最後使用options。

from oslo.config import cfg

opts = [
cfg.StrOpt('bind_host', default='0.0.0.0'),
cfg.IntOpt('bind_port', default=9292),
]
                        
CONF = cfg.CONF
CONF.register_opts(opts)
                                
def start(server, app):
    server.start(app, CONF.bind_port, CONF.bind_host)
           

總結

這一部分隻是按照源碼中的注釋來介紹了下oslo.config的使用,下一篇,将分析cfg.py的代碼結構,因為也隻有這一個關鍵的檔案,代碼在兩千行左右,任務不是很重,是以争取将其看仔細,寫明白。

在閱讀oslo wiki的時候,發現了一個很有趣的問題,

Why does oslo.config have a CONF object? Global object SUCK!

,看來社群對這個Global Object有很大的争論,導緻作者還特意在wiki上做個專門的介紹!我們在使用cfg時,一般都是通過 CONF = cfg.CONF方式來擷取這個全局的執行個體。作者提到在Folsom Design Summit上,有人想

remove our dependence on a global object like this

,顯然,很多争論,結果的結果是,大家達成了一個初步共識,還是堅持使用這種global object的方式。作者還提到了一句話:

The idea is that having all projects use the same apporach is more important than the objections to the approach.

 不明覺曆的模樣!這還是強調了OpenStack的projects使用同樣的方法更好!具體的回答大家可以點選後面的wiki連結,自己細讀。這個問題的最後,作者留下了一句

This debate will probably never completely go away,though.

由于對這個問題了解不深,也不知道大神們為什麼要争論這幾種方式,難道這個global objects還不夠好!

繼續閱讀