【轉】Openstack中oslo_config模塊學習

OpenStack的項目貌似愈來愈多了,在Grizzly版以前,每一個項目都得實現一套處理配置文件的代碼。在每一個項目的源碼中基本上均可以找到openstack/common/cfg.py,iniparser.py文件,固然,這些不一樣項目之間的cfg.py等文件很大多是copy-and-paste分分鐘來搞定。這種狀況確定沒法被大神忍受,最終,社區決定改變這一切,提出了Oslo項目。Oslo項目的宗旨是提供一系列OpenStack Projects共享的基礎庫,能夠從wiki的原話中瞭解到。python

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

社區顯然已經沒法忍受在不一樣項目中大量重複的代碼了。Oslo 項目提供了一系列的庫,咱們接觸的最多的爲oslo.config這個庫,要來處理程序命令行參數和配置文件。固然還有其餘的,例如,pbr(Python Build Reasonableness)與setuptools相關的庫,hacking,用來處理編碼風格的庫,還有oslo.messageing等等。這些庫有的是在oslo這個namespace下,有的是徹底獨立的。Oslo開發者是這樣考慮的,若是這個庫存在被普遍使用的潛質的話,則不將其放在oslo命名空間下。github

Oslo項目包含的庫較多,咱們將目光彙集到接觸最多的oslo.config這個庫上,這個庫應該是全部OpenStack項目中重用最多的。包括測試框架Tempest等sql


在瞭解oslo.confg的使用和實現以前,咱們須要知道,這個庫是用來解決什麼樣的問題。在有了此問題答案的基礎上,而後沿着怎樣來使用這個庫和這個庫究竟是如何實現的路線來分析。api

前面咱們介紹了,OpenStack在G版以前的幾乎每一個項目都得拷貝一份cfg.py,iniparser.py兩個文件放到openstack/common/目錄下,這兩個文件主要致力於解決讀取配置文件和解析命令行參數的問題。在實際使用OpenStack的過程當中,咱們啓動一個服務,例如nova-api或者glance-api,每每都是這樣的形式:app

/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須要解決的幾點問題:ide

1.command line option parsing
2.common command line options
3.configuration file parsing
4.option value lookup

  在wiki中還提到一種場景,建議最好將一些options的默認值寫在code裏面,同時也在config file中做爲註釋代表。這應該就是在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連接,本身細讀。

相關文章
相關標籤/搜索