0%

OpenStack_oslo_config

OpenStack Oslo – oslo.config


在學習Python的過程中,寫的程式通常是小型的、獨立的、一次性且不牽涉到系統的。
但是當我們希望可以寫一個長時間執行的service或是agent。他們是有能力週期性執行任務,並且提供配置文件供使用者客製化設定,甚至可以產生log訊息讓使用者了解程式目前狀況,通常會不知道如何下手。
還好我們不必自己摸索,透過Open Source項目我們可以看到世界級的系統軟體是如何被實現的,而裡面通常也有我們要的答案。

什麼是Oslo項目

在深入了解OpenStack的實作過程中,經常能夠看到程式中import了帶有oslo字樣的模組。
是什麼樣的模組可以在OpenStack中每一個服務的實作都會用到呢?
這些模組跟Oslo這個項目又有什麼關係?

我們來看看Oslo這個項目的Mission Statement:

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這個項目的起源是因為來自世界各地開發OpenStack的大神們,也無法忍受開發過程中不斷的重複製造輪子。
特別將經常使用的功能寫成一系列的模組,整合起來成為一個OpenStack中各服務都共享的項目,並且講求質量、穩定度、一致性、擁有完整的文件,最後就是可以廣泛使用。
而這一系列的模組,有些如oslo_config可以用來處理命令行參數和配置文件,或是如oslo_service提供了一個開發者創建新的long-running services的框架。

而這也是這一系列文章的目的,我們也許可以透過學習世界級的軟體,來實現心目中想達成的功能,而且最重要的是 — 我們也不用重複製造輪子!

oslo_config

通常一個大型的系統,為了滿足使用者在不同情境下的需求,會提供許多可以讓使用者更改的配置選項 (Options)。
而這些配置選項值的來源有些是從命令行參數、有些是來自於一個包含所有配置選項的配置文件
理所當然程式中便需要一個模組來專門處理數量眾多且來源不同的配置選項。而這任務就是交由oslo_config這個模組負責。
根據OpenStack Wiki關於oslo_config的說明,該模組負責以下四個任務

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

透過oslo_config這個模組,我們將有能力從命令行中直接設定配置選項或是從設定檔中讀取配置選項的值。
以下我們直接透過實作來瞭解如何使用oslo_config。

環境安裝

首先,利用virtualenv來創造一個獨立的環境,並安裝oslo_config

1
2
3
4
5
$ pip install virtualenv
$ virtualenv example
$ cd example
$ source bin/activate
$ pip install oslo.config

oslo_config使用

假設一個使用情境,我們今天希望能夠寫一個程式能夠週期性得執行任務,例如:定期對網頁或另一個服務發送請求。這時我們便希望這個程式可以自由設定以下的配置選項:
(1).是否執行週期性任務 (2).週期 (3).目標網址 (4).個人帳號 (5).個人密碼
那要如何使用oslo_config來幫我們達到這樣的使用情境呢?

配置選項定義

每一個配置選項有其名稱和對應的值。
而oslo_config的配置選項支援以下幾種型態:
strings, integers, floats, booleans, lists, ‘multi strings’ , ‘key/value pairs’ (dictionary)

由於配置選項數量龐大,所以勢必要根據他們的性質或是用途來做分組,才能讓使用者快速找到他們需要更改的配置選項。
我們先將上面五個配置選項分成兩組,分別是periodic_task與request_api,來看oslo_config如何處理。
首先我們先創建如下的資目錄結構。
注: 不在自訂組別內的會被歸類到DEAULT組

1
2
3
4
5
6
conf
|_ __init__.py
|_ periodic_conf.py //處理 (1)是否執行週期性任務 (2)週期配置
|_ request_conf.py //處理 (3)目標網址 (4)帳號 (5)密碼
your_app.py
your_app.conf //配置文件

接下來先從period_conf.py開始看
period_conf.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from oslo_config import cfg

opt_periodic_task_group = cfg.OptGroup(name='periodic_task',
title='Periodic Tasks Options')

periodic_task_opts = [
cfg.BoolOpt('periodic_enable',
default=True,
help='Enable periodic tasks.'),

cfg.IntOpt('periodic_interval',
default=60,
help='Max interval size between periodic tasks'
'execution in seconds.'),
]

首先我們需要先從oslo_config中引入cfg模組,
並且透過cfg.OptGroup函式來定義一個配置選項組opt_periodic_task_group,
同時給予name與title。

接下來,我們定義兩個配置選項並包在一個名為periodic_task_opts的list中,分別是
periodic_enable、periodic_interval。
這邊可以看到透過cfg.BoolOpt等函式我們可以設定該配置選項的名稱、預設值、提示…等。
其他變數類型也有相對應的函式。

註冊配置選項

period_conf.py:

1
2
3
4
5
def register_opts(conf):
conf.register_group(opt_periodic_task_group)

conf.register_cli_opts(periodic_task_opts,opt_periodic_task_group)
conf.register_opts(periodic_task_opts, opt_periodic_task_group)

延續上面的程式碼,在period_conf.py中寫一個register_opts的函式,負責將我們的配置選項註冊到conf變數中,這邊可以看到有三種函式:

  • register_group: 第一步驟是將group的定義註冊。
  • register_cli_opts: 如果希望能夠在命令行設定配置參數,則利用此函式註冊配置選項
  • register_opts: 若是希望透過配置文件來設定參數,則利用此函式註冊配置選項。

