Ocata Neutron代碼分析(六)——APIRouter的初始化(3)頂級resource的map過程

在完成了plugins和extensions的加載後,進行四個頂級resource(分別是network、subnet、subnetpool和port)的map過程。map過程指的是,將各個resource的相關請求(例如建立network、刪除subnet)映射到相應的處理函數的過程。APIRouter構造函數中相關代碼以下:web

RESOURCES = {'network': 'networks',
             'subnet': 'subnets',
             'subnetpool': 'subnetpools',
             'port': 'ports'}
SUB_RESOURCES = {}
class APIRouter(base_wsgi.Router):
    ......
    def __init__(self, **local_config):
        mapper = routes_mapper.Mapper()
        ......

        mapper.connect('index', '/', controller=Index(RESOURCES))

        for resource in RESOURCES:
            _map_resource(RESOURCES[resource], resource,
                          attributes.RESOURCE_ATTRIBUTE_MAP.get(
                              RESOURCES[resource], dict()))
            resource_registry.register_resource_by_name(resource)

        for resource in SUB_RESOURCES:
            _map_resource(SUB_RESOURCES[resource]['collection_name'], resource,
                          attributes.RESOURCE_ATTRIBUTE_MAP.get(
                              SUB_RESOURCES[resource]['collection_name'],
                              dict()),
                          SUB_RESOURCES[resource]['parent'])
map過程的關鍵就是調用Mapper類的函數來實現,這裏首先實例化了Mapper類。
接着,調用其connect函數來進行map。這是一個最簡單的map方式。這裏匹配到的url爲http://controller_ip:neutron_server_port/v2.0/,controller_ip和neutron_server_port在配置文件中指定,/v2.0爲api-paste.ini中指定的匹配url。能與該url匹配的請求的controller是Index類的__call__函數,即能與該url匹配的request將交給Index類中的__call__函數來處理,這樣的處理函數通常爲一個通過webob.dec.wsgify修飾後的函數。
最後,依次調用_map_resource函數對RESOURCES中的各個頂級resource進行map,並註冊各個頂級resource。向_map_resource傳入的參數分別是collection(resource的集合的名稱,string)、resource(resource的名稱,string)和resource相關的屬性(dict)。
下面重點分析_map_resource函數:
class APIRouter(base_wsgi.Router):
    def __init__(self, **local_config):
        col_kwargs = dict(collection_actions=COLLECTION_ACTIONS,
                          member_actions=MEMBER_ACTIONS)

        def _map_resource(collection, resource, params, parent=None):
            allow_bulk = cfg.CONF.allow_bulk
            controller = base.create_resource(                                   # 1
                collection, resource, plugin, params, allow_bulk=allow_bulk,
                parent=parent, allow_pagination=True,
                allow_sorting=True)
            path_prefix = None
            if parent:
                path_prefix = "/%s/{%s_id}/%s" % (parent['collection_name'],
                                                  parent['member_name'],
                                                  collection)
            mapper_kwargs = dict(controller=controller,                          # 2
                                 requirements=REQUIREMENTS,
                                 path_prefix=path_prefix,
                                 **col_kwargs)
            return mapper.collection(collection, resource,                       # 3
                                     **mapper_kwargs)
_map_resource函數主要包含了三個步驟:
  1. 調用/neutron/api/v2/base.py中的create_resource函數來構造請求的處理函數(即上面提到的controller);
  2. 構造傳入mapper.collection函數的參數mapper_kwargs;
  3. 調用mapper.collection函數來實際進行map動做。
先分析create_resource函數:
def create_resource(collection, resource, plugin, params, allow_bulk=False,
                    member_actions=None, parent=None, allow_pagination=False,
                    allow_sorting=False):
    controller = Controller(plugin, collection, resource, params, allow_bulk,
                            member_actions=member_actions, parent=parent,
                            allow_pagination=allow_pagination,
                            allow_sorting=allow_sorting)

    return wsgi_resource.Resource(controller, FAULT_MAP)
