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項目中重用最多的。包括測試框架Tempest等
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需要解決的幾點問題:
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使用
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鏈接,自己細讀。