openstack源码阅读基础:openstack中Nova组件RESTful请求的具体处理函数确定

       由于openstack项目是通过RESTful API向外提供服务,比如当我们通过openstackclient创建虚拟机时,openstackclient不直接调用创建虚拟机的具体python lib,而是发送一个http请求,当nova组件接收到请求后再调用具体函数和其它服务去创建虚拟机。这种机制使openstack具有很好的扩展性和可移植性,但也为我们阅读源码带来了困难,特别对于初学者想寻找函数入口添加远程断点都比较困难(openstack已经有上百万行代码了)。本文主要介绍如何去确定http请求和具体处理函数。

       首先,需要了解一些openstack wsgi框架的知识,可以参见http://blog.csdn.net/happyanger6/article/details/54518491。

       nova组件执行nova-api时根据api-paste.ini来构建web应用,截取的一段api-paste.ini文件如下:

[composite:osapi_compute]
use = call:nova.api.openstack.urlmap:urlmap_factory
/: oscomputeversions
# v21 is an exactly feature match for v2, except it has more stringent
# input validation on the wsgi surface (prevents fuzzing early on the
# API). It also provides new features via API microversions which are
# opt into for clients. Unaware clients will receive the same frozen
# v2 API feature set, but with some relaxed validation
/v2: openstack_compute_api_v21_legacy_v2_compatible
/v2.1: openstack_compute_api_v21
...
[composite:openstack_compute_api_v21]
use = call:nova.api.auth:pipeline_factory_v21
noauth2 = cors http_proxy_to_wsgi compute_req_id faultwrap request_log sizelimit osprofiler noauth2 osapi_compute_app_v21
keystone = cors http_proxy_to_wsgi compute_req_id faultwrap request_log sizelimit osprofiler authtoken keystonecontext osapi_compute_app_v21
...
[app:osapi_compute_app_v21]
paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory

        从中可以看到一个/v2.1/***的请求将首先被路由到openstack_compute_api_V21,经过一些filter处理后,最终由/nova/api/openstack/compute下的APIRouterV21类的factory方法处理。所以关于url如何路由到具体函数的确定应该在于APIRouterV21类。

      APIRouterV21位于/nova/api/openstack/compute/routes.py下,部分代码如下:

from nova.api.openstack.compute import flavors
# /nova/api/openstack/compute/flavor.py部分代码如下:
class FlavorsController(wsgi.Controller):
    """Flavor controller for the OpenStack API."""

    _view_builder_class = flavors_view.ViewBuilder

    @validation.query_schema(schema.index_query)
    @wsgi.expected_errors(400)
    def index(self, req):
        """Return all flavors in brief."""
        limited_flavors = self._get_flavors(req)
        return self._view_builder.index(req, limited_flavors)

    @validation.query_schema(schema.index_query)
    @wsgi.expected_errors(400)
    def detail(self, req):
        """Return all flavors in detail."""
        limited_flavors = self._get_flavors(req)
        req.cache_db_flavors(limited_flavors)
        return self._view_builder.detail(req, limited_flavors)

def _create_controller(main_controller, controller_list,
                      action_controller_list):
    """This is a helper method to create controller with a
    list of extended controller. This is for backward compatible
    with old extension interface. Finally, the controller for the
    same resource will be merged into single one controller.
    """

    controller = wsgi.Resource(main_controller())
    for ctl in controller_list:
        controller.register_extensions(ctl())
    for ctl in action_controller_list:
        controller.register_actions(ctl())
    return controller

flavor_controller = functools.partial(_create_controller,
# 如下类中包含detail()方法,查看/nova/api/openstack/compute/flavor.py知道 flavors.FlavorsController, [], [ flavor_manage.FlavorManageController, flavor_access.FlavorActionController ] )
# NOTE(alex_xu): This is structure of this route list as below: # ( # ('Route path': { # 'HTTP method: [ # 'Controller', # 'The method of controller is used to handle this route' # ], # ... # }), # ... # ) # # Also note that this is ordered tuple. For example, the '/servers/detail' # should be in the front of '/servers/{id}', otherwise the request to # '/servers/detail' always matches to '/servers/{id}' as the id is 'detail'. ROUTE_LIST = ( # NOTE: This is a redirection from '' to '/'. The request to the '/v2.1' # or '/2.0' without the ending '/' will get a response with status code # '302' returned. ('/flavors', { 'GET': [flavor_controller, 'index'], 'POST': [flavor_controller, 'create'] }),
# 我们以openstack flavor list为例,当执行这条语句后openstackclient就会发出一个后缀包含/flavors/detail的请求,并且http方法是GET;此时,这个请求就被路由到flavor_controller这个APP
中,具体方法是detail()。关于flavor_controller如何确定,看前一段代码。 (
'/flavors/detail', { 'GET': [flavor_controller, 'detail'] }), ('/flavors/{id}', { 'GET': [flavor_controller, 'show'], 'PUT': [flavor_controller, 'update'], 'DELETE': [flavor_controller, 'delete'] }), ('/flavors/{id}/action', { 'POST': [flavor_controller, 'action'] }), ('/flavors/{flavor_id}/os-extra_specs', { 'GET': [flavor_extraspec_controller, 'index'], 'POST': [flavor_extraspec_controller, 'create'] }), ('/flavors/{flavor_id}/os-extra_specs/{id}', { 'GET': [flavor_extraspec_controller, 'show'], 'PUT': [flavor_extraspec_controller, 'update'], 'DELETE': [flavor_extraspec_controller, 'delete'] }), ('/flavors/{flavor_id}/os-flavor-access', { 'GET': [flavor_access_controller, 'index'] }), ) class APIRouterV21(base_wsgi.Router): """Routes requests on the OpenStack API to the appropriate controller and method. The URL mapping based on the plain list `ROUTE_LIST` is built at here. """ def __init__(self, custom_routes=None): """:param custom_routes: the additional routes can be added by this parameter. This parameter is used to test on some fake routes primarily. """ super(APIRouterV21, self).__init__(nova.api.openstack.ProjectMapper()) if custom_routes is None: custom_routes = tuple()
# 将ROUTE_LIST表中的路由规则加载到map中,可见ROUTE_LIST表中包含了路由信息
for path, methods in ROUTE_LIST + custom_routes: # NOTE(alex_xu): The variable 'methods' is a dict in normal, since # the dict includes all the methods supported in the path. But # if the variable 'method' is a string, it means a redirection. # For example, the request to the '' will be redirect to the '/' in # the Nova API. To indicate that, using the target path instead of # a dict. The route entry just writes as "('', '/)". if isinstance(methods, str): self.map.redirect(path, methods) continue for method, controller_info in methods.items(): # TODO(alex_xu): In the end, I want to create single controller # instance instead of create controller instance for each # route. controller = controller_info[0]() action = controller_info[1] self.map.create_route(path, method, controller, action) @classmethod def factory(cls, global_config, **local_config): """Simple paste factory, :class:`nova.wsgi.Router` doesn't have one.""" return cls()

       具体确定方法:1.找到ROUTE_LIST中的url后缀,确定APP方法和ACTION名称;2.根据APP方法和ACTION名称确定具体执行函数。上文中我们就可以知道当openstackclient执行openstack flavor list时,最终会调用到/nova/api/openstack/compute/flavors.py中FlavorsController类的detail()方法。

posted on 2018-02-02 16:40  carrot_hrp  阅读(1279)  评论(0编辑  收藏  举报

导航