本文是上一篇 探索 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. C#基础--之数据类型

    C#基础--之数据类型 摘自:http://www.cnblogs.com/tonney/archive/2011/03/18/1987577.html 在第一章我们了解了C#的输入.输出语句后,我这 ...

  2. C#十五子游戏

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...

  3. asp.net.web如何简单生成和保存二维码图片的例子

    首先,要有生成二维码图片,需要二维码生成的类库,到官网下载thoughtWorks.QRCode.dll 例子的步骤: 1.创建项目QRCodeTest1,选择asp.net.web窗体应用程序

  4. 怎样用C#代码知道是否已连接网络

    有时,上传数据和下载数据都需要用到网络,但是不知道程序是否已连接到网络,下面是简单测试是否已连接网络的小功能 1.在winform窗体上添加一个按钮和多选框

  5. 不经意间网易开源镜像去掉了FreeBSD的镜像

    http://mirrors.163.com/ FreeBSD已经到了这么不招人待见的地步了么? 网易曾经可是FreeBSD的大户啊.

  6. 【Android】Android SDK Manager更新慢/失败的问题

    前言:更新下载Intel x86 Atom_64 System Image的时候总是失败,速度只有几KB,我这是10M的网啊. 最后找到一篇日志,解决了这个问题.非常感谢!其参考地址:http://w ...

  7. Android Xutils 框架(转)

    Android Xutils 框架 (转) 目录(?)[-] xUtils简介 目前xUtils主要有四大模块 使用xUtils快速开发框架需要有以下权限 混淆时注意事项 DbUtils使用方法 Vi ...

  8. 使用openssl创建自签名证书及部署到IIS教程

    概要 本文讲解三个部分:1. 创建自签名证书2. 创建自己的证书颁发机构3. 以及如何配置IIS 创建自签名证书 首先,创建一个私钥文件: openssl genrsa -out myselfsign ...

  9. .NET破解之分享给新注册的朋友

    前些日子,在论坛里看了有人发过这个软件,也有大神分析过网络版,如果是重帖,请删除吧:正好11.11注册了很多新会员,给他们一个见面礼吧,抛砖引玉,我才来论坛的时候,也是看着前人教程慢慢学习的:好久没冒 ...

  10. Android项目实战(二):安卓应用程序退出的三种方法

    现在的APP退出的时候都不是让用户点击了“后退键”就退出.防止用户点错了后退键而造成的用户体检不好. 一年前搞的Demo代码不见了,重新写下就当是复习和以后直接拿来用把 目前流行的解决一般分为两种: ...