nova-api是nova对外提供Restful API的服务,Horizon、novaclient等均通过该api与nova进行通信。
nova其实对外提供了多个api服务,包括下面这些服务:
nova-api
nova-api-ec2
nova-api-metadata
nova-api-os-compute
其中,nova-api用于启动其他三个服务。下面逐个分析下。
nova-api
入口在 nova.cmd.api:main ,主要是基于WSGI、PasteDeploy、Webob、Routes等框架实现Restful API:
launcher = service.process_launcher()
for api in CONF.enabled_apis:
should_use_ssl = api in CONF.enabled_ssl_apis
if api == 'ec2':
server = service.WSGIService(api, use_ssl=should_use_ssl,
max_url_len=16384)
else:
server = service.WSGIService(api, use_ssl=should_use_ssl)
launcher.launch_service(server, workers=server.workers or 1)
launcher.wait()
对每一个enabled_api,都会创建一个WSGIService,然后调用launch_service:
def _start_child(self, wrap):
if len(wrap.forktimes) > wrap.workers:
# Limit ourselves to one process a second (over the period of
# number of workers * 1 second). This will allow workers to
# start up quickly but ensure we don't fork off children that
# die instantly too quickly.
if time.time() - wrap.forktimes[0] < wrap.workers:
LOG.info(_LI('Forking too fast, sleeping'))
time.sleep(1) wrap.forktimes.pop(0) wrap.forktimes.append(time.time()) pid = os.fork()
if pid == 0:
# create a green thread to run child
# 实际上最终调用的service的start/wait方法
launcher = self._child_process(wrap.service)
while True:
self._child_process_handle_signal()
status, signo = self._child_wait_for_exit_or_signal(launcher)
if not _is_sighup_and_daemon(signo):
break
launcher.restart() os._exit(status) LOG.info(_LI('Started child %d'), pid) wrap.children.add(pid)
self.children[pid] = wrap return pid def launch_service(self, service, workers=1):
wrap = ServiceWrapper(service, workers) LOG.info(_LI('Starting %d workers'), wrap.workers)
while self.running and len(wrap.children) < wrap.workers:
self._start_child(wrap)
既然每个服务都是 service.WSGIService ,那么WSGIService是如何初始化的呢
class WSGIService(object):
"""Provides ability to launch API from a 'paste' configuration.""" def __init__(self, name, loader=None, use_ssl=False, max_url_len=None):
"""Initialize, but do not start the WSGI server. :param name: The name of the WSGI server given to the loader.
:param loader: Loads the WSGI application using the given name.
:returns: None """
self.name = name
# 根据self.name从配置文件中获取manager,若有则实例化对应的manager
self.manager = self._get_manager()
self.loader = loader or wsgi.Loader()
# 通过paste.deploy加载app,对应配置文件为api-paste.ini
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)
self.workers = (getattr(CONF, '%s_workers' % name, None) or
processutils.get_worker_count())
if self.workers and self.workers < 1:
worker_name = '%s_workers' % name
msg = (_("%(worker_name)s value of %(workers)s is invalid, "
"must be greater than 0") %
{'worker_name': worker_name,
'workers': str(self.workers)})
raise exception.InvalidInput(msg)
self.use_ssl = use_ssl
self.server = wsgi.Server(name,
self.app,
host=self.host,
port=self.port,
use_ssl=self.use_ssl,
max_url_len=max_url_len)
# Pull back actual port used
self.port = self.server.port
self.backdoor_port = None
即从api-paste.ini加载app后,创建wsgi.Server
Paste Deployment是用于发现和配置WSGI appliaction和server的系统。对于WSGI application,用户提供一个单独的函数(loadapp),用于从配置文件或者python egg中加载WSGI application。因为WSGI application提供了唯一的单独的简单的访问入口,所以application不需要暴露application的内部的实现细节。首先了解几个基本的概念:
application: 应用,符合WSGI规范的可调用对象,接受参数(environ,start_response), 调用start_response返回状态和消息头,返回结果作为消息体。
filter:过滤器,可调用对象,类型python中的装饰器,接受一个application对象作为参数,返回一个封装后的application。
app_factory:可调用对象,接受参数(global,**local_conf),返回application对象
composite_factory: 可调用对象,接受参数(loader,global_config,**local_conf), loader有几个方法, get_app用于获取wsgi_app, get_filter用于加载filter, 返回application对象。
filter_factory: 可调用对象,接受参数(global_config, **local_conf),返回filter对象
以nova-api-os-compute为例看看这个配置文件是咋回事
# 这儿是整个api的入口,将不同版本转发到compiste处理
[composite:osapi_compute]
use = call:nova.api.openstack.urlmap:urlmap_factory
/: oscomputeversions
/v1.1: openstack_compute_api_v2
/v2: openstack_compute_api_v2
/v3: openstack_compute_api_v3 # 比如v3版本api在这儿处理,在使用keystone配置时,要经过request_id faultwrap sizelimit authtoken keystonecontext这些filter的处理最后再交给osapi_compute_app_v3这个app来处理
# 注意这些filter的顺序:先把前n-1个filter逆序,然后逐个应用到app上
[composite:openstack_compute_api_v3]
use = call:nova.api.auth:pipeline_factory_v3
noauth = request_id faultwrap sizelimit noauth_v3 osapi_compute_app_v3
keystone = request_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v3 # filter部分定义了这个filter由哪个类来处理
[filter:request_id]
paste.filter_factory = nova.openstack.common.middleware.request_id:RequestIdMiddleware.factory [filter:compute_req_id]
paste.filter_factory = nova.api.compute_req_id:ComputeReqIdMiddleware.factory [filter:faultwrap]
paste.filter_factory = nova.api.openstack:FaultWrapper.factory [filter:noauth]
paste.filter_factory = nova.api.openstack.auth:NoAuthMiddleware.factory [filter:noauth_v3]
paste.filter_factory = nova.api.openstack.auth:NoAuthMiddlewareV3.factory [filter:ratelimit]
paste.filter_factory = nova.api.openstack.compute.limits:RateLimitingMiddleware.factory [filter:sizelimit]
paste.filter_factory = nova.api.sizelimit:RequestBodySizeLimiter.factory # 这儿是v3的处理app,即APIRouterV3.factory
[app:osapi_compute_app_v3]
paste.app_factory = nova.api.openstack.compute:APIRouterV3.factory
APIRouterV3会加载setup.cfg中配置的extensions:
# 初始化api_extension_manager并加载所有的API_EXTENSION_NAMESPACE对应的extensions。
# 这儿加载的就是setup.cfg中配置的nova.api.v3.extensions部分,比如:
# nova.api.v3.extensions =
# access_ips = nova.api.openstack.compute.plugins.v3.access_ips:AccessIPs
# admin_actions = nova.api.openstack.compute.plugins.v3.admin_actions:AdminActions
# admin_password = nova.api.openstack.compute.plugins.v3.admin_password:AdminPassword
# agents = nova.api.openstack.compute.plugins.v3.agents:Agents
# ...
# 当然如果有第三方的应用也声明了此namespace的extension,这里也会一并load进来。
# 其中的check_func为check_load_extension,其所作的工作主要包括:
# a) 检测是否为相V3APIExtensionBase类型
# b) extension的黑白名单过滤和验证
self.api_extension_manager = stevedore.enabled.EnabledExtensionManager(
namespace=self.API_EXTENSION_NAMESPACE, # 'nova.api.v3.extensions'
check_func=_check_load_extension,
invoke_on_load=True,
invoke_kwds={"extension_info": self.loaded_extension_info}) mapper = PlainMapper()
self.resources = {} # NOTE(cyeoh) Core API support is rewritten as extensions
# but conceptually still have core
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.
# Extensions define what resources they want to add through a get_resources function
self.api_extension_manager.map(self._register_resources,
mapper=mapper)
# Extensions define what resources they want to add through
# a get_controller_extensions function
self.api_extension_manager.map(self._register_controllers) # 检测core extensions是否全部加载成功
missing_core_extensions = self.get_missing_core_extensions(
self.loaded_extension_info.get_extensions().keys())
if not self.init_only and missing_core_extensions:
LOG.critical(_("Missing core API extensions: %s"),
missing_core_extensions)
raise exception.CoreAPIMissing(
missing_apis=missing_core_extensions)
以 servers = nova.api.openstack.compute.plugins.v3.servers:Servers 这个extension为例来看看加载过程是怎么样的。从前面的代码可以看到对每个extension,会调用get_resources和get_controller_extensions两个函数,其所作的工作分别为新增资源和扩展现有资源以添加action。
class Servers(extensions.V3APIExtensionBase):
"""Servers.""" name = "Servers"
alias = "servers"
version = 1 def get_resources(self):
member_actions = {'action': 'POST'}
collection_actions = {'detail': 'GET'}
resources = [
extensions.ResourceExtension(
'servers',
ServersController(extension_info=self.extension_info),
member_name='server', collection_actions=collection_actions,
member_actions=member_actions)] return resources def get_controller_extensions(self):
return []
这里需要注意的是,V2 API中会区分Core API和Extension API。在V3 API中,所有的API均作为extension的形式提供。如V2 API中的servers是Core API,在V3 API中也是一个普通的extension,此extension会在get_resources方法中返回servers资源对象,而get_controller_extensions方法会返回空list。
而当disk_config等需要对servers资源扩展action时,其所需做的就是在get_controller_extensions中返回对servers资源的扩展:
class Disk_config(extensions.ExtensionDescriptor):
"""Disk Management Extension.""" name = "DiskConfig"
alias = ALIAS
namespace = XMLNS_DCF
updated = "2011-09-27T00:00:00Z" def get_controller_extensions(self):
servers_extension = extensions.ControllerExtension(
self, 'servers', ServerDiskConfigController()) images_extension = extensions.ControllerExtension(
self, 'images', ImageDiskConfigController()) return [servers_extension, images_extension]
最后再来看看V3版本API是如何避免各Extension之间代码耦合的
V3中,主要是利用了Stevedore避免了各Extension之间的代码耦合,从而避免了上述问题。
还是以当前对核心资源“虚拟机”的create方法扩展来看,在servers资源对应的Controller nova.api.openstack.compute.pulgins.v3.servers:ServersController中,将create作为了一个extension的namespace,并会在初始化时就load所有的extensions:
# Look for implementation of extension point of server creation
self.create_extension_manager = \
stevedore.enabled.EnabledExtensionManager(
namespace=self.EXTENSION_CREATE_NAMESPACE, #nova.api.v3.extensions.server.create
check_func=_check_load_extension('server_create'),
invoke_on_load=True,
invoke_kwds={"extension_info": self.extension_info},
propagate_map_exceptions=True)
if not list(self.create_extension_manager):
LOG.debug("Did not find any server create extensions")
然后在create方法中,会通过map方法依次调用各个Extensions的server_create方法:
# Query extensions which want to manipulate the keyword
# arguments.
# NOTE(cyeoh): This is the hook that extensions use
# to replace the extension specific code below.
# When the extensions are ported this will also result
# in some convenience function from this class being
# moved to the extension
if list(self.create_extension_manager):
self.create_extension_manager.map(self._create_extension_point,
server_dict, create_kwargs) def _create_extension_point(self, ext, server_dict, create_kwargs):
handler = ext.obj
LOG.debug("Running _create_extension_point for %s", ext.obj) handler.server_create(server_dict, create_kwargs)
在nova的setup.cfg中也配置了此namespace对应的所有的extensions,比如 nova.api.openstack.compute.plugins.v3.config_drive:ConfigDrive,其server_create方法如下所示,会更新ServersController:create方法中传入的create_kwargs字典:
def server_create(self, server_dict, create_kwargs):
create_kwargs['config_drive'] = server_dict.get(ATTRIBUTE_NAME)
最终,ServersController:create方法会以如下方式调用内部API来创建VM,即直接使用被各个extensions更新过的create_kwargs,而不用感知其实际的内容。
(instances, resv_id) = self.compute_api.create(context,
inst_type,
image_uuid,
display_name=name,
display_description=name,
metadata=server_dict.get('metadata', {}),
admin_password=password,
requested_networks=requested_networks,
**create_kwargs)
由上面的分析可知,如上所有的所谓的Extension,最终均依赖的还是一个内部的API,如果此API本身不具有扩展性,那么如上所有的Extension,均只能在此内部API支持的功能的基础上进行发挥。 比如文中提到的创建VM的内部create API,此API的参数是固定的:
def create(self, context, instance_type,
image_href, kernel_id=None, ramdisk_id=None,
min_count=None, max_count=None,
display_name=None, display_description=None,
key_name=None, key_data=None, security_group=None,
availability_zone=None, user_data=None, metadata=None,
injected_files=None, admin_password=None,
block_device_mapping=None, access_ip_v4=None,
access_ip_v6=None, requested_networks=None, config_drive=None,
auto_disk_config=None, scheduler_hints=None, legacy_bdm=True):
"""Provision instances, sending instance information to the
scheduler. The scheduler will determine where the instance(s)
go and will handle creating the DB entries. Returns a tuple of (instances, reservation_id)
"""
参考文档:
http://blog.csdn.net/cloudresearch/article/details/19050595
http://www.choudan.net/2013/07/30/OpenStack-API分析%28一%29.html
http://www.choudan.net/2013/07/31/OpenStack-API分析%28二%29.html