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

猜你喜欢

转载自blog.csdn.net/qingyuanluofeng/article/details/84145441
今日推荐