標籤:
二、nova-api中app載入過程分析
目錄結構如下:
上面介紹了nova-api發布所用到的一些lib庫,有了上面的基礎知識,再來分析nova-api的發布流程,就比較輕鬆了。nova-api可以提供多種api服務:ec2, osapi_compute, osapi_volume, metadata。可以通過配置項enabled_apis來設定啟動哪些服務,預設情況下,四種服務都是啟動的。從nova-api的可執行指令碼中,可以看出每個nova-api服務都是通過nova.service.WSGIService來管理的:
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和要求方法將請求分發到具體的具體函數處理的。
1、 composite:osapi_compute
上個語句self.loader.load_app(name)中的loader是nova.wsgi.Loader的執行個體。Loader.load_app(name)執行下面指令,使用deploy來載入wsgi 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的配置如下
[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”。
2、composite:openstack_compute_api_v2
經上面配置載入的osapi_compute為一個URLMap執行個體,wsgi server接受的HTTP請求將直接交給該執行個體處理。它將url為‘/v2‘的請求將交給openstack_compute_api_v2,url為‘/‘的請求交給oscomputerversions處理(它直接返回系統版本號碼)。其它的url請求,則返回NotFound。下面繼續分析openstack_compute_api_v2,其配置如下:
[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_v2keystone_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的大概配置如下:
[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是正序調用,逆序返回。即正序處理請求,逆序處理結果!
3、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的各個函數。
nova-api源碼分析(APP的建立)