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

nova分析(3)—— nova-api的更多相关文章

  1. nova分析(10)—— nova-rootwrap

    一.nova-rootwrap的作用 部署玩过openstack的都应该知道,它会生成一个nova用户来管理所有服务.nova身份在linux中属于普通用户级别,避免了一些需要root身份运行的操作, ...

  2. 在Openstack H版部署Nova Cell 时 ,终端输入nova service-list 和 nova host-list 命令将报错

    关于Cell的基本介绍,可以参考贤哥的一篇文章: [OpenStack]G版中关于Nova的Cell  http://blog.csdn.net/lynn_kong/article/details/8 ...

  3. KVM 介绍(8):使用 libvirt 迁移 QEMU/KVM 虚机和 Nova 虚机 [Nova Libvirt QEMU/KVM Live Migration]

    学习 KVM 的系列文章: (1)介绍和安装 (2)CPU 和 内存虚拟化 (3)I/O QEMU 全虚拟化和准虚拟化(Para-virtulizaiton) (4)I/O PCI/PCIe设备直接分 ...

  4. MediaInfo源代码分析 2:API函数

    本文主要分析MediaInfo的API函数.它的API函数位于MediaInfo.h文件中的一个叫做MediaInfo的类中. 该类如下所示,部分重要的方法已经加上了注释: //MediaInfo类 ...

  5. openstack私有云布署实践【11.3 计算nova - compute节点-nova用户免密登录(用于云主机冷迁移+扩展云主机大小)】

    云主机迁移+扩展云主机大小 ,官方说它依赖nova用户之间的免密登录.确保每个resion区域的compute节点服务器他们可以相互SSH免密   compute1-7     他们相互SSH免密 k ...

  6. nova创建虚拟机源码分析系列之六 api入口create方法

    openstack 版本:Newton 注:博文图片采用了很多大牛博客图片,仅作为总结学习,非商用.该图全面的说明了nova创建虚机的过程,从逻辑的角度清晰的描述了前端请求创建虚拟机之后发生的一系列反 ...

  7. nova分析(6)—— nova service启动过程

    Nova project下面具有多个service,api,compute,sceduler等等,他们的启动过程都几乎类似,这一篇博客就详细记录nova-sceduler的启动过程.文章中贴出的源码都 ...

  8. nova分析(7)—— nova-scheduler

    Nova-Scheduler主要完成虚拟机实例的调度分配任务,创建虚拟机时,虚拟机该调度到哪台物理机上,迁移时若没有指定主机,也需要经过scheduler.资源调度是云平台中的一个很关键问题,如何做到 ...

  9. nova分析(8)—— nova-compute

    nova-compute是管理和配置虚拟机的入口,在所有compute机器上都需要该服务来创建和管理虚拟机. nova-compute服务的入口在 nova.cmd.compute:main ,其启动 ...

随机推荐

  1. JVM监控命令详解(转)

    JVM监控命令基本就是 jps.jstack.jmap.jhat.jstat 几个命令的使用就可以了 JDK本身提供了很多方便的JVM性能调优监控工具,除了集成式的VisualVM和jConsole外 ...

  2. POM的配置文件

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/20 ...

  3. (实用篇)PHP缓存类完整实例

    本文完整描述了一个简洁实用的PHP缓存类,可用来检查缓存文件是否在设置更新时间之内.清除缓存文件.根据当前动态文件生成缓存文件名.连续创建目录.缓存文件输出静态等功能.对于采用PHP开发CMS系统来说 ...

  4. uva562 Dividing coins 01背包

    link:http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem& ...

  5. 第五课,T语言转义字符()(版本5.0)

    转义字符 字符串取值没什么限制,在引号""中可以填:数字.中文.字母 .特殊字符.以及他们的组合,字符串的值都要用双引号扩起来,比如 "我是字符型",当然,有人 ...

  6. 关于freemarker标签+Spring3.0 V层学习

    import标签 就是把其他的ftl页面引用进来 <#import "/common/ui.ftl" as ui> 使用时 <@ui.message/>,m ...

  7. 4-1 yum源文件

    1.Yum源文件 <1>在Linux中,有这样一个目录 /etc/yum.repos.d/,里面有默认4个yum源文件, 其中Base是基本yum源文件,它是默认生效的 其他的几个默认都是 ...

  8. Java泛型和链表

    泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数.这种参数类型可以用在类.接口和方法的创建中,分别称为泛型类.泛型接口.泛型方法. Java语言引 ...

  9. Java—面向对象—权限修饰符及思维导图

    课上老师所讲实例整理: package org.hanqi.pn0120; //汽车 public class Car { //颜色 private String yanse; //品牌 privat ...

  10. Linux-IP地址后边加个/8(16,24,32)是什么意思?

    是掩码的位数        A类IP地址的默认子网掩码为255.0.0.0(由于255相当于二进制的8位1,所以也缩写成“/8”,表示网络号占了8位);    B类的为255.255.0.0(/16) ...