nova-api是nova对外提供Restful API的服务,Horizon、novaclient等均通过该api与nova进行通信。

nova其实对外提供了多个api服务,包括下面这些服务:

  1. nova-api
    nova-api-ec2
    nova-api-metadata
    nova-api-os-compute
  1. 其中,nova-api用于启动其他三个服务。下面逐个分析下。

nova-api

入口在 nova.cmd.api:main ,主要是基于WSGI、PasteDeploy、Webob、Routes等框架实现Restful API:

  1. launcher = service.process_launcher()
  2. for api in CONF.enabled_apis:
  3. should_use_ssl = api in CONF.enabled_ssl_apis
  4. if api == 'ec2':
  5. server = service.WSGIService(api, use_ssl=should_use_ssl,
  6. max_url_len=16384)
  7. else:
  8. server = service.WSGIService(api, use_ssl=should_use_ssl)
  9. launcher.launch_service(server, workers=server.workers or 1)
  10. launcher.wait()

对每一个enabled_api,都会创建一个WSGIService,然后调用launch_service:

  1. def _start_child(self, wrap):
  2. if len(wrap.forktimes) > wrap.workers:
  3. # Limit ourselves to one process a second (over the period of
  4. # number of workers * 1 second). This will allow workers to
  5. # start up quickly but ensure we don't fork off children that
  6. # die instantly too quickly.
  7. if time.time() - wrap.forktimes[0] < wrap.workers:
  8. LOG.info(_LI('Forking too fast, sleeping'))
  9. time.sleep(1)
  10.  
  11. wrap.forktimes.pop(0)
  12.  
  13. wrap.forktimes.append(time.time())
  14.  
  15. pid = os.fork()
  16. if pid == 0:
  17. # create a green thread to run child
  18. # 实际上最终调用的service的start/wait方法
  19. launcher = self._child_process(wrap.service)
  20. while True:
  21. self._child_process_handle_signal()
  22. status, signo = self._child_wait_for_exit_or_signal(launcher)
  23. if not _is_sighup_and_daemon(signo):
  24. break
  25. launcher.restart()
  26.  
  27. os._exit(status)
  28.  
  29. LOG.info(_LI('Started child %d'), pid)
  30.  
  31. wrap.children.add(pid)
  32. self.children[pid] = wrap
  33.  
  34. return pid
  35.  
  36. def launch_service(self, service, workers=1):
  37. wrap = ServiceWrapper(service, workers)
  38.  
  39. LOG.info(_LI('Starting %d workers'), wrap.workers)
  40. while self.running and len(wrap.children) < wrap.workers:
  41. self._start_child(wrap)

