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:

具体过程描述如下:

  1. 服务器为 Applicaiton 实例化一个 WSGIService 实例,实例化过程中调用 Paste Deployment 来加载/注册各 middleware 和app。
  2. 服务器启动 WSGIService , 包括创建 socket,监听端口,然后等待客户端连接。
  3. 当有请求来时,服务器解析客户端信息放到环境变量 environ 中,并调用绑定的 handler 来处理请求。handler 解析这个 http 请求,将请求信息例如 method,path 等放到 environ 中。wsgi handler还会将一些服务器端信息也放到 environ 中,最后服务器信息,客户端信息,本次请求信息全部都保存到了环境变量environ中
  4. wsgi handler 调用注册的各 app (包括 middleware) 来处理 request,比如 middleware 来进行数据检查、整理、用户验证等, wsgi app 生成 reponse header/status/body 回传给wsgi handler。
  5. response 生成以后和回传之前,一些等待 response 的 被阻塞的 middleware 的方法可以对response 进行进一步的处理,比如添加新的数据等
  6. 最终 handler 通过socket将response信息塞回给客户端

以 cinder-api 为例,其 WSGI 实例初始化和启动 service 的代码如下:

  1. launcher = service.process_launcher()
  2. server = service.WSGIService('osapi_volume') # 使用 Paste delopment 方式 加载 osapi_volume 这个 application,这过程中包括加载各 middleware app
  3. 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 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) 类的子类的实例。

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方法中传入的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 处理过程。

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/2f07ad0f1beb4b629e42e1113196c04b/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。
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:

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}  

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 目录下的文件都按照不同的用途分别放在不同的文件夹中,小结如下:

下一篇文章将分析 cinder-api 处理 HTTP Request 的过程。

探索 OpenStack 之(11):cinder-api Service 启动过程分析 以及 WSGI / Paste deploy / Router 等介绍的更多相关文章

  1. Service启动过程分析

    Service是一种计算型组件,用于在后台执行一系列的计算任务.由于工作在后台,因此用户是无法直接感知到它的存在.Service组件和Activity组件略有不同,Activity组件只有一种运行模式 ...

  2. 探索 OpenStack 之(12):cinder-api Service 处理 HTTP Request 的过程分析

    本文是上一篇 探索 OpenStack 之(11):cinder-api Service 启动过程分析 以及 WSGI / Paste deploy / Router 等介绍> 的后续篇. os ...

  3. 探索 OpenStack 之(9):深入块存储服务Cinder (功能篇)

    继研究了Neutron之后,继续Nova的外围研究之旅.本站是研究块存储服务Cinder. 0.验证环境 环境包括: 1.一个controller节点,运行nova-api, nova-schedul ...

  4. 探索 OpenStack 之(13):研究 Keystone

    Keystone 是 OpenStack Identity Service 的项目名称.本文就试着尽可能深入地研究 Keystone. 1. Keystone 的功能 做为 OpenStack 云系统 ...

  5. Activity启动过程分析

    Android的四大组件中除了BroadCastReceiver以外,其他三种组件都必须在AndroidManifest中注册,对于BroadCastReceiver来说,它既可以在AndroidMa ...

  6. 探索 OpenStack 之(15):oslo.messaging 和 Cinder 中 MessageQueue 消息的发送和接收

    前言:上一篇文章 只是 RabbitMQ 的科普,本文将仔细分析 Cinder 中 RabbitMQ 的各组件的使用.消息的发送和接收等.由于各流程步骤很多,本文只会使用若干流程图来加以阐述,尽量做到 ...

  7. cinder api启动过程源码分析

    1.启动cinder-api服务 当你通过cinder-api命令(如:/usr/bin/cinder-api --config-file /etc/cinder/cinder.conf)启动api服 ...

  8. OpenStack 存储服务 Cinder介绍和控制节点部署(十五)

    Cinder介绍 OpenStack块存储服务(cinder)为虚拟机添加持久的存储,块存储提供一个基础设施为了管理卷,以及和OpenStack计算服务交互,为实例提供卷.此服务也会激活管理卷的快照和 ...

  9. 探索 OpenStack 之(14):OpenStack 中 RabbitMQ 的使用

    本文是 OpenStack 中的 RabbitMQ 使用研究 两部分中的第一部分,将介绍 RabbitMQ 的基本概念,即 RabbitMQ 是什么.第二部分将介绍其在 OpenStack 中的使用. ...

随机推荐

  1. treap树---营业额统计

    台州学院  2924 描述 Tiger最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况.Tiger拿出了公司的账本,账本上记录了公司成立以来每天的营业额 ...

  2. PPP模式下的融资结构优化

    PPPcode{white-space: pre;} pre:not([class]) { background-color: white; }if (window.hljs && d ...

  3. C#读写ini文件操作

    ini文件,是windows操作系统下的配置文件,ini文件是一种按照特点方式排列的文本文件,它的构成分为三部分,结构如下: [Section1] key 1 = value2 key 1 = val ...

  4. emberjs初学记要

    code { margin: 0; padding: 0; white-space: pre; border: none; background: transparent; } code, pre t ...

  5. 简单理解JavaScript闭包

    很多关于JS的书籍例如<JavaScript权威指南>或者<高程>都把闭包解释的晦涩难懂,萌新们是怎么也看不懂啊!不过别怕,今天我就用很简单的方式给大家讲解下到底什么是闭包.这 ...

  6. 利用jQuery的淡入淡出实现轮播器

    基本原理:将所有图片绝对定位在同一位置,透明度设为0,然后通过jQuery的淡入淡出实现图片的切换效果: 但我在使用fadeIn淡入时却无效果,最后只能使用fadeTo实现,求大神指教 HTML: & ...

  7. 简单好用的Toast封装类——EasyToast

    我们用toast时不能设置显示的时间,而且不支持在线程中展示toast,下面我对原始的toast进行了封装,这样我们可以很方便的进行toast的使用了. package com.kale.lib.ut ...

  8. Android 缓存目录 Context.getExternalFilesDir()和Context.getExternalCacheDir()方法

    一.基础知识 应用程序在运行的过程中如果需要向手机上保存数据,一般是把数据保存在SDcard中的.大部分应用是直接在SDCard的根目录下创建一个文件夹,然后把数据保存在该文件夹中.这样当该应用被卸载 ...

  9. iOS打包ipa select a method for export几个选项的意思

    他们的意思分别为:Save for iOS App Store Deployment保存到本地 准备上传App Store 或者在越狱的iOS设备上使用,需要提供发布证书
Save for Ad Ho ...

  10. 基础学习day03---程序结构与控制、函数与数组入门

    一.程序结构   1.顺序结构 2.选择结构 3.循环结构 二.顺序结构 程序至上而下逐行执行,一条语句执行完之后继续执行下一条语句,一直到程序的末尾 三.条件选择结构 选择结构是根据条件的成立与否, ...