Nova API

作为OpenStack两种主要的通信方式(RESTful API和消息总线)之一, Nova API保持着很高的稳定性。
目前存在三种API服务:
- ec2————————-Amazon EC2 API
- openstack—————-OpenStack API
- metadata——————Metadata API
均位于nova/api/openstack/compute目录下。
OpenStack定义了两种类型的资源: 核心资源和扩展资源。
1. 核心资源: 云平台的基础资源,image,servers。
2. 扩展资源: 根据是否为核心资源的扩展分为两种情况,如keypairs是对servers的扩展,cells是独立资源。

Nova API执行过程

三个过程:
novaclient将用户命令转换为HTTP请求 ———–> Paste Deploy将请求路由到具体地WSGI Application————-> Routes将请求路由到具体函数并执行

1. novaclient将用户命令转换为HTTP请求


如nova list命令
发送两个HTTP请求:
1,请求给Keystone获取授权,从Keystone拿到一个授权的token,将其填入随后API请求中的“X-Auth-Token”字段。
2,发送第二个请求给Nova获取虚拟机列表。
两种方式发送HTTP请求:
1,novaclient提供的命令
2,直接使用curl命令发送

2,HTTP请求到WSGI Application


Nova API服务nova-api启动时,会根据Nova配置文件的enable_apis选项内容创建一个或多个WSGI Server,Paste Deploy会在各个WSGI Server创建时参与进来,基于Paste配置文件/etc/nova/api-paste.ini去加载WSGI Application。

class WSGIService(service.Service):
    """Provides ability to launch API from a 'paste' configuration."""
    def __init__(self, name, loader=None, use_ssl=False, max_url_len=None):
        self.name = name
        self.manager = self._get_manager()
        # 从Paste配置文件加载API对应的WSGI Application
        self.loader = loader or wsgi.Loader()
        self.app = self.loader.load_app(name)
...
        self.host = getattr(CONF, '%s_listen' % name, "0.0.0.0")
        self.port = getattr(CONF, '%s_listen_port' % name, 0)
...
        # 使用指定的IP和端口创建监听Socket,与普通多线程模式下的WSGI Server不同,
        # Nova中使用Eventlet对WSGI进行封装,在监听到一个HTTP请求时,并不会创建
        # 一个独立的线程去处理,而是交给某个协程去处理。
        self.server = wsgi.Server(name,
                                  self.app,
                                  host=self.host,
                                  port=self.port,
                                  use_ssl=self.use_ssl,
                                  max_url_len=max_url_len)

在随后nova-api的运行过程中,Paste Deploy会将Socket上监听到的HTTP请求根据Paste配置文件准确地路由到特定的WSGI Application。

[composite:osapi_compute]
use = call:nova.api.openstack.urlmap:urlmap_factory
/: oscomputeversions
/v1.1: openstack_compute_api_v21_legacy_v2_compatible
/v2: openstack_compute_api_v21_legacy_v2_compatible
/v2.1: openstack_compute_api_v21
/v3: openstack_compute_api_v3

nova list使用到OpenStack的v3 API,WSGI服务器osapi_compute将监听这个HTTP请求,使用nova.api.openstack.urlmap模块的urlmap_factory函数来进行分发,v3版本的API对应了openstack_compute_api_v3应用。

[composite:openstack_compute_api_v3]
use = call:nova.api.auth:pipeline_factory_v21
noauth = request_id faultwrap sizelimit noauth_v3 osapi_compute_app_v3
keystone = request_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v3

接下来WSGI应用openstack_compute_api_v3使用nova.api.auth模块中的pipeline_factory_v21函数进行分发,并根据/etc/nova/nova.conf文件中的“auth_strategy”选项的定义,使用参数noauth或是keystone(默认)
noauth和keystone都对应了一个pipeline,request_id faultwrap sizelimit authtoken keystonecontext这些均为filter的角色,执行完后,调用应用osapi_compute_app_v3。

[app:osapi_compute_app_v3]
paste.app_factory = nova.api.openstack.compute:APIRouterV3.factory

3,WSGI Application到具体地执行函数


类APIRouterV3继承自nova.api.openstack.APIRouterV21,主要完成对所有资源的加载以及路由规则的创建,自此WSGI Routes模块参与进来。

class APIRouterV21(base_wsgi.Router):
    """Routes requests on the OpenStack v2.1 API to the appropriate controller
    and method.
    """
...
    @staticmethod
    def api_extension_namespace():
        return 'nova.api.v21.extensions'

    def __init__(self, init_only=None, v3mode=False):
...
    # 使用stevedore的EnableExtensionManager类载入位于setup.cfg中命名空间nova.api.v21.extensions下的所有资源
    # 采用EnableExtensionManager的形式,可以在加载的时候使用check_func函数进行检查。
        self.api_extension_manager = stevedore.enabled.EnabledExtensionManager(
            namespace=self.api_extension_namespace(),
            check_func=_check_load_extension,
            invoke_on_load=True,
            invoke_kwds={"extension_info": self.loaded_extension_info})
        if v3mode:
            mapper = PlainMapper()
        else:
            mapper = ProjectMapper()
        self.resources = {}
        if list(self.api_extension_manager):
            # NOTE(cyeoh): Stevedore raises an exception if there are
            # no plugins detected. I wonder if this is a bug.
            # 对所有资源调用_register_resources(),此函数会调用各个资源的get_resources()函数进行资源注册,同事使用mapper对象建立路由规则
            self._register_resources_check_inherits(mapper)
            # 调用各个资源的get_controller_extensions()函数扩展现有资源及其操作。
            self.api_extension_manager.map(self._register_controllers)
...
    super(APIRouterV21, self).__init__(mapper)

Nova里,每个资源都被封装成一个nova.api.openstack.wsgi.Resource对象,从而对应着一个WSGI应用,保证了资源之间的独立性。
最后APIRouterV21将mapper对象交给基类Router。

class Router(object):
    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
        # 使用routes模块将mapper与_dispatch()关联起来。
        # routes.middleware.RoutesMiddleware会调用mapper.routematch()函数
        # 来获取url的controller等参数,保存在match中,并设置environ变量供_dispatch()使用
        self._router = routes.middleware.RoutesMiddleware(self._dispatch,
                                                          self.map)
 def __call__(self, req):
        """Route the incoming request to a controller based on self.map.
        If no match, return a 404.
        """
        # 根据mapper将请求路由到适当的WSGI应用,即资源上,每个资源在自己的_call_()方法中,
        # 根据HTTP请求的url将其路由到对应的Controller上的方法。
        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.
        """
        # 读取HTTP请求的environ信息并根据前面设置的environ找到url对应的Controller。
        match = req.environ['wsgiorg.routing_args'][1]
        if not match:
            return webob.exc.HTTPNotFound()
        app = match['controller']
        return app

最终 nova list命令的例子”GET /v3/servers/detail”请求将会调用资源servers所对应的Controller的detail操作,即nova.api.openstack.compute.plugins.v3.servers.ServersController.detail()函数。