how to read openstack code : paste deploy

本篇分爲如下幾個部分java

  • paste 是什麼
  • 怎樣使用paste
  • paste of neutron

paste 是什麼

WSGI 是python 中application 和 web server互通的標準。 咱們知道了wsgi 中包括 app, middleware , server並且middleware能夠有不少個。wsgi結構的系統最大的好處就是middleware像積木同樣,能夠靈活的添加組成不一樣的功能。python

咱們上一篇文章中,把2個middleware和一個app組合到了一塊兒,但咱們採用的寫法是在代碼中這樣寫:c++

httpd = make_server('10.79.99.86', 8051, CheckError(AuthToken(check_number)))

把各個模塊之間的關係硬編碼到了代碼中,這很顯然不夠靈活,假設有一天想從新組織middleware的結構,咱們還須要找到相關的代碼去修改代碼。最好的辦法是經過配置文件來組織各個模塊之間的關係,經過修改配置文件來影響系統中各個模塊的組合與調用。 Paste 就是解決這個問題的。程序員

paste 是python的一個module,經過paste, 你能夠把wsgi的模塊寫入ini風格的配置文件,靈活部署。web

下面咱們用paste構建一個小系統,進一步瞭解設計模式

怎樣使用paste

首先,咱們將構建三個wsgi組件, 一個application叫check_number, 該application的功能是check http req中的數字是奇數仍是偶數,若是是奇數返回odd不然返回even。 代碼以下:api

class CheckNumber(object):

    def __call__(self, env, start_response):
        number = int(env.get('PATH_INFO').split('/')[-1])
        response_body = 'even' if number % 2 == 0 else 'odd'

        status = '200 OK'
        response_headers = [('Content-Type', 'text/plain'), ('Content-Length', str(len(response_body)))]
        start_response(status, response_headers)
        return [response_body]

    @classmethod
    def factory(cls, global_config, **local_config):
        return cls()

__call__是wsgi application的入口,邏輯很是簡單,這裏不作解釋。服務器

why use factory
看factory函數的邏輯,其實至關於一個構造函數。 下面兩行代碼其實是同樣的效果app

CheckNumber()
CheckNumber.factory()

但爲何還要多加一個factory呢?兩個緣由curl

  1. paste就這麼規定的
  2. factory 函數多出自java等靜態類型語言的設計模式。對java,c/c++是一種很好的設計模式,但python中其實並不須要。不過不少python程序員有較深的java/c++ OOP背景,因此代碼中延續了這種風格

無論怎樣,咱們知道這是個構造函數便可。

接下來,咱們構造一個middleware,名字叫AuthToken。 該middleware將驗證http請求header中的token,若是知足要求則放行request繼續傳遞給application,不然返回錯誤,代碼以下:

class AuthToken(object):
    def __init__(self, app):
        self.app = app

    def __call__(self, env, start_response):
        if env.get('HTTP_TOKEN') == '222':
            return self.app(env, start_response)
        else:
            response_body = 'Auth failed'
            status = '403 forbidden'
            response_headers = [('Content-Type', 'text/plain'), ('Content-Length', str(len(response_body)))]
            start_response(status, response_headers)
            return [response_body]

    @classmethod
    def factory(cls, global_config, **local_config):
        def _factory(app):
            return cls(app)
        return _factory

Middleware的特性就是能夠調用application,同時自身能夠像application同樣被調用。因此__init__中接受一個app做爲參數,而且在__call__中調用這個參數。所以

AuthToken(CheckNumber)

的效果是返回一個可調用的object。 在調用該object的__call__方法時,首先會檢查token,若是token符合要求則繼續調用CheckNumber。一樣要注意的是factory跟以前同樣,徹底能夠用構造函數替代。

咱們繼續構造第三個middleware,叫CheckError。 該middleware 用於檢查系統中其它application , middleware返回的錯誤異常等信息,並轉化成用戶友好的信息。 邏輯以下:

