本文是上一篇 探索 OpenStack 之(11):cinder-api Service 启动过程分析 以及 WSGI / Paste deploy / Router 等介绍> 的后续篇。

osapi_volume 的 WSGI Service 进程在收到 HTTP Request 后,首先将HTTP request 封装成wsgi request,然后依次执行以下步骤。

1. 按顺序调用已经注册的 middleware (filter) 实例的 __call__ 方法

1.1 filter:keystonecontext

该 filter 会提取 request header 中 token 相关数据,组成一个 ctx, 放入 request.environ 的 cinder.context 项中,供后续 app 使用。

def __call__(self, req):
'keystonecontext : Make a request context from keystone headers.'
ctx = context.RequestContext(user_id,
project_id,
project_name=project_name,
roles=roles,
auth_token=auth_token,
remote_address=remote_address,
service_catalog=service_catalog,
request_id=req_id) req.environ['cinder.context'] = ctx #添加 cinder.context
return self.application //If you are not modifying the output, you can just return the app.

1.2 filter:authtoken

该 filter 的代码在 https://github.com/openstack/keystonemiddleware。 该 filter 会通过校验request所带的 token 来校验用户身份。当request来的时候,它根据 token 来判断这个请求是否合法。校验失败则直接返回 401 错误,校验通过则提取 token 中的信息,放入到env中,供后续的其它app使用。具体会在 token 相关的文章中详细分析。

1.3  filter:osprofiler

该 filter 的代码在 https://github.com/stackforge/osprofiler, 它用来 enables tracing for an application。似乎是在 enabled = yes 的情况下,记录 request 的如下信息:

"request": {
"host_url": request.host_url,
"path": request.path,
"query": request.query_string,
"method": request.method,
"scheme": request.scheme
}

具体细节待查。

1.4 filter:sizelimit

该 filer 会检查 request 的 conent length,如果超过 cinder.conf 中 osapi_max_request_body_size 定义的最大size,则直接报 HTTPRequestEntityTooLarge

,不再进行后续处理。

def __call__(self, req):
if req.content_length > CONF.osapi_max_request_body_size: #判断 req.content_length,大于 CONF.osapi_max_request_body_size 则直接失败
msg = _("Request is too large.")
raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg)
if req.content_length is None and req.is_body_readable:
limiter = LimitingReader(req.body_file, CONF.osapi_max_request_body_size)
req.body_file = limiter
return self.application 

1.5 filter:faultwrap

该 filter 不会修改 request 和 respone,而是在一旦之前的App 处理 response 出错的话,它 catch 住错误并调用 _error 方法,返回一个适当的错误信息。

def __call__(self, req):
try:
return req.get_response(self.application)
except Exception as ex:
return self._error(ex, req) //return wsgi.Fault(outer) 如果application返回response过程出错,则返回Traceback (most recent call last)

1.6 filter:request_id

该 filter 不会修改 request,而是做为第一个修改 APIRouter 生成的 response 的 filter,在 response headers里面添加 x-openstack-request-id 项,其值为一个随机的 UUID.

def __call__(self, req):
req_id = context.generate_request_id() //req-<UUID>
req.environ[ENV_REQUEST_ID] = req_id //添加 'openstack.request_id'
response = req.get_response(self.application)
if HTTP_RESP_HEADER_REQUEST_ID not in response.headers: // 'x-openstack-request-id'
response.headers.add(HTTP_RESP_HEADER_REQUEST_ID, req_id) #在response上添加x-openstack-request-id,比如 x-openstack-request-id: 123567890
return response //return 修改后的 response

2. 调用已注册的 APIRouter 实例的 __call__ 方法

该方法直接返回 self._router,而它是一个 routes.middleware.RoutesMiddleware 类型的 WSGI app,所以调用其 __call__ 方法。

2.1 以 cinder list 为例分析 Core Resource 基本方法的分发过程

2.1.1 调用 routes.middleware.RoutesMiddleware 的 __call__ 方法

过程见注释部分:

