cinder-api启动过程学习
Openstack中的xxx-api都是WSGI service,负责接收、分析、处理HTTP request,并返回response。其中最重要的两个Python package是paste.delopy和routes。前者负责查找、配置、启动WSGI app,后者负责把RESTful的HTTP requests dispatch到相应的处理方法上。
paste.deploy官网:http://pythonpaste.org/deploy/
routes官网:http://routes.readthedocs.org/en/latest/
下面是cinder-api(juno)启动过程的学习,难免有不对的地方,请联系我哦:-)
api-paste.ini
cinder/etc/cinder/api-paste.ini是WSGI app的配置文件。
安装cinder之后会copy到/etc/cinder/
[root@all ~]# ll /etc/cinder/
total 92
-rw-------. 1 cinder cinder 2136 Mar 2 10:46 api-paste.ini
-rw-------. 1 cinder cinder 79250 Mar 2 10:46 cinder.conf
-rw-r-----. 1 root cinder 3200 Dec 5 13:00 policy.json
-rw-r-----. 1 root cinder 942 Dec 5 13:00 rootwrap.conf
drwxr-xr-x. 2 cinder root 6 Jan 18 07:35 volumes
入口
[composite:osapi_volume]
use = call:cinder.api:root_app_factory
/: apiversions
/v1: openstack_volume_api_v1
/v2: openstack_volume_api_v2
- composite表示把HTTP request dispatch到一个或多个app上
- use表示用什么方法来dispatch
- 其他的KEY=VALUE都是app
找到cinder.api:root_app_factory
:
#cinder/api/__init__.py
def root_app_factory(loader, global_conf, **local_conf):
if CONF.enable_v1_api:
LOG.warn(_('The v1 api is deprecated and will be removed after the '
'Juno release. You should set enable_v1_api=false and '
'enable_v2_api=true in your cinder.conf file.'))
else:
del local_conf['/v1']
if not CONF.enable_v2_api:
del local_conf['/v2']
return paste.urlmap.urlmap_factory(loader, global_conf, **local_conf)
从注释看主要用openstack_volume_api_v2
openstack_volume_api_v2
[composite:openstack_volume_api_v2]
use = call:cinder.api.middleware.auth:pipeline_factory
noauth = request_id faultwrap sizelimit osprofiler noauth apiv2
keystone = request_id faultwrap sizelimit osprofiler authtoken keystonecontext apiv2
keystone_nolimit = request_id faultwrap sizelimit osprofiler authtoken keystonecontext apiv2
也是composite类型。定义3个app:noauth
,keystone
, keystone_nolimit
。
找到cinder.api.middleware.auth:pipeline_factory
:
# cinder/api/middleware/auth.py
def pipeline_factory(loader, global_conf, **local_conf):
"""A paste pipeline replica that keys off of auth_strategy."""
#debug on all_in_one 2015/3/14
#(Pdb) global_conf
#{'__file__': '/etc/cinder/api-paste.ini', 'here': '/etc/cinder'}
#(Pdb) local_conf
#{'keystone': 'request_id faultwrap sizelimit osprofiler authtoken keystonecontext apiv2', 'noauth': 'request_id faultwrap sizelimit osprofiler noauth apiv2', 'keystone_nolimit': 'request_id faultwrap sizelimit osprofiler authtoken keystonecontext apiv2'}
#(Pdb) CONF.auth_strategy
#'keystone'
#(Pdb) CONF.api_rate_limit
#True
pipeline = local_conf[CONF.auth_strategy]
if not CONF.api_rate_limit:
limit_name = CONF.auth_strategy + '_nolimit'
pipeline = local_conf.get(limit_name, pipeline)
pipeline = pipeline.split()
#依次load各个filter
filters = [loader.get_filter(n) for n in pipeline[:-1]]
#(Pdb) pipeline
#['request_id', 'faultwrap', 'sizelimit', 'osprofiler', 'authtoken', 'keystonecontext', 'apiv2']
#(Pdb) pipeline[:-1]
#['request_id', 'faultwrap', 'sizelimit', 'osprofiler', 'authtoken', 'keystonecontext']
#load app
app = loader.get_app(pipeline[-1])
filters.reverse()
#(Pdb) filters
#[<function _factory at 0x3a2f9b0>, <function auth_filter at 0x3a2f938>, <function filter_ at 0x3548050>, <function _factory at 0x3541cf8>, <function _factory at 0x3541aa0>, <class 'cinder.openstack.common.middleware.request_id.RequestIdMiddleware'>]
#依次用filter来包装app
for filter in filters:
app = filter(app)
return app
- 配置文件中的
KEY=VALUE
都放在了local_conf
这个dict中 - 首先根据cinder.conf中的
auth_strategy
参数来决定走哪个app keystone = aaa bbb ccc
,前面的都是filter,最后一个是app。一个HTTP request过来之后,先由filters过滤一遍,最后由app处理。
Filter
以下为例,看filter是怎么包装app的:
keystone = request_id faultwrap sizelimit osprofiler authtoken keystonecontext apiv2
1.request_id
[filter:request_id]
paste.filter_factory = cinder.openstack.common.middleware.request_id:RequestIdMiddleware.factory
找到RequestIdMiddleware.factory
:
#cinder/openstack/common/middleware/request_id.py
from cinder.openstack.common.middleware import base
ENV_REQUEST_ID = 'openstack.request_id'
HTTP_RESP_HEADER_REQUEST_ID = 'x-openstack-request-id'
class RequestIdMiddleware(base.Middleware):
@webob.dec.wsgify
def __call__(self, req):
req_id = context.generate_request_id()
req.environ[ENV_REQUEST_ID] = req_id
response = req.get_response(self.application)
if HTTP_RESP_HEADER_REQUEST_ID not in response.headers:
response.headers.add(HTTP_RESP_HEADER_REQUEST_ID, req_id)
return response
其父类为:
# cinder/openstack/common/middleware/base.py
class Middleware(object):
"""Base WSGI middleware wrapper.
These classes require an application to be initialized that will be called
next. By default the middleware will simply call its wrapped app, or you
can override __call__ to customize its behavior.
"""
@classmethod
def factory(cls, global_conf, **local_conf):
"""Factory method for paste.deploy."""
return cls
def __init__(self, application):
self.application = application
factory
方法就是返回cls
,也就调用了__init__
,也就是return了application。
顺便看一下RequestIdMiddleware.__call__
。当这个filer被调用的时候,会调用的这个__call__
,它在response的header中加了一个x-openstack-request-id
2.faultwrap
[filter:faultwrap]
paste.filter_factory = cinder.api.middleware.fault:FaultWrapper.factory
# fault.py
from cinder import wsgi as base_wsgi
class FaultWrapper(base_wsgi.Middleware):
"""Calls down the middleware stack, making exceptions into faults."""
其父类:
# cinder/wsgi.py
class Middleware(Application):
"""Base WSGI middleware.
"""
@classmethod
def factory(cls, global_config, **local_config):
"""Used for paste app factories in paste.deploy config files.
Any local configuration (that is, values under the [filter:APPNAME]
section of the paste config) will be passed into the `__init__` method
as kwargs.
A hypothetical configuration would look like:
[filter