既然每个服务都是 service.WSGIService ,那么WSGIService是如何初始化的呢

  1. class WSGIService(object):
  2. """Provides ability to launch API from a 'paste' configuration."""
  3.  
  4. def __init__(self, name, loader=None, use_ssl=False, max_url_len=None):
  5. """Initialize, but do not start the WSGI server.
  6.  
  7. :param name: The name of the WSGI server given to the loader.
  8. :param loader: Loads the WSGI application using the given name.
  9. :returns: None
  10.  
  11. """
  12. self.name = name
  13. # 根据self.name从配置文件中获取manager,若有则实例化对应的manager
  14. self.manager = self._get_manager()
  15. self.loader = loader or wsgi.Loader()
  16. # 通过paste.deploy加载app,对应配置文件为api-paste.ini
  17. self.app = self.loader.load_app(name)
  18. self.host = getattr(CONF, '%s_listen' % name, "0.0.0.0")
  19. self.port = getattr(CONF, '%s_listen_port' % name, 0)
  20. self.workers = (getattr(CONF, '%s_workers' % name, None) or
  21. processutils.get_worker_count())
  22. if self.workers and self.workers < 1:
  23. worker_name = '%s_workers' % name
  24. msg = (_("%(worker_name)s value of %(workers)s is invalid, "
  25. "must be greater than 0") %
  26. {'worker_name': worker_name,
  27. 'workers': str(self.workers)})
  28. raise exception.InvalidInput(msg)
  29. self.use_ssl = use_ssl
  30. self.server = wsgi.Server(name,
  31. self.app,
  32. host=self.host,
  33. port=self.port,
  34. use_ssl=self.use_ssl,
  35. max_url_len=max_url_len)
  36. # Pull back actual port used
  37. self.port = self.server.port
  38. 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为例看看这个配置文件是咋回事

  1. # 这儿是整个api的入口,将不同版本转发到compiste处理
  2. [composite:osapi_compute]
  3. use = call:nova.api.openstack.urlmap:urlmap_factory
  4. /: oscomputeversions
  5. /v1.1: openstack_compute_api_v2
  6. /v2: openstack_compute_api_v2
  7. /v3: openstack_compute_api_v3
  8.  
  9. # 比如v3版本api在这儿处理,在使用keystone配置时,要经过request_id faultwrap sizelimit authtoken keystonecontext这些filter的处理最后再交给osapi_compute_app_v3这个app来处理
  10. # 注意这些filter的顺序:先把前n-1个filter逆序,然后逐个应用到app上
  11. [composite:openstack_compute_api_v3]
  12. use = call:nova.api.auth:pipeline_factory_v3
  13. noauth = request_id faultwrap sizelimit noauth_v3 osapi_compute_app_v3
  14. keystone = request_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v3
  15.  
  16. # filter部分定义了这个filter由哪个类来处理
  17. [filter:request_id]
  18. paste.filter_factory = nova.openstack.common.middleware.request_id:RequestIdMiddleware.factory
  19.  
  20. [filter:compute_req_id]
  21. paste.filter_factory = nova.api.compute_req_id:ComputeReqIdMiddleware.factory
  22.  
  23. [filter:faultwrap]
  24. paste.filter_factory = nova.api.openstack:FaultWrapper.factory
  25.  
  26. [filter:noauth]
  27. paste.filter_factory = nova.api.openstack.auth:NoAuthMiddleware.factory
  28.  
  29. [filter:noauth_v3]
  30. paste.filter_factory = nova.api.openstack.auth:NoAuthMiddlewareV3.factory
  31.  
  32. [filter:ratelimit]
  33. paste.filter_factory = nova.api.openstack.compute.limits:RateLimitingMiddleware.factory
  34.  
  35. [filter:sizelimit]
  36. paste.filter_factory = nova.api.sizelimit:RequestBodySizeLimiter.factory
  37.  
  38. # 这儿是v3的处理app,即APIRouterV3.factory
  39. [app:osapi_compute_app_v3]
  40. paste.app_factory = nova.api.openstack.compute:APIRouterV3.factory

