目錄結構以下:html
上面介紹了nova-api發佈所用到的一些lib庫,有了上面的基礎知識,再來分析nova-api的發佈流程,就比較輕鬆了。nova-api能夠提供多種api服務:ec2, osapi_compute, osapi_volume, metadata。能夠經過配置項enabled_apis來設置啓動哪些服務,默認狀況下,四種服務都是啓動的。從nova-api的可執行腳本中,能夠看出每一個nova-api服務都是經過nova.service.WSGIService來管理的:web
class WSGIService(object):
def __init__(self, name, loader=None): self.name = name self.manager = self._get_manager() self.loader = loader or wsgi.Loader() self.app = self.loader.load_app(name) self.host = getattr(FLAGS, '%s_listen' % name, "0.0.0.0") self.port = getattr(FLAGS, '%s_listen_port' % name, 0) self.workers = getattr(FLAGS, '%s_workers' % name, None) self.server = wsgi.Server(name, #這裏經過eventlet來啓動服務 self.app, host=self.host, port=self.port) def start(self): if self.manager: self.manager.init_host() self.server.start() .......
從上可知,WSGIService使用self.app = self.loader.load_app(name)來加載wsgi app,app加載完成後,使用nova.wsgi.Server來發布服務。Server首先用指定ip和port實例化一個監聽socket,並使用wsgi.server以協程的方式來發布socket,並將監聽到的http請求交給app處理。下面咱們主要來分析處理HTTP請求的wsgi app是如何構建的,對於每個請求,它是如何根據url和請求方法將請求分發到具體的具體函數處理的。json
一、 composite:osapi_computeapi
上個語句self.loader.load_app(name)中的loader是nova.wsgi.Loader的實例。Loader.load_app(name)執行下面指令,使用deploy來加載wsgi app:app
deploy.loadapp("config:%s" % self.config_path, name=name)
self.config_path爲api-paste.ini文件路徑,通常爲/etc/nova/api-paste.ini。name爲ec2, osapi_compute, osapi_volume, metadata之一,根據指定的name不一樣來加載不一樣的wsgi app。下面以name=「osapi_compute」時,加載提供openstack compute API服務的wsgi app做爲具體分析。osapi_compute的配置以下socket
[composite:osapi_compute]
use = call:nova.api.openstack.urlmap:urlmap_factory /: oscomputeversions /v2: openstack_compute_api_v2
osapi_compute是調用urlmap_factory函數返回的一個nova.api.openstack.urlmap.URLMap實例,nova.api.openstack.urlmap.URLMap繼承paste.urlmap.URLMap,它提供了wsgi調用接口,因此該實例爲wsgi app。可是函數nova.api.openstack.urlmap.urlmap_factory與paste.urlmap.urlmap_factory定義徹底同樣,不過因爲它們所在的module不一樣,使得它們所用的URLMap分別爲與它處於同一module的URLMap。paste.urlmap.URLMap實現的功能很簡單:根據配置將url映射到特定wsgi app,並根據url的長短做一個優先級排序,url較長的將優先進行匹配。因此/v2將先於/進行匹配。URLMap在調用下層的wsgi app前,會更新SCRIPT_NAME和PATH_INFO。nova.api.openstack.urlmap.URLMap繼承了paste.urlmap.URLMap,並寫了一堆代碼,其實只是爲了實現對請求類型的判斷,並設置environ['nova.best_content_type']:若是url的後綴名爲json(如/xxxx.json),那麼environ['nova.best_content_type']=「application/json」。若是url沒有後綴名,那麼將經過HTTP headers的content_type字段中mimetype判斷。不然默認environ['nova.best_content_type']=「application/json」。函數
二、composite:openstack_compute_api_v2ui
經上面配置加載的osapi_compute爲一個URLMap實例,wsgi server接受的HTTP請求將直接交給該實例處理。它將url爲'/v2'的請求將交給openstack_compute_api_v2,url爲'/'的請求交給oscomputerversions處理(它直接返回系統版本號)。其它的url請求,則返回NotFound。下面繼續分析openstack_compute_api_v2,其配置以下:url
[composite:openstack_compute_api_v2]
use = call:nova.api.auth:pipeline_factory noauth = faultwrap sizelimit noauth ratelimit osapi_compute_app_v2 keystone = faultwrap sizelimit authtoken keystonecontext ratelimit osapi_compute_app_v2 keystone_nolimit = faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v2
openstack_compute_api_v2是調用nova.api.auth.pipeline_factory()返回的wsgi app。pipeline_factory()根據配置項auth_strategy來加載不一樣的filter和最終的osapi_compute_app_v2。filter的大概配置以下:spa
[filter:faultwrap]
paste.filter_factory = nova.api.openstack:FaultWrapper.factory
filter在nova中對應的是nova.wsgi.Middleware,它的定義以下:
class Middleware(Application):
@classmethod def factory(cls, global_config, **local_config): def _factory(app): return cls(app, **local_config) return _factory def __init__(self, application): self.application = application def process_request(self, req): return None def process_response(self, response): return response @webob.dec.wsgify(RequestClass=Request) def __call__(self, req): response = self.process_request(req) if response: return response response = req.get_response(self.application) return self.process_response(response)
Middleware初始化接收一個wsgi app,在調用wsgi app以前,執行process_request()對請求進行預處理,判斷請求是否交給傳入的wsgi app,仍是直接返回,或者修改些req再給傳入的wsgi app處理。wsgi app返回的response再交給process_response()處理。例如,對於進行驗證的邏輯,能夠放在process_request中,若是驗證經過則繼續交給app處理,不然返回「Authentication required」。不過查看nova全部Mddlerware的編寫,彷佛都不用這種定義好的結構,而是把處理邏輯都放到__call__中,這樣致使__call__變得複雜,代碼不夠整潔。當auth_strategy=「keystone」時,openstack_compute_api_v2=FaultWrapper-> RequestBodySizeLimiter-> auth_token-> NovaKeystoneContext-> RateLimitingMiddleware-> osapi_compute_app_v2。因此HTTP請求須要通過五個Middleware的處理,才能到達osapi_compute_app_v2。這五個Middleware分別完成:
1)異常捕獲,防止服務內部處理異常致使wsgi server掛掉;
2)限制HTTP請求body大小,對於太大的body,將直接返回BadRequest;
3)對請求keystone對header中token id進行驗證;
4)利用headers初始化一個nova.context.RequestContext實例,並賦給req.environ['nova.context'];
5)限制用戶的訪問速度。
注意以上filter是正序調用,逆序返回。即正序處理請求,逆序處理結果!
三、app:osapi_compute_app_v2
當HTTP請求通過上面五個Middlerware處理後,最終交給osapi_compute_app_v2,它是怎麼繼續處理呢?它的配置以下:
[app:osapi_compute_app_v2]
paste.app_factory = nova.api.openstack.compute:APIRouter.factory
osapi_compute_app_v2是調用nova.api.openstack.compute.APIRouter.factory()返回的一個APIRouter實例。具體的請求會調用APIRouter的__call__函數。大體爲,app加載主要是構造APIRouter,調用__init__函數,而後發散開去構造其餘的Resource(或者Controller),走這一條線索;而請求的處理主要是調用APIRouter的__call__函數,而後匹配到相應的Controller,調用Controller的函數,走這一條線索。能夠參考:
http://www.choudan.net/2013/07/30/OpenStack-API%E5%88%86%E6%9E%90(%E4%B8%80).html
先看看這部分的類關係,以下圖所示:
3.1 首先分析APIRouter的構造
nova/api/openstack/__init__.py
APIRouter類:
def __init__(self, ext_mgr=None, init_only=None):
if ext_mgr is None: if self.ExtensionManager: ext_mgr = self.ExtensionManager() else: raise Exception(_("Must specify an ExtensionManager class")) mapper = ProjectMapper() self.resources = {} self._setup_routes(mapper, ext_mgr, init_only) self._setup_ext_routes(mapper, ext_mgr, init_only) self._setup_extensions(ext_mgr) super(APIRouter, self).__init__(mapper)
nova/wsgi.py
Router類:
def __init__(self, mapper):
self.map = mapper self._router = routes.middleware.RoutesMiddleware(self._dispatch, self.map)
APIRouter經過它的成員變量self.map(mapper)來創建和維護url與controller之間的映射,該mapper是nova.api.openstack.ProjectMapper的實例,它繼承nova.api.openstack.APIMapper(routes.Mapper)。APIMapper將每一個url的format限制爲json或xml,對於其它擴展名的url將返回NotFound。ProjectMapper在每一個請求url前面加上一個project_id,這樣每一個請求的url都須要帶上用戶所屬的project id,因此通常請求的url爲/v2/project_id/resources。
APIRouter經過self._setup_routes,self._setup_ext_routes,self._setup_extensions三個函數對mapper進行設置,下面會進行詳細分析。其中使用到的ExtensionManager實例,該實例的構造能夠參見:http://www.cnblogs.com/littlebugfish/p/4049853.html,此處再也不詳述,ExtensionManager中會有屬性索引到擴展模塊中對應的擴展類的實例。最後調用父類Router的__init__函數,其中self._router是routes.middleware.RoutesMiddleware的實例,使用self._dispatch和self.map來初始化的。
3.1.1 _setup_routes分析(核心Resource)
class APIRouter(nova.api.openstack.APIRouter):
ExtensionManager = extensions.ExtensionManager def _setup_routes(self, mapper, ext_mgr): self.resources['servers'] = servers.create_resource(ext_mgr) mapper.resource("server", "servers", controller=self.resources['servers']) self.resources['ips'] = ips.create_resource() mapper.resource("ip", "ips", controller=self.resources['ips'], parent_resource=dict(member_name='server', collection_name='servers')) .......
APIRouter經過調用routes.Mapper.resource()函數創建RESTFUL API,也能夠經過routes.Mapper.connect()來創建url與controller的映射。如上所示,servers相關請求的controller設爲servers.create_resource(ext_mgr),該函數返回的是一個用nova.api.openstack.compute.servers.Controller()做爲初始化參數的nova.api.openstack.wsgi.Resource實例,ips相關請求的controller設爲由nova.api.openstack.ips.Controller()初始化的nova.api.openstack.wsgi.Resource實例。由於調用mapper.resource創建ips的url映射時,添加了一個parent_resource參數,使得請求ips相關api的url形式爲/v2/project_id/servers/server_id/ips。對於limits、flavors、metadata等請求狀況相似。
3.1.2 _setup_ext_routes分析(擴展Resource)
def _setup_ext_routes(self, mapper, ext_mgr, init_only):
for resource in ext_mgr.get_resources(): LOG.debug(_('Extended resource: %s'), resource.collection) if init_only is not None and resource.collection not in init_only: continue inherits = None if resource.inherits: inherits = self.resources.get(resource.inherits) if not resource.controller: resource.controller = inherits.controller wsgi_resource = wsgi.Resource(resource.controller, inherits=inherits) self.resources[resource.collection] = wsgi_resource kargs = dict( controller=wsgi_resource, collection=resource.collection_actions, member=resource.member_actions) if resource.parent: kargs['parent_resource'] = resource.parent mapper.resource(resource.collection, resource.collection, **kargs) if resource.custom_routes_fn: resource.custom_routes_fn(mapper, wsgi_resource)
首先會調用ExtensionManager的get_resources函數,該函數會遍歷調用註冊在ExtensionManager中的擴展類實例的get_resources函數(即nova/api/openstack/compute/contrib中每一個文件裏面對應文件名的類的get_resources函數),從而獲取一個nova.api.openstack.ResourceExtension對象列表。接着將這些ResourceExtension對象的controller封裝成nova.api.openstack.wsgi.Resource對象,最後使用mapper.resource函數構建URL到這些Resource的映射。
3.1.3 _setup_extensions分析(擴展Controller)
def _setup_extensions(self, ext_mgr):
for extension in ext_mgr.get_controller_extensions(): collection = extension.collection controller = extension.controller msg_format_dict = {'collection': collection, 'ext_name': extension.extension.name} if collection not in self.resources: LOG.warning(_('Extension %(ext_name)s: Cannot extend ' 'resource %(collection)s: No such resource'), msg_format_dict) continue LOG.debug(_('Extension %(ext_name)s extending resource: ' '%(collection)s'), msg_format_dict) resource = self.resources[collection] resource.register_actions(controller) resource.register_extensions(controller)
首先會調用ExtensionManager的get_controller_extensions函數,該函數會遍歷調用註冊在ExtensionManager中的擴展類實例的get_controller_extensions函數(即nova/api/openstack/compute/contrib中每一個文件裏面對應文件名的類的get_controller_extensions函數),從而獲取一個nova.api.openstack. ControllerExtension對象列表。接着獲取每一個ControllerExtension中collection對應的nova.api.openstack.wsgi.Resource資源,向該資源註冊ControllerExtension中controller的各個函數。