1. 程式人生 > >Ceilometer 18、openstack元件api框架分析

Ceilometer 18、openstack元件api框架分析

以gnocchi-api為例具體分析openstack元件api啟動流程和框架

1 setup.cfg分析


setup.cfg中有:
wsgi_scripts中gnocchi-api = gnocchi.rest.app:build_wsgi_app

2 setup.py分析


setup.py 安裝gnocchi時,根據pbr生成了/usr/bin/gnocchi-api檔案

3 /usr/bin/gnocchi-api分析


/usr/bin/gnocchi-api檔案中呼叫:
server = wsgiref.simple_server.make_server('', args.port, build_wsgi_app())
server.serve_forever()
分析:
這裡起到了監聽gnocchi埠,開啟gnocchi-api服務的功能

wsgiref.simple_server.make_server(host, port, app)
作用:實現HTTP伺服器為WSGI應用程式提供服務
引數:
host: ip,可以為空
port: 監聽的埠
app: 待啟動的app

本質:監聽主機和埠對應的WSGI伺服器,接收應用程式連線
app是wsgi應用程式物件。
wsgi: 讓python應用可以與web伺服器相連。
架構如下:
    invoke
server    ----------> application(例如ceilometer-api)
    <----------
    return
處理過程: server按照wsgi規範呼叫application,application處理請求並返回給server
實現: server端例如apache+mod_wsgi, application端例如自己編寫的應用(例如ceilometer-api) 

參考:
https://segmentfault.com/a/1190000003069785

4 build_wsgi_app方法分析


其中build_wsgi_app方法呼叫load_app(使用api-paste.ini和name為gnocchi+keystone)
例如gnocchi中具體程式碼如下:
    app = deploy.loadapp("config:" + cfg_path, name=appname,
                         global_conf={'configkey': configkey})
分析:
deploy.loadapp(uri, name=None, **kw)
作用: 載入wsgi應用,返回一個wsgi應用
引數:
uri: 是一個.ini配置檔案的路徑,需要以 config開頭,例如:
     形如:'config:/path/to/config.ini',
     請將: 上述/path/to/config.ini 替換為實際的.ini檔案的絕對路徑
name: 是.ini檔案中對應[composite:xxx]中的xxx,表示請求分發的入口名稱
kw: 是其他字典引數

參考:
https://pastedeploy.readthedocs.io/en/latest/

4.1 api-paste.ini分析

中根據name為gnocchi+keystone找到如下內容
[composite:gnocchi+keystone]
use = egg:Paste#urlmap
/ = gnocchiversions_pipeline
/v1 = gnocchiv1+keystone
/healthcheck = healthcheck
解釋: 
1) 其中composite將http請求分發到指定app
2) use表示採用何種方法處理,這裡表示使用Paste的egg-info中的urlmap對應的方法urlmap_factory
而下面
/ = gnocchiversions_pipeline
/v1 = gnocchiv1+keystone
/healthcheck = healthcheck
都做為urlmap的字典引數,
3) urlmap_factory方法具體如下
def urlmap_factory(loader, global_conf, **local_conf):
    ...
    urlmap = URLMap(not_found_app=not_found_app)
    for path, app_name in local_conf.items():
        path = parse_path_expression(path)
        app = loader.get_app(app_name, global_conf=global_conf)
        urlmap[path] = app
    return urlmap
顯然上述/, /v1等鍵值對被用於了urlmap_factory中的local_conf字典引數了。
這個方法的作用是:
建立了: <路徑, app>的對映,即以上述為例,應該會建立:
{/: gnocchiversions_pipeline對應的app, /v1: gnocchiv1+keystone對應的app, 
/healthcheck: healthcheck對應的app}
這樣的字典。

4.2 請求解析分析

由於gnocchi中的請求是/v1開頭,所以這裡轉向
[composite:gnocchi+keystone]
use = egg:Paste#urlmap
/ = gnocchiversions_pipeline
/v1 = gnocchiv1+keystone
/healthcheck = healthcheck
解釋: composite部分中定義的鍵值對,例如定義的:
/healthcheck = healthcheck
其中 healthcheck 必須是一個app或者是pipeline(pipeline本質是由
一些列filter+最後一個app組成,所以本質還是一個app)
中/v1對應的gnocchiv1+keystone的app,由於找不到
[app:gnocchiv1+keystone]
尋找
[pipeline:gnocchiv1+keystone]
有如下內容:
[pipeline:gnocchiv1+keystone]
pipeline = http_proxy_to_wsgi keystone_authtoken gnocchiv1
其中:
http_proxy_to_wsgi是一個過濾器,具體如下:
[filter:http_proxy_to_wsgi]
use = egg:oslo.middleware#http_proxy_to_wsgi
oslo_config_project = gnocchi

