cinder-api启动过程学习

cinder-api启动过程学习

Openstack中的xxx-api都是WSGI service,负责接收、分析、处理HTTP request,并返回response。其中最重要的两个Python package是paste.delopy和routes。前者负责查找、配置、启动WSGI app,后者负责把RESTful的HTTP requests dispatch到相应的处理方法上。

paste.deploy官网:http://pythonpaste.org/deploy/
routes官网:http://routes.readthedocs.org/en/latest/

下面是cinder-api(juno)启动过程的学习,难免有不对的地方,请联系我哦:-)

api-paste.ini

cinder/etc/cinder/api-paste.ini是WSGI app的配置文件。

安装cinder之后会copy到/etc/cinder/

[root@all ~]# ll /etc/cinder/
total 92
-rw-------. 1 cinder cinder  2136 Mar  2 10:46 api-paste.ini
-rw-------. 1 cinder cinder 79250 Mar  2 10:46 cinder.conf
-rw-r-----. 1 root   cinder  3200 Dec  5 13:00 policy.json
-rw-r-----. 1 root   cinder   942 Dec  5 13:00 rootwrap.conf
drwxr-xr-x. 2 cinder root       6 Jan 18 07:35 volumes
入口
[composite:osapi_volume]
use = call:cinder.api:root_app_factory
/: apiversions
/v1: openstack_volume_api_v1
/v2: openstack_volume_api_v2
  • composite表示把HTTP request dispatch到一个或多个app上
  • use表示用什么方法来dispatch
  • 其他的KEY=VALUE都是app

找到cinder.api:root_app_factory

#cinder/api/__init__.py
def root_app_factory(loader, global_conf, **local_conf):
    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)

从注释看主要用openstack_volume_api_v2

