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的更多相关文章

  1. openstack nova 源码解析 — Nova API 执行过程从(novaclient到Action)

    目录 目录 Nova API Nova API 的执行过程 novaclient 将 Commands 转换为标准的HTTP请求 PasteDeploy 将 HTTP 请求路由到具体的 WSGI Ap ...

  2. OpenStack源码系列---neutron-server

    在看过了nova模块的源码之后,再去看OpenStack其它模块的源码会轻松很多,因为框架也是大同小异的.自四月份开通博客写了几篇文章后,真心觉得写篇技术文章如果要把前前后后牵扯到的其它技术内容都做介 ...

  3. OpenStack源码系列---nova-conductor

    nova-conductor启动的也是一个rpc server,代码框架和nova-compute类似,所以我也懒得再详细分析一遍服务启动的过程.nova-api那篇文章的最后我说"cctx ...

  4. OpenStack源码系列---起始篇

    近一年来我负责公司云点的自动化部署工作,包括公司自有云平台方案.XenServer.vSphere.Ovirt和OpenStack的自动化安装部署,目前已经到了OpenStack这一部分.自动化部署首 ...

  5. OpenStack源码系列---nova-compute

    nova-compute运行的节点为计算节点,虚拟机运行于计算节点上.例如对于创建虚拟机请求,nova-api接收到客户端请求后,经过nova-scheduler调度器调度,再将请求发送给某个选定的n ...

  6. (转)如何阅读OpenStack源码

    1 关于该项目 本项目使用在线绘图工具web sequencediagrams完成,目标是图形化OpenStack的所有操作流程,通过操作序列图能快速学习Openstack的工作原理,理清各个组件的关 ...

  7. AOP执行增强-Spring 源码系列(5)

    AOP增强实现-Spring 源码系列(5) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostProc ...

  8. 大白话Vue源码系列(05):运行时鸟瞰图

    阅读目录 Vue 实例的生命周期 实例创建 响应的数据绑定 挂载到 DOM 节点 结论 研究 runtime 一边 Vue 一边源码 初看 Vue 是 Vue 源码是源码 再看 Vue 不是 Vue ...

  9. Spark源码系列:RDD repartition、coalesce 对比

    在上一篇文章中 Spark源码系列:DataFrame repartition.coalesce 对比 对DataFrame的repartition.coalesce进行了对比,在这篇文章中,将会对R ...

随机推荐

  1. joyoi1935 「Poetize3」导弹防御塔

    #include <iostream> #include <cstring> #include <cstdio> #include <queue> #i ...

  2. appium+python自动化-adb shell模拟点击事件(input tap)

    前言 appium有时候定位一个元素很难定位到,或者说明明定位到这个元素了,却无法点击,这个时候该怎么办呢? 求助大神是没用的,点击不了就是点击不了,appium不是万能的,这个时候应该转换思路,换其 ...

  3. js中取绝对值的2种方法!

    1.abs() var aaa=-20; var bbb=Math.abs(aaa); 2.加减法 var aaa=-20; var bbb=-aaa

  4. 【Luogu】P3800点收集(DP)

    题目链接 原题解 代码 #include<iostream> #include<cstdio> #include<cstring> #include<ccty ...

  5. [洛谷P2580]于是他错误的点名开始了(Trie树)

    传送门 洛谷P2580的一个水题,用啥都能过,不过为了练习一下刚刚学会的字典树,还是认真做一下吧. #include <cstdio> #include <cstring> u ...

  6. net8:XML的读写操作【广告控件的XML文件实例】

    原文发布时间为:2008-08-05 -- 来源于本人的百度文章 [由搬家工具导入] 【用了datalist控件,datalist控件自己学会,主要知道其他按钮COMMANDNAME属性应该改为edi ...

  7. [MFC] CFile读写文件实现(高效)

    1.文件写入 void CMFCApplication1Dlg::Write() { CFile file; CString FileName = "D:\\100w.txt"; ...

  8. POJ 2411 状压dp

    F - Mondriaan's Dream Time Limit:3000MS     Memory Limit:65536KB     64bit IO Format:%I64d & %I6 ...

  9. Python入门--12--函数与变量

    python只有函数没有过程 函数有运行完了之后会有返回值.过程没有 def back(): return 1,'gg',2 #会有返回值(1,'gg',2) #注意默认返回元祖 一.全局和局部变量 ...

  10. android layout

    android的视图分为两类,一类是布局,另一个类是控件 一.LinearLayout(线性布局) 最常用布局之一,线性布局的特性是每添加一个控件默认会在上个控件的下面占一行. <LinearL ...