Openstack Cinder中建立volume过程的源码解析(1)

感谢朋友支持本博客,欢迎共同探讨交流,由于能力和时间有限,错误之处在所难免,欢迎指正!
如果转载,请保留作者信息。
博客地址:http://blog.csdn.net/gaoxingnengjisuan
邮箱地址:dong.liu@siat.ac.cn


从这篇博客开始,我将以cinder中卷的建立的过程的源码解析为例,来说明客户端传递过来的request的执行过程。

示例:

我们执行命令:cinder create --display_name shinian01 1

便可获取响应信息:

+---------------------+--------------------------------------+
|       Property      |                Value                 |
+---------------------+--------------------------------------+
|     attachments     |                  []                  |
|  availability_zone  |                 nova                 |
|       bootable      |                false                 |
|      created_at     |      2014-03-29T12:36:23.998481      |
| display_description |                 None                 |
|     display_name    |              shinian01               |
|          id         | 3f0aa242-9dab-48db-b63d-92b6dd38cf20 |
|       metadata      |                  {}                  |
|         size        |                  1                   |
|     snapshot_id     |                 None                 |
|     source_volid    |                 None                 |
|        status       |               creating               |
|     volume_type     |                 None                 |
+---------------------+--------------------------------------+

实际上,cinder模块中实现命令行指定操作主要有以下几个步骤:

1.应用若干中间件对客户端发送过来的请求信息进行过滤(封装)处理;

2.获取要执行的方法及其相关的扩展方法;

3.请求中body部分的反序列化;

4.获取的扩展方法的执行;

5.执行具体的方法,如卷的建立方法create;

6.响应信息的生成;


下面我们来进行逐条分析:

1.应用若干中间件对客户端发送过来的请求信息进行过滤(封装)处理;

当请求信息过来以后,会先后调用以下类中的__call__方法对其进行处理:

FaultWrapper->RequestBodySizeLimiter->CinderKeystoneContext->APIRouter->Resource

其中:

FaultWrapper:关于错误异常的处理;

RequestBodySizeLimiter:请求信息中body部分长度的检测;

CinderKeystoneContext:身份验证相关;

APIRouter:请求信息的路由处理;

Resource:action执行前的一些预处理;

这五个类中的__call__方法都是以语句@webob.dec.wsgify(RequestClass=Request)实现装饰的,其中前三个类为cinder定义的中间件,这里不进行深入的分析。我们具体来看APIRouterResource

1.1 class APIRouter----def __call__

这里调用的是类APIRouter的父类class Router中的__call__方法,我们来看具体代码:

class Router(object):
    """WSGI middleware that maps incoming requests to WSGI apps."""

    def __init__(self, mapper):
        """Create a router for the given routes.Mapper.

        Each route in `mapper` must specify a 'controller', which is a
        WSGI app to call.  You'll probably want to specify an 'action' as
        well and have your controller be an object that can route
        the request to the action-specific method.

        Examples:
          mapper = routes.Mapper()
          sc = ServerController()

          # Explicit mapping of one route to a controller+action
          mapper.connect(None, '/svrlist', controller=sc, action='list')

          # Actions are all implicitly defined
          mapper.resource('server', 'servers', controller=sc)

          # Pointing to an arbitrary WSGI app.  You can specify the
          # {path_info:.*} parameter so the target app can be handed just that
          # section of the URL.
          mapper.connect(None, '/v1.0/{path_info:.*}', controller=BlogApp())

        """
        self.map = mapper
        self._router = routes.middleware.RoutesMiddleware(self._dispatch, self.map)

    @webob.dec.wsgify(RequestClass=Request)
    def __call__(self, req):
        """Route the incoming request to a controller based on self.map.

        If no match, return a 404.

        """
        return self._router
我们可以看到在 __call__方法 中,返回了变量self._router,而可以得到:

self._router = routes.middleware.RoutesMiddleware(self._dispatch, self.map)

这就是我要说的重点,在前面的博客中,我们分析了cinder API的路由表形成过程,这条语句实现的功能就是根据之前形成的API路由表,实现请求信息的映射和匹配,得到实现这个请求所要执行的具体action的方法。比如上面例子,在这里就可以匹配到具体方法create。

