天天看點

Glance源碼架構探秘(三)

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用法非常清晰明了

# Setup a mapper
from routes import Mapper
map = Mapper()
map.connect(None, "/error/{action}/{id}, controller="error")
map.connect("home", "/", controller="main", action="index")

# Match a URL, returns a dict or None if no match
result = map.match('/error/myapp/4')
# result == {'controller': 'main', 'action': 'myapp', 'id': '4'}
           

導入routes,建立一個Mapper執行個體。之後,我們就可以用Mapper.connect()綁定路由。最後用Mapper.match()方法可以将給定的URL轉換為路由資訊,包括controller,action或其他使用者自定義的路由類型。

在我們正式講解Glance的代碼之前,還要給大家介紹其WSGI中用到的另外一個開源元件webob。這也是我常用以及非常喜歡的一個開源元件,使用webob可以大幅簡化編寫架構的複雜度和代碼量。webob用來包裝WSGI應用程式的請求以及響應的類庫。

>>> from webob import Request
>>> environ = {'wsgi.url_scheme': 'http', ...}  
>>> req = Request(environ)                            
>>> req = Request.blank('/article?id=1')
>>> from pprint import pprint
>>> pprint(req.environ)
{'HTTP_HOST': 'localhost:80',
 'PATH_INFO': '/article',
 'QUERY_STRING': 'id=1',
 'REQUEST_METHOD': 'GET',
 'SCRIPT_NAME': '',
 'SERVER_NAME': 'localhost',
 'SERVER_PORT': '80',
 'SERVER_PROTOCOL': 'HTTP/1.0',
 'wsgi.errors': <open file '<stderr>', mode 'w' at ...>,
 'wsgi.input': <...IO... object at ...>,
 'wsgi.multiprocess': False,
 'wsgi.multithread': False,
 'wsgi.run_once': False,
 'wsgi.url_scheme': 'http',
 'wsgi.version': (1, 0)}      

上面的示例程式就簡單說明了如何用webob的Request生成一個request請求和解析一個request請求的環境資訊。

然而,webob的功能不止于此。其還有一個功能就是将一個普通的函數function包裝為一個WSGI函數,并符合WSGI函數的規範(實作規範的接口,evn,start_response)。webob.dec.wsgify

@wsgify
def myfunc(req):
    return webob.Response('hey there')
           

如上示例,就将myfunc包裝為了一個可被webserver調用的wsgi函數。如果我們不用這個包裝,就要非常繁瑣的寫為

def myfunc(req, start_response):
    start_response('200 OK', header)
    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。

class API(wsgi.Router):

    """WSGI router for Glance v2 API requests."""

    def __init__(self, mapper):
        custom_image_properties = images.load_custom_properties()

        schemas_resource = schemas.create_resource(custom_image_properties)
        mapper.connect('/schemas/image',
                       controller=schemas_resource,
                       action='image',
                       conditions={'method': ['GET']})
        mapper.connect('/schemas/images',
                       controller=schemas_resource,
                       action='images',
                       conditions={'method': ['GET']})

        images_resource = images.create_resource(custom_image_properties)
        mapper.connect('/images',
                       controller=images_resource,
                       action='index',
                       conditions={'method': ['GET']})
        mapper.connect('/images',
                       controller=images_resource,
                       action='create',
                       conditions={'method': ['POST']})
        mapper.connect('/images/{image_id}',
                       controller=images_resource,
                       action='update',
                       conditions={'method': ['PATCH']})
        mapper.connect('/images/{image_id}',
                       controller=images_resource,
                       action='show',
                       conditions={'method': ['GET']})
        mapper.connect('/images/{image_id}',
                       controller=images_resource,
                       action='delete',
                       conditions={'method': ['DELETE']})

       ......
       ......

        super(API, self).__init__(mapper)
           

上面我們已經介紹過了routes的mapper類,這裡的路由配置設定設定就一目了然了。因為是使用的RESTful接口,路由配置設定的時候還可以指定method。API這個類的基類是wsgi.Router,剛剛WSGI也是調用的其基類的工廠方法router.factory。這樣我們繼續介紹上一章沒有介紹玩的wsgi.py的後半部分。

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 a wsgi.Controller, who will route
        the request to the action 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)

    @classmethod
    def factory(cls, global_conf, **local_conf):
        return cls(routes.Mapper())

    @webob.dec.wsgify
    def __call__(self, req):
        """
        Route the incoming request to a controller based on self.map.
        If no match, return a 404.
        """
        return self._router

    @staticmethod
    @webob.dec.wsgify
    def _dispatch(req):
        """
        Called by self._router after matching the incoming request to a route
        and putting the information into req.environ.  Either returns 404
        or the routed WSGI app's response.
        """
        match = req.environ['wsgiorg.routing_args'][1]
        if not match:
            return webob.exc.HTTPNotFound()
        app = match['controller']
        return app
           

這個基類的工廠方法被包裝為了類方法,實際就會建立剛才的API子類。此類的可調用對象方法__call__被包裝成了wsgi,是以這個類的對象可以作為WSGI函數進行調用。該思想可參見我的另一篇文章Python可調用對象__call__方法的用法分析。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的最後一部分。

class Resource(object):
    """
    WSGI app that handles (de)serialization and controller dispatch.

    Reads routing information supplied by RoutesMiddleware and calls
    the requested action method upon its deserializer, controller,
    and serializer. Those three objects may implement any of the basic
    controller action methods (create, update, show, index, delete)
    along with any that may be specified in the api router. A 'default'
    method may also be implemented to be used in place of any
    non-implemented actions. Deserializer methods must accept a request
    argument and return a dictionary. Controller methods must accept a
    request argument. Additionally, they must also accept keyword
    arguments that represent the keys returned by the Deserializer. They
    may raise a webob.exc exception or return a dict, which will be
    serialized by requested content type.
    """
    def __init__(self, controller, deserializer=None, serializer=None):
        """
        :param controller: object that implement methods created by routes lib
        :param deserializer: object that supports webob request deserialization
                             through controller-like actions
        :param serializer: object that supports webob response serialization
                           through controller-like actions
        """
        self.controller = controller
        self.serializer = serializer or JSONResponseSerializer()
        self.deserializer = deserializer or JSONRequestDeserializer()

    @webob.dec.wsgify(RequestClass=Request)
    def __call__(self, request):
        """WSGI method that controls (de)serialization and method dispatch."""
        action_args = self.get_action_args(request.environ)
        action = action_args.pop('action', None)

        deserialized_request = self.dispatch(self.deserializer,
                                             action, request)
        action_args.update(deserialized_request)

        action_result = self.dispatch(self.controller, action,
                                      request, **action_args)
        try:
            response = webob.Response(request=request)
            self.dispatch(self.serializer, action, response, action_result)
            return response

        # return unserializable result (typically a webob exc)
        except Exception:
            return action_result

    def dispatch(self, obj, action, *args, **kwargs):
        """Find action-specific method on self and call it."""
        try:
            method = getattr(obj, action)
        except AttributeError:
            method = getattr(obj, 'default')

        return method(*args, **kwargs)

    def get_action_args(self, request_environment):
        """Parse dictionary created by routes library."""
        try:
            args = request_environment['wsgiorg.routing_args'][1].copy()
        except Exception:
            return {}

        try:
            del args['controller']
        except KeyError:
            pass

        try:
            del args['format']
        except KeyError:
            pass

        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在接到請求指令後,如何進行鏡像的上傳,下載下傳,更新等,同樣包括資料庫和鏡像中繼資料更新等的操作,其中用到了許多非常棒的設計模式,敬請期待!

繼續閱讀