class CheckError(object):
    def __init__(self, app):
        self.app = app

    def __call__(self, env, start_response):
        try:
            return self.app(env, start_response)
        except Exception as e:
            response_body = 'Server is maintaining '
            status = '503 service unavailable now'
            response_headers = [('Content-Type', 'text/plain'), ('Content-Length', str(len(response_body)))]
            start_response(status, response_headers)
            return [response_body]

    @classmethod
    def factory(cls, global_config, **local_config):
        def _factory(app):
            return cls(app)

        return _factory

CheckError 的構造函數接受一個app做爲參數並返回一個callable對象。調用該對象,即運行__call__方法時會用try catch去包裹一個對app的調用。若是調用有異常則進入異常處理返回,不然返回app調用

若是沒有paste deploy,咱們須要像下面這樣寫:

CatchError(AuthToken(CheckNumber(env, callback)))

經過程序來組織各個wsgi組件。這樣很是不方便,一旦想添加或者刪除甚至更改各個component的關係,須要修改代碼。 經過paste deploy,咱們能夠利用配置文件,以下:

[DEFAULT]
security = high

[composite: my_pipeline]
use = call:my_module:pipeline_factory
auth = check_error auth_token check_number
noauth = check_error check_number

[app:check_number]
paste.app_factory = my_module:CheckNumber.factory

[filter:auth_token]
paste.filter_factory = my_module:AuthToken.factory

[filter:check_error]
paste.filter_factory = my_module:CheckError.factory

paste 配置文件採用INI風格。每一箇中括號及其下面跟着的一個區域叫作 section 如:

[filter:check_error]
paste.filter_factory = my_module:CheckError.factory

這裏filter爲section type。paste中有不少種類型。app類型表明wsgi application, filter 表明wsgi middleware,server表明server。其它類型咱們後面遇到再說。 check_error 是section的name。下面跟着的是section的變量,paste.filter_factory = my_module:CheckError.factory 告訴paste,能夠用my_module模塊的CheckError.factory load catch_error filter。 簡單的說,這段配置告訴paste 咱們有一個catch_error middleware 以及怎樣加載這個middleware。

paste中,每一個section(除了[DEFAULT])表明一個wsgi組件,paste 經過配置文件能夠加載該組件,在加載組件的同時能夠讀取該section下面的配置信息。這裏的信息是該section對應的wsgi組件獨享的。可是有個section除外,即[DEFAULT],DEFAULT section不是wsgi組件,只是用來存配置。這裏的配置是全局的,每一個section均可以訪問。

auth_token, check_number其實和這裏同樣,都至關於聲明一個wsgi組件而且告訴paste如何加載。不一樣的是check_number是一個app。

咱們重點看一下my_pipeline區域。這裏咱們用的是composite類型。 回顧wsgi 會發現咱們有middle/app/server 但就是沒有composite。composite不是wsgi原生的類型,它表明多個組件的組合。好比這裏是兩個pipeline auth和noauth。paste中pipeline也是一種類型,表明串聯多個wsgi組件,好比:auth 至關於 CatchError(AuthToken(CheckNumber)) 而 noauth表明CatchError(CheckNumber)。

use = call:my_module:pipeline_factory

這一行表示對應的代碼在my_module 模塊的pipline_factory裏。 表示從這行代碼加載composite組件。看一下代碼的詳細信息:

def pipeline_factory(loader, global_config, **local_config):

    if global_config.get('security') == 'high':
        pipeline = local_config.get('auth')
    else:
        pipeline = local_config.get('noauth')
    # space-separated list of filter and app names:
    pipeline = pipeline.split()
    filters = [loader.get_filter(n) for n in pipeline[:-1]]
    app = loader.get_app(pipeline[-1])
    filters.reverse()  # apply in reverse order!
    for filter in filters:
        app = filter(app)
    return app

由於配置文件中指定該區域是composite,因此該函數接受3個參數loader, global_config 和 local_config。這是paste語法規則指定的。
loader用於加載app,而global_config是配置文件中全局區的配置{security:high}。local_config是 {auth:"catch_error auth_token check_number"}和{noauth:"catch_error auth_token check_number"}。

