在完成了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'])
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)
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)
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)
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)
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