本文是上一篇 探索 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. ahjesus ubuntu10.4安装ruby2.1.1

    sudo apt-get install python-software-properties sudo apt-add-repository ppa:brightbox/ruby-ng sudo a ...

  2. 二进制打印与逆序_C语言(转)

    //二进制逆序 by MoreWindows( http://blog.csdn.net/MoreWindows ) #include <stdio.h> //二进制打印函数 templa ...

  3. CMD和AMD

    CMD是国内玉伯在开发SeaJS的时候提出来的,属于CommonJS的一种规范,此外还有AMD,其对于的框架是RequireJS. 二者的异同之处: 二者都是异步(Asynchronuous Modu ...

  4. win7 下配置Openssl

    最近刚刚装了openssl,遇到了很多问题,于是记录了下来: 我的PC环境是:系统win7,32位,Microsoft Visual Studio 2010: 下面开始安装: 1.安装前的准备:首先下 ...

  5. 基于h5的图片无刷新上传(uploadifive)

    基于h5的图片无刷新上传(uploadifive) uploadifive简介 了解uploadify之前,首先了解来一下什么是uploadify,uploadfy官网,uploadify和uploa ...

  6. CSS类选择器和ID选择器

    CSS类选择器和ID选择器皆允许以一种独立于文档元素的方式来指定样式,同时二者皆区分大小写. 区别如下: 第一:在同一个页面内,不允许有相同名字的id对象出现,但是允许相同名字的class 第二:当页 ...

  7. ABAP modify screen:修改屏幕,实现隐藏、禁止输入字段

    Loop at screen会loop处理屏幕上的每一个组件,并对其做相应的处理. SELECTION-SCREEN: BEGIN OF BLOCK B1 WITH FRAME.PARAMETERS  ...

  8. C# DevExpress 的gridControl或gridView数据导出失败解决方法

    来自:http://blog.csdn.net/lybwwp/article/details/8049464 谢谢 在使用DevExpress 的GridPanel控件的时候出现了一个莫名其妙的现象, ...

  9. Android Studio 插件的使用

    1.GsonFormat https://github.com/zzz40500/GsonFormat 2.Android SelectorChapek     http://blog.csdn.ne ...

  10. 得到设备是何种iPhone设备 + 怎么获得启动页面图片

    一.前言 今天做一个功能,需要动态的获得启动页,然后根据不同设备去使用不用的启动页图片. 二.正文 常规来说,我们直接判断是何种设备,然后通过name去获得图片选择性加载即可.但是实际上遇到的两个问题 ...