作为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是独立资源。
三个过程:
novaclient将用户命令转换为HTTP请求 ———–> Paste Deploy将请求路由到具体地WSGI Application————-> Routes将请求路由到具体函数并执行
如nova list命令
发送两个HTTP请求:
1,请求给Keystone获取授权,从Keystone拿到一个授权的token,将其填入随后API请求中的“X-Auth-Token”字段。
2,发送第二个请求给Nova获取虚拟机列表。
两种方式发送HTTP请求:
1,novaclient提供的命令
2,直接使用curl命令发送
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
类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()函数。