1.2 classResource----def __call__

我们上面说过这几个中间件的__call__方法,都通过装饰器@webob.dec.wsgify(RequestClass=Request)进行了封装,我们以classResource----def __call__为例,来看看这一过程是怎么实现的,来看方法/cinder/api/openstack/wsgi.py----class Resource(wsgi.Application)----def __call__(self, request)的源码:

    @webob.dec.wsgify(RequestClass=Request)
    def __call__(self, request):
        """
        WSGI method that controls (de)serialization and method dispatch.
        """

        LOG.info("%(method)s %(url)s" % {"method": request.method,
                                         "url": request.url})

        # Identify the action, its arguments, and the requested
        # content type

        action_args = self.get_action_args(request.environ)
        # 输出:action_args = {'action': u'create', 'project_id': u'ecf0109bda814fa1a548af63f9ada370'}

        action = action_args.pop('action', None)
        # 输出:action = create
        
        # get_body:通过检测header,确定request body的内容类型;获取request中的body部分;
        content_type, body = self.get_body(request)
        # 输出:content_type = application/json
        # 输出:body = {"volume": {"status": "creating", "availability_zone": null, "source_volid": null, 
        #                         "display_description": null, "snapshot_id": null, "user_id": null, "size": 1, 
        #                         "display_name": "shinian01", "imageRef": null, "attach_status": "detached", 
        #                         "volume_type": null, "project_id": null, "metadata": {}}}

        # best_match_content_type:确定请求响应的内容类型;
        accept = request.best_match_content_type()
        # 输出:accept = application/json

        #request = POST /v1/ecf0109bda814fa1a548af63f9ada370/volumes HTTP/1.0
                  ......       
        #action = create
        #action_args = {'project_id': u'ecf0109bda814fa1a548af63f9ada370'}
        #content_type = application/json
        #body = {"volume": {"status": "creating", 
        #                   "availability_zone": null, 
        #                   "source_volid": null, 
        #                   "display_description": null, 
        #                   "snapshot_id": null, 
        #                   "user_id": null, 
        #                   "size": 1, 
        #                   "display_name": "shinian01", 
        #                   "imageRef": null, 
        #                   "attach_status": "detached", 
        #                   "volume_type": null, 
        #                   "project_id": null, 
        #                   "metadata": {}}}
        #accept = application/json
        
        return self._process_stack(request, action, action_args,
                                   content_type, body, accept)

来看/webob/dec.py----class wsgify(object)----def __call__(self, req, *args, **kw)的源码实现:

    def __call__(self, req, *args, **kw):
        """
        Call this as a WSGI application or with a request
        
        req = {'HTTP_X_TENANT_NAME': u'admin', 'routes.route': <routes.route.Route object at 0x240b090>, 'HTTP_X_ROLE': u'_member_,admin', 'HTTP_X_USER_NAME': u'admin', 'SCRIPT_NAME': '/v1', 'webob.adhoc_attrs': {'response': <Response at 0x257e3d0 200 OK>}, 'REQUEST_METHOD': 'POST', 'PATH_INFO': '/ecf0109bda814fa1a548af63f9ada370/volumes', ......}
        args = (<function start_response at 0x2552050>,)
        kw = {}
        """
        
        func = self.func
        #输出示例:func = <bound method Resource.__call__ of <cinder.api.openstack.wsgi.Resource object at 0x240b4d0>>
        
        if func is None:
            if args or kw:
                raise TypeError(
                    "Unbound %s can only be called with the function it "
                    "will wrap" % self.__class__.__name__)
            func = req
            return self.clone(func)
        
        if isinstance(req, dict):
            if len(args) != 1 or kw:
                raise TypeError(
                    "Calling %r as a WSGI app with the wrong signature")
            environ = req
            start_response = args[0]
            #输出示例:start_response = args[0] = <function start_response at 0x2552050>            
            
            #self.RequestClass = <class 'cinder.api.openstack.wsgi.Request'>
            #获取类Request的初始化对象req = Request(req),形成正式的req;
            req = self.RequestClass(environ)

            #获取响应信息格式;
            req.response = req.ResponseClass()
                       
            try:
                args = self.args
                #args = self.args = ()

                #self.middleware_wraps = None
                if self.middleware_wraps:
                    args = (self.middleware_wraps,) + args
                    
                # 这里调用指定中间件的__call__方法;
                resp = self.call_func(req, *args, **self.kwargs)
                
            except HTTPException as exc:
                resp = exc
                
            if resp is None:
                ## FIXME: I'm not sure what this should be?
                resp = req.response
                
            if isinstance(resp, text_type):
                resp = bytes_(resp, req.charset)
                
            if isinstance(resp, bytes):
                body = resp
                resp = req.response
                resp.write(body)

            if resp is not req.response:
                resp = req.response.merge_cookies(resp)
            
            return resp(environ, start_response)
        
        else:
            if self.middleware_wraps:
                args = (self.middleware_wraps,) + args

            return self.func(req, *args, **kw)
