17. nova-api分析

下面从 nova --debug list 着手分析 nova-api 的处理流程!

root@juno-controller:/smbshare/paste_test# nova --debug list
REQ: curl -i 'http://100.100.100.254:5000/v2.0/tokens' -X POST -H "Accept: application/json" -H "Content-Type: application/json" -H "User-Agent: python-novaclient" -d '{"auth": {"tenantName": "csq", "passwordCredentials": {"username": "chensq", "password": "{SHA1}c60f964054e2080b1c827fae07ef0e5d92d2d285"}}}'
…………

REQ: curl -i 'http://100.100.100.254:8774/v2/a0e0c1b46fe94e1c90bd15e358d39486/servers/detail' -X GET -H "Accept: application/json" -H "User-Agent: python-novaclient" -H "X-Auth-Project-Id: csq" -H "X-Auth-Token: {SHA1}69c17566ddec8dc5488593dd36e6484c82242d71"
…………

可以看到,获取租户虚机列表命令 nova --debug list 先要向 keystone 发起请求,获取用户token。这一步分析省略!

然后获取token后,然后利用该token,向 nova-api 发起请求,获取虚机列表。

首先,我们需要定位到该url: v2/a0e0c1b46fe94e1c90bd15e358d39486/servers/detail 对应的处理程序。

# vi /etc/nova/api-paste.ini
[composite:osapi_compute]
use = call:nova.api.openstack.urlmap:urlmap_factory
/: oscomputeversions
/v1.1: openstack_compute_api_v2
/v2: openstack_compute_api_v2
/v2.1: openstack_compute_api_v21
/v3: openstack_compute_api_v3

[composite:openstack_compute_api_v2]
use = call:nova.api.auth:pipeline_factory
noauth = compute_req_id faultwrap sizelimit noauth ratelimit osapi_compute_app_v2
keystone = compute_req_id faultwrap sizelimit authtoken keystonecontext ratelimit osapi_compute_app_v2
keystone_nolimit = compute_req_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v2

[app:osapi_compute_app_v2]
paste.app_factory = nova.api.openstack.compute:APIRouter.factory

这里只选取了 api-paste.ini 文件的部分配置项!可以看到, URL以/v2开头的请求,最终由 osapi_compute_app_v2 处理!

/nova/api/openstack/compute/__init__.py 中定义的 APIRouter 作为 os_api_compute_app_v2 的入口!

继承体系:

nova.api.openstack.compute:APIRouter –> nova.api.openstack.APIRouter

nova.api.openstack.APIRouter 定义了 factory() 类方法:

class APIRouter(base_wsgi.Router):
    """Routes requests on the OpenStack API to the appropriate controller
    and method.
    """
    ExtensionManager = None  # override in subclasses

    @classmethod
    def factory(cls, global_config, **local_config):
        """Simple paste factory, :class:`nova.wsgi.Router` doesn't have one."""
        return cls()

该方法返回的app对象作为以/v2开头的http请求的入口:即当有req请求到来,并且req.path 以/v2开头, paste.deploy 包装会把该请求转发给 APIRouter 对象处理,因此会调用 APIRouter.__call__() 函数!

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 an object that can route
        the request to the action-specific 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)

    @webob.dec.wsgify(RequestClass=Request)
    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(RequestClass=Request)
    def _dispatch(req):
        """Dispatch the request to the appropriate controller.

        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

这一步的处理流程是:APIRouter.__call__() –> APIRouter._dispatch() , 由于这两个函数返回的都是 wsgi app 对象,因此会继续调用该对象。 最后 APIRouter._dispatch() 返回app, 实际上该app的值是在 mapper.connect() 或者 mapper.resource() 时定义的 controller 实参对象!

# /nova/api/openstack/compute/__init__.py
class APIRouter(nova.api.openstack.APIRouter):
    """Routes requests on the OpenStack API to the appropriate controller
    and method.
    """
    ExtensionManager = extensions.ExtensionManager

    def _setup_routes(self, mapper, ext_mgr, init_only):
        if init_only is None or 'versions' in init_only:
            self.resources['versions'] = versions.create_resource()
            mapper.connect("versions", "/",
                        controller=self.resources['versions'],
                        action='show',
                        conditions={"method": ['GET']})

        mapper.redirect("", "/")

        if init_only is None or 'consoles' in init_only:
            self.resources['consoles'] = consoles.create_resource()
            mapper.resource("console", "consoles",
                        controller=self.resources['consoles'],
                        parent_resource=dict(member_name='server',
                        collection_name='servers'))

        if init_only is None or 'consoles' in init_only or \
                'servers' in init_only or 'ips' in init_only:
            self.resources['servers'] = servers.create_resource(ext_mgr)
            mapper.resource("server", "servers",
                            controller=self.resources['servers'],
                            collection={'detail': 'GET'},
                            member={'action': 'POST'})

根据这里可以看到,Router._dispatch() 返回的app实际上是 /nova/api/openstack/wsgi:Resource 对象, 因此会继续调用它的 __call__() 方法, 实际上,我们根据 mapper.resource()controller 参数可以知道。最终会调用 /nova/api/openstack/compute/servers:Controller.detail() 方法!然后可以进入该方法,进行具体分析。 由于我这里只分析 nova-api 的处理流程,因此代码细节分析略!