分享

Glance源码架构探秘(三)

坎蒂丝_Swan 发表于 2014-12-6 19:12:48 [显示全部楼层] 回帖奖励 阅读模式 关闭右栏 0 12137
本帖最后由 坎蒂丝_Swan 于 2014-12-6 19:32 编辑

问题导读


问题1:什么是webob?有什么功能?发挥功能时有什么优势?

问题2:Resource这个类发挥了什么作用?








Glance源码架构探秘(一)

Glance源码架构探秘(二)

Glance源码架构探秘(三)

Glance源码架构探秘(四)


上一章我们分析了OpenStack中如何用eventlet库创建一个“绿化”的协程式web server,以及WSGI相关概念及在Python中的参考实现,本章我们将具体地讲解WSGI程序内部的框架。

其实,简单的说,Glance及其他OpenStack的组件和大多数的web service一样,也是实现了一个MVC的框架的(关于MVC框架的概念,并不是本文的重点,可自行展开阅读),这个框架称之为RackSpace框架。包括消息路由,控制器Controller、action等等。

这个框架并非完全自己实现,而是借用了许多开源的组件,比如消息路由就用到了Routes这个组件。Routes是借鉴了ruby on rails的某些思想后在Python上的一个实现,用于路由URL消息至对应的控制器和动作action,也可以用来生成URLs。

