设为首页 收藏本站
查看: 1910|回复: 0

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

[复制链接]

尚未签到

发表于 2015-4-11 13:39:04 | 显示全部楼层 |阅读模式
  
  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:
DSC0000.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 的方法:


  • Spec文档: Paste Deployment
  • Python paste deploy 的实现代码: https://bitbucket.org/ianb/pastedeploy
  • 这篇 文章 也解释了 OpenStack 使用 Paste deployment 的方法
  简单来说,它提供一个方法 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.iyunv.com/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= 个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= # 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() #[, , , , , ]
    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:
Enter cinder.wsgi.factory with cls:  factory /usr/lib/python2.7/dist-packages/cinder/wsgi.py:385
Enter cinder.wsgi.factory with cls:  factory /usr/lib/python2.7/dist-packages/cinder/wsgi.py:385
Enter cinder.wsgi.factory with cls:  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/ResourceExtension nameFile (/api/contrib 目录下)Controllerwsgi actionswsgi extends
volumesSchedulerHintsscheduler_hints.py

SchedulerHintsController





('create', None)
VolumeTenantAttributevolume_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.pyVolumeReplicationController{'os-reenable-replica': 'reenable', 'os-promote-replica': 'promote'}('show', None), ('detail', None)
VolumeUnmanage   volume_unmange.pyVolumeUnmanageController{'os-unmanage': 'unmanage'}
VolumeActions volume_actions.pyVolumeActionsController{'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)
typesTypesManagetypes_manage.py

VolumeTypesManageController


{'create': '_create', 'delete': '_delete'}


VolumeTypeEncryption


limitsUsedLimitsused_limits.py

UsedLimitsController
backupsAdminActions

BackupAdminController


{'os-reset_status': '_reset_status', 'os-force_delete': '_force_delete'}
snapshotsSnapshotActionssnapshot_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': , '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 来获取映射关系。
  
  以上这些类之间的关系如下:
DSC0001.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。
DSC0002.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 文件:



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 为例描述其具体过程:
DSC0003.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': , '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': , 'os-reserve': , 'os-promote-replica': , ...... 'os-attach': }
  (2)Resource 还有个 wsgi_extensions 数组属性,它为每一个 Resource 的基本方法保存扩展的方法。以 volumes 的 detail 方法为例,它有两个扩展方法:



[, ]
  (3)收到一个 action 后,RoutesMiddleware 根据 URL 找到 Resource 的地址,然后 Resource 从 request body 中取出 action 的 alias,然后查找该 pairs,得到真正的method。
  (4)首先会查找wsgi_extensions,获取该 method 对应的所有扩展方法的Controller 类,分别调用其method; 然后再把 request dispatch 到该方法上得到其输出了。
  wsgi_actions['os-extend']:
  (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 目录下的文件都按照不同的用途分别放在不同的文件夹中,小结如下:
DSC0004.jpg
  
  下一篇文章将分析 cinder-api 处理 HTTP Request 的过程。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-55992-1-1.html 上篇帖子: Openstack Grizzly 发布概览 下篇帖子: OpenStack Swift集群部署流程与简单使用
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表