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函数调用tgadd_thread函数,传递一个self.run_service静态方法,一个WSGIService对象和一个Event.event对象。Services.run_service函数调用WSGIService.start函数启动wsgi服务。WSGIService.start函数如下图:

上图中,self.server.start()函数被调用,根据前面的分析,self.serverWSGIService.__init__()函数中被初始化,实际为wsgi.Server类的实例,那么我们再看下wsgi.Server类的start方法,如下图:

 

这里,我们看到了eventlet.spawn函数被调用,传递的wsgi_kwargs字典中,funceventlet.wsgi.serversiteself.app。从上面的一系列分析中,我们不难看出,self.appWSGIService.__init__构造函数中通过self.loader.load_app(name)得到的,其中name值为打印出来的“osapi_compute”。至此,一个作为nova-api服务的WSGI应用服务已经起来了,并且运行在一个greenthread中,它将接收用户的各种请求。

nova-api实现的是wsgiApplication,而不是ServerServer是由wsgi库来实现,就是那个eventlet.wsgi.server,那么问题来了,我们的Application是如何被调用的呢?这时候,你是不是想到了那个eventlet.spawn被调用的时候,字典参数里那个“site”的值了?对了,就是它了,它是在WSGIService.__init__构造函数中通过self.loader.load_app(name)得到的,name为“osapi_compute”,self.loaderwsgi.Loader实例。如下图,load_app调用了deploy.loadapp,传入参数为:“config:/etc/nova/api-paste.ininame=osapi_compute”。

我们再看下deploy.loadapp都调用了哪些函数,如下图:

可以看到,它调用了api/openstack/urlmap.py模块中的urlmap_factory函数,该函数接收三个参数,分别为loaderglobal_conflocal_conf。这是怎么做到的呢?且让我们看下api-paste.ini这个文件,如下图:

 

看到了吗,原来deploy.loadapp根据传入的配置文件路径和name值,找到了api-paste.ini文件中对应的section,顺藤摸瓜找到了urlmap_factory函数。接下来我们以请求路径/v2为例进行阐述,其对应的Appopenstack_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的请求为例,它对应的controllerself.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类,代表一个wsgiApp

 下面看下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变量的值,它是一个dictaction值为“detail”,controller值为一个nova.api.openstack.wsgi.Resource对象,还有一个project_id。作为一个wsgiAppnova.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.Controllerdetail函数。至此,真相已经大白了。

我们再看下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中的Controllercreate函数,然后会调用到nova.compute.api.API中的create函数,再调用到nova.compute.api.API_create_instance函数,_create_instance调用nova.compute.api.APIcompute_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.ComputeTaskAPIbuild_instance函数。

 

如上图,nova.conductor.ComputeTaskAPIbuild_instance函数转而调用nova.conductor.rpcapi.ComputeTaskAPIbuild_instance函数,后者再调用cctxt.cast将请求发送到消息队列。

nova-api主要类关系图如下:

 以上是个人的粗浅理解,由于细节过多这里只解析关键部分,欢迎各位同仁指正!