該函數首先實例化Controller類賦給controller變量,而後將controller做爲參數調用/neutron/api/v2/resource.py的Resource函數。
先看看Controller的構造函數:
class Controller(object):
    LIST = 'list'
    SHOW = 'show'
    CREATE = 'create'
    UPDATE = 'update'
    DELETE = 'delete'

    def __init__(self, plugin, collection, resource, attr_info,
                 allow_bulk=False, member_actions=None, parent=None,
                 allow_pagination=False, allow_sorting=False):
        if member_actions is None:
            member_actions = []
        self._plugin = plugin
        self._collection = collection.replace('-', '_')
        self._resource = resource.replace('-', '_')
        self._attr_info = attr_info
        self._allow_bulk = allow_bulk
        self._allow_pagination = allow_pagination
        self._allow_sorting = allow_sorting
        self._native_bulk = self._is_native_bulk_supported()
        self._native_pagination = self._is_native_pagination_supported()
        self._native_sorting = self._is_native_sorting_supported()
        self._policy_attrs = self._init_policy_attrs()
        self._notifier = n_rpc.get_notifier('network')
        self._member_actions = member_actions
        self._primary_key = self._get_primary_key()
        if self._allow_pagination and self._native_pagination:
            # Native pagination need native sorting support
            if not self._native_sorting:
                raise exceptions.Invalid(
                    _("Native pagination depend on native sorting")
                )
            if not self._allow_sorting:
                LOG.info(_LI("Allow sorting is enabled because native "
                             "pagination requires native sorting"))
                self._allow_sorting = True
        self.parent = parent
        if parent:
            self._parent_id_name = '%s_id' % parent['member_name']
            parent_part = '_%s' % parent['member_name']
        else:
            self._parent_id_name = None
            parent_part = ''
        self._plugin_handlers = {
            self.LIST: 'get%s_%s' % (parent_part, self._collection),
            self.SHOW: 'get%s_%s' % (parent_part, self._resource)
        }
        for action in [self.CREATE, self.UPDATE, self.DELETE]:
            self._plugin_handlers[action] = '%s%s_%s' % (action, parent_part,
                                                         self._resource)
Controller的構造函數主要進行了參數的賦值、參數的合理性檢查等。這裏最重要的成員變量是self._plugin_handlers,它是一個對該resource採起的action名稱與對應處理函數名稱的字典。以network這一resource爲例,其_plugin_handlers以下:
{'list': 'get_networks', 'show': 'get_network', 'create': 'create_network', 'update': 'update_network', 'delete': 'delete_network'}
分別表示:獲取network列表,獲取某個network,建立某個network,更新某個network和刪除某個network。
後續流程會經過這個變量來獲取相應處理函數的名稱。
實例化後的controller變量傳入/neutron/api/v2/resource.py的Resource函數:
def Resource(controller, faults=None, deserializers=None, serializers=None,
             action_status=None):
    """Represents an API entity resource and the associated serialization and
    deserialization logic
    """
    default_deserializers = {'application/json': wsgi.JSONDeserializer()}
    default_serializers = {'application/json': wsgi.JSONDictSerializer()}
    format_types = {'json': 'application/json'}
    action_status = action_status or dict(create=201, delete=204)

    default_deserializers.update(deserializers or {})
    default_serializers.update(serializers or {})

    deserializers = default_deserializers
    serializers = default_serializers
    faults = faults or {}

    @webob.dec.wsgify(RequestClass=Request)
    def resource(request):
        ......
    # NOTE(blogan): this is something that is needed for the transition to
    # pecan.  This will allow the pecan code to have a handle on the controller
    # for an extension so it can reuse the code instead of forcing every
    # extension to rewrite the code for use with pecan.
    setattr(resource, 'controller', controller)
    setattr(resource, 'action_status', action_status)
    return resource

