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代碼:python

import pdbweb

pdb.set_trace()api

 而後再運行/usr/bin/nova-api服務。上述代碼至關於設置了一個斷點,程序運行到該位置會停下來以便咱們調試分析。這時候,咱們能夠單步執行程序,查看調用棧,顯示當前源碼位置等。app

nova源碼文件安裝到目錄「/usr/lib/python2.7/dist-packages/nova」,在以後的分析中,我所列出的目錄均是相對於該目錄的路徑,當我說一個文件路徑爲api/openstack/wsgi.py時,則該文件存放位置爲「/usr/lib/python2.7/dist-packages/nova/api/openstack/wsgi.py」,依此類推。python2.7

 /usr/bin/nova-api的啓動代碼以下:函數

 

從上面的圖中咱們知道,它調用了cmd/api.py文件裏的main函數,該函數的代碼以下圖:ui

 

咱們看到,main函數中有個launcher變量,實際爲nova.openstack.common.service.ProcessLauncher對象實例,有個server變量,實際爲nova.service.WSGIService對象實例。在我調試的時候,api值爲「osapi_compute」,這個值是在/etc/nova/nova.conf文件中配置的。在獲取了這樣兩個對象以後,launcher調用了它的launch_service函數,並將server變量做爲參數傳遞。url

 咱們來看一下WSGIService類的構造函數__init__(),以下圖:spa

 

在構造函數中,有一個self.app變量,一個self.server變量,後者爲wsgi.Server類實例。關於這兩個變量,咱們後續再作分析。咱們先來了解下ProcessLauncher類的launch_service函數:.net

 

如上圖,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主要類關係圖以下:


 

 以上是我的的粗淺理解,因爲細節過多這裏只解析關鍵部分,歡迎各位同仁指正!

相關文章
相關標籤/搜索