分享

探索 OpenStack 之(9):cinder-api Service 启动过程分析 以及 WSGI / Paste dep...


问题导读
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:

012212173626013.jpg
具体过程描述如下:
  • 服务器为 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 (获取映射结果)。一个简单例子:
  1. # Setup a mapper
  2. from routes import Mapper
  3. map = Mapper()
  4. map.connect(None, "/error/{action}/{id}", controller="error")
  5. map.connect("home", "/", controller="main", action="index")
  6. # Match a URL, returns a dict or None if no match
  7. result = map.match('/error/myapp/4')
  8. # 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:
  1. if __name__ == '__main__':
  2.     CONF(sys.argv[1:], project='cinder',
  3.          version=version.version_string())
  4.     logging.setup("cinder")
  5.     utils.monkey_patch()
  6.     rpc.init(CONF)
  7.     launcher = service.process_launcher()
  8.     server = service.WSGIService('osapi_volume') #初始化WSGIService,load osapi_volume app
  9.     launcher.launch_service(server, workers=server.workers) launcher.wait() #启动该 WSGI service
复制代码
在执行 server = service.WSGIService('osapi_volume') 时,首先会 load app。具体 load 过程下面 2  Paste 加载 osapi_volume 的过程。
  1. class WSGIService(object):
  2. """Provides ability to launch API from a 'paste' configuration."""
  3. def __init__(self, name, loader=None):
  4. self.name = name
  5. self.manager = self._get_manager()
  6. #load APP
  7. self.loader = loader or wsgi.Loader() #paste.deploy.loadwsgi.ConfigLoader
  8. self.app = self.loader.load_app(name)
  9. def load_app(self, name):
  10.         """Return the paste URLMap wrapped WSGI application.
  11.         """
  12.         try:
  13.             return deploy.loadapp("config:%s" % self.config_path, name=name) #从配置文件 paste-api.ini 中加载WSGI application
  14.         except LookupError as err:
  15.             LOG.error(err)
  16.             raise exception.PasteAppNotFound(name=name, path=self.config_path)
  17. ......
  18. 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 方法:

  1. def root_app_factory(loader, global_conf, **local_conf): #local_conf 包括 /,/v1,/v2 三个pair
  2.     if CONF.enable_v1_api:
  3.         LOG.warn(_('The v1 api is deprecated and will be removed after the Juno release. You should set enable_v1_api=false and '
  4.                    'enable_v2_api=true in your cinder.conf file.'))
  5.     else:
  6.         del local_conf['/v1']
  7.     if not CONF.enable_v2_api:
  8.         del local_conf['/v2']
  9.     return paste.urlmap.urlmap_factory(loader, global_conf, **local_conf)
  10. def urlmap_factory(loader, global_conf, **local_conf):
  11.       for path, app_name in local_conf.items():
  12.        path = paste.urlmap.parse_path_expression(path)
  13.         app = loader.get_app(app_name, global_conf=global_conf)
  14.         urlmap[path] = app
  15.       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 方法如下:

  1. def pipeline_factory(loader, global_conf, **local_conf):
  2.     """A paste pipeline replica that keys off of auth_strategy."""
  3.     pipeline = local_conf[CONF.auth_strategy] #读取CONF.auth_strategy,其值默认为 token,再获取 'token' 对应的 pipeline
  4.     pipeline = pipeline.split() # ['request_id', 'faultwrap', 'sizelimit', 'osprofiler', 'authtoken', 'keystonecontext', 'apiv2']
  5.     filters = [loader.get_filter(n) for n in pipeline[:-1]] #依次使用 deploy loader 来 load 各个 filter
  6.     app = loader.get_app(pipeline[-1]) #使用 deploy loader 来 load app 即 apiv2
  7.     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'>]
  8.     for filter in filters:
  9.         app = filter(app) #然后依次调用每个 filter 来 wrapper 该 app
  10. 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的指针)。比如:
  1. Enter Middleware.factory with cls: <class 'cinder.openstack.common.middleware.request_id.RequestIdMiddleware'>
  2. Enter cinder.wsgi.factory with cls: <class 'cinder.api.middleware.fault.FaultWrapper'> factory /usr/lib/python2.7/dist-packages/cinder/wsgi.py:385
  3. Enter cinder.wsgi.factory with cls: <class 'cinder.api.middleware.sizelimit.RequestBodySizeLimiter'> factory /usr/lib/python2.7/dist-packages/cinder/wsgi.py:385
  4. 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)基本操作
  • 基本操作: 即核心资源和扩展资源拥有的最基本的操作,使用 HTTP Method 比如 GET, PUT, POST,DELETE 等方法访问这些资源。
  • 以 volumes 为例,其 Controller 类 VolumeController 定义了对 volumes 的 index,create,delete,show, update等。比如 GET /v2/{tenant-id}/volumes/detail。
  • 对于这类操作,http://developer.openstack.org/api-ref-blockstorage-v2.html 有详细的列表。
(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)


  • 使用:使用 HTTP Post method,在 URL 中使用其 Core resource 的 alias 比如 ’volumes‘,'action' method,在Rquest body 中包含 action 的 alias 和 参数。比如:
    POST /{tenant-id}/volumes/{volume-id}/action
    body: {"os-force_delete": null}

