探索 OpenStack 之(12):cinder-api Service 處理 HTTP Request 的過程分析


 

本文是上一篇 探索 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 發還給客戶端。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM