问题导读
1、 WSGI Server、Paste deploy、Router先关知识有那些?
2、 启动 Cinder api/scheduler/volume service 的总体过程是怎样的 ?
3、使用 Paste deploy 加载 osapi_volume 的过程是怎样的 ?
4、Cinder 目录下文件的用途是什么?
OpenStack 中的每一个提供 REST API Service 的组件,比如 cinder-api,nova-api 等,其实是一个 WSGI App,其主要功能是接受客户端发来的 HTTP Requst,然后进行用户身份校验和消息分发。代码实现上,主要使用了几种技术:WSGI Server、WSGI Application、Paste deploy 和 Router。本文希望结合 cinder-api 的启动过程,对这些相关技术和 cinder-api 的代码做一个总结。
0. WSGI Server、Paste deploy、Router相关知识
0.1 WSGI Server
参考文档:
PEP 333 - Python Web Server Gateway Interface v1.0
Web服务器网关接口
http://wiki.woodpecker.org.cn/moin/WSGI
简单来说,python中的 WSGI 是 Python 应用程序或框架与 Web 服务器之间的一种接口,它定义了一套接口来实现服务器与应用端的通信规范,它将 web 组件分为三类:
web 服务器(Service):接受客户端发来的 request,并返回 app 产生的 response 发回给客户端
web 应用程序(App): 每个 app 是一个 callable 对象。一个函数, 方法, 类, 或者实现了 __call__ 方法的实例对象都可以用来作为应用程序对象。服务器每次收到 http 客户端发来的请求都会调用相应的应用程序的 __call__ 方法去去处理。
web 中间件(Middleware):某些对象(app)可以在一些应用程序面前是服务器, 而从另一些服务器看来却是应用程序。可参考 http://k0s.org/mozilla/craft/middleware.html 。
(1)WSGI Server唯一的任务就是接收来自 client 的请求,然后将请求传给 application,最后将 application 的response 传递给 client。
(2)在使用 Middleware 的情况下,WSGI的处理模式为 WSGI Server -> (WSGI Middleware)* -> WSGI Application。其实 middleware 本身已是一个 app,只是你需要有一个 App 做为实现主要功能的 Application。Middleware可以:
可以根据目的 URL 将一个请求分发 (routing) 给不同的应用程序对象, 并对 environ 做相应修改。这种 middleware 称为 router。
允许多个应用程序或框架在同一个进程中一起运行。可以依次调用多个 middleware。
通过分析 request,在网络上转发请求和响应, 进行负载均衡和远程处理.
对 response 内容进行后加工, 比如应用 XSL 样式.
(3)在 Server 和 Application 之间,可以使用若干个 Middleware 来处理 request 和 response:
具体过程描述如下:
服务器为 Applicaiton 实例化一个 WSGIService 实例,实例化过程中调用 Paste Deployment 来加载/注册各 middleware 和app。 服务器启动 WSGIService , 包括创建 socket,监听端口,然后等待客户端连接。 当有请求来时,服务器解析客户端信息放到环境变量 environ 中,并调用绑定的 handler 来处理请求。handler 解析这个 http 请求,将请求信息例如 method,path 等放到 environ 中。wsgi handler还会将一些服务器端信息也放到 environ 中,最后服务器信息,客户端信息,本次请求信息全部都保存到了环境变量environ中 wsgi handler 调用注册的各 app (包括 middleware) 来处理 request,比如 middleware 来进行数据检查、整理、用户验证等, wsgi app 生成 reponse header/status/body 回传给wsgi handler。 response 生成以后和回传之前,一些等待 response 的 被阻塞的 middleware 的方法可以对response 进行进一步的处理,比如添加新的数据等 最终 handler 通过socket将response信息塞回给客户端
以 cinder-api 为例,其 WSGI 实例初始化和启动 service 的代码如下:
launcher = service.process_launcher() server = service.WSGIService('osapi_volume') # 使用 Paste delopment 方式 加载 osapi_volume 这个 application,这过程中包括加载各 middleware app launcher.launch_service(server, workers=server.workers) launcher.wait() #真正启动service
0.2 Paste delpoy
Paste deploy 是一种配置 WSGI Service 和 app 的方法:
简单来说,它提供一个方法 loadapp,它可以用来从 config 配置文件或者 Python egg 文件加载 app(包括middleware/filter和app),它只要求 app 给它提供一个入口函数,该函数通过配置文件告诉Paste depoly loader。一个简单的调用 loadapp 的 Python code 的例子:
from paste.deploy import loadappwsgi_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) 类的子类的实例。
0.3 Route
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
1. 启动 Cinder api/scheduler/volume service 的总体过程
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线程。
osapi_volume_listen=0.0.0.0 # IP address on which OpenStack Volume API listens (string # value)
osapi_volume_listen_port=8776 # Port on which OpenStack Volume API listens (integer value)
osapi_volume_workers=<None> # Number of workers for OpenStack Volume API service. The # default is equal to the number of CPUs available. (integer # value)
(3). 启动cinder-scheduler 会启动一个名为 cinder-scheduler 的 Service。与 cinder-api 的WSGI Service 不同的是,它需要创建 RPC 连接,启动消费者线程,然后等待队列消息。它的启动参数主要包括:
report_interval = 10 #(IntOpt) Interval, in seconds, between nodes reporting state to datastore
periodic_interval = 60 #(IntOpt) Interval, in seconds, between running periodic tasks
periodic_fuzzy_delay = 60 #(IntOpt) Range, in seconds, to randomly delay when startingthe periodic task scheduler to reduce stampeding. (Disable by setting to 0)
(4). 启动cinder-volume的过程类似于 cinder-scheduler。
2. 使用 Paste deploy 加载 osapi_volume 的过程
2.1 总体过程
osapi-volume 服务启动的过程中,调用 deploy.loadpp 使用 config 方式从 paste-api.conf 文件来 load 名为osapi_volume 的应用,其入口在文件的 [composite:osapi_volume]部分:
[composite:osapi_volume] #osapi_volume 即 deploy.loadapp方法中传入的nameuse = 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_factorynoauth = request_id faultwrap sizelimit osprofiler noauth apiv2keystone = request_id faultwrap sizelimit osprofiler authtoken keystonecontext apiv2keystone_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 处理过程。
2.2 Cinder 中的资源、Controller 操作及管理
在进入具体的 load apiv2 application 过程之前,我们先来了解了解下 Cinder 里面的资源管理。
2.2.1 Cinder 的资源(Resource)类型
OpenStack 定义了两种类型的资源:
Core resource (Resource): 核心资源。核心资源的定义文件在 /cinder/api/v2/ 目录下面。Cinder 的核心资源包括:volumes,types,snapshots,limits等。 Extension resource: 扩展资源也是资源,使用同核心资源,只是它们的地位稍低。在 ./cinder/api/contrib/ 目录下面有很多文件,这些文件定义的都是扩展资源,例如 quotas.py 等。扩展资源又分为两种情况:
一种扩展资源本身也是一种资源,只是没那么核心,比如 os-quota-sets。对扩展资源的访问方法同核心资源,比如 PUT /v2/2f07ad0f1beb4b629e42e1113196c04b/os-quota-sets/2f07ad0f1beb4b629e42e1113196c04b 另一种扩展资源是对核心资源的扩展(Resource extension),包括对 action 的扩展和基本操作的扩展,现在的 Cinder 中只有对 Resource 基本操作的扩展,例如 SchedulerHints 是对 volumes 提供了扩展方法。一些扩展资源同时具备这两种功能。
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。
2.2.2 Cinder 的对资源(Resource)的操作
(1)基本操作
(2)action
Action: 资源扩展拥有的使用 @wsgi.action(alias) 装饰的方法,这些方法是Core Resource 的 CRUD 基本操作不能满足的对资源的操作。它们本身是看做 Core resource 的访问方法,只是访问方法和 CRUD 的访问方法不同。 比如: volumes 的扩展资源的 Controller 类 VolumeAdminController 定义的 os-migrate_volume_completion action:
@wsgi.action('os-migrate_volume_completion') #该方法的 alias 是 os-migrate_volume_completion
def _migrate_volume_completion(self, req, id, body)
(3)extends
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"}
Cinder 在接收到核心资源的 CRUD 访问 Request 时,在调用其基本功能的前面或者后面(根据extend 方法的具体实现决定是前面还是后面),它会找到该核心资源的所有扩展了该方法的所有资源扩展,再分别调用这些资源扩展中的方法,这些方法的 output 会被插入到 volumes 的 create 方法产生的 response 内。
(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 方法
对于有输入的操作,在调用其方法前需要把 HTTP Request body 反序列化再传给该方法。如果不使用默认的 deserializer,你可以指定你实现的特定 deserializer。以 os-force_delete 为例,它没定义特定的 deserializer,所以 Cinder 会根据其 Request content-type 选择默认的 deserializer。
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}} 复制代码
对于有输出的操作,在调用该操作后,需要序列化其输出。除了跟 content-type 配对的默认 serializer 外,你还可以自定义序列化方法。代码:
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:
其中,Admin actions 是必须 Administrator 才能操作,以volumes 的 os-force-delete 为例:
2.2.3 管理 Resource 几个类
(1)APIRouter
APIRouter 是 Cinder 中的核心类之一,它负责分发 HTTP Request 到其管理的某个 Resource:
它的几个重要属性
属性 resources 是个数组,用来保存所有的 Resource 的 Controller 类的instance;每个Resource 拥有一个该数组的数组项,比如 self.resources['versions'] = versions.create_resource() 会为 versions 核心资源创建一个 Resource 并保存到 resources 中。 属性 mapper 用来保存保存所有 resource, extension resource,resource extension 的 routes 供 RoutesMiddleware 使用。它其实是一张路由表。 每个表项表示一个 URL 和 controller 以及 action 的映射关系,每个 controller 就是 Resource 的一个实例。比如:{'action': u'detail', 'controller': <cinder.api.openstack.wsgi.Resource object at 0x7fa137be8950>, 'project_id': u'fa2046aaead44a698de8268f94759fc1'} 属性 routes 是 routes.middleware.RoutesMiddleware 的实例,它其实是一个 WSGI app,它使用 mapper 和 _dispatch_进行初始化。功能是根据 URL 得到 controller 和它的 action。
在 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_ 方法,实现消息的分发。
(2) Controller
了解上面的各种资源之后,我们还需要了解 Controller,所谓的 Controller 实际上就是代表对该资源的操作集合,每个 Resource 都有一个相应的 Controller 类,其基类在 /cinder/api/v2/wsgi.py 中定义,在每个资源对应的 py 文件中定义对应的资源的Controller 类。
以 Volumes 为例,其 Controller 子类 VolumeController 在 /api/v2/volumes.py 中定义 class VolumeController(wsgi.Controller)。它包含上面描述的对 volumes 的基本操作操作。
(3)ExtensionManager
既然存在这么多的资源,Cinder 需要使用一个集中的方式对他们进行管理。OpenStack 使用 ExensionManager 对这些扩展资源进行统一的管理。此类提供如下方法:
def __init__(self) def register(self, ext) #注册一个extension def get_resources(self) #获取Extension resource def get_controller_extensions(self) #获取Resource extension def _check_extension(self, extension) #检查指定的 extension def load_extension(self, ext_factory) #加载所有的extensions
cinder.conf 的 osapi_volume_extension 配置项 (其默认值是 'cinder.api.contrib.standard_extensions')告诉 ExtensionManager 去哪里找扩展的类文件,默认的路径是 /cinder/api/contrib 目录下的 py 文件。
(4) ProjectMapper
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 去做消息分发。
2.3 Cinder REST API Rotues 建立过程
该过程为 Cinder 各 Resource 建立 Routes。
2.3.1 load extension
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 为例描述其具体过程:
2.3.2 load routes
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 的规则:
如果 URL 是 /volumes/{volume ID} 并且 HTTP method 是 POST,那么该 URL 会被映射到 action 方法,该 action 会被转化为 VolumeController的具体方法。 如果 URL 是 /volumes 并且HTTP Method 是 GET,那么映射到 detail 方法,其对应的是 VolumeConroller 的 detail 方法。
注意这里 Core resource 的 mapper 的处理单个Resource 的 action (member={'action': 'POST'})和处理Resource集合的 action (collection={'detail': 'GET'})都是hard coded 的。 所有的映射规则都会被保存到 APIRouter 类的 mapper 属性中。
2.3.3 load extension routes
建立所有 Extension resource 的 routes。
方法 _setup_ext_routes 在类 cinder.api.v2.router.APIRouter 中实现:
首先调用 ExtensionManager.get_resources 获取所有的 extension resource (它同样会在 /api/contrib 目录下所有文件中进行遍历,如果一个类实现了 get_controller_extensions 方法,则加载其 extension resource)
为每个 extension resource 对应的 Controller 类的每个 Member 和 Collection 方法建立映射规则 (mapper.resource(resource.collection, resource.collection, **kargs)),该规则同样保存在 APIRouter 的 mapper 属性中。以 os-snapshot-actions 为例,其 mapper 为:
mapper.resource('snapshot', 'snapshots', {'member': {}, 'controller': <cinder.api.openstack.wsgi.Resource object at 0x7f854ab630d0>, 'collection': {'detail': 'GET'}})
2.3.4 Setup (Controller) extensions
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。
2.3.5 调用 RoutesMiddleware 的 __init__ 方法
将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 了。
3. Cinder 目录下文件的用途
Cinder 目录下的文件都按照不同的用途分别放在不同的文件夹中,小结如下:
相关内容:
学习OpenStack之 (1):安装devstack
学习OpenStack之 (2):Cinder LVM 配置
学习OpenStack之 (3):Devstack Screen 使用技巧
学习OpenStack之(4):在Mac上部署Juno版本OpenStack 四节点环境
学习OpenStack之(5):Neutron 深入学习之 OVS + GRE 之 Compute node 篇
探索 OpenStack 之(6):Neutron 深入探索之 OVS + GRE 之 完整网络流程 篇
探索 OpenStack 之(7):深入块存储服务Cinder (功能篇)
探索 OpenStack 之(8):深入镜像服务Glance
探索 OpenStack 之(9):cinder-api Service 启动过程分析 以及 WSGI / Paste dep
探索 OpenStack 之(10):cinder-api Service 处理 HTTP Request 的过程分析
探索 OpenStack 之(11):研究 Keystone