分享

探索 OpenStack 之(10):cinder-api Service 处理 HTTP Request 的过程分析

旧收音机 发表于 2015-4-4 22:02:55 [显示全部楼层] 回帖奖励 阅读模式 关闭右栏 2 18965
问题导读
1、怎么按顺序调用已经注册的 middleware (filter) 实例的 __call__ 方法?
2、怎么调用已注册的 APIRouter 实例的 __call__ 方法?
3、osapi-volume 处理 HTTP Request过程是怎样的








本文是上一篇 探索 OpenStack 之(9):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 使用。
  1. def __call__(self, req):
  2. 'keystonecontext : Make a request context from keystone headers.'
  3. ctx = context.RequestContext(user_id,
  4.                                      project_id,
  5.                                      project_name=project_name,
  6.                                      roles=roles,
  7.                                      auth_token=auth_token,
  8.                                      remote_address=remote_address,
  9.                                      service_catalog=service_catalog,
  10.                                      request_id=req_id)
  11. req.environ['cinder.context'] = ctx #添加 cinder.context
  12. 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,不再进行后续处理。
  1. def __call__(self, req):
  2.      if req.content_length > CONF.osapi_max_request_body_size: #判断 req.content_length,大于 CONF.osapi_max_request_body_size 则直接失败
  3.             msg = _("Request is too large.")
  4.             raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg)
  5.         if req.content_length is None and req.is_body_readable:
  6.             limiter = LimitingReader(req.body_file, CONF.osapi_max_request_body_size)
  7.             req.body_file = limiter
  8.         return self.application
复制代码
1.5 filter:faultwrap
该 filter 不会修改 request 和 respone,而是在一旦之前的App 处理 response 出错的话,它 catch 住错误并调用 _error 方法,返回一个适当的错误信息。
  1. def __call__(self, req):
  2.        try:
  3.            return req.get_response(self.application)
  4.        except Exception as ex:
  5.            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.
  1. def __call__(self, req):
  2.         req_id = context.generate_request_id()  //req-<UUID>
  3.         req.environ[ENV_REQUEST_ID] = req_id //添加 'openstack.request_id'
  4.         response = req.get_response(self.application)
  5.         if HTTP_RESP_HEADER_REQUEST_ID not in response.headers: // 'x-openstack-request-id'
  6.             response.headers.add(HTTP_RESP_HEADER_REQUEST_ID, req_id) #在response上添加x-openstack-request-id,比如 x-openstack-request-id: 123567890
  7.         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__ 方法
过程见注释部分:
  1. def __call__(self, environ, start_response):
  2.         """Resolves the URL in PATH_INFO, and uses wsgi.routing_args to pass on URL resolver results."""
  3.                
  4.         # Run the actual route matching
  5.         # -- Assignment of environ to config triggers route matching
  6.         if self.singleton: #singleton 默认值为 True,所以执行该分支
  7.             config = request_config()
  8.             config.mapper = self.mapper
  9.             config.environ = environ
  10.             match = config.mapper_dict #获取match,({'action': u'detail', 'controller': <cinder.api.openstack.wsgi.Resource object at 0x7fa137be8950>, 'project_id': u'fa2046aaead44a698de8268f94759fc1'})
  11.             route = config.route #获取route
  12.         ...               
  13.         url = URLGenerator(self.mapper, environ) #比如 <routes.util.URLGenerator object at 0x7fa137a15590>
  14.         environ['wsgiorg.routing_args'] = ((url), match)
  15.         environ['routes.route'] = route #比如 'routes.route': <routes.route.Route object at 0x7fa137bef4d0>
  16.         environ['routes.url'] = url #比如 'routes.url': <routes.util.URLGenerator object at 0x7fa137a15590>
  17.         ...
  18.       
  19.         response = self.app(environ, start_response) #调用 _dispatch WSGI App
  20.         ...
  21.         return response