if global_config.get('security') == 'high':
        pipeline = local_config.get('auth')
    else:
        pipeline = local_config.get('noauth')

這段的邏輯是,若是全局配置中指定了security爲high, 則pipeline中包含auth不然不包含。

pipeline = pipeline.split()
    filters = [loader.get_filter(n) for n in pipeline[:-1]]

這段的邏輯是從pipeline中找到filter的列表(pipeline最後一項是app,其他都是filter)。

app = loader.get_app(pipeline[-1])
    filters.reverse()  # apply in reverse order!
    for filter in filters:
        app = filter(app)

這段的邏輯是,獲取app,而且把filter列表中的中間件反過來逐層包裹,達到

CatchError(AuthToken(CheckNumber))

的效果。

接下來咱們看一下如何使用。 咱們把3個wsgi組件和這個pipeline_factory 放入config.ini。 而後運行以下代碼:

if __name__ == '__main__':
    from paste import httpserver
    from paste.deploy import loadapp
    httpserver.serve(loadapp('config:conf.ini', name='my_pipeline', relative_to='.'), host='127.0.0.1', port='8080')

如今經過下面的命令

curl http://127.0.0.1/check_number/100

訪問會返回noauth錯誤,由於你沒有加上 -H 「token:222」 的驗證信息。 但設置security=False重啓服務器,繼續訪問,則不用加驗證信息,由於根據composite section對應的代碼邏輯,AuthToken組件並無加到pipeline

paste of neutron

neutron的paste配置文件在

cat /usr/share/neutron/api-paste.ini 
[composite:neutron]
use = egg:Paste#urlmap
/: neutronversions
/v2.0: neutronapi_v2_0

[composite:neutronapi_v2_0]
use = call:neutron.auth:pipeline_factory
noauth = request_id catch_errors extensions neutronapiapp_v2_0
keystone = request_id catch_errors authtoken keystonecontext extensions neutronapiapp_v2_0

[filter:request_id]
paste.filter_factory = oslo_middleware:RequestId.factory

[filter:catch_errors]
paste.filter_factory = oslo_middleware:CatchErrors.factory

[filter:keystonecontext]
paste.filter_factory = neutron.auth:NeutronKeystoneContext.factory

[filter:authtoken]
paste.filter_factory = keystonemiddleware.auth_token:filter_factory

[filter:extensions]
paste.filter_factory = neutron.api.extensions:plugin_aware_extension_middleware_factory

[app:neutronversions]
paste.app_factory = neutron.api.versions:Versions.factory

[app:neutronapiapp_v2_0]
paste.app_factory = neutron.api.v2.router:APIRouter.factory

整個neutron api server就是一個wsgi server 加一堆 wsgi middleware 和 wsgi app組成的一個大的pipeline。 啓動加載該配置文件是neutron 的wsgi server負責的事情。這部分咱們後面再看。咱們先看該配置文件。一樣,從下向上看發現有兩個APP neutronversions和neutronapiapp_v2_0 以及5個middleware。 在composite區域中又組合了兩個pipeline。一個不帶驗證,一個帶有keystone驗證。最後composite部分經過paste包中的urlmap函數指定,若是URL是/則訪問neutronversion中間件,若是是/v2.0則訪問composite部分的pipeline,而這也正是咱們真正API訪問所走的路徑。

大部分環境中所採用的pipeline是keystone這條。由於咱們經過devstack或者官方文檔安裝的環境通常都會帶有權限校驗。 對於咱們來講,真正須要關注的代碼在extensions和neutron_app_v2_0這兩部分,其餘部分都和業務無關。 由配置文件能夠很容易找到各個組件的代碼位置,如neutron.api.v2.router表明 neutron/api/v2/router。從這裏開始,就能夠逐步閱讀代碼追蹤每一個API的調用流程。

相關文章
相關標籤/搜索