APIRouterV3会加载setup.cfg中配置的extensions:

  1. # 初始化api_extension_manager并加载所有的API_EXTENSION_NAMESPACE对应的extensions。
  2. # 这儿加载的就是setup.cfg中配置的nova.api.v3.extensions部分,比如:
  3. # nova.api.v3.extensions =
  4. # access_ips = nova.api.openstack.compute.plugins.v3.access_ips:AccessIPs
  5. # admin_actions = nova.api.openstack.compute.plugins.v3.admin_actions:AdminActions
  6. # admin_password = nova.api.openstack.compute.plugins.v3.admin_password:AdminPassword
  7. # agents = nova.api.openstack.compute.plugins.v3.agents:Agents
  8. # ...
  9. # 当然如果有第三方的应用也声明了此namespace的extension,这里也会一并load进来。
  10. # 其中的check_func为check_load_extension,其所作的工作主要包括:
  11. # a) 检测是否为相V3APIExtensionBase类型
  12. # b) extension的黑白名单过滤和验证
  13. self.api_extension_manager = stevedore.enabled.EnabledExtensionManager(
  14. namespace=self.API_EXTENSION_NAMESPACE, # 'nova.api.v3.extensions'
  15. check_func=_check_load_extension,
  16. invoke_on_load=True,
  17. invoke_kwds={"extension_info": self.loaded_extension_info})
  18.  
  19. mapper = PlainMapper()
  20. self.resources = {}
  21.  
  22. # NOTE(cyeoh) Core API support is rewritten as extensions
  23. # but conceptually still have core
  24. if list(self.api_extension_manager):
  25. # NOTE(cyeoh): Stevedore raises an exception if there are
  26. # no plugins detected. I wonder if this is a bug.
  27. # Extensions define what resources they want to add through a get_resources function
  28. self.api_extension_manager.map(self._register_resources,
  29. mapper=mapper)
  30. # Extensions define what resources they want to add through
  31. # a get_controller_extensions function
  32. self.api_extension_manager.map(self._register_controllers)
  33.  
  34. # 检测core extensions是否全部加载成功
  35. missing_core_extensions = self.get_missing_core_extensions(
  36. self.loaded_extension_info.get_extensions().keys())
  37. if not self.init_only and missing_core_extensions:
  38. LOG.critical(_("Missing core API extensions: %s"),
  39. missing_core_extensions)
  40. raise exception.CoreAPIMissing(
  41. missing_apis=missing_core_extensions)

以 servers = nova.api.openstack.compute.plugins.v3.servers:Servers 这个extension为例来看看加载过程是怎么样的。从前面的代码可以看到对每个extension,会调用get_resources和get_controller_extensions两个函数,其所作的工作分别为新增资源和扩展现有资源以添加action。

  1. class Servers(extensions.V3APIExtensionBase):
  2. """Servers."""
  3.  
  4. name = "Servers"
  5. alias = "servers"
  6. version = 1
  7.  
  8. def get_resources(self):
  9. member_actions = {'action': 'POST'}
  10. collection_actions = {'detail': 'GET'}
  11. resources = [
  12. extensions.ResourceExtension(
  13. 'servers',
  14. ServersController(extension_info=self.extension_info),
  15. member_name='server', collection_actions=collection_actions,
  16. member_actions=member_actions)]
  17.  
  18. return resources
  19.  
  20. def get_controller_extensions(self):
  21. 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资源的扩展:

  1. class Disk_config(extensions.ExtensionDescriptor):
  2. """Disk Management Extension."""
  3.  
  4. name = "DiskConfig"
  5. alias = ALIAS
  6. namespace = XMLNS_DCF
  7. updated = "2011-09-27T00:00:00Z"
  8.  
  9. def get_controller_extensions(self):
  10. servers_extension = extensions.ControllerExtension(
  11. self, 'servers', ServerDiskConfigController())
  12.  
  13. images_extension = extensions.ControllerExtension(
  14. self, 'images', ImageDiskConfigController())
  15.  
  16. 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:

  1. # Look for implementation of extension point of server creation
  2. self.create_extension_manager = \
  3. stevedore.enabled.EnabledExtensionManager(
  4. namespace=self.EXTENSION_CREATE_NAMESPACE, #nova.api.v3.extensions.server.create
  5. check_func=_check_load_extension('server_create'),
  6. invoke_on_load=True,
  7. invoke_kwds={"extension_info": self.extension_info},
  8. propagate_map_exceptions=True)
  9. if not list(self.create_extension_manager):
  10. LOG.debug("Did not find any server create extensions")

然后在create方法中,会通过map方法依次调用各个Extensions的server_create方法:

  1. # Query extensions which want to manipulate the keyword
  2. # arguments.
  3. # NOTE(cyeoh): This is the hook that extensions use
  4. # to replace the extension specific code below.
  5. # When the extensions are ported this will also result
  6. # in some convenience function from this class being
  7. # moved to the extension
  8. if list(self.create_extension_manager):
  9. self.create_extension_manager.map(self._create_extension_point,
  10. server_dict, create_kwargs)
  11.  
  12. def _create_extension_point(self, ext, server_dict, create_kwargs):
  13. handler = ext.obj
  14. LOG.debug("Running _create_extension_point for %s", ext.obj)
  15.  
  16. 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字典:

  1. def server_create(self, server_dict, create_kwargs):
  2. create_kwargs['config_drive'] = server_dict.get(ATTRIBUTE_NAME)