复制代码
2.1.2 调用 Router.def _dispatch(req) 方法
其代码如下:
  1. match = req.environ['wsgiorg.routing_args'][1] #match:{'action': u'detail', 'controller': <cinder.api.openstack.wsgi.Resource object at 0x7fa137be8950>, 'project_id': u'fa2046aaead44a698de8268f94759fc1'}
  2. if not match:
  3.      return webob.exc.HTTPNotFound()
  4. app = match['controller'] #<cinder.api.openstack.wsgi.Resource object at 0x7fa137be8950>
  5. return app #调用该 App 的 __call__ 方法
复制代码
2.1.3 执行 cinder.api.openstack.wsgi.Resource 的 def __call__(self, request) 方法
该方法负责(反)序列化和方法分发。
  1. # Identify the action, its arguments, and the requested content type
  2. action_args = self.get_action_args(request.environ) #从environ 获取 match
  3. action = action_args.pop('action', None) #得到 action 'detail'
  4. content_type, body = self.get_body(request) #得到request 的 content_type 和 body。body 为空。
  5. accept = request.best_match_content_type() #如果 environ 中有 'cinder.best_content_type' 的话,直接返回,比如 'application/json';没有的话,则找个最合适的content type,并设置到environ。
  6. return self._process_stack(request, action, action_args, content_type, body, accept) #调用 _process_stack