keystone_authtoken時一個過濾器,具體如下:
[filter:keystone_authtoken]
use = egg:keystonemiddleware#auth_token
oslo_config_project = gnocchi

gnocchiv1是一個app,具體定義如下:
[app:gnocchiv1]
paste.app_factory = gnocchi.rest.app:app_factory
root = gnocchi.rest.V1Controller
具體程式碼: 
gnocchi/rest/app.py中定義app_factory方法如下
def app_factory(global_config, **local_conf):
    global APPCONFIGS
    appconfig = APPCONFIGS.get(global_config.get('configkey'))
    return _setup_app(root=local_conf.get('root'), **appconfig)

分析:
1) paste.app_factory 表示呼叫哪個函式來獲得這個app,一般在對應的py檔案中會重寫
這個方法表示,比如上述gnocchi中
2) 為自己的應用建立工廠,每個工廠期待一個可呼叫的物件(例如函式,方法,或者類)
paste.app_factory
這個應用是最常見的,應該定義為如下形式
def app_factory(global_config, **local_conf):
    return wsgi_app
作用: 返回一個wsgi應用
引數:
global_config: 是一個字典
local_conf: 是一個字典引數

參考:
https://segmentfault.com/a/1190000003718606
https://pastedeploy.readthedocs.io/en/latest/sss#id4

5 _setup_app方法分析


_setup_app方法具體定義如下
def _setup_app(root, conf, indexer, storage, not_implemented_middleware):
    app = pecan.make_app(
        root,
        hooks=(GnocchiHook(storage, indexer, conf),),
        guess_content_type_from_ext=False,
    )

    if not_implemented_middleware:
        app = webob.exc.HTTPExceptionMiddleware(NotImplementedMiddleware(app))

    return app
分析:
上述呼叫了pecan.make_app方法來建立wsgi應用,該方法定義如下:
def make_app(root, **kw):
    ...
作用: 建立並返回一個wsgi應用
引數: 
root: 是一個字串,表示定義api的主入口處理的controller,
      例如,aodh中是'aodh.api.controllers.root.RootController'
kw: 是字典引數

gnocchi中的root應該是:
gnocchi/rest/__init__.py中的V1Controller類,具體如下:
class V1Controller(object):

    def __init__(self):
        self.sub_controllers = {
            "search": SearchController(),
            "archive_policy": ArchivePoliciesController(),
            "archive_policy_rule": ArchivePolicyRulesController(),
            "metric": MetricsController(),
            "batch": BatchController(),
            "resource": ResourcesByTypeController(),
            "resource_type": ResourceTypesController(),
            "aggregation": AggregationController(),
            "capabilities": CapabilityController(),
            "status": StatusController(),
        }
        for name, ctrl in self.sub_controllers.items():
            setattr(self, name, ctrl)

hooks? 暫不清楚
這裡最終返回了一個wsgi應用。


6 路由分發


根據url中後面的路徑,尋找到對應的controller繼續向下進行處理或路由分發。

7總結


7.1元件api服務啟動流程


1)利用setup.cfg中的wsgi_scripts配置的build_wsgi_app方法,
setup.py安裝元件的時候利用pbr讀取setup.cfg生成了元件api可執行檔案,
2)在元件api可執行檔案中通過呼叫
wsgiref.simple_server.make_server(host, port, app)來啟動api監聽,
其中app來是通過paste.deploy.loadapp(uri, name=appname)生成,
其中uri來自於對應的.ini檔案。
3)最後根據傳送的url請求中的路徑,查詢到對應在.ini檔案找到最終的處理的app對應的方法app_factory,
app_factory方法中則根據pecan.make_app(root, **kw)中指定root做為主入口controller,
進行最後的路由分發。

7.2 api框架組成


pbr(專案安裝,生成元件api可執行檔案)+wsgiref(啟動api服務)+paste(.ini檔案以及部署application)+pecan(路由分發)


參考:
[1] https://pecan.readthedocs.io/en/latest/
[2] https://cloud.tencent.com/developer/section/1368716
[3] https://pastedeploy.readthedocs.io/en/latest/
[4] https://segmentfault.com/a/1190000003069785
[5] https://segmentfault.com/a/1190000003718606