最後,我們需要再寫一個__init__.py,目的是讓conf這個資料被做為一個模組,並且在裡面將所有不同組別的配置選項做統一的註冊。
下面附上__init__.pyperiod_conf.pyrequest_conf.py完整程式碼。

_init_.py

1
2
3
4
5
6
7
8
from oslo_config import cfg
import period_conf
import request_conf

CONF = cfg.CONF

period_conf.register_opts(CONF)
request_conf.register_opts(CONF)

period_conf.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from oslo_config import cfg

opt_periodic_task_group = cfg.OptGroup(name='periodic_task',
title='Periodic Tasks Options')

periodic_task_opts = [
cfg.BoolOpt('periodic_enable',
default=True,
help='Enable periodic tasks.'),

cfg.IntOpt('periodic_interval',
default=60,
help='Max interval size between periodic tasks'
'execution in seconds.'),
]
def register_opts(conf):
conf.register_group(opt_periodic_task_group)

conf.register_cli_opts(periodic_task_opts,opt_periodic_task_group)
conf.register_opts(periodic_task_opts, opt_periodic_task_group)

request_conf.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from oslo_config import cfg

opt_request_api_group = cfg.OptGroup(name='request_api',
title='Request Api Options')

request_api_opts = [
cfg.StrOpt('host',
default='localhost',
help='IP/hostname'),

cfg.ListOpt('username',
default=None,
help='A list of usernames'),

cfg.DictOpt('password',
default=None,
help='password for each usernames')
]

def register_opts(conf):
conf.register_group(opt_request_api_group)
conf.register_opts(request_api_opts, opt_request_api_group

配置文件

現在讓我們看一下配置文件內部的格式
your_app.conf:

1
2
3
4
5
6
7
8
[request_api]
host = localhost //strings
username = Jack, John //lists
password = Jack: XXXX, John: YYYY //dictionary

[periodic_task]
periodic_enable = False //booleans
periodic_interval = 20 //integers

還記得我們剛剛在宣告組別的時候給了name嗎?
當時定義的name在配置文件中就是用來表示不同的group。

使用

終於來到最後在我們自己的程式中取用這些配置的參數。
your_app.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import conf  //將我們的conf資料夾作為模組import
import sys

CONF = conf.CONF

if __name__ == "__main__":
CONF(sys.argv[1:], project='your_app')

print( CONF.request_api.host )
print( CONF.request_api.username[0])
print( CONF.request_api.password['Jack'] )

print( CONF.periodic_task.periodic_enable )
print( CONF.periodic_task.periodic_interval )

首先這邊有幾個重點:

  1. 可以看到CONF以全域變數的角色存在,這點在OpenStack community是一個爭執很久的議題。有興趣的可以至下面連結看開發者們的結論。
    Why does oslo.config have a CONF object? Global object SUCK!

  2. CONF透過命令行參數來找到配置文件位置,那第一行為什麼需要project='your_app'呢?
    這是因為在oslo_config設計中,如果沒有指定參數的話,預設會到/etc/project來尋找配置文件。以這邊的例子來說就會至/etc/your_app/底下尋找。

最後執行

1
$ python your_app.py --config-file your_app.conf

即可看到你在配置文件中配置的參數。

等等,那如果我希望能夠從命令行來指定配置參數的話該如何做呢?
首先我們可以透過-h來看命令行參數可以設定哪些配置,以及每個參數的說明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$ python your_app.py -h

usage: your_app [-h] [--config-dir DIR] [--config-file PATH]
[--periodic_task-noperiodic_enable]
[--periodic_task-periodic_enable]
[--periodic_task-periodic_interval PERIODIC_TASK_PERIODIC_INTERVAL]

optional arguments:
-h, --help show this help message and exit
--config-dir DIR Path to a config directory to pull *.conf files from.
This file set is sorted, so as to provide a
predictable parse order if individual options are
over-ridden. The set is parsed after the file(s)
specified via previous --config-file, arguments hence
over-ridden options in the directory take precedence.
--config-file PATH Path to a config file to use. Multiple config files
can be specified, with values in later files taking
precedence. Defaults to None.

Periodic Tasks Options:
--periodic_task-noperiodic_enable
The inverse of --periodic_enable
--periodic_task-periodic_enable
Enable periodic tasks.
--periodic_task-periodic_interval PERIODIC_TASK_PERIODIC_INTERVAL
Max interval size between periodic tasks execution in
seconds.

這時在透過以下指令,即可在命令行中指定配置參數。

1
$ python your_app.py --config-file your_app.conf --periodic_task-periodic_interval 1000

以上,即是最簡單但也是最核心的Oslo_config使用方法。

參考資料

1.https://wiki.openstack.org/wiki/Oslo Oslo WiKi
2.https://wiki.openstack.org/wiki/Oslo/Config Oslo.config WiKi
3.https://github.com/openstack/magnum OpenStack Magnum Source code
4.http://www.choudan.net/2013/11/27/OpenStack-Oslo.config-%E5%AD%A6%E4%B9%A0(%E4%B8%80).html
5.http://gtcsq.readthedocs.io/en/latest/openstack/oslo_cfg.html