复制代码
函数 _process_stack 的主要代码如下:
  1. def _process_stack(self, request, action, action_args,content_type, body, accept):
  2. #action_args:{'project_id': u'43f66bb82e684bbe9eb9ef6892bd7fd6'}, action: detail, body:,
  3. meth, extensions = self.get_method(request, action, content_type, body)
  4. #找到 action 对应的 Controller 的 method 和 这个 Core Resource 的所有带这个 aciton 的 Resource  extensions(如果 action 是 ‘action’ 的话,根据 request body 中的信息,会把 action 转化为具体的 Controller 的 method)。
  5. #返回值 meth: <bound method VolumeController.detail of <cinder.api.v2.volumes.VolumeController object at 0x7f2fd96863d0>>。
  6. #返回值 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>>]
  7. contents = self.deserialize(meth, content_type, body) #根据 content_type 反序列化 request body
  8. response, post = self.pre_process_extensions(extensions, request, action_args) # 找出 extensions 的 _call__ 方法,分别调用,得到 response
  9. def pre_process_extensions(self, extensions, request, action_args)
  10. {
  11.    for ext in extensions:
  12.        #遍历所有的Resource extension,比如 <bound method VolumeTenantAttributeController.detail of <cinder.api.contrib.volume_tenant_attribute.VolumeTenantAttributeController object at 0x7f2fd885ac50>>
  13.     if inspect.isgeneratorfunction(ext): #如果object是一个生成器函数(任何包含yield表达式的函数即为生成器方法), 则返回True。现在还没有这样的方法。这里会返回false
  14.         response = None
  15.                 # If it's a generator function, the part before the yield is the preprocessing stage
  16.                 try:
  17.                     with ResourceExceptionHandler():
  18.                         gen = ext(req=request, **action_args)
  19.                         response = gen.next()
  20.                 except Fault as ex:
  21.                     response = ex
  22.                 # We had a response...
  23.                 if response:
  24.                     return response, []
  25.                 # No response, queue up generator for post-processing
  26.                 post.append(gen)
  27. else:
  28.                 # Regular functions only perform post-processing
  29.                 post.append(ext) #等待执行post-processing
  30.         # Run post-processing in the reverse order
  31. #将所有不是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>>]
  32.         return None, reversed(post)
  33. }
  34. if not response: #response 为 null,调用 method 方法,比如 <bound method VolumeController.detail of <cinder.api.v1.volumes.VolumeController object at 0x7fa137be8650>>
  35.     action_result = self.dispatch(meth, request, action_args) #执行Resource Controller 的基本方法,得到其返回值   
  36. def dispatch(self, method, request, action_args):
  37.         """Dispatch a call to the action-specific method."""
  38.         #method: <bound method VolumeController.detail of <cinder.api.v2.volumes.VolumeController object at 0x7f2fd96863d0>>
  39.         #action_args: {}
  40.         #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}]}
  41.         return method(req=request, **action_args)
  42. resp_obj = ResponseObject(action_result) #初始化其返回值为一个ResponseObject对象
  43. _set_request_id_header(request, resp_obj) #设置 headers['x-compute-request-id'] = req.environ.get('cinder.context').request_id
  44. serializers = getattr(meth, 'wsgi_serializers', {}) #获取Controller的 action 方法使用的serializers。detail 方法的serizlizer 是 {'xml': <class 'cinder.api.v2.volumes.VolumesTemplate'>}
  45. resp_obj._bind_method_serializers(serializers) #设置 resp_obj 的序列化方法
  46. resp_obj.preserialize(accept, self.default_serializers) # resp_obj 做序列化准备
  47.   response = self.post_process_extensions(post, resp_obj, request, action_args) # 对每个 extension 执行操作,获取response:  response = ext(req=request, resp_obj=resp_obj, **action_args)     
  48. def post_process_extensions(self, extensions, resp_obj, request,action_args):
  49.     for ext in extensions:
  50.              #遍历 Resource extension。以 <bound method VolumeImageMetadataController.detail of <cinder.api.contrib.volume_image_metadata.VolumeImageMetadataController object at 0x7f2fd870e790>> 为例。
  51.             response = None
  52.             if inspect.isgenerator(ext): #Return true if the object is a generator. 这里返回 false
  53.                 # If it's a generator, run the second half of
  54.                 # processing
  55.                 try:
  56.                     with ResourceExceptionHandler():
  57.                         response = ext.send(resp_obj)
  58.                     except StopIteration:
  59.                     # Normal exit of generator
  60.                     continue
  61.                 except Fault as ex:
  62.                     response = ex
  63.             else:
  64.                 # Regular functions get post-processing...
  65.                 try:
  66.                     with ResourceExceptionHandler():
  67.                         response = ext(req=request, resp_obj=resp_obj, **action_args) #调用 extension method 的方法,它会把 reponse 插进 resp_obj。本例子中将返回none。
  68. @wsgi.extends
  69.     def detail(self, req, resp_obj):
  70.         context = req.environ['cinder.context']
  71.         if authorize(context):
  72.             resp_obj.attach(xml=VolumesImageMetadataTemplate())
  73.             all_meta = self._get_all_images_metadata(context)
  74.             for vol in list(resp_obj.obj.get('volumes', [])):
  75.                 image_meta = all_meta.get(vol['id'], {})
  76.                 self._add_image_metadata(context, vol, image_meta)
  77.                 except Fault as ex:
  78.                     response = ex
  79.             # We had a response...
  80.             if response:
  81.                 return response
  82.         return None #返回none
  83.   if resp_obj and not response:
  84.       response = resp_obj.serialize(request, accept, self.default_serializers) #执行序列化,得到 response string
  85.   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/2f07 ... 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过程
小结 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之 (1):安装devstack

学习OpenStack之 (2):Cinder LVM 配置

学习OpenStack之 (3):Devstack Screen 使用技巧

学习OpenStack之(4):在Mac上部署Juno版本OpenStack 四节点环境

学习OpenStack之(5):Neutron 深入学习之 OVS + GRE 之 Compute node 篇

探索 OpenStack 之(6):Neutron 深入探索之 OVS + GRE 之 完整网络流程 篇

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


探索 OpenStack 之(8):深入镜像服务Glance

探索 OpenStack 之(9):cinder-api Service 启动过程分析 以及 WSGI / Paste dep

探索 OpenStack 之(10):cinder-api Service 处理 HTTP Request 的过程分析

探索 OpenStack 之(11):研究 Keystone











已有(2)人评论

跳转到指定楼层
feng01301218 发表于 2015-4-5 11:04:34
问题导读
1、怎么按顺序调用已经注册的 middleware (filter) 实例的 __call__ 方法?
2、怎么调用已注册的 APIRouter 实例的 __call__ 方法?
3、osapi-volume 处理 HTTP Request过程是怎样的?
回复

使用道具 举报

ainubis 发表于 2015-4-5 18:42:12
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条