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

简介:

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

 



   本文转自SammyLiu博客园博客,原文链接:http://www.cnblogs.com/sammyliu/p/4272611.html,如需转载请自行联系原作者



相关文章
|
3月前
|
JSON API 开发工具
【Azure 应用服务】调用Azure REST API来获取 App Service的访问限制信息(Access Restrictions)以及修改
【Azure 应用服务】调用Azure REST API来获取 App Service的访问限制信息(Access Restrictions)以及修改
|
1月前
|
存储 数据挖掘 API
购物平台数据抓取实战指南:从API到深度分析
本指南介绍如何通过API接口抓取淘宝、京东、拼多多等电商平台的数据,涵盖API选择、注册配置、数据抓取与处理、深度分析等内容,帮助企业和开发者挖掘数据价值,支持市场分析和决策制定。
|
1月前
|
安全 API 数据安全/隐私保护
商品详情API接口的优势分析与应用价值
在数字化时代,商品详情API接口为商家和开发者提供了实时更新、高效集成、丰富功能、安全稳定、易于扩展及提升用户体验的解决方案,助力提高运营效率、降低成本并增强市场竞争力。
|
1月前
|
JSON JavaScript API
(API接口系列)商品详情数据封装接口json数据格式分析
在成长的路上,我们都是同行者。这篇关于商品详情API接口的文章,希望能帮助到您。期待与您继续分享更多API接口的知识,请记得关注Anzexi58哦!
|
1月前
|
API 数据安全/隐私保护 开发者
淘宝 API:关键词搜商品列表接口,助力商家按价格销量排序分析数据
此接口用于通过关键词搜索淘宝商品列表。首先需在淘宝开放平台注册并创建应用获取API权限,之后利用应用密钥和访问令牌调用接口。请求参数包括关键词、页码、每页数量、排序方式及价格区间等。返回结果含总商品数量及具体商品详情。使用时需注意签名验证及官方文档更新。
|
3月前
|
存储 JavaScript 前端开发
探索React状态管理:Redux的严格与功能、MobX的简洁与直观、Context API的原生与易用——详细对比及应用案例分析
【8月更文挑战第31天】在React开发中,状态管理对于构建大型应用至关重要。本文将探讨三种主流状态管理方案:Redux、MobX和Context API。Redux采用单一存储模型,提供预测性状态更新;MobX利用装饰器语法,使状态修改更直观;Context API则允许跨组件状态共享,无需第三方库。每种方案各具特色,适用于不同场景,选择合适的工具能让React应用更加高效有序。
86 0
|
3月前
|
安全 API 网络安全
【Azure API 管理】APIM不能连接到 App Service (APIM cannot connect to APP service)
【Azure API 管理】APIM不能连接到 App Service (APIM cannot connect to APP service)
|
3月前
|
存储 Linux API
Linux源码阅读笔记08-进程调度API系统调用案例分析
Linux源码阅读笔记08-进程调度API系统调用案例分析
|
3月前
|
UED 开发工具 iOS开发
Uno Platform大揭秘:如何在你的跨平台应用中,巧妙融入第三方库与服务,一键解锁无限可能,让应用功能飙升,用户体验爆棚!
【8月更文挑战第31天】Uno Platform 让开发者能用同一代码库打造 Windows、iOS、Android、macOS 甚至 Web 的多彩应用。本文介绍如何在 Uno Platform 中集成第三方库和服务,如 Mapbox 或 Google Maps 的 .NET SDK,以增强应用功能并提升用户体验。通过 NuGet 安装所需库,并在 XAML 页面中添加相应控件,即可实现地图等功能。尽管 Uno 平台减少了平台差异,但仍需关注版本兼容性和性能问题,确保应用在多平台上表现一致。掌握正确方法,让跨平台应用更出色。
54 0
|
3月前
|
数据采集 API TensorFlow
简化目标检测流程:深入探讨TensorFlow Object Detection API的高效性与易用性及其与传统方法的比较分析
【8月更文挑战第31天】TensorFlow Object Detection API 是一项强大的工具,集成多种先进算法,支持 SSD、Faster R-CNN 等模型架构,并提供预训练模型,简化目标检测的开发流程。用户只需准备数据集并按要求处理,选择预训练模型进行微调训练即可实现目标检测功能。与传统方法相比,该 API 极大地减少了工作量,提供了从数据预处理到结果评估的一站式解决方案,降低了目标检测的技术门槛,使初学者也能快速搭建高性能系统。未来,我们期待看到更多基于此 API 的创新应用。
37 0
下一篇
无影云桌面