Routes用法非常清晰明了

  1. # Setup a mapper
  2. from routes import Mapper
  3. map = Mapper()
  4. map.connect(None, "/error/{action}/{id}, controller="error")
  5. map.connect("home", "/", controller="main", action="index")
  6. # Match a URL, returns a dict or None if no match
  7. result = map.match('/error/myapp/4')
  8. # result == {'controller': 'main', 'action': 'myapp', 'id': '4'}
复制代码
导入routes,创建一个Mapper实例。之后,我们就可以用Mapper.connect()绑定路由。最后用Mapper.match()方法可以将给定的URL转换为路由信息,包括controller,action或其他用户自定义的路由类型。


在我们正式讲解Glance的代码之前,还要给大家介绍其WSGI中用到的另外一个开源组件webob。这也是我常用以及非常喜欢的一个开源组件,使用webob可以大幅简化编写框架的复杂度和代码量。webob用来包装WSGI应用程序的请求以及响应的类库。

  1. >>> from webob import Request
  2. >>> environ = {'wsgi.url_scheme': 'http', ...}  
  3. >>> req = Request(environ)      
复制代码
  1. >>> req = Request.blank('/article?id=1')
  2. >>> from pprint import pprint
  3. >>> pprint(req.environ)
  4. {'HTTP_HOST': 'localhost:80',
  5. 'PATH_INFO': '/article',
  6. 'QUERY_STRING': 'id=1',
  7. 'REQUEST_METHOD': 'GET',
  8. 'SCRIPT_NAME': '',
  9. 'SERVER_NAME': 'localhost',
  10. 'SERVER_PORT': '80',
  11. 'SERVER_PROTOCOL': 'HTTP/1.0',
  12. 'wsgi.errors': <open file '<stderr>', mode 'w' at ...>,
  13. 'wsgi.input': <...IO... object at ...>,
  14. 'wsgi.multiprocess': False,
  15. 'wsgi.multithread': False,
  16. 'wsgi.run_once': False,
  17. 'wsgi.url_scheme': 'http',
  18. 'wsgi.version': (1, 0)}
复制代码
上面的示例程序就简单说明了如何用webob的Request生成一个request请求和解析一个request请求的环境信息。


然而,webob的功能不止于此。其还有一个功能就是将一个普通的函数function包装为一个WSGI函数,并符合WSGI函数的规范(实现规范的接口,evn,start_response)。webob.dec.wsgify
  1. @wsgify
  2. def myfunc(req):
  3.     return webob.Response('hey there')
复制代码

如上示例,就将myfunc包装为了一个可被webserver调用的wsgi函数。如果我们不用这个包装,就要非常繁琐的写为
  1. def myfunc(req, start_response):
  2.     start_response('200 OK', header)
  3.     return ['hey there']
复制代码
另外,用webob.exc通过生成Python异常的方式,生成HTTP的一些错误响应,如404,500等等。


下面我们看Glance的源码节选分析
我们先来看router.py这个文件。大家应该还记得,在上一章我们启动http server中填入的WSGI function是从glance-api-paste.ini中读取的,这个app正是glance.api.v2.router.API.factory。
  1. class API(wsgi.Router):
  2.     """WSGI router for Glance v2 API requests."""
  3.     def __init__(self, mapper):
  4.         custom_image_properties = images.load_custom_properties()
  5.         schemas_resource = schemas.create_resource(custom_image_properties)
  6.         mapper.connect('/schemas/image',
  7.                        controller=schemas_resource,
  8.                        action='image',
  9.                        conditions={'method': ['GET']})
  10.         mapper.connect('/schemas/images',
  11.                        controller=schemas_resource,
  12.                        action='images',
  13.                        conditions={'method': ['GET']})
  14.         images_resource = images.create_resource(custom_image_properties)
  15.         mapper.connect('/images',
  16.                        controller=images_resource,
  17.                        action='index',
  18.                        conditions={'method': ['GET']})
  19.         mapper.connect('/images',
  20.                        controller=images_resource,
  21.                        action='create',
  22.                        conditions={'method': ['POST']})
  23.         mapper.connect('/images/{image_id}',
  24.                        controller=images_resource,
  25.                        action='update',
  26.                        conditions={'method': ['PATCH']})
  27.         mapper.connect('/images/{image_id}',
  28.                        controller=images_resource,
  29.                        action='show',
  30.                        conditions={'method': ['GET']})
  31.         mapper.connect('/images/{image_id}',
  32.                        controller=images_resource,
  33.                        action='delete',
  34.                        conditions={'method': ['DELETE']})
  35.        ......
  36.        ......
  37.         super(API, self).__init__(mapper)
复制代码

上面我们已经介绍过了routes的mapper类,这里的路由分配设置就一目了然了。因为是使用的RESTful接口,路由分配的时候还可以指定method。API这个类的基类是wsgi.Router,刚刚WSGI也是调用的其基类的工厂方法router.factory。这样我们继续介绍上一章没有介绍玩的wsgi.py的后半部分。
  1. class Router(object):
  2.     """
  3.     WSGI middleware that maps incoming requests to WSGI apps.
  4.     """
  5.     def __init__(self, mapper):
  6.         """
  7.         Create a router for the given routes.Mapper.
  8.         Each route in `mapper` must specify a 'controller', which is a
  9.         WSGI app to call.  You'll probably want to specify an 'action' as
  10.         well and have your controller be a wsgi.Controller, who will route
  11.         the request to the action method.
  12.         Examples:
  13.           mapper = routes.Mapper()
  14.           sc = ServerController()
  15.           # Explicit mapping of one route to a controller+action
  16.           mapper.connect(None, "/svrlist", controller=sc, action="list")
  17.           # Actions are all implicitly defined
  18.           mapper.resource("server", "servers", controller=sc)
  19.           # Pointing to an arbitrary WSGI app.  You can specify the
  20.           # {path_info:.*} parameter so the target app can be handed just that
  21.           # section of the URL.
  22.           mapper.connect(None, "/v1.0/{path_info:.*}", controller=BlogApp())
  23.         """
  24.         self.map = mapper
  25.         self._router = routes.middleware.RoutesMiddleware(self._dispatch,
  26.                                                           self.map)
  27.     @classmethod
  28.     def factory(cls, global_conf, **local_conf):
  29.         return cls(routes.Mapper())
  30.     @webob.dec.wsgify
  31.     def __call__(self, req):
  32.         """
  33.         Route the incoming request to a controller based on self.map.
  34.         If no match, return a 404.
  35.         """
  36.         return self._router
  37.     @staticmethod
  38.     @webob.dec.wsgify
  39.     def _dispatch(req):
  40.         """
  41.         Called by self._router after matching the incoming request to a route
  42.         and putting the information into req.environ.  Either returns 404
  43.         or the routed WSGI app's response.
  44.         """
  45.         match = req.environ['wsgiorg.routing_args'][1]
  46.         if not match:
  47.             return webob.exc.HTTPNotFound()
  48.         app = match['controller']
  49.         return app
复制代码
这个基类的工厂方法被包装为了类方法,实际就会创建刚才的API子类。此类的可调用对象方法__call__被包装成了wsgi,所以这个类的对象可以作为WSGI函数进行调用。routes.middleware是routes组件中另外一个重要的模块,他可以从上层wsgi程序中接收request请求的URL,并自动调用map.match()方法,将URL进行消息路由,并返回路由的结果。路由结果将会被存入request请求的环境变量['wsgiorg.routing_args']中。最后会调用其第一个参数给出的函数接口,继续下一级的WSGI调用,代码中就是self._dispatch。


这层的dispatch分发会从路由结果中找到是哪个controller,然后从router.py中查找并创建controller对应的resource,分析其创建过程,实质上最后还是会在wsgi.py中创建一个resource基类,所以我们继续看wsgi.py的最后一部分。
  1. class Resource(object):
  2.     """
  3.     WSGI app that handles (de)serialization and controller dispatch.
  4.     Reads routing information supplied by RoutesMiddleware and calls
  5.     the requested action method upon its deserializer, controller,
  6.     and serializer. Those three objects may implement any of the basic
  7.     controller action methods (create, update, show, index, delete)
  8.     along with any that may be specified in the api router. A 'default'
  9.     method may also be implemented to be used in place of any
  10.     non-implemented actions. Deserializer methods must accept a request
  11.     argument and return a dictionary. Controller methods must accept a
  12.     request argument. Additionally, they must also accept keyword
  13.     arguments that represent the keys returned by the Deserializer. They
  14.     may raise a webob.exc exception or return a dict, which will be
  15.     serialized by requested content type.
  16.     """
  17.     def __init__(self, controller, deserializer=None, serializer=None):
  18.         """
  19.         :param controller: object that implement methods created by routes lib
  20.         :param deserializer: object that supports webob request deserialization
  21.                              through controller-like actions
  22.         :param serializer: object that supports webob response serialization
  23.                            through controller-like actions
  24.         """
  25.         self.controller = controller
  26.         self.serializer = serializer or JSONResponseSerializer()
  27.         self.deserializer = deserializer or JSONRequestDeserializer()
  28.     @webob.dec.wsgify(RequestClass=Request)
  29.     def __call__(self, request):
  30.         """WSGI method that controls (de)serialization and method dispatch."""
  31.         action_args = self.get_action_args(request.environ)
  32.         action = action_args.pop('action', None)
  33.         deserialized_request = self.dispatch(self.deserializer,
  34.                                              action, request)
  35.         action_args.update(deserialized_request)
  36.         action_result = self.dispatch(self.controller, action,
  37.                                       request, **action_args)
  38.         try:
  39.             response = webob.Response(request=request)
  40.             self.dispatch(self.serializer, action, response, action_result)
  41.             return response
  42.         # return unserializable result (typically a webob exc)
  43.         except Exception:
  44.             return action_result
  45.     def dispatch(self, obj, action, *args, **kwargs):
  46.         """Find action-specific method on self and call it."""
  47.         try:
  48.             method = getattr(obj, action)
  49.         except AttributeError:
  50.             method = getattr(obj, 'default')
  51.         return method(*args, **kwargs)
  52.     def get_action_args(self, request_environment):
  53.         """Parse dictionary created by routes library."""
  54.         try:
  55.             args = request_environment['wsgiorg.routing_args'][1].copy()
  56.         except Exception:
  57.             return {}
  58.         try:
  59.             del args['controller']
  60.         except KeyError:
  61.             pass
  62.         try:
  63.             del args['format']
  64.         except KeyError:
  65.             pass
  66.         return args
复制代码

Resource这个类可以对请求解串行化,或串行化响应信息,并可以分发controller控制器action的方法。这个类的初始化方法会给对象添加三个属性,controller、serializer、deserializer,分别为控制器,串行化,逆串行化。对象函数__call__也被装饰为WSGI函数,接受上一级WSGI函数的请求,并将上一级Routes后的路由信息(controller=?action=?)通过dispatch方法将指定的action分发到controller控制器类所对应的方法,在源码中用到了getattr,动态的获得对象的绑定方法。

通过上述一系列步骤,就将glance命令请求消息经过路由、分发等步骤送到了控制器类所对应的action方法中,让glance得以进一步的执行客户发来的请求命令。前面通过三章的篇幅,为大家讲解了glance/common/wsgi.py这个文件,主要实现了一个web框架的大部分功能。因为OpenStack的组件都是基于同一套rackspace框架的,这对于我们今后学习Nova,Cinder等其他OpenStack都是颇有益处的。所以今后如有Nova等组件的源码探秘,此部分也不再会单独分析,将把经历多投入到其他方面。

后面几章将开始具体分析glance在接到请求命令后,如何进行镜像的上传,下载,更新等,同样包括数据库和镜像元数据更新等的操作,其中用到了许多非常棒的设计模式,敬请期待!


欢迎加入about云群90371779322273151432264021 ,云计算爱好者群,亦可关注about云腾讯认证空间||关注本站微信

没找到任何评论,期待你打破沉寂

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

本版积分规则

关闭

推荐上一条 /2 下一条