总体来讲,这个方法主要实现了对请求信息req和执行请求之后的响应信息进行了一些格式和内容上的处理操作。这里有一条比较重要的语句:

req = self.RequestClass(environ)

就这里的示例来讲,输出示例为self.RequestClass = <class 'cinder.api.openstack.wsgi.Request'>,所实现的功能就是通过现有的请求信息,对类Request进行实例初始化,形成后面所要应用到的常见格式的req。我们可以看看这里的输出示例:

req = POST /v1/ecf0109bda814fa1a548af63f9ada370/volumes HTTP/1.0
Accept: application/json
Accept-Encoding: gzip, deflate, compress
Content-Length: 294
Content-Type: application/json
Host: 172.21.5.164:8776
User-Agent: python-cinderclient
X-Auth-Project-Id: admin
X-Auth-Token: MIIQKQYJKoZIhvc......
X-Domain-Id: None
X-Domain-Name: None
X-Identity-Status: Confirmed
X-Project-Domain-Id: None
X-Project-Domain-Name: None
X-Project-Id: ecf0109bda814fa1a548af63f9ada370
X-Project-Name: admin
X-Role: _member_,admin
X-Roles: _member_,admin
X-Service-Catalog: [{"endpoints_links": [], ......]
X-Tenant: admin
X-Tenant-Id: ecf0109bda814fa1a548af63f9ada370
X-Tenant-Name: admin
X-User: admin
X-User-Domain-Id: None
X-User-Domain-Name: None
X-User-Id: d2ee2dd06c9e49098a3d2a278c650b6c
X-User-Name: admin
{"volume": {"status": "creating", 
            "availability_zone": null, 
            "source_volid": null, 
            "display_description": null, 
            "snapshot_id": null, 
            "user_id": null, 
            "size": 1, 
            "display_name": "shinian01", 
            "imageRef": null, 
            "attach_status": "detached", 
            "volume_type": null, 
            "project_id": null, 
            "metadata": {}}}
再来看语句req.response = req.ResponseClass(),这条语句在这里实现的就是形成响应信息的初步格式,来看看输出示例:

req.response = 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 0
再来看语句resp = self.call_func(req, *args, **self.kwargs),这条语句实现的就是调用上面提到的方法 /cinder/api/openstack/wsgi.py----class Resource(wsgi.Application)----def __call__(self, request)。来看方法call_func的实现:

    def call_func(self, req, *args, **kwargs):
        """Call the wrapped function; override this in a subclass to
        change how the function is called."""
        return self.func(req, *args, **kwargs)
这里的self.func的值在类 class wsgify(object)的初始化方法中已经进行了赋值,此处self.func = <bound method Resource.__call__ of <cinder.api.openstack.wsgi.Resource object at 0x240b4d0>>。

待到方法class Resource(wsgi.Application)----def __call__(self, request)执行过后,会回到方法/webob/dec.py----class wsgify(object)----def __call__(self, req, *args, **kw)中继续后面代码的执行,其具体功能也是对获取到的响应信息进行若干格式化上的转换和处理。

现在,应用若干中间件对客户端发送过来的请求信息进行过滤(封装)处理部分简单的解析完成,下一篇博客中会具体解析方法class Resource(wsgi.Application)----def __call__(self, request),具体深入解析cinder建立volume的过程。

好困啊又感冒了,明后天我会继续这部分博客的撰写,谢谢大家批评指正!


  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值