def __call__(self, environ, start_response):
"""Resolves the URL in PATH_INFO, and uses wsgi.routing_args to pass on URL resolver results.""" # Run the actual route matching
# -- Assignment of environ to config triggers route matching
if self.singleton: #singleton 默认值为 True,所以执行该分支
config = request_config()
config.mapper = self.mapper
config.environ = environ
match = config.mapper_dict #获取match,({'action': u'detail', 'controller': <cinder.api.openstack.wsgi.Resource object at 0x7fa137be8950>, 'project_id': u'fa2046aaead44a698de8268f94759fc1'})
route = config.route #获取route
...
url = URLGenerator(self.mapper, environ) #比如 <routes.util.URLGenerator object at 0x7fa137a15590>
environ['wsgiorg.routing_args'] = ((url), match)
environ['routes.route'] = route #比如 'routes.route': <routes.route.Route object at 0x7fa137bef4d0>
environ['routes.url'] = url #比如 'routes.url': <routes.util.URLGenerator object at 0x7fa137a15590>
... response = self.app(environ, start_response) #调用 _dispatch WSGI App
...
return response

2.1.2 调用 Router.def _dispatch(req) 方法

其代码如下:

match = req.environ['wsgiorg.routing_args'][1] #match:{'action': u'detail', 'controller': <cinder.api.openstack.wsgi.Resource object at 0x7fa137be8950>, 'project_id': u'fa2046aaead44a698de8268f94759fc1'}
if not match:
return webob.exc.HTTPNotFound()
app = match['controller'] #<cinder.api.openstack.wsgi.Resource object at 0x7fa137be8950>
return app #调用该 App 的 __call__ 方法

2.1.3 执行 cinder.api.openstack.wsgi.Resource 的 def __call__(self, request) 方法

该方法负责(反)序列化和方法分发。

# Identify the action, its arguments, and the requested content type
action_args = self.get_action_args(request.environ) #从environ 获取 match
action = action_args.pop('action', None) #得到 action 'detail'
content_type, body = self.get_body(request) #得到request 的 content_type 和 body。body 为空。
accept = request.best_match_content_type() #如果 environ 中有 'cinder.best_content_type' 的话,直接返回,比如 'application/json';没有的话,则找个最合适的content type,并设置到environ。
return self._process_stack(request, action, action_args, content_type, body, accept) #调用 _process_stack

函数 _process_stack 的主要代码如下:

def _process_stack(self, request, action, action_args,content_type, body, accept):

#action_args:{'project_id': u'43f66bb82e684bbe9eb9ef6892bd7fd6'}, action: detail, body:, 