能夠看到,Resource函數只是在其中的resource函數外多加了一層殼,這層殼主要是進行序列化和反序列化組件(serializers和deserializers)的配置。其中的resource函數通過webob.dec.wsgify修飾,因此各個resource的request均是交給這裏的resource函數來處理。下面分析這個resource函數:json

    @webob.dec.wsgify(RequestClass=Request)
    def resource(request):                                          # 處理Request的函數
        route_args = request.environ.get('wsgiorg.routing_args')
        if route_args:
            args = route_args[1].copy()
        else:
            args = {}

        # NOTE(jkoelker) by now the controller is already found, remove
        #                it from the args if it is in the matchdict
        args.pop('controller', None)
        fmt = args.pop('format', None)
        action = args.pop('action', None)                                   # 獲取請求中的format和action,移除controller。
        content_type = format_types.get(fmt,
                                        request.best_match_content_type())
        language = request.best_match_language()
        deserializer = deserializers.get(content_type)                      # 從deserializers中獲取Content_type對應的deserializer。
        serializer = serializers.get(content_type)                          # 序列化同理。

        try:
            if request.body:
                args['body'] = deserializer.deserialize(request.body)['body']   # 將request中的body反序列化,e.g.str -> json。

            # Routes library is dumb and cuts off everything after last dot (.)
            # as format. At the same time, it doesn't enforce format suffix,
            # which combined makes it impossible to pass a 'id' with dots
            # included (the last section after the last dot is lost). This is
            # important for some API extensions like tags where the id is
            # really a tag name that can contain special characters.
            #
            # To work around the Routes behaviour, we will attach the suffix
            # back to id if it's not one of supported formats (atm json only).
            # This of course won't work for the corner case of a tag name that
            # actually ends with '.json', but there seems to be no better way
            # to tackle it without breaking API backwards compatibility.
            if fmt is not None and fmt not in format_types:
                args['id'] = '.'.join([args['id'], fmt])

            method = getattr(controller, action)                        # 從controller獲取執行action的函數。
            result = method(request=request, **args)                    # 執行action,相關參數經反序列化後經過args參數傳入controller。
        except Exception as e:
            mapped_exc = api_common.convert_exception_to_http_exc(e, faults,
                                                                  language)
            if hasattr(mapped_exc, 'code') and 400 <= mapped_exc.code < 500:
                LOG.info(_LI('%(action)s failed (client error): %(exc)s'),
                         {'action': action, 'exc': mapped_exc})
            else:
                LOG.exception(
                    _LE('%(action)s failed: %(details)s'),
                    {
                        'action': action,
                        'details': utils.extract_exc_details(e),
                    }
                )
            raise mapped_exc

        status = action_status.get(action, 200)
        body = serializer.serialize(result)                             # 將result序列化,e.g.json -> str。
        # NOTE(jkoelker) Comply with RFC2616 section 9.7
        if status == 204:
            content_type = ''
            body = None

        return webob.Response(request=request, status=status,           # 構造Response進行返回。
                              content_type=content_type,
                              body=body)
總而言之,resource函數主要完成如下幾個工做:
  1. 從租戶提交的請求中獲取相關參數(formate、action等);
  2. 根據action的類型從controller中獲取相應處理函數;
  3. 調用獲取到的處理函數獲得結果;
  4. 最後返回response;
  5. 此外,還進行了序列化和反序列化的工做。
因此,先在create_resource函數中實例化的controller,包含了全部action類型(包括index、show、create、update和delete)的實現函數。針對某個resource的某個action通過resource函數,最終仍是來到了controller中的相應action函數中進行處理。具體action函數的處理會在後續章節中分析。
下面回到_map_resource函數的分析中:
class APIRouter(base_wsgi.Router):
    def __init__(self, **local_config):
        col_kwargs = dict(collection_actions=COLLECTION_ACTIONS,
                          member_actions=MEMBER_ACTIONS)

        def _map_resource(collection, resource, params, parent=None):
            allow_bulk = cfg.CONF.allow_bulk
            controller = base.create_resource(                                   # 1
                collection, resource, plugin, params, allow_bulk=allow_bulk,
                parent=parent, allow_pagination=True,
                allow_sorting=True)
            path_prefix = None
            if parent:
                path_prefix = "/%s/{%s_id}/%s" % (parent['collection_name'],
                                                  parent['member_name'],
                                                  collection)
            mapper_kwargs = dict(controller=controller,                          # 2
                                 requirements=REQUIREMENTS,
                                 path_prefix=path_prefix,
                                 **col_kwargs)
            return mapper.collection(collection, resource,                       # 3
                                     **mapper_kwargs)

create_resource返回的(即這裏的controller變量)是通過webob.dec.wsgify修飾後的resource函數。而後,在2中,構造傳入mapper.collection函數的參數,其中最關鍵的就是controller變量。最後,在3中,調用mapper.collection完成該resource的各個action的map動做。api

相關文章
相關標籤/搜索