openstack_volume_api_v2
[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

也是composite类型。定义3个app:noauthkeystone, keystone_nolimit

找到cinder.api.middleware.auth:pipeline_factory

# cinder/api/middleware/auth.py
def pipeline_factory(loader, global_conf, **local_conf):
    """A paste pipeline replica that keys off of auth_strategy."""
    #debug on all_in_one 2015/3/14
    #(Pdb) global_conf
    #{'__file__': '/etc/cinder/api-paste.ini', 'here': '/etc/cinder'}
    #(Pdb) local_conf
    #{'keystone': 'request_id faultwrap sizelimit osprofiler authtoken keystonecontext apiv2', 'noauth': 'request_id faultwrap sizelimit osprofiler noauth apiv2', 'keystone_nolimit': 'request_id faultwrap sizelimit osprofiler authtoken keystonecontext apiv2'}
    #(Pdb) CONF.auth_strategy
    #'keystone'
    #(Pdb) CONF.api_rate_limit
    #True
    pipeline = local_conf[CONF.auth_strategy]
    if not CONF.api_rate_limit:
        limit_name = CONF.auth_strategy + '_nolimit'
        pipeline = local_conf.get(limit_name, pipeline)
    pipeline = pipeline.split()
    #依次load各个filter
    filters = [loader.get_filter(n) for n in pipeline[:-1]]
    #(Pdb) pipeline
    #['request_id', 'faultwrap', 'sizelimit', 'osprofiler', 'authtoken', 'keystonecontext', 'apiv2']
    #(Pdb) pipeline[:-1]
    #['request_id', 'faultwrap', 'sizelimit', 'osprofiler', 'authtoken', 'keystonecontext']
    #load app
    app = loader.get_app(pipeline[-1])
    filters.reverse()
    #(Pdb) filters
    #[<function _factory at 0x3a2f9b0>, <function auth_filter at 0x3a2f938>, <function filter_ at 0x3548050>, <function _factory at 0x3541cf8>, <function _factory at 0x3541aa0>, <class 'cinder.openstack.common.middleware.request_id.RequestIdMiddleware'>]
    #依次用filter来包装app
    for filter in filters:
        app = filter(app)
    return app
  1. 配置文件中的KEY=VALUE都放在了local_conf这个dict中
  2. 首先根据cinder.conf中的auth_strategy参数来决定走哪个app
  3. keystone = aaa bbb ccc,前面的都是filter,最后一个是app。一个HTTP request过来之后,先由filters过滤一遍,最后由app处理。
Filter

以下为例,看filter是怎么包装app的:
keystone = request_id faultwrap sizelimit osprofiler authtoken keystonecontext apiv2

1.request_id

[filter:request_id]
paste.filter_factory = cinder.openstack.common.middleware.request_id:RequestIdMiddleware.factory

找到RequestIdMiddleware.factory:

#cinder/openstack/common/middleware/request_id.py
from cinder.openstack.common.middleware import base

ENV_REQUEST_ID = 'openstack.request_id'
HTTP_RESP_HEADER_REQUEST_ID = 'x-openstack-request-id'

class RequestIdMiddleware(base.Middleware):

    @webob.dec.wsgify
    def __call__(self, req):
        req_id = context.generate_request_id()
        req.environ[ENV_REQUEST_ID] = req_id
        response = req.get_response(self.application)
        if HTTP_RESP_HEADER_REQUEST_ID not in response.headers:
            response.headers.add(HTTP_RESP_HEADER_REQUEST_ID, req_id)
        return response

其父类为:

# cinder/openstack/common/middleware/base.py
class Middleware(object):
    """Base WSGI middleware wrapper.

    These classes require an application to be initialized that will be called
    next.  By default the middleware will simply call its wrapped app, or you
    can override __call__ to customize its behavior.
    """

    @classmethod
    def factory(cls, global_conf, **local_conf):
        """Factory method for paste.deploy."""
        return cls

    def __init__(self, application):
        self.application = application

factory方法就是返回cls,也就调用了__init__,也就是return了application。

顺便看一下RequestIdMiddleware.__call__。当这个filer被调用的时候,会调用的这个__call__,它在response的header中加了一个x-openstack-request-id

2.faultwrap

[filter:faultwrap]
paste.filter_factory = cinder.api.middleware.fault:FaultWrapper.factory
# fault.py
from cinder import wsgi as base_wsgi

class FaultWrapper(base_wsgi.Middleware):
    """Calls down the middleware stack, making exceptions into faults."""

其父类:

# cinder/wsgi.py
class Middleware(Application):
    """Base WSGI middleware.
    """

    @classmethod
    def factory(cls, global_config, **local_config):
        """Used for paste app factories in paste.deploy config files.

        Any local configuration (that is, values under the [filter:APPNAME]
        section of the paste config) will be passed into the `__init__` method
        as kwargs.

        A hypothetical configuration would look like:

            [filter:analytics]
            redis_host = 127.0.0.1
            paste.filter_factory = cinder.api.analytics:Analytics.factory

        which would result in a call to the `Analytics` class as

            import cinder.api.analytics
            analytics.Analytics(app_from_paste, redis_host='127.0.0.1')

        You could of course re-implement the `factory` method in subclasses,
        but using the kwarg passing it shouldn't be necessary.

        """
        def _factory(app):
            return cls(app, **local_config)
        return _factory

    def __init__(self, application):
        self.application = application

request_id一样,factory也是调用了__init__

其实这个faultwrap从名字就能看出,它会在出错的时候做一下修饰,增加一些详细信息。

3.sizelimit
faultwrap完全一样,也调用了cinder/wsgi.py中的Middleware:factory

4.osprofiler
这个有点不一样,它的配置文件里有参数:

[filter:osprofiler]
paste.filter_factory = osprofiler.web:WsgiMiddleware.factory
hmac_keys = SECRET_KEY
enabled = yes

osprofiler不是Openstack的原生的package,是用来增强调试信息的。

# /usr/lib/python2.7/site-packages/osprofiler/web.py
class WsgiMiddleware(object):
    """WSGI Middleware that enables tracing for an application."""

    def __init__(self, application, hmac_keys, enabled=False):
        """Initialize middleware with api-paste.ini arguments.
        """
        self.application = application
        self.name = "wsgi"
        self.enabled = enabled
        self.hmac_keys = utils.split(hmac_keys or "")

    @classmethod
    def factory(cls, global_conf, **local_conf):
        def filter_(app):
            return cls(app, **local_conf)
        return filter_

4.authtoken

[filter:authtoken]
paste.filter_factory = keystonemiddleware.auth_token:filter_factory

keystonemiddleware:

This package contains middleware modules designed to provide authentication and authorization features to web services other than Keystone https://github.com/openstack/keystone. The most prominent module is keystonemiddleware.auth_token. This package does not expose any CLI or Python API features.

5.keystonecontext

[filter:keystonecontext]
paste.filter_factory = cinder.api.middleware.auth:CinderKeystoneContext.factory

faultwrapper一样,都调用了cinder/wsgi.py中的Middleware:factory。只是它干的活高级一点:

class CinderKeystoneContext(base_wsgi.Middleware):
    """Make a request context from keystone headers."""

    @webob.dec.wsgify(RequestClass=base_wsgi.Request)
    def __call__(self, req):
        user_id = req.headers.get('X_USER')
        user_id = req.headers.get('X_USER_ID', user_id)
        if user_id is None:
            LOG.debug("Neither X_USER_ID nor X_USER found in request")
            return webob.exc.HTTPUnauthorized()
        # get the roles
        roles = [r.strip() for r in req.headers.get('X_ROLE', '').split(',')]
        if 'X_TENANT_ID' in req.headers:
        ......
        # Get the auth token
        # Build a context, including the auth_token...

到这里,osapi_volume 的 loading 就结束了。在此过程中,各个 filter middleware 以及 WSGI Application 都被加载/注册,并被初始化。WSGI Server 在被启动后,开始等待 HTTP request,然后进入HTTP request 处理过程。

App

配置文件:

[app:apiv2]
paste.app_factory = cinder.api.v2.router:APIRouter.factory
# cinder/api/v2/router.py
class APIRouter(cinder.api.openstack.APIRouter):
    """Routes requests on the API to the appropriate controller and method."""
    ExtensionManager = extensions.ExtensionManager

    def _setup_routes(self, mapper, ext_mgr):
        self.resources['versions'] = versions.create_resource()
        mapper.connect("versions", "/",
                       controller=self.resources['versions'],
                       action='show')

        mapper.redirect("", "/")
    ......

没有factory方法,查看其父类:

# cinder/api/openstack/__init__.py
class APIRouter(base_wsgi.Router):
    """Routes requests on the API to the appropriate controller and method."""
    ExtensionManager = None  # override in subclasses

    @classmethod
    def factory(cls, global_config, **local_config):
        """Simple paste factory, :class:`cinder.wsgi.Router` doesn't have."""
        return cls()

    def __init__(self, ext_mgr=None):
    ...
    def _setup_ext_routes(self, mapper, ext_mgr):
    ...
    def _setup_extensions(self, ext_mgr):
    ...
    def _setup_routes(self, mapper, ext_mgr):
        raise NotImplementedError

factory实际上调用了__init__方法,这个很重要:

# cinder/api/openstack/__init__.py
def __init__(self, ext_mgr=None):
    if ext_mgr is None:
        if self.ExtensionManager:
            ext_mgr = self.ExtensionManager()
        else:
            raise Exception(_("Must specify an ExtensionManager class"))

    mapper = ProjectMapper()
    self.resources = {}
    self._setup_routes(mapper, ext_mgr)
    self._setup_ext_routes(mapper, ext_mgr)
    self._setup_extensions(ext_mgr)
    super(APIRouter, self).__init__(mapper)

干了几件事:
1. 实例化ExtensionManager(实际由子类实例化,作用是加载extension资源)
2. 创建mapper
3. _setup_routes:为core资源创建routes
4. _setup_ext_routes:为extension资源创建routes
5. _setup_extensions:保存extension资源的方法

下面一个一个看:

ExtensionManager

从RESTful的角度来讲,一切都是围绕资源的。在Openstack中,包括两种资源:
1. 核心资源(core resource),存放在cinder/api/v2/
2. 扩展资源(extension resource), 存放在cinder/api/contrib/
扩展资源又分为两种:
1. 一种扩展资源本身也是一种资源,只是没那么核心,比如 os-quota-sets。对扩展资源的访问方法同核心资源,比如 PUT /v2/2f07ad0f1beb4b629e42e1113196c04b/os-quota-sets/2f07ad0f1beb4b629e42e1113196c04b
2. 另一种扩展资源是对核心资源的扩展(Resource extension),包括对 action 的扩展和基本操作的扩展,例如 SchedulerHints 是对 volumes 提供了扩展方法。一些扩展资源同时具备这两种功能。

ExtensionManager是用来管理扩展资源的。

# cinder/api/extensions.py
class ExtensionManager(object):
    """Load extensions from the configured extension path.
    """

    def __init__(self):
        LOG.info(_('Initializing extension manager.'))

        self.cls_list = CONF.osapi_volume_extension
        self.extensions = {}
        self._load_extensions()

    def is_loaded(self, alias):
        return alias in self.extensions

    def register(self, ext):
        ...
    def get_resources(self):
        """Returns a list of ResourceExtension objects."""

    def get_controller_extensions(self):
        """Returns a list of ControllerExtension objects."""

    def _check_extension(self, extension):
        """Checks for required methods in extension objects."""

    def load_extension(self, ext_factory):
        """Execute an extension factory.
        """

    def _load_extensions(self):
        """Load extensions specified on the command line."""    

默认情况下,ExtensionManager会到 cinder/api/contrib/ 目录下的 py 文件中查找扩展资源。以下是_load_extensions log:

(Pdb) n
> /usr/lib/python2.7/site-packages/cinder/api/extensions.py(189)__init__()
-> self._load_extensions()
(Pdb) n
2015-03-14 12:53:23.372 26008 INFO cinder.api.extensions [-] Loaded extension: os-admin-actions
2015-03-14 12:53:23.398 26008 INFO cinder.api.extensions [-] Loaded extension: os-availability-zone
2015-03-14 12:53:23.421 26008 INFO cinder.api.extensions [-] Loaded extension: backups
2015-03-14 12:53:23.448 26008 INFO cinder.api.extensions [-] Loaded extension: cgsnapshots
2015-03-14 12:53:23.475 26008 INFO cinder.api.extensions [-] Loaded extension: consistencygroups
2015-03-14 12:53:23.481 26008 INFO cinder.api.extensions [-] Loaded extension: os-extended-services
2015-03-14 12:53:23.497 26008 INFO cinder.api.extensions [-] Loaded extension: os-extended-snapshot-attributes
2015-03-14 12:53:23.513 26008 INFO cinder.api.extensions [-] Loaded extension: os-hosts
2015-03-14 12:53:23.517 26008 INFO cinder.api.extensions [-] Loaded extension: os-image-create
2015-03-14 12:53:23.524 26008 INFO cinder.api.extensions [-] Loaded extension: qos-specs
2015-03-14 12:53:23.537 26008 INFO cinder.api.extensions [-] Loaded extension: os-quota-class-sets
2015-03-14 12:53:24.759 26008 INFO cinder.api.extensions [-] Loaded extension: os-quota-sets
2015-03-14 12:53:24.766 26008 INFO cinder.api.extensions [-] Loaded extension: OS-SCH-HNT
2015-03-14 12:53:24.770 26008 INFO cinder.api.extensions [-] Loaded extension: scheduler-stats
2015-03-14 12:53:24.774 26008 INFO cinder.api.extensions [-] Loaded extension: os-services
2015-03-14 12:53:24.778 26008 INFO cinder.api.extensions [-] Loaded extension: os-snapshot-actions
2015-03-14 12:53:24.782 26008 INFO cinder.api.extensions [-] Loaded extension: os-types-extra-specs
2015-03-14 12:53:24.788 26008 INFO cinder.api.extensions [-] Loaded extension: os-types-manage
2015-03-14 12:53:24.791 26008 INFO cinder.api.extensions [-] Loaded extension: os-used-limits
2015-03-14 12:53:24.796 26008 INFO cinder.api.extensions [-] Loaded extension: os-volume-actions
2015-03-14 12:53:24.804 26008 INFO cinder.api.extensions [-] Loaded extension: os-volume-encryption-metadata
2015-03-14 12:53:24.808 26008 INFO cinder.api.extensions [-] Loaded extension: os-vol-host-attr
2015-03-14 12:53:24.812 26008 INFO cinder.api.extensions [-] Loaded extension: os-vol-image-meta
2015-03-14 12:53:24.816 26008 INFO cinder.api.extensions [-] Loaded extension: os-volume-manage
2015-03-14 12:53:24.820 26008 INFO cinder.api.extensions [-] Loaded extension: os-vol-mig-status-attr
2015-03-14 12:53:24.827 26008 INFO cinder.api.extensions [-] Loaded extension: os-volume-replication
2015-03-14 12:53:24.830 26008 INFO cinder.api.extensions [-] Loaded extension: os-vol-tenant-attr
2015-03-14 12:53:24.851 26008 INFO cinder.api.extensions [-] Loaded extension: os-volume-transfer
2015-03-14 12:53:24.855 26008 INFO cinder.api.extensions [-] Loaded extension: encryption
2015-03-14 12:53:24.859 26008 INFO cinder.api.extensions [-] Loaded extension: os-volume-unmanage
  • 每一个py文件代表一个资源
  • 这里的资源可以是“名词”,如hosts.pyquotas.py;也可以是“动词”,如admin_actions.pyvolume_transfer.py
创建mapper
# cinder/api/openstack/__init__.py
class APIMapper(routes.Mapper):
    def routematch(self, url=None, environ=None):
        if url is "":
            result = self._match("", environ)
            return result[0], result[1]
        return routes.Mapper.routematch(self, url, environ)

    def connect(self, *args, **kwargs):
        # NOTE(inhye): Default the format part of a route to only accept json
        #             and xml so it doesn't eat all characters after a '.'
        #             in the url.
        kwargs.setdefault('requirements', {})
        if not kwargs['requirements'].get('format'):
            kwargs['requirements']['format'] = 'json|xml'
        return routes.Mapper.connect(self, *args, **kwargs)


class ProjectMapper(APIMapper):
    def resource(self, member_name, collection_name, **kwargs):
        if 'parent_resource' not in kwargs:
            kwargs['path_prefix'] = '{project_id}/'
        else:
            parent_resource = kwargs['parent_resource']
            p_collection = parent_resource['collection_name']
            p_member = parent_resource['member_name']
            kwargs['path_prefix'] = '{project_id}/%s/:%s_id' % (p_collection,
                                                                p_member)
        routes.Mapper.resource(self,
                               member_name,
                               collection_name,
                               **kwargs)

mapper = ProjectMapper()

ProjectMapper.resource()调用了routes.Mapper.resource()
APIMapper.routematch()调用了routes.Mapper.routematch()
APIMapper.connect()调用了routes.Mapper.connect()

_setup_routes

为core资源创建routes

# cinder/api/v2/router.py
from cinder.api.v2 import limits
from cinder.api.v2 import snapshot_metadata
from cinder.api.v2 import snapshots
from cinder.api.v2 import types
from cinder.api.v2 import volume_metadata
from cinder.api.v2 import volumes
from cinder.api import versions

def _setup_routes(self, mapper, ext_mgr):
        self.resources['versions'] = versions.create_resource()
        mapper.connect("versions", "/",
                       controller=self.resources['versions'],
                       action='show')

        mapper.redirect("", "/")

        self.resources['volumes'] = volumes.create_resource(ext_mgr)
        mapper.resource("volume", "volumes",
                        controller=self.resources['volumes'],
                        collection={'detail': 'GET'},
                        member={'action': 'POST'})

        self.resources['types'] = types.create_resource()
        mapper.resource("type", "types",
                        controller=self.resources['types'])
        ...
        ...
  1. 和extension资源不一样,core资源还没创建,所以建立routes之前需要创建一下。
  2. 创建完成之后,调用mapper.resource()建立URL和controller的映射关系。
  3. controller实际上定义了对资源的操作

看几个资源的创建:
1. versions
“`pthon
#cinder/api/versions.py
from cinder.api.openstack import wsgi

class VolumeVersionV1(object):
    @wsgi.serializers(xml=VersionTemplate,
                      atom=VersionAtomSerializer)
    def show(self, req):
        builder = views_versions.get_view_builder(req)
        return builder.build_version(_KNOWN_VERSIONS['v1.0'])

    def create_resource():
        return wsgi.Resource(VolumeVersionV1())
```
注意到这个versions资源实际上是一个`wsgi.Resource`实例
  1. volume

    
    # cinder/api/v2/volumes.py
    
    class VolumeController(wsgi.Controller):
        """The Volumes API controller for the OpenStack API."""
    
        _view_builder_class = volume_views.ViewBuilder
    
        def __init__(self, ext_mgr):
            self.volume_api = cinder_volume.API()
            self.consistencygroup_api = consistencygroupAPI.API()
            self.ext_mgr = ext_mgr
            super(VolumeController, self).__init__()
    
    def create_resource(ext_mgr):
        return wsgi.Resource(VolumeController(ext_mgr))

    这个资源也是一个wsgi.Resource实例
    VolumeController定义了一些操作(action):showdeleteindexdetail

  2. snapshots

    
    # cinder/api/v2/snapshots.py
    
    class SnapshotsController(wsgi.Controller):
        """The Snapshots API controller for the OpenStack API."""
    
        def __init__(self, ext_mgr=None):
            self.volume_api = volume.API()
            self.ext_mgr = ext_mgr
            super(SnapshotsController, self).__init__()
    
    def create_resource(ext_mgr):
        return wsgi.Resource(SnapshotsController(ext_mgr))

    SnapshotsController定义了一些action:showindexdeletedetailcreateupdate

所以:
1. 所有resource都是wsgi.Resource实例,因此能处理HTTP request
2. xxxController定义对resource的操作,如showindexdeletedetailcreateupdate

Openstack中对资源的操作大致上也可以分为三类:(参考一参考二
- CRUD:最基本的操作,对资源的index,create,delete,show,update。
- action:基本操作不可能满足所有对资源的操作,若要增加,则使用@wsgi.action装饰
- extends:使用来@wsgi.extends装饰了的函数,如果扩展资源扩展某个核心资源,扩展资源增加的函数就会添加这个装饰

_setup_ext_routes

为extension资源创建routes

#cinder/api/openstack/__init__.py
def _setup_ext_routes(self, mapper, ext_mgr):
    for resource in ext_mgr.get_resources():
        LOG.debug('Extended resource: %s',
                  resource.collection)

        wsgi_resource = wsgi.Resource(resource.controller)
        self.resources[resource.collection] = wsgi_resource
        kargs = dict(
            controller=wsgi_resource,
            collection=resource.collection_actions,
            member=resource.member_actions)

        if resource.parent:
            kargs['parent_resource'] = resource.parent

        #print '========'
        #print 'collection: %r' % resource.collection
        #print 'kargs: %r' % kargs
        mapper.resource(resource.collection, resource.collection, **kargs)

        if resource.custom_routes_fn:
            resource.custom_routes_fn(mapper, wsgi_resource)

以下是print的log:

========
collection: 'extensions'
kargs: {'member': {}, 'controller': <cinder.api.openstack.wsgi.Resource object at 0x4d08410>, 'collection': {}}
========
collection: 'os-hosts'
kargs: {'member': {'startup': 'GET', 'reboot': 'GET', 'shutdown': 'GET'}, 'controller': <cinder.api.openstack.wsgi.Resource object at 0x4d08990>, 'collection': {'update': 'PUT'}}
========
collection: 'os-quota-sets'
kargs: {'member': {'defaults': 'GET'}, 'controller': <cinder.api.openstack.wsgi.Resource object at 0x4d13210>, 'collection': {}}
========
collection: 'encryption'
kargs: {'member': {}, 'parent_resource': {'collection_name': 'volumes', 'member_name': 'volume'}, 'controller': <cinder.api.openstack.wsgi.Resource object at 0x4d13790>, 'collection': {}}
========
collection: 'backups'
kargs: {'member': {'action': 'POST', 'restore': 'POST', 'export_record': 'GET'}, 'controller': <cinder.api.openstack.wsgi.Resource object at 0x4d13c50>, 'collection': {'import_record': 'POST', 'detail': 'GET'}}
========
collection: 'consistencygroups'
kargs: {'member': {'delete': 'POST'}, 'controller': <cinder.api.openstack.wsgi.Resource object at 0x4eb8690>, 'collection': {'detail': 'GET'}}
========
collection: 'encryption'
kargs: {'member': {}, 'parent_resource': {'collection_name': 'types', 'member_name': 'type'}, 'controller': <cinder.api.openstack.wsgi.Resource object at 0x4eb8dd0>, 'collection': {}}
========
collection: 'os-availability-zone'
kargs: {'member': {}, 'controller': <cinder.api.openstack.wsgi.Resource object at 0x4eb8e90>, 'collection': {}}
========
collection: 'cgsnapshots'
kargs: {'member': {}, 'controller': <cinder.api.openstack.wsgi.Resource object at 0x4ec8a10>, 'collection': {'detail': 'GET'}}
========
collection: 'extra_specs'
kargs: {'member': {}, 'parent_resource': {'collection_name': 'types', 'member_name': 'type'}, 'controller': <cinder.api.openstack.wsgi.Resource object at 0x4ed90d0>, 'collection': {}}
========
collection: 'os-volume-manage'
kargs: {'member': {}, 'controller': <cinder.api.openstack.wsgi.Resource object at 0x4ed9190>, 'collection': {}}
========
collection: 'qos-specs'
kargs: {'member': {'associations': 'GET', 'disassociate_all': 'GET', 'delete_keys': 'PUT', 'disassociate': 'GET', 'associate': 'GET'}  'controller': <cinder.api.openstack.wsgi.Resource object at 0x4ed9ad0>, 'collection': {}}
▽=======
collection: 'os-quota-class-sets'
kargs: {'member': {}, 'controller': <cinder.api.openstack.wsgi.Resource object at 0x4ee93d0>, 'collection': {}}
========
collection: 'os-volume-transfer'
kargs: {'member': {'accept': 'POST'}, 'controller': <cinder.api.openstack.wsgi.Resource object at 0x4ee9b50>, 'collection': {'detail': 'GET'}}
========
collection: 'os-services'
kargs: {'member': {}, 'controller': <cinder.api.openstack.wsgi.Resource object at 0x4fed550>, 'collection': {}}
========
collection: 'scheduler-stats'
kargs: {'member': {}, 'controller': <cinder.api.openstack.wsgi.Resource object at 0x4fedb10>, 'collection': {'get_pools': 'GET'}}

关于collection的用法,可以参考这里

collection

Additional URLs to allow for the collection. Example:

map.resource(“message”, “messages”, collection={“rss”: “GET”})
# “GET /message/rss” => Messages.rss().
# Defines a named route “rss_messages”.

_setup_extensions

保存extension资源的方法

# cinder/api/openstack/__init__.py
def _setup_extensions(self, ext_mgr):
    for extension in ext_mgr.get_controller_extensions():
        # len(ext_mgr.get_controller_extensions()) == 16
        collection = extension.collection
        controller = extension.controller
        #print 'collection: %r\rcontronller: %r' % (collection, controller)

        if collection not in self.resources:
            # len(self.resources) == 22
            LOG.warning(_('Extension %(ext_name)s: Cannot extend '
                          'resource %(collection)s: No such resource'),
                        {'ext_name': extension.extension.name,
                         'collection': collection})
            continue
        LOG.debug('Extension %(ext_name)s extending resource: '
                  '%(collection)s',
                  {'ext_name': extension.extension.name,
                   'collection': collection})
        resource = self.resources[collection]
        resource.register_actions(controller)
        resource.register_extensions(controller)
  1. 这里面的collection,controller就是Python routes里面的概念
  2. len(ext_mgr.get_controller_extensions()) == 16
    并不是所有的extension都定义了collection,像availability_zones.py,scheduler_stats.py就没有collection。
    print collection/controller的log如下:

    collection: 'volumes', controller: <cinder.api.contrib.scheduler_hints.SchedulerHintsController object at 0x374c190>
    collection: 'volumes', controller: <cinder.api.contrib.volume_tenant_attribute.VolumeTenantAttributeController object at 0x374c310>
    collection: 'types', controller: <cinder.api.contrib.types_manage.VolumeTypesManageController object at 0x39b82d0>
    collection: 'volumes', controller: <cinder.api.contrib.volume_replication.VolumeReplicationController object at 0x39b8390>
    collection: 'snapshots', controller: <cinder.api.contrib.snapshot_actions.SnapshotActionsController object at 0x39b8b50>
    collection: 'volumes', controller: <cinder.api.contrib.volume_actions.VolumeActionsController object at 0x39b8bd0>
    collection: 'limits', controller: <cinder.api.contrib.used_limits.UsedLimitsController object at 0x39b8f50>
    collection: 'volumes', controller: <cinder.api.contrib.volume_unmanage.VolumeUnmanageController object at 0x39b8fd0>
    collection: 'volumes', controller: <cinder.api.contrib.volume_host_attribute.VolumeHostAttributeController object at 0x39bc390>
    collection: 'types', controller: <cinder.api.contrib.volume_type_encryption.VolumeTypeEncryptionController object at 0x39bc710>
    collection: 'volumes', controller: <cinder.api.contrib.volume_mig_status_attribute.VolumeMigStatusAttributeController object at 0x39bc790>
    collection: 'snapshots', controller: <cinder.api.contrib.extended_snapshot_attributes.ExtendedSnapshotAttributesController object at 0x39bcb10>
    collection: 'volumes', controller: <cinder.api.contrib.volume_image_metadata.VolumeImageMetadataController object at 0x39bce90>
    collection: 'volumes', controller: <cinder.api.contrib.admin_actions.VolumeAdminController object at 0x39bd290>
    collection: 'snapshots', controller: <cinder.api.contrib.admin_actions.SnapshotAdminController object at 0x39bda50>
    collection: 'backups', controller: <cinder.api.contrib.admin_actions.BackupAdminController object at 0x39be250>

    一共16个。extension共有30个,所以有一半都没使用collection。

  3. resource = self.resources[collection]
    resource.register_actions(controller)
    resource.register_extensions(controller)

    方法定义是在cinder/api/openstack/wsgi.py class Resource(wsgi.Application)中,这是处理所有contrib/*.py中用@wsgi.action@wsgi.extends修饰的方法。

参考

Openstack Paste Deploy介绍
python paste.deploy 探索
如何使用Paste.Deploy - very good
WSGI和PASTE
Python.Paste指南之Deploy(1)-概念
探索 OpenStack 之(11):cinder-api Service 启动过程分析 以及 WSGI / Paste deploy / Router 等介绍
Openstack Api分析(一)

转载于:https://www.cnblogs.com/qeelee/p/4356140.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值