meth, extensions = self.get_method(request, action, content_type, body) 
#找到 action 对应的 Controller 的 method 和 这个 Core Resource 的所有带这个 aciton 的 Resource extensions(如果 action 是 ‘action’ 的话,根据 request body 中的信息,会把 action 转化为具体的 Controller 的 method)。
#返回值 meth: <bound method VolumeController.detail of <cinder.api.v2.volumes.VolumeController object at 0x7f2fd96863d0>>。
#返回值 extensions: [<bound method VolumeTenantAttributeController.detail of <cinder.api.contrib.volume_tenant_attribute.VolumeTenantAttributeController object at 0x7f2fd885ac50>>, <bound method VolumeReplicationController.detail of <cinder.api.contrib.volume_replication.VolumeReplicationController object at 0x7f2fd86eacd0>>, <bound method VolumeHostAttributeController.detail of <cinder.api.contrib.volume_host_attribute.VolumeHostAttributeController object at 0x7f2fd87088d0>>, <bound method VolumeMigStatusAttributeController.detail of <cinder.api.contrib.volume_mig_status_attribute.VolumeMigStatusAttributeController object at 0x7f2fd870e090>>, <bound method VolumeImageMetadataController.detail of <cinder.api.contrib.volume_image_metadata.VolumeImageMetadataController object at 0x7f2fd870e790>>] contents = self.deserialize(meth, content_type, body) #根据 content_type 反序列化 request body
response, post = self.pre_process_extensions(extensions, request, action_args) # 找出 extensions 的 _call__ 方法,分别调用,得到 response
def pre_process_extensions(self, extensions, request, action_args)
{ for ext in extensions:
#遍历所有的Resource extension,比如 <bound method VolumeTenantAttributeController.detail of <cinder.api.contrib.volume_tenant_attribute.VolumeTenantAttributeController object at 0x7f2fd885ac50>>

if inspect.isgeneratorfunction(ext): #如果object是一个生成器函数(任何包含yield表达式的函数即为生成器方法), 则返回True。现在还没有这样的方法。这里会返回false
        response = None

# If it's a generator function, the part before the yield is the preprocessing stage
try:
with ResourceExceptionHandler():
gen = ext(req=request, **action_args)
response = gen.next()
except Fault as ex:
response = ex

# We had a response...
if response:
return response, []

# No response, queue up generator for post-processing
post.append(gen)

else:
# Regular functions only perform post-processing
post.append(ext) #等待执行post-processing

# Run post-processing in the reverse order

#将所有不是generatorfunciton 的 ext 放进 post, 比如 [<bound method VolumeTenantAttributeController.detail of <cinder.api.contrib.volume_tenant_attribute.VolumeTenantAttributeController object at 0x7f2fd885ac50>>, <bound method VolumeReplicationController.detail of <cinder.api.contrib.volume_replication.VolumeReplicationController object at 0x7f2fd86eacd0>>, <bound method VolumeHostAttributeController.detail of <cinder.api.contrib.volume_host_attribute.VolumeHostAttributeController object at 0x7f2fd87088d0>>, <bound method VolumeMigStatusAttributeController.detail of <cinder.api.contrib.volume_mig_status_attribute.VolumeMigStatusAttributeController object at 0x7f2fd870e090>>, <bound method VolumeImageMetadataController.detail of <cinder.api.contrib.volume_image_metadata.VolumeImageMetadataController object at 0x7f2fd870e790>>]
return None, reversed(post)

}

if not response: #response 为 null,调用 method 方法,比如 <bound method VolumeController.detail of <cinder.api.v1.volumes.VolumeController object at 0x7fa137be8650>>
action_result = self.dispatch(meth, request, action_args) #执行Resource Controller 的基本方法,得到其返回值
def dispatch(self, method, request, action_args):
"""Dispatch a call to the action-specific method."""
#method: <bound method VolumeController.detail of <cinder.api.v2.volumes.VolumeController object at 0x7f2fd96863d0>>
#action_args: {}
#method执行结果为: {'volumes': [{'status': u'error', 'user_id': u'1dc0db32a936496ebfc50be54924a7cc', 'attachments': [], 'links': [{'href': u'http://controller:8776/v2/43f66bb82e684bbe9eb9ef6892bd7fd6/volumes/42ddb30e-8a36-4301-99e4-9547ce4e860d', 'rel': 'self'}, {'href': u'http://controller:8776/43f66bb82e684bbe9eb9ef6892bd7fd6/volumes/42ddb30e-8a36-4301-99e4-9547ce4e860d', 'rel': 'bookmark'}], 'availability_zone': u'nova', 'bootable': 'false', 'encrypted': False, 'created_at': datetime.datetime(2015, 1, 3, 2, 47, 52), 'description': None, 'volume_type': None, 'name': None, 'replication_status': u'disabled', 'consistencygroup_id': None, 'source_volid': None, 'snapshot_id': None, 'metadata': {}, 'id': u'42ddb30e-8a36-4301-99e4-9547ce4e860d', 'size': 1L}]}
return method(req=request, **action_args)
resp_obj = ResponseObject(action_result) #初始化其返回值为一个ResponseObject对象
_set_request_id_header(request, resp_obj) #设置 headers['x-compute-request-id'] = req.environ.get('cinder.context').request_id
serializers = getattr(meth, 'wsgi_serializers', {}) #获取Controller的 action 方法使用的serializers。detail 方法的serizlizer 是 {'xml': <class 'cinder.api.v2.volumes.VolumesTemplate'>}
resp_obj._bind_method_serializers(serializers) #设置 resp_obj 的序列化方法
resp_obj.preserialize(accept, self.default_serializers) # resp_obj 做序列化准备