最终,ServersController:create方法会以如下方式调用内部API来创建VM,即直接使用被各个extensions更新过的create_kwargs,而不用感知其实际的内容。

  1. (instances, resv_id) = self.compute_api.create(context,
  2. inst_type,
  3. image_uuid,
  4. display_name=name,
  5. display_description=name,
  6. metadata=server_dict.get('metadata', {}),
  7. admin_password=password,
  8. requested_networks=requested_networks,
  9. **create_kwargs)

由上面的分析可知,如上所有的所谓的Extension,最终均依赖的还是一个内部的API,如果此API本身不具有扩展性,那么如上所有的Extension,均只能在此内部API支持的功能的基础上进行发挥。 比如文中提到的创建VM的内部create API,此API的参数是固定的:

  1. def create(self, context, instance_type,
  2. image_href, kernel_id=None, ramdisk_id=None,
  3. min_count=None, max_count=None,
  4. display_name=None, display_description=None,
  5. key_name=None, key_data=None, security_group=None,
  6. availability_zone=None, user_data=None, metadata=None,
  7. injected_files=None, admin_password=None,
  8. block_device_mapping=None, access_ip_v4=None,
  9. access_ip_v6=None, requested_networks=None, config_drive=None,
  10. auto_disk_config=None, scheduler_hints=None, legacy_bdm=True):
  11. """Provision instances, sending instance information to the
  12. scheduler. The scheduler will determine where the instance(s)
  13. go and will handle creating the DB entries.
  14.  
  15. Returns a tuple of (instances, reservation_id)
  16. """

参考文档:

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. C++的虚函数表

    这里的例子全部来自陈皓的C++ 虚函数表解析,经过修改的. 编译器:g++ (Ubuntu 4.9.2-10ubuntu13) 4.9.2 环境:ubuntu 15.04  64位系统(地址占8字节) ...

  2. python字典中的元素类型

    python字典默认的是string item={"browser " : 'webdriver.irefox()', 'url' : 'http://xxx.com'} 如果这样 ...

  3. GFF format

    后记: ************************************************************************ 在使用cufflinks和cuffmerge中 ...

  4. linux安装sqlcmd登录sqlserver

    首先从微软网站下载sqlncli安装文件,link. 因为是在内网安装,首先手工下载unixODBC2.3.0.tar.gz,下载后上传到服务器. 将下载的tar文件文件,放在同build_dm.sh ...

  5. sdust 2410 Mine Number

    今天看了3个这种题了  枚举第一行即可 #include<cstdio> #include<cstring> #include<iostream> #include ...

  6. CentOS 6.6 中设置Terminal快捷键

    排版比较乱,参见 https://www.zybuluo.com/Jpz/note/144583 CentOS 6.6 中设置Terminal快捷键 Linux开发环境配置 Terminal是Cent ...

  7. ListView滚动到顶部

    videoAdapter.notifyDataSetChanged();videoListView.setSelection(0); 注意顺序先notify后setSelection

  8. php部分--数组(包含指针思想遍历数组);

    1.创建并输出数组 (1)相同数据类型的数组$attr=array(1,2,3,4,5); print_r($attr); echo "<br>"; $sttr1=ar ...

  9. (转载)Hadoop map reduce 过程获取环境变量

    来源:http://www.linuxidc.com/Linux/2012-07/66337.htm   作者: lmc_wy Hadoop任务执行过程中,在每一个map节点或者reduce节点能获取 ...

  10. System program problem detected 解决

    每次开机都出现:System program problem detected 管理员权限打开:/etc/default/apport   su root   vim /etc/default/app ...