更新时间:2022-04-04 12:51:46
OpenStack 中的每一个提供 REST API Service 的组件,比如 cinder-api,nova-api 等,其实是一个 WSGI App,其主要功能是接受客户端发来的 HTTP Requst,然后进行用户身份校验和消息分发。代码实现上,主要使用了几种技术:WSGI Server、WSGI Application、Paste deploy 和 Router。本文希望结合 cinder-api 的启动过程,对这些相关技术和 cinder-api 的代码做一个总结。
参考文档:
PEP 333 - Python Web Server Gateway Interface v1.0
http://wiki.woodpecker.org.cn/moin/WSGI
简单来说,python中的 WSGI 是 Python 应用程序或框架与 Web 服务器之间的一种接口,它定义了一套接口来实现服务器与应用端的通信规范,它将 web 组件分为三类:
(1)WSGI Server唯一的任务就是接收来自 client 的请求,然后将请求传给 application,最后将 application 的response 传递给 client。
(2)在使用 Middleware 的情况下,WSGI的处理模式为 WSGI Server -> (WSGI Middleware)* -> WSGI Application。其实 middleware 本身已是一个 app,只是你需要有一个 App 做为实现主要功能的 Application。Middleware可以:
(3)在 Server 和 Application 之间,可以使用若干个 Middleware 来处理 request 和 response:
具体过程描述如下:
以 cinder-api 为例,其 WSGI 实例初始化和启动 service 的代码如下:
Paste deploy 是一种配置 WSGI Service 和 app 的方法:
简单来说,它提供一个方法 loadapp,它可以用来从 config 配置文件或者 Python egg 文件加载 app(包括middleware/filter和app),它只要求 app 给它提供一个入口函数,该函数通过配置文件告诉Paste depoly loader。一个简单的调用 loadapp 的 Python code 的例子:
from paste.deploy import loadapp
wsgi_app = loadapp('config:/path/to/config.ini')
/path/to/config.ini 文件:
[app:myapp] paste.app_factory = myapp.modulename:factory
myapp.modulename:factory 的实现代码:
@classmethod
def factory(cls, global_conf, **local_conf):
"""Factory method for paste.deploy."""
return cls
OpenStack 的 Paste deploy 使用的是各组件的 api-paste.ini 配置文件,比如 /etc/cinder/api-paste.ini。OpenStack WSGI app 是个实现了 __call__ 方法的类 class Router(object) 的一个实例对象。OpenStack WSGI middleware 是个实现了 __call__ 方法的类 class Middleware(Application) 类的子类的实例。
Route 是用 Python 重新实现的 Rails routes system,它被用来做将 URL 映射为 App 的 action,以及为 App的action 产生 URL。
1. 两个重要的方法:map.connect (定义映射规则) 和 map.match (获取映射结果)。一个简单例子:
# Setup a mapper
from routes import Mapper
map = Mapper()
map.connect(None, "/error/{action}/{id}", controller="error")
map.connect("home", "/", controller="main", action="index")
# Match a URL, returns a dict or None if no match
result = map.match('/error/myapp/4')
# result == {'controller': 'error', 'action': 'myapp', 'id': '4'}
2. map.resource 方法
当映射规则很多的时候,需要使用很多次 map.connect,这时可以使用 map.resource 方法。其定义为:resource(member_name, collection_name, **kwargs). 有很多种定义映射规则的方法,具体见 Routes 的官方文档。
(1) map.resource("message","messages",controller=a)
第一个参数 message 为 member_name(资源名),第二个参数 messages 为 collection_name(资源集合名),一般定义资源集合名为资源名的复数。该规则对应于:
map.connect('/messages',controller=a,action='index',conditions={'method':['GET']})
map.connect('/messages',controller=a,action='create',conditions={'method':['POST']})
map.connect('/messages/{id}',controller=a,action='show',conditions={'method':['GET']})
map.connect('/messages/{id}',controller=a,action='update',conditions={'method':['PUT']})
map.connect('/messages/{id}',controller=a,action='delete',conditions={'method':['DELETE']})
(2) map.resource('message', 'messages',controller=a, collection={'search':'GET','create_many':'POST'}, member={'update_many':'POST','delete_many':'POST'})
map.resource除了默认的路由条件外,还可以额外的定义‘资源集合的方法’以及‘单个资源的方法’
collection={'search':'GET','create_many':'POST'} 定义了资源集合方法 search,其curl动作为GET,create_many,其curl动作为POST
member={'update_many':'POST','delete_many':'POST'} 定义了单个资源方法 update_many,其curl动作为POST,delete_many,其curl动作为POST
(3) map.resource('message', 'messages',controller=a,path_prefix='/{projectid}', collection={'list_many':'GET','create_many':'POST'}, member={'update_many':'POST','delete_many':'POST'})
map.resource 初始化时还可以指定 curl 访问路径的前缀路径,没有指定时默认为collection_name(资源集合名)路径为path_prefix/collection_name。
参考文档:
http://routes.readthedocs.org/en/latest/setting_up.html
http://blog.csdn.net/bellwhl/article/details/8956088
Cinder 的 cinder-api 是 WSGIService 类型,其它服务是 Service 类型。这两个类的定义在文件 cinder/cinder/service.py 中。具体步骤如下:
(1). 用户调用 cinder/bin/ 目录中的脚本来启动相关服务,比如 cinder-all 会启动cinder所有服务,cinder-api 会启动 cinder-api服务。以 cinder-api 为例,它会启动名为 osapi_volume 的 WSGI Service:
if __name__ == '__main__': CONF(sys.argv[1:], project='cinder', version=version.version_string()) logging.setup("cinder") utils.monkey_patch() rpc.init(CONF) launcher = service.process_launcher() server = service.WSGIService('osapi_volume') #初始化WSGIService,load osapi_volume app
launcher.launch_service(server, workers=server.workers) launcher.wait() #启动该 WSGI service
在执行 server = service.WSGIService('osapi_volume') 时,首先会 load app。具体 load 过程下面 2 Paste 加载 osapi_volume 的过程。
class WSGIService(object):
"""Provides ability to launch API from a 'paste' configuration."""
def __init__(self, name, loader=None):
self.name = name
self.manager = self._get_manager()
#load APP
self.loader = loader or wsgi.Loader() #paste.deploy.loadwsgi.ConfigLoader
self.app = self.loader.load_app(name)
def load_app(self, name):
"""Return the paste URLMap wrapped WSGI application.
"""
try:
return deploy.loadapp("config:%s" % self.config_path, name=name) #从配置文件 paste-api.ini 中加载WSGI application
except LookupError as err:
LOG.error(err)
raise exception.PasteAppNotFound(name=name, path=self.config_path)
......
self.server = wsgi.Server(name,self.app,host=self.host,port=self.port) #定义WSGI Server
执行 launcher.launch_service(server, workers=server.workers) 会启动该 service。它会绑定网卡 osapi_volume_listen=0.0.0.0 并在 osapi_volume_listen_port=5900 端口监听,并且可以启动 osapi_volume_workers=<None> 个worker线程。
(3). 启动cinder-scheduler 会启动一个名为 cinder-scheduler 的 Service。与 cinder-api 的WSGI Service 不同的是,它需要创建 RPC 连接,启动消费者线程,然后等待队列消息。它的启动参数主要包括:
(4). 启动cinder-volume的过程类似于 cinder-scheduler。
osapi-volume 服务启动的过程中,调用 deploy.loadpp 使用 config 方式从 paste-api.conf 文件来 load 名为osapi_volume 的应用,其入口在文件的 [composite:osapi_volume]部分:
[composite:osapi_volume] #osapi_volume 即 deploy.loadapp方法中传入的name
use = call:cinder.api:root_app_factory
/: apiversions
/v1: openstack_volume_api_v1
/v2: openstack_volume_api_v2
调用 cinder/api/__init__.py 的 root_app_factory 方法:
def root_app_factory(loader, global_conf, **local_conf): #local_conf 包括 /,/v1,/v2 三个pair if CONF.enable_v1_api: LOG.warn(_('The v1 api is deprecated and will be removed after the Juno release. You should set enable_v1_api=false and ' 'enable_v2_api=true in your cinder.conf file.')) else: del local_conf['/v1'] if not CONF.enable_v2_api: del local_conf['/v2'] return paste.urlmap.urlmap_factory(loader, global_conf, **local_conf)
def urlmap_factory(loader, global_conf, **local_conf):
for path, app_name in local_conf.items():
path = paste.urlmap.parse_path_expression(path)
app = loader.get_app(app_name, global_conf=global_conf)
urlmap[path] = app
return urlmap
在该方法中,如果 cinder.conf 中 enable_v1_api = False 则不加载 V1对应的app;如果 enable_v2_api = False 则不加载V2对应的app。否则三个都会被加载,所以在不需要使用 Cinder V1 API 的情况下,建议 disable V1 来节省 cinder-api 的启动时间和资源消耗。
加载 openstack_volume_api_v2,它对应的composite 是:
[composite:openstack_volume_api_v2]
use = call:cinder.api.middleware.auth:pipeline_factory
noauth = request_id faultwrap sizelimit osprofiler noauth apiv2
keystone = request_id faultwrap sizelimit osprofiler authtoken keystonecontext apiv2
keystone_nolimit = request_id faultwrap sizelimit osprofiler authtoken keystonecontext apiv2
cinder/api/middleware/auth.py 中的 pipeline_factory 方法如下:
def pipeline_factory(loader, global_conf, **local_conf):
"""A paste pipeline replica that keys off of auth_strategy."""
pipeline = local_conf[CONF.auth_strategy] #读取CONF.auth_strategy,其值默认为 token,再获取 'token' 对应的 pipeline
pipeline = pipeline.split() # ['request_id', 'faultwrap', 'sizelimit', 'osprofiler', 'authtoken', 'keystonecontext', 'apiv2']
filters = [loader.get_filter(n) for n in pipeline[:-1]] #依次使用 deploy loader 来 load 各个 filter
app = loader.get_app(pipeline[-1]) #使用 deploy loader 来 load app 即 apiv2
filters.reverse() #[<function _factory at 0x7fe44485ccf8>, <function auth_filter at 0x7fe44485cc80>, <function filter_ at 0x7fe43fe5f398>, <function _factory at 0x7fe43fe59ed8>, <function _factory at 0x7fe43fe595f0>, <class 'cinder.openstack.common.middleware.request_id.RequestIdMiddleware'>]
for filter in filters:
app = filter(app) #然后依次调用每个 filter 来 wrapper 该 app
return app ,
它首先会读取 cinder.conf 中 auth_strategy 的值。我们一般会使用 token,所以它会从 local_conf 中读取keystone 的 pipeline 。
第一步,loader.get_filter(filter)来使用 deploy loader 来 load 每一个 filter,它会调用每个 filter 对应的入口 factory 函数,该函数在不同的 MiddleWare 基类中定义,都是返回函数 _factory 的指针(keystonemiddleware 是 函数auth_filter的指针)。比如:
Enter Middleware.factory with cls: <class 'cinder.openstack.common.middleware.request_id.RequestIdMiddleware'>
Enter cinder.wsgi.factory with cls: <class 'cinder.api.middleware.fault.FaultWrapper'> factory /usr/lib/python2.7/dist-packages/cinder/wsgi.py:385
Enter cinder.wsgi.factory with cls: <class 'cinder.api.middleware.sizelimit.RequestBodySizeLimiter'> factory /usr/lib/python2.7/dist-packages/cinder/wsgi.py:385
Enter cinder.wsgi.factory with cls: <class 'cinder.api.middleware.auth.CinderKeystoneContext'> factory /usr/lib/python2.7/dist-packages/cinder/wsgi.py:385
第二步,app = loader.get_app(pipeline[-1]):使用 deploy loader 来 load app apiv2,调用入口函数 cinder.api.v2.router:APIRouter.factory,factory 函数会调用其__init__方法,这方法里面会setup routes,setup ext_routes 和 setup extensions 等,具体分析见 2.3 章节。
第三步,app = filter(app):然后调用每个 filter 来 wrapper APIRouter。
依次调用 filter 类的 factory 方法或者 keystonemiddleware 的 audit_filter方法,传入 app 做为参数。
以keystonemiddleware为例,在 audit_filter 方法中,首先会初始化个 AuthProtocol 的实例,再调用其__init__ 函数,会设置 auth token 需要的一些环境,比如_signing_dirname、_signing_cert_file_name 等。
到这里,osapi_volume 的 loading 就结束了。在此过程中,各个 filter middleware 以及 WSGI Application 都被加载/注册,并被初始化。WSGI Server 在被启动后,开始等待 HTTP request,然后进入HTTP request 处理过程。
在进入具体的 load apiv2 application 过程之前,我们先来了解了解下 Cinder 里面的资源管理。
OpenStack 定义了两种类型的资源:
Extension resource 的加载见下面 2.3.1 load extension 章节。
Cinder 中的Extension resource 包括 extensions,os-hosts,os-quota-sets,encryption,backups,cgsnapshots,consistencygroups,encryption,os-availability-zone,extra_specs,os-volume-manage,qos-specs,os-quota-class-sets,os-volume-transfer,os-services,scheduler-stats。
(1)基本操作
(2)action
@wsgi.action('os-migrate_volume_completion') #该方法的 alias 是 os-migrate_volume_completion
def _migrate_volume_completion(self, req, id, body)
POST /{tenant-id}/volumes/{volume-id}/action
body: {"os-force_delete": null}
(3)extends
@wsgi.extends
def create(self, req, body)
body:
{
"volume": {
"availability_zone": null,
"source_volid": null,
"description": null,
"snapshot_id": null,
"size": 1,
"name": "hintvolume3",
"imageRef": null,
"volume_type": null,
"metadata": {}
},
"OS-SCH-HNT:scheduler_hints": {"near": "2b7c42eb-7736-4a0f-afab-f23969a35ada"}
(4)操作的操作对象:操作对象分两类:collection 和 member。 collection 比如对 volumes 的操作,POST 到 /volumes 会创建新的volume; member 比如对单个volume 的操作:GET /volumes/{volume-ID} 返回指定 volume 的信息。
(5)操作的输出的(反)序列化 - (de)serializers
@wsgi.response(202)
@wsgi.serializers(xml=BackupTemplate)
@wsgi.deserializers(xml=CreateDeserializer)
def create(self, req, body): #BackupsController 的 create 方法
def deserialize(self, meth, content_type, body):
#比如 body: {"os-force_delete": null} meth_deserializers = getattr(meth, 'wsgi_deserializers', {}) #获取该方法的 deserializer try: mtype = _MEDIA_TYPE_MAP.get(content_type, content_type) if mtype in meth_deserializers: deserializer = meth_deserializers[mtype] else: deserializer = self.default_deserializers[mtype] #没有定义则使用默认的 deserializer except (KeyError, TypeError): raise exception.InvalidContentType(content_type=content_type) return deserializer().deserialize(body) #对 request body 反序列化,比如 {'body': {u'os-force_delete': None}}
def _process_stack(self, request, action, action_args,content_type, body, accept):
......
# Run post-processing extensions if resp_obj: _set_request_id_header(request, resp_obj) # Do a preserialize to set up the response object serializers = getattr(meth, 'wsgi_serializers', {}) #获取该方法的 serializer resp_obj._bind_method_serializers(serializers) if hasattr(meth, 'wsgi_code'): resp_obj._default_code = meth.wsgi_code resp_obj.preserialize(accept, self.default_serializers) # Process post-processing extensions response = self.post_process_extensions(post, resp_obj,request, action_args) if resp_obj and not response: response = resp_obj.serialize(request, accept, self.default_serializers) #序列化方法的 output 为 response
(6)以下表格显示了 Cinde 中部分 Resource extensions 的 actions 和 extends:
Collection/Resource | Extension name | File (/api/contrib 目录下) | Controller | wsgi actions | wsgi extends |
volumes | SchedulerHints | scheduler_hints.py |
SchedulerHintsController
|
|
('create', None)
|
VolumeTenantAttribute | volume_tenant_attribute.py |
VolumeTenantAttributeController
|
|
('detail', None), ('show', None)
|
|
AdminActions |
VolumeAdminController
|
{'os-migrate_volume_completion': '_migrate_volume_completion', 'os-reset_status': '_reset_status', 'os-force_delete': '_force_delete', 'os-migrate_volume': '_migrate_volume', 'os-force_detach': '_force_detach'}
|
|||
VolumeReplication | volume_replication.py | VolumeReplicationController | {'os-reenable-replica': 'reenable', 'os-promote-replica': 'promote'} | ('show', None), ('detail', None) | |
VolumeUnmanage | volume_unmange.py | VolumeUnmanageController | {'os-unmanage': 'unmanage'} | ||
VolumeActions | volume_actions.py | VolumeActionsController | {'os-reserve': '_reserve', 'os-roll_detaching': '_roll_detaching', 'os-terminate_connection': '_terminate_connection', 'os-set_bootable': '_set_bootable', 'os-unreserve': '_unreserve', 'os-detach': '_detach', 'os-retype': '_retype', 'os-volume_upload_image': '_volume_upload_image', 'os-update_readonly_flag': '_volume_readonly_update', 'os-begin_detaching': '_begin_detaching', 'os-extend': '_extend', 'os-initialize_connection': '_initialize_connection', 'os-attach': '_attach'} | ||
VolumeHostAttribute VolumeMigStatusAttribute VolumeImageMetadata |
('detail', None), ('show', None) | ||||
types | TypesManage | types_manage.py |
VolumeTypesManageController
|
{'create': '_create', 'delete': '_delete'}
|
|
VolumeTypeEncryption |
|
||||
limits | UsedLimits | used_limits.py |
UsedLimitsController
|
||
backups | AdminActions |
BackupAdminController
|
{'os-reset_status': '_reset_status', 'os-force_delete': '_force_delete'}
|
||
snapshots | SnapshotActions | snapshot_actions.py |
SnapshotActionsController
|
{'os-update_snapshot_status': '_update_snapshot_status'} |
|
AdminActions |
SnapshotAdminController
|
{'os-reset_status': '_reset_status', 'os-force_delete': '_force_delete'}
|
|||
ExtendedSnapshotAttributes |
其中,Admin actions 是必须 Administrator 才能操作,以volumes 的 os-force-delete 为例:
URL:http://9.123.245.88:8776/v2/2f07ad0f1beb4b629e42e1113196c04b/volumes/bac9c184-e375-4191-9602-93a1c74c5336/action
Method:POST
body:{"os-force_delete": null}
APIRouter 是 Cinder 中的核心类之一,它负责分发 HTTP Request 到其管理的某个 Resource:
{'action': u'detail', 'controller': <cinder.api.openstack.wsgi.Resource object at 0x7fa137be8950>, 'project_id': u'fa2046aaead44a698de8268f94759fc1'}
在 cinder-api service 启动阶段,它的 __init__ 方法被调用,会 setup resource routes,setup extension resource routes 以及 resource extension 的routes,然后把生成的 mapper 传给初始化了的 routes.middleware.RoutesMiddleware。
ext_mgr = self.ExtensionManager() #初始化 ext_mgr,用于管理所有的 extensions
mapper = ProjectMapper() #生成 mapper
self.resources = {} #初始化 resources 数组
self._setup_routes(mapper, ext_mgr) #为核心资源建立 routes
self._setup_ext_routes(mapper, ext_mgr) #为扩展资源建立 routes
self._setup_extensions(ext_mgr) #保存资源扩展的方法
super(APIRouter, self).__init__(mapper) #初始化RoutesMiddleware
在处理 HTTP Request 阶段,它负责接收经过 Middleware filters 处理过的 HTTP Request,再把它转发给 RoutesMiddleware 实例,它会使用 mapper 得到 Request 的URL 对应的 controller 和 action,并设置到 Request 的 environ 中,然后再调用 APIRoutes 的 dispatch 方法。该方法会从 environ 中得到controller,其实是个 Resource 的实力,然后调用其 __call_ 方法,实现消息的分发。
了解上面的各种资源之后,我们还需要了解 Controller,所谓的 Controller 实际上就是代表对该资源的操作集合,每个 Resource 都有一个相应的 Controller 类,其基类在 /cinder/api/v2/wsgi.py 中定义,在每个资源对应的 py 文件中定义对应的资源的Controller 类。
以 Volumes 为例,其 Controller 子类 VolumeController 在 /api/v2/volumes.py 中定义 class VolumeController(wsgi.Controller)。它包含上面描述的对 volumes 的基本操作操作。
既然存在这么多的资源,Cinder 需要使用一个集中的方式对他们进行管理。OpenStack 使用 ExensionManager 对这些扩展资源进行统一的管理。此类提供如下方法:
cinder.conf 的 osapi_volume_extension 配置项 (其默认值是 'cinder.api.contrib.standard_extensions')告诉 ExtensionManager 去哪里找扩展的类文件,默认的路径是 /cinder/api/contrib 目录下的 py 文件。
APIRouters 使用一个 mapper 属性来保存为所有resource 建立的 mapping,该 mapper 是个ProjectManager 的实例。其集成关系是 ProjectMapper -> APIMapper -> routes.Mapper,它提供一个重要的方法 def match(self, url=None, environ=None) 来根据其 routes 来获取映射关系。
以上这些类之间的关系如下:
从上图可以看出 APIRouter 类的支配地位:
(1)它被deploy loadapp调用, 然后它调用 ExtensionManager 的方法去获取各个Resource;保存 mapper,router,resource 等所有数据
(2)它接受Middleware filters 处理过的 Request,交给 router (RoutesMiddleware) 去做 URL 匹配,然后交给匹配得到的 Resource 去做消息分发。
该过程为 Cinder 各 Resource 建立 Routes。
APIRourter 调用类 ExtensionManager 的 __init__ 方法, 它再调用其 _load_extensions 方法获取所有的 extensions,其过程如下:
1. 首先读取 cinder.conf 中有配置项 osapi_volume_extension, 其默认值为 cinder.api.contrib.standard_extensions。该配置项可以设置多个value。
2. 使用 importutils.import_class('cinder.api.contrib.standard_extensions'),然后调用方法其入口方法 load_standard_extensions,这方法又会调用 extensions.load_standard_extensions(ext_mgr, LOG, __path__, __package__)
3. 该方法会对 /cinder/api/contrib 目录下所有的 py 文件中的类调用 load_extension 来加载。
ExtensionManager 类会遍历 contrib 目录下每个 py 文件,检查其中的 class 定义,判断它是哪一种资源。比如处理 volume_type_encryption.py 文件:
class Volume_type_encryption(extensions.ExtensionDescriptor): """Encryption support for volume types.""" def get_resources(self): #ExtensionManager 遍历 contrib 文件夹下每个文件的时候会尝试调用这个方法。如果这个方法存在,则该类会被认为是个Extension Resource。 resources = [] res = extensions.ResourceExtension( Volume_type_encryption.alias, #Extension resource alias VolumeTypeEncryptionController(), parent=dict(member_name='type', collection_name='types')) resources.append(res) return resources def get_controller_extensions(self): #ExtensionManager遍历每个文件的时候会尝试调用本方法。如果该方法存在,则认为该类提供了Resource extensions controller = VolumeTypeEncryptionController() extension = extensions.ControllerExtension(self, 'types', controller) #这里的‘types'就是被扩展了的 Core Resource 名字 return [extension]
4. load 每个extension时,会执行每个 extension 类的 __init__ 方法,并将其实例指针存放在 ExtensionManager 的 extensions 内,供以后调用。
Ext name: VolumeMigStatusAttribute
Ext alias: os-vol-mig-status-attr
至此,该 loading extensions 完成。下图以 Loaded extension os-vol-mig-status-attr 为例描述其具体过程:
APIRouter 的方法 _setup_routes 方法 为每个Core Resource 类建立 URL 到其 Controller 类的 method 的映射规则。
以 volumes Resource 为例,其 mapper 规则为:
1 self.resources['volumes'] = volumes.create_resource(ext_mgr) 2 mapper.resource("volume", "volumes", controller=self.resources['volumes'], collection={'detail': 'GET'}, member={'action': 'POST'})
第1行:会该 Resource 创建一个 Resource 实例,初始化其controller 为VolumeController,调用 Resource 的 __init__ 方法去获取 Controller 的 wsgi_actions 并保存(其实都是空的),Resource 实例会被保存到 APIRouter 的 resources 数组中以’volume‘ 为键值的一个数组项。其实 Resource 是个 wsgi Application,将来会调用它的 __call__ 方法去做消息分发。
第2行:定义了一个将 URL 映射到 VolumeController method 的规则:
注意这里 Core resource 的 mapper 的处理单个Resource 的 action (member={'action': 'POST'})和处理Resource集合的 action (collection={'detail': 'GET'})都是hard coded 的。 所有的映射规则都会被保存到 APIRouter 类的 mapper 属性中。
建立所有 Extension resource 的 routes。
方法 _setup_ext_routes 在类 cinder.api.v2.router.APIRouter 中实现:
mapper.resource('snapshot', 'snapshots', {'member': {}, 'controller': <cinder.api.openstack.wsgi.Resource object at 0x7f854ab630d0>, 'collection': {'detail': 'GET'}})
APIRouter 类的 _setup_extensions 方法遍历每个extension,找到每个 extension 的 wsgi actions 和 wsgi extends,保存到其 resources 数组的该 extension 对应的 resource 数组项中。
通过以上几个步骤,APIRouter 为所有的 Resouce 和 Extension resource 建立了URL 到 Resource 的 Controller 类的方法的映射规则并保存到 mapper 属性中,还保存了Resource extension 的方法到 Resource 中。
我们来看看 APIRouter 到底是怎么保存和使用这些信息的:
(0)以 os-extend 为例,URL 中使用 'action', Request body 中带有具体的action: {"os-extend": {"new_size": 2}}
(1)APIRoutes 有个数组属性 resources,每一个数组项是每一个资源拥有的 class Resource(wsgi.Application) 实例。该实例的 wsgi_actions 属性保存该Resource 支持的所有 actions,每个 action 的数据是个 pair (alias,action 对应的 method 的Controller 类实例的地址)。以 volumes 为例,
{'os-migrate_volume_completion': <bound method VolumeAdminController._migrate_volume_completion of <cinder.api.contrib.admin_actions.VolumeAdminController object at 0x7f459d256b90>>, 'os-reserve': <bound method VolumeActionsController._reserve of <cinder.api.contrib.volume_actions.VolumeActionsController object at 0x7f459d254d10>>, 'os-promote-replica': <bound method VolumeReplicationController.promote of <cinder.api.contrib.volume_replication.VolumeReplicationController object at 0x7f459d237d10>>, ...... 'os-attach': <bound method VolumeActionsController._attach of <cinder.api.contrib.volume_actions.VolumeActionsController object at 0x7f459d254d10>>}
(2)Resource 还有个 wsgi_extensions 数组属性,它为每一个 Resource 的基本方法保存扩展的方法。以 volumes 的 detail 方法为例,它有两个扩展方法:
[<bound method VolumeTenantAttributeController.detail of <cinder.api.contrib.volume_tenant_attribute.VolumeTenantAttributeController object at 0x7f459d3a5c90>>, <bound method VolumeReplicationController.detail of <cinder.api.contrib.volume_replication.VolumeReplicationController object at 0x7f459d237d10>>]
(3)收到一个 action 后,RoutesMiddleware 根据 URL 找到 Resource 的地址,然后 Resource 从 request body 中取出 action 的 alias,然后查找该 pairs,得到真正的method。
(4)首先会查找wsgi_extensions,获取该 method 对应的所有扩展方法的Controller 类,分别调用其method; 然后再把 request dispatch 到该方法上得到其输出了。
wsgi_actions['os-extend']: <bound method VolumeActionsController._extend of <cinder.api.contrib.volume_actions.VolumeActionsController object at 0x7f459d254d10>>
(5)该 Resource 实例同样还使用属性 wsgi_action_extensions 保存其所有的 action extensions。
将RouterMiddleware 后面要执行的 WSGI app 即 def _dispatch(req), 和上面步骤中生成的 mapper 传给 RoutesMiddleware,完成它的初始化,其实 RoutesMiddleware 也是一个 WSGI middleware app,只是它没有定义在 api-paste.ini 文件中,而是在 APIRouter 里面显式调用。将来它会根据传入的 mapper 进行 URL 和 Controller action 的匹配。
至此,cinder-api 中的各 Middleware (Filters) 和 Application (APIRoute,RoutesMiddleware)都完成了注册和初始化,cinder-api WSGI Service 进程也启动完毕,可以接受REST API Request 了。
Cinder 目录下的文件都按照不同的用途分别放在不同的文件夹中,小结如下:
本文转自SammyLiu博客园博客,原文链接:http://www.cnblogs.com/sammyliu/p/4272611.html,如需转载请自行联系原作者