response = self.post_process_extensions(post, resp_obj, request, action_args) # 对每个 extension 执行操作,获取response:  response = ext(req=request, resp_obj=resp_obj, **action_args)     

def post_process_extensions(self, extensions, resp_obj, request,action_args):

for ext in extensions:

#遍历 Resource extension。以 <bound method VolumeImageMetadataController.detail of <cinder.api.contrib.volume_image_metadata.VolumeImageMetadataController object at 0x7f2fd870e790>> 为例。

response = None

if inspect.isgenerator(ext): #Return true if the object is a generator. 这里返回 false

                # If it's a generator, run the second half of
# processing
try:
with ResourceExceptionHandler():
response = ext.send(resp_obj)
except StopIteration:
# Normal exit of generator
continue
except Fault as ex:
response = ex
else:
# Regular functions get post-processing...
try:
with ResourceExceptionHandler():
response = ext(req=request, resp_obj=resp_obj, **action_args) #调用 extension method 的方法,它会把 reponse 插进 resp_obj。本例子中将返回none。
@wsgi.extends
def detail(self, req, resp_obj):
context = req.environ['cinder.context']
if authorize(context):
resp_obj.attach(xml=VolumesImageMetadataTemplate())
all_meta = self._get_all_images_metadata(context)
for vol in list(resp_obj.obj.get('volumes', [])):
image_meta = all_meta.get(vol['id'], {})
self._add_image_metadata(context, vol, image_meta)
                except Fault as ex:
response = ex # We had a response...
if response:
return response
return None #返回none

if resp_obj and not response:

response = resp_obj.serialize(request, accept, self.default_serializers) #执行序列化,得到 response string

return response #返回response,后续需要改response的middleware的__call__方法将得到继续执行,指导回到 WSGI Server,它会将 response 返回给cinderclient

2.2 cinder quota-update 的简要过程 (os-quota-set  是Extension resource)

Request:

PUT /43f66bb82e684bbe9eb9ef6892bd7fd6/os-quota-sets/43f66bb82e684bbe9eb9ef6892bd7fd6

body: {"quota_set": {"gigabytes": 1000, "tenant_id": "43f66bb82e684bbe9eb9ef6892bd7fd6", "snapshots": 20, "volumes": 10}}

Route:

Route path: '/{project_id}/os-quota-sets/:(id)', defaults: {'action': u'update', 'controller': <cinder.api.openstack.wsgi.Resource object at 0x7f1662934c90>}

Dispatch:

method: <bound method QuotaSetsController.update of <cinder.api.contrib.quotas.QuotaSetsController object at 0x7f1662983c50>>

body: {"quota_set": {"gigabytes": 1000, "tenant_id": "43f66bb82e684bbe9eb9ef6892bd7fd6", "snapshots": 20, "volumes": 10}}

action_args: {'body': {u'quota_set': {u'gigabytes': 1000, u'tenant_id': u'43f66bb82e684bbe9eb9ef6892bd7fd6', u'volumes': 10, u'snapshots': 20}}, 'project_id': u'43f66bb82e684bbe9eb9ef6892bd7fd6', 'id': u'43f66bb82e684bbe9eb9ef6892bd7fd6'}

action_result: {'quota_set': {'snapshots': 20L, u'gigabytes_type-network': -1, u'snapshots_type-network': -1, u'volumes_newtype': -1, 'backups': 10, u'snapshots_typelvm': -1, u'volumes_type-b1': -1, u'volumes_type-b2': -1, u'snapshots_lvmtype': -1, u'volumes_lvmtype': -1, u'gigabytes_lvmtype': -1, u'volumes_typelvm': -1, u'snapshots_newtype': -1, 'gigabytes': 1000L, 'backup_gigabytes': 1000, u'gigabytes_type-b2': -1, u'snapshots_type-b1': -1, u'snapshots_type-b2': -1, u'gigabytes_type-b1': -1, u'gigabytes_newtype': -1, u'volumes_type-network': -1, u'gigabytes_typelvm': -1, 'volumes': 10L}}

