OpenStack源码系列---nova-api
OpenStack源码实际上是比较规范的,但是对刚刚接触到源代码的人来说,却感觉有点混乱。我刚开始的时候也常常搞乱,比如service.Service类继承自openstack.common.service.Service类,有个openstack.common.service.Services类,有个openstack.common.service.Launcher类,有个openstack.common.service.ServiceLauncher类,有个openstack.common.service.ProcessLauncher类,nova根目录下有个wsgi.py文件,api/openstack目录下也有个wsgi.py,在总是搞不清这些关系的时候,我也会脱口而出“你妹的”。这篇文章,我将对nova-api涉及的一些关键代码和调用流程以及类关系进行剖析,如果你和我一样是个OpenStack初学者并且也有“你妹的”情节,希望你看过之后,对于OpenStack,少一点“你妹的”的抱怨。
我安装的是Juno版的OpenStack,并通过阅读源代码加调试运行的手段来分析nova-api服务的调用流程。在阅读到某些源码片段,当我理解不了时,我就会在当前源码位置插入Python代码:
import pdb
pdb.set_trace()
然后再运行/usr/bin/nova-api服务。上述代码相当于设置了一个断点,程序运行到该位置会停下来以便我们调试分析。这时候,我们可以单步执行程序,查看调用栈,显示当前源码位置等。
nova源码文件安装到目录“/usr/lib/python2.7/dist-packages/nova”,在之后的分析中,我所列出的目录均是相对于该目录的路径,当我说一个文件路径为api/openstack/wsgi.py时,则该文件存放位置为“/usr/lib/python2.7/dist-packages/nova/api/openstack/wsgi.py”,依此类推。
/usr/bin/nova-api的启动代码如下:
从上面的图中我们知道,它调用了cmd/api.py文件里的main函数,该函数的代码如下图:
我们看到,main函数中有个launcher变量,实际为nova.openstack.common.service.ProcessLauncher对象实例,有个server变量,实际为nova.service.WSGIService对象实例。在我调试的时候,api值为“osapi_compute”,这个值是在/etc/nova/nova.conf文件中配置的。在获取了这样两个对象之后,launcher调用了它的launch_service函数,并将server变量作为参数传递。
我们来看一下WSGIService类的构造函数__init__(),如下图:
在构造函数中,有一个self.app变量,一个self.server变量,后者为wsgi.Server类实例。关于这两个变量,我们后续再做分析。我们先来了解下ProcessLauncher类的launch_service函数:
如上图,launch_service调用了_start_child函数,后者又调用_child_process函数。_child_process代码如下图,其中生成了一个nova.openstack.common.service.Launcher对象,并调用对象的方法launch_service,并传递一个service参数,该service参数实际为在main函数中生成的WSGIService对象,也就是那个刚开始碰到的server变量。
launch_service函数调用了self.services.add(service),从Launcher的__init__初始化函数中我们知道services实际为nova.openstack.common.service.Services对象,那么我们看下这个add函数干了些什么。
从上图我们知道,Services对象中有一个services列表,保存各个WSGIService对象,有一个tg变量为threadgroup.ThreadGroup对象实例,add函数调用tg的add_thread函数,传递一个self.run_service静态方法,一个WSGIService对象和一个Event.event对象。Services.run_service函数调用WSGIService.start函数启动wsgi服务。WSGIService.start函数如下图:
上图中,self.server.start()函数被调用,根据前面的分析,self.server在WSGIService.__init__()函数中被初始化,实际为wsgi.Server类的实例,那么我们再看下wsgi.Server类的start方法,如下图:
这里,我们看到了eventlet.spawn函数被调用,传递的wsgi_kwargs字典中,func为eventlet.wsgi.server,site为self.app。从上面的一系列分析中,我们不难看出,self.app为WSGIService.__init__构造函数中通过self.loader.load_app(name)得到的,其中name值为打印出来的“osapi_compute”。至此,一个作为nova-api服务的WSGI应用服务已经起来了,并且运行在一个greenthread中,它将接收用户的各种请求。
nova-api实现的是wsgi的Application,而不是Server,Server是由wsgi库来实现,就是那个eventlet.wsgi.server,那么问题来了,我们的Application是如何被调用的呢?这时候,你是不是想到了那个eventlet.spawn被调用的时候,字典参数里那个“site”的值了?对了,就是它了,它是在WSGIService.__init__构造函数中通过self.loader.load_app(name)得到的,name为“osapi_compute”,self.loader为wsgi.Loader实例。如下图,load_app调用了deploy.loadapp,传入参数为:“config:/etc/nova/api-paste.ini,name=osapi_compute”。
我们再看下deploy.loadapp都调用了哪些函数,如下图:
可以看到,它调用了api/openstack/urlmap.py模块中的urlmap_factory函数,该函数接收三个参数,分别为loader、global_conf和local_conf。这是怎么做到的呢?且让我们看下api-paste.ini这个文件,如下图:
看到了吗,原来deploy.loadapp根据传入的配置文件路径和name值,找到了api-paste.ini文件中对应的section,顺藤摸瓜找到了urlmap_factory函数。接下来我们以请求路径/v2为例进行阐述,其对应的App为openstack_compute_api_v2,后者又找到了osapi_compute_app_v2,后者又找到了nova.api.openstack.compute:APIRouter.factory,最终调用的是这个函数获取到一个App,而这一系列调用关系的完成是在urlmap_factory函数里边通过loader.get_app()调用来实现的,这里loader的类型为paste.deploy.loadwsgi.ConfigLoader。
我们来看一下nova.api.openstack.compute.APIRouter这个类,并没有看到factory函数,不过它继承自nova.api.openstack.APIRouter类,factory函数就在这个基类中。因此,当nova.api.openstack.compute:APIRouter.factory函数被调用时,返回的应该是一个nova.api.openstack.compute.APIRouter类实例。nova.api.openstack.compute.APIRouter类中有一个_setup_routes函数,用来完成路径到App的映射关系,该函数又是在其基类nova.api.openstack.APIRouter类的__init__()函数中被调用。
我们以名称为servers的请求为例,它对应的controller为self.resources[‘servers’],后者又是通过servers.create_resource(ext_mgr)获取,那我们看下这个函数干了些什么:
上图中,create_resource直接返回了一个nova.api.openstack.wsgi.Resource类实例。Resource类聚合了一个nova.api.openstack.compute.servers.Controller类对象,该Controller类从nova.api.openstack.wsgi.Controller类继承而来。Resource类本身又继承自nova.wsgi.Application类,代表一个wsgi的App。
下面看下nova.api.openstack.APIRouter类的__init__()函数。
上图中,nova.api.openstack.APIRouter类的__init__()函数中有个mapper变量,为ProjectMapper类实例,该mapper变量在调用_setup_routes时作为参数传递以供子类完成路径映射,又传递给其基类wsgi.Router。好了,我们看下这个基类:
在基类的__init__()函数中,调用了routes.middleware.RoutesMiddleware()函数,这个mapper当作参数传递,同时作为参数的还有wsgi.Router的静态函数_dispatch,返回值赋值给self._router变量。我们还看到,wsgi.Router类有个__call__函数,这个函数在子类中均没有实现,再看它的注释就知道,当wsgi请求到来时,webob会调用到这个函数,这个函数将变量self._router变量返回。接着,_dispatch函数将被调用,这个函数将返回一个对应的App,这个App类型为nova.api.openstack.wsgi.Resource。_dispatch函数被调用时,其局部变量的类型和值如下图所示:
上图所示为请求调用detail方法时,match变量的值,它是一个dict,action值为“detail”,controller值为一个nova.api.openstack.wsgi.Resource对象,还有一个project_id。作为一个wsgi的App,nova.api.openstack.wsgi.Resource类实现了__call__方法,该方法接着就会被调用,如下图:
Resource的__call__函数调用_process_stack函数,后者又调用dispatch函数,dispatch函数调用了被当作参数传进来的函数并返回,从上图中我们可以看到,这个函数就是nova.api.openstack.compute.servers.Controller.detail函数。那么问题又来了,Controller的这个detail方法是怎么被找到的?预知详情,请看如下图:
在Resource类的_process_stack函数中,调用dispatch函数之前,调用了get_method函数,get_method函数又调用了_get_method函数,_get_method中有那么一行:“meth = getattr(self.controller, action)”。这里的self.controller就是Resource类实例中聚合的nova.api.openstack.compute.servers.Controller实例,action就是detail函数,getattr返回的meth就是一个函数,_process_stack再将这个meth作为参数传递给dispatch,在dispatch中真正调用了nova.api.openstack.compute.servers.Controller的detail函数。至此,真相已经大白了。
我们再看下nova.api.openstack.compute.servers.Controller的__init__构造函数。在该构造函数中,有一行代码:self.compute_api = compute.API(),compute.API()通过调用importutils.import_object返回一个类对象,这个类对象的类型为nova.compute.api.API。
nova.compute.api.API类如下图:
nova.compute.api.API类聚合了一个nova.image.api.API对象,一个nova.network.neutronv2.api.API对象,一个nova.volume.cinder.API对象,一个nova.api.openstack.compute.contrib.security_groups.NativeNeutronSecurityGroupAPI对象,一个nova.compute.rpcapi.ComputeAPI对象等。我们可以猜测,nova.api.openstack.compute.servers.Controller类的很多操作将是通过nova.compute.api.API来完成。
接下来,我们以创建虚拟机的请求为例,阐述各个类之间的调用流程。下图中,当创建虚拟机请求到达时,会调用到servers.py中的Controller的create函数,然后会调用到nova.compute.api.API中的create函数,再调用到nova.compute.api.API中_create_instance函数,_create_instance调用nova.compute.api.API的compute_task_api函数,该函数只是给实例变量self._compute_task_api赋值,从图中我们知道该值类型为nova.conductor.ComputeTaskAPI。
nova.conductor.ComputeTaskAPI源码如下图:
上图中,nova.conductor.ComputeTaskAPI的初始化函数__init__中,初始化了一个变量self.conductor_compute_rpcapi,其类型为nova.conductor.rpcapi.ComputeTaskAPI,对应源码如下图:
如下图,在nova.compute.api.API的_create_instance函数中,调用了nova.conductor.ComputeTaskAPI的build_instance函数。
如上图,nova.conductor.ComputeTaskAPI的build_instance函数转而调用nova.conductor.rpcapi.ComputeTaskAPI的build_instance函数,后者再调用cctxt.cast将请求发送到消息队列。
nova-api主要类关系图如下:
以上是个人的粗浅理解,由于细节过多这里只解析关键部分,欢迎各位同仁指正!
OpenStack源码系列---nova-api的更多相关文章
- openstack nova 源码解析 — Nova API 执行过程从(novaclient到Action)
目录 目录 Nova API Nova API 的执行过程 novaclient 将 Commands 转换为标准的HTTP请求 PasteDeploy 将 HTTP 请求路由到具体的 WSGI Ap ...
- OpenStack源码系列---neutron-server
在看过了nova模块的源码之后,再去看OpenStack其它模块的源码会轻松很多,因为框架也是大同小异的.自四月份开通博客写了几篇文章后,真心觉得写篇技术文章如果要把前前后后牵扯到的其它技术内容都做介 ...
- OpenStack源码系列---nova-conductor
nova-conductor启动的也是一个rpc server,代码框架和nova-compute类似,所以我也懒得再详细分析一遍服务启动的过程.nova-api那篇文章的最后我说"cctx ...
- OpenStack源码系列---起始篇
近一年来我负责公司云点的自动化部署工作,包括公司自有云平台方案.XenServer.vSphere.Ovirt和OpenStack的自动化安装部署,目前已经到了OpenStack这一部分.自动化部署首 ...
- OpenStack源码系列---nova-compute
nova-compute运行的节点为计算节点,虚拟机运行于计算节点上.例如对于创建虚拟机请求,nova-api接收到客户端请求后,经过nova-scheduler调度器调度,再将请求发送给某个选定的n ...
- (转)如何阅读OpenStack源码
1 关于该项目 本项目使用在线绘图工具web sequencediagrams完成,目标是图形化OpenStack的所有操作流程,通过操作序列图能快速学习Openstack的工作原理,理清各个组件的关 ...
- AOP执行增强-Spring 源码系列(5)
AOP增强实现-Spring 源码系列(5) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostProc ...
- 大白话Vue源码系列(05):运行时鸟瞰图
阅读目录 Vue 实例的生命周期 实例创建 响应的数据绑定 挂载到 DOM 节点 结论 研究 runtime 一边 Vue 一边源码 初看 Vue 是 Vue 源码是源码 再看 Vue 不是 Vue ...
- Spark源码系列:RDD repartition、coalesce 对比
在上一篇文章中 Spark源码系列:DataFrame repartition.coalesce 对比 对DataFrame的repartition.coalesce进行了对比,在这篇文章中,将会对R ...
随机推荐
- oracle sequence的用法
在oracle中sequence就是序号,每次取的时候它会自动增加.sequence与表没有关系. 1.Create Sequence 首先要有CREATE SEQUENCE或者CREATE ...
- JMeter学习笔记21-如何添加思考时间
本文来介绍,JMeter如何插入思考时间.前面介绍过一个真实的性能测试场景,是需要加入思考时间,来模拟真实用户行为.本文就来介绍,如何在三个请求之间添加思考时间. 1. 在Test Plan下新建一个 ...
- Mac 生成public_key
1.首先查看是否已经生成过public_key 打开终端查看是否已经存在SSH密钥:cd ~/.ssh 输出:No such file or directory表示没有 2.生成public_key ...
- 学习笔记4——WordPress插件介绍
1.什么是WordPress插件? WordPress有三大组件:核心.主题.插件. 插件是扩展了WordPress核心功能的代码包.WordPress插件由PHP代码和其他资源(如图像,CSS和JS ...
- HDU 3341 Lost's revenge
Lost's revenge Time Limit: 5000ms Memory Limit: 65535KB This problem will be judged on HDU. Original ...
- Leetcode 368.最大整除子集
最大整除子集 给出一个由无重复的正整数组成的集合,找出其中最大的整除子集,子集中任意一对 (Si,Sj) 都要满足:Si % Sj = 0 或 Sj % Si = 0. 如果有多个目标子集,返回其中任 ...
- 九度oj 题目1111:单词替换
题目描述: 输入一个字符串,以回车结束(字符串长度<=100).该字符串由若干个单词组成,单词之间用一个空格隔开,所有单词区分大小写.现需要将其中的某个单词替换成另一个单词,并输出替换之后的字符 ...
- 九度oj 题目1472:求两个多项式的和
题目描述: 输入两个多项式,计算它们的和. 每个多项式有若干对整数表示,每组整数中,第一个整数表示系数(非0),第二个整数表示该项的次数. 如由3 3 5 -2 1 4 0表示3x^5 - 2 * x ...
- xmpp 环境配置
XMPP框架地址:http://xmpp.org/xmpp-software/libraries/ 配置流程
- BZOJ 3130 [Sdoi2013]费用流 ——网络流
[题目分析] 很容易想到,可以把P放在流量最大的边上的时候最优. 所以二分网络流,判断什么时候可以达到最大流. 流量不一定是整数,所以需要实数二分,整数是会WA的. [代码] #include < ...