(3)extends

  • extends: 资源扩展使用 @wsgi.extends 装饰了的函数。extends 是对某个 Core Resource 的某个 CURD 方法比如 create, index,show,detail 等的扩展,你在使用标准 HTTP Method 访问 Core resource 时,可以附加 extension 信息,在 response 中你可以得到这些方法的output。尚不清楚实际应用场景。
  • 例如: volumes 的资源扩展 SchedulerHints 的 Controller 类 SchedulerHintsController 定义了 volumes.create 方法的 extends 方法:
    @wsgi.extends
    def create(self, req, body)
  • 以OS-SCH-HNT/SchedulerHints 扩展的 volumes 的 create 方法为例,其使用方法如下:                                                POST http://9.123.245.88:8776/v2/2f07 ... 1113196c04b/volumes

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。
  1. def deserialize(self, meth, content_type, body):
  2.         #比如 body: {"os-force_delete": null}
  3.         meth_deserializers = getattr(meth, 'wsgi_deserializers', {}) #获取该方法的 deserializer
  4.         try:
  5.             mtype = _MEDIA_TYPE_MAP.get(content_type, content_type)
  6.             if mtype in meth_deserializers:
  7.                 deserializer = meth_deserializers[mtype]
  8.             else:
  9.                 deserializer = self.default_deserializers[mtype] #没有定义则使用默认的 deserializer
  10.         except (KeyError, TypeError):
  11.             raise exception.InvalidContentType(content_type=content_type)
  12.         return deserializer().deserialize(body) #对 request body 反序列化,比如 {'body': {u'os-force_delete': None}}
复制代码
  • 对于有输出的操作,在调用该操作后,需要序列化其输出。除了跟 content-type 配对的默认 serializer 外,你还可以自定义序列化方法。代码:
  1. def _process_stack(self, request, action, action_args,content_type, body, accept):
  2. ......  
  3. # Run post-processing extensions
  4.             if resp_obj:
  5.                 _set_request_id_header(request, resp_obj)
  6.                 # Do a preserialize to set up the response object
  7.                 serializers = getattr(meth, 'wsgi_serializers', {}) #获取该方法的 serializer
  8.                 resp_obj._bind_method_serializers(serializers)
  9.                 if hasattr(meth, 'wsgi_code'):
  10.                     resp_obj._default_code = meth.wsgi_code
  11.                 resp_obj.preserialize(accept, self.default_serializers)
  12.                 # Process post-processing extensions
  13.                 response = self.post_process_extensions(post, resp_obj,request, action_args)
  14.             if resp_obj and not response:
  15.                 response = resp_obj.serialize(request, accept, self.default_serializers) #序列化方法的 output 为 response
复制代码
(6)以下表格显示了 Cinde 中部分 Resource extensions 的 actions 和 extends:
1.png 2.png
3.png
4.png



其中,Admin actions 是必须 Administrator 才能操作,以volumes 的 os-force-delete 为例:




URL:http://9.123.245.88:8776/v2/2f07 ... 93a1c74c5336/action
Method:POST
body:{"os-force_delete": null}  

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。
  1. ext_mgr = self.ExtensionManager() #初始化 ext_mgr,用于管理所有的 extensions         
  2.         mapper = ProjectMapper() #生成 mapper
  3.         self.resources = {} #初始化 resources 数组
  4.         self._setup_routes(mapper, ext_mgr) #为核心资源建立 routes
  5.         self._setup_ext_routes(mapper, ext_mgr) #为扩展资源建立 routes
  6.         self._setup_extensions(ext_mgr) #保存资源扩展的方法
  7.         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 来获取映射关系。
以上这些类之间的关系如下:
041240118289358.jpg

从上图可以看出 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。
031043453285790.jpg

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 文件:
  1. class Volume_type_encryption(extensions.ExtensionDescriptor):
  2.     """Encryption support for volume types."""
  3.     def get_resources(self):  #ExtensionManager 遍历 contrib 文件夹下每个文件的时候会尝试调用这个方法。如果这个方法存在,则该类会被认为是个Extension Resource。
  4.         resources = []
  5.         res = extensions.ResourceExtension(
  6.             Volume_type_encryption.alias, #Extension resource alias
  7.             VolumeTypeEncryptionController(),
  8.             parent=dict(member_name='type', collection_name='types'))
  9.         resources.append(res)
  10.         return resources
  11.     def get_controller_extensions(self): #ExtensionManager遍历每个文件的时候会尝试调用本方法。如果该方法存在,则认为该类提供了Resource extensions
  12.         controller = VolumeTypeEncryptionController()
  13.         extension = extensions.ControllerExtension(self, 'types', controller) #这里的‘types'就是被扩展了的 Core Resource 名字
  14.         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 为例描述其具体过程:
041312037817836.jpg
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 目录下的文件都按照不同的用途分别放在不同的文件夹中,小结如下:
041355341402250.jpg





相关内容:

学习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

没找到任何评论,期待你打破沉寂

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条