Response:

serializers: {'xml': <class 'cinder.api.contrib.quotas.QuotaTemplate'>}

response = ['{"quota_set": {"snapshots": 20, "gigabytes_type-network": -1, "snapshots_type-network": -1, "volumes_newtype": -1, "backups": 10, "snapshots_typelvm": -1, "volumes_type-b1": -1, "volumes_type-b2": -1, "snapshots_lvmtype": -1, "volumes_lvmtype": -1, "gigabytes_lvmtype": -1, "volumes_typelvm": -1, "snapshots_newtype": -1, "gigabytes": 1000, "backup_gigabytes": 1000, "gigabytes_type-b2": -1, "snapshots_type-b1": -1, "snapshots_type-b2": -1, "gigabytes_type-b1": -1, "gigabytes_newtype": -1, "volumes_type-network": -1, "gigabytes_typelvm": -1, "volumes": 10}}']

2.3 以 cinder extend 的简要过程 (os-extend 是 volumes 资源扩展的 wsgi.action)

Request

POST http://9.123.245.88:8776/v2/2f07ad0f1beb4b629e42e1113196c04b/volumes/8ec62cc2-2f88-409c-a249-bfc1614eb8ab/action

{"os-extend": {"new_size": 2}}

Route:

Route path: '/{project_id}/volumes/:(id)/action', defaults: {'action': u'action', 'controller': <cinder.api.openstack.wsgi.Resource object at 0x7f459d3d4d90>}

match: {'action': u'action', 'controller': <cinder.api.openstack.wsgi.Resource object at 0x7f459d3d4d90>, 'project_id': u'2f07ad0f1beb4b629e42e1113196c04b', 'id': u'8ec62cc2-2f88-409c-a249-bfc1614eb8ab'}

Dispatch:

method:<bound method VolumeActionsController._extend of <cinder.api.contrib.volume_actions.VolumeActionsController object at 0x7f459d254d10>>

action_args: {'body': {u'os-extend': {u'new_size': 2}}, 'id': u'42ddb30e-8a36-4301-99e4-9547ce4e860d'}

Result:Invalid volume: Volume status must be available to extend. (因为此volume 的状态为 error)

3. 小结

小结 osapi-volume 处理 HTTP Request 的全过程::

1. Middleware filters 处理 HTTP Request header 的子过程,这其中主要是用户校验。

2. APIRouter 生成 HTTP Request 的 response 的子过程。依次: RoutesMiddleware 产生 route -> Resource 的 request body 反序列化、消息派发给 Resource Controller 的具体方法、Resource controller 的具体方法执行、结果的序列化等。

3. 个别 Middleware 还要处理一下 response,包括 RoutesMiddleware。

4. 最终的 response 发还给客户端。

探索 OpenStack 之(12):cinder-api Service 处理 HTTP Request 的过程分析的更多相关文章

  1. 探索 OpenStack 之(9):深入块存储服务Cinder (功能篇)

    继研究了Neutron之后,继续Nova的外围研究之旅.本站是研究块存储服务Cinder. 0.验证环境 环境包括: 1.一个controller节点,运行nova-api, nova-schedul ...

  2. 探索 OpenStack 之(11):cinder-api Service 启动过程分析 以及 WSGI / Paste deploy / Router 等介绍

    OpenStack 中的每一个提供 REST API Service 的组件,比如 cinder-api,nova-api 等,其实是一个 WSGI App,其主要功能是接受客户端发来的 HTTP R ...

  3. 探索 OpenStack 之(13):研究 Keystone

    Keystone 是 OpenStack Identity Service 的项目名称.本文就试着尽可能深入地研究 Keystone. 1. Keystone 的功能 做为 OpenStack 云系统 ...

  4. 探索 OpenStack 之(16):计量模块 Ceilometer 介绍及优化

    0. 背景 0.1 为什么要有 Ceilometer? 通常云,特别是公有云在计费方面有三个层次: 计量 (Metering): 收集资源的使用数据,其数据信息主要包括:使用对象(what), 使用者 ...

  5. 探索 OpenStack 之(17):计量模块 Ceilometer 中的数据收集机制

    本文将阐述 Ceilometer 中的数据收集机制.Ceilometer 使用三种机制来收集数据: Notifications:Ceilometer 接收 OpenStack 其它服务发出的 noti ...

  6. OpenStack IceHouse版cinder模块新添加功能

    感谢朋友支持本博客.欢迎共同探讨交流.因为能力和时间有限.错误之处在所难免,欢迎指正! 假设转载,请保留作者信息. 博客地址:http://blog.csdn.net/gaoxingnengjisua ...

  7. cinder api启动过程源码分析

    1.启动cinder-api服务 当你通过cinder-api命令(如:/usr/bin/cinder-api --config-file /etc/cinder/cinder.conf)启动api服 ...

  8. Request Entity Too Large for Self Hosted ASP.Net Web API在Selfhost的api后台怎么解决Request Entity Too Large问题

    Request Entity Too Large for Self Hosted ASP.Net Web API在Selfhost的api后台怎么解决Request Entity Too Large问 ...

  9. 探索 OpenStack 之(15):oslo.messaging 和 Cinder 中 MessageQueue 消息的发送和接收

    前言:上一篇文章 只是 RabbitMQ 的科普,本文将仔细分析 Cinder 中 RabbitMQ 的各组件的使用.消息的发送和接收等.由于各流程步骤很多,本文只会使用若干流程图来加以阐述,尽量做到 ...

随机推荐

  1. 【JS复习笔记】04 数组

    JS里的数组其实并不是一个数组,它其实是一个对象,a[1]这种调用方式其实就是一个字面量为1的属性. 因为这东西实际上是一个对象,所以你就可以理解下面这种声明了吧! var arrName=['我可以 ...

  2. asp.net中,<%#%>,<%=%>和<%%>分别是什么意思,有什么区别

    在asp.net中经常出现包含这种形式<%%>的html代码,总的来说包含下面这样几种格式:一.  <%%>这种格式实际上就是和asp的用法一样的,只是asp中里面是vbscr ...

  3. js倒计时防页面刷新

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  4. 从零开始学习Linux(mkdir and rmdir)

    今天说mkdir 和 rmdir.因为mkdir 内容比较少.而且也很好理解. 对于mkdir来说,一般只用到 -p -m,我只用过-p参数,-m也是刚刚看的. 先说不带参数的: mkdir  tes ...

  5. 机器学习实战 - 读书笔记(05) - Logistic回归

    解释 Logistic回归用于寻找最优化算法. 最优化算法可以解决最XX问题,比如如何在最短时间内从A点到达B点?如何投入最少工作量却获得最大的效益?如何设计发动机使得油耗最少而功率最大? 我们可以看 ...

  6. CSS3随内容自动伸缩的背景

    CSS3给我们带来一个非常实用的新属性:border-image,利用这个属性我们可以做出随着内容的增减自动伸缩的背景.废话不多说,看代码! HTML: <ol> <li>第一 ...

  7. C#加密算法总结

    C#加密算法总结 MD5加密 /// <summary> /// MD5加密 /// </summary> /// <param name="strPwd&qu ...

  8. db2死锁分析与处理

    在数据库中,锁的主要功能是为了控制并发数据的完整性而引入的机制,在并发应用中出现锁现象并不可怕,锁现象通常分为死锁和锁等待两种情形. 死锁是因为两个并发的进程或者线程同时各自占有一个资源,又需要占有对 ...

  9. SAPScript、Smartforms动态打印图像或者背景图片

    在利用 SMARTFORMS 进行打印的时候有时候要求输出的图片可能是随着打印内容的不同而不同了,也就是动态输出图片,SMARTFORMS的提供了相关的支持技术,下面是实现截图 1.创建要显示的图片 ...

  10. SharePoint 中用户控件的开发及应用

    1.新建解决方案以及SharePoint项目,步骤比较简单略过,然后映射CONTROLTEMPLATES文件夹,在里面添加用户控件(仅场解决方案),如下图: 2.解决方案结构,如下图: 简单介绍一下, ...