cat /usr/bin/cinder-api
#!/usr/bin/python
# EASY-INSTALL-SCRIPT: 'cinder==2013.1.g3','cinder-api'
__requires__ = 'cinder==2013.1.g3'
import pkg_resources
pkg_resources.run_script('cinder==2013.1.g3', 'cinder-api')
run_script第一参数指向了site-packages/cinder-2013.1.g3-py2.6.egg,第二个参数为要运行的脚本名cinder-api,查看该目录下的EGG-INFO/scripts目录,找到cinder-api
cat cinder-api
import eventlet
eventlet.monkey_patch()
import os
import sys
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(
sys.argv[0]), os.pardir, os.pardir))
if os.path.exists(os.path.join(possible_topdir, "cinder", "__init__.py")):
sys.path.insert(0, possible_topdir)
from cinder import flags
from cinder.openstack.common import log as logging
from cinder import service
from cinder import utils
if __name__ == '__main__':
flags.parse_args(sys.argv)
logging.setup("cinder")
utils.monkey_patch()
server =
service.WSGIService('osapi_volume')
service.serve(server)
service.wait()
启动了一个WSGIService服务,下面具体分析启动过程。
cinder/service.py
class WSGIService(object):
"""Provides ability to launch API from a 'paste' configuration."""
def __init__(self, name, loader=None):
"""Initialize, but do not start the WSGI server.
:param name: The name of the WSGI server given to the loader.
:param loader: Loads the WSGI application using the given name.
:returns: None
"""
self.name = name
self.manager = self._get_manager() //初始化服务管理对象
self.loader = loader or wsgi.Loader() //指定apploader
self.app = self.loader.load_app(name) //加载app,这个过程相对复杂,涉及到paste模块-配置文件解析、模块加载
self.host = getattr(FLAGS, '%s_listen' % name, "0.0.0.0") //从flages.py或配置文件中获取osapi_volume_listen配置参数
self.port = getattr(FLAGS, '%s_listen_port' % name, 0)
self.server = wsgi.Server(name,
self.app,
host=self.host,
port=self.port) //初始化一个wsgi的Server
def _get_manager(self):
fl = '%s_manager' % self.name //在flages.py中无osapi_volume_manager配置项,因此返回None
if fl not in FLAGS:
return None
manager_class_name = FLAGS.get(fl, None)
if not manager_class_name:
return None
manager_class = importutils.import_class(manager_class_name)
return manager_class()
def start(self):
if self.manager:
self.manager.init_host()
self.server.start()
self.port = self.server.port
def serve(*servers):
global _launcher
if not _launcher:
_launcher = Launcher()
for server in servers:
_launcher.launch_server(server)
self.loader = loader or wsgi.Loader()
self.app = self.loader.load_app(name)
既加载osapi_volume程序
cinder/wsgi.py
class Loader(object):
def __init__(self, config_path=None):
config_path = config_path or FLAGS.api_paste_config //此处指向 /etc/cinder/api-paste.ini
self.config_path = utils.find_config(config_path)
def load_app(self, name):
try:
return
deploy.loadapp("config:%s" % self.config_path, name=name)
except LookupError as err:
LOG.error(err)
raise exception.PasteAppNotFound(name=name, path=self.config_path)
deploy.loadapp("config:%s" % self.config_path, name=name)
deploy源自PasteDeploy-1.5.0-py2.6.egg/paste/deploy,加载了loadwsgi.py
PasteDeploy-xxx/pasete/deploy/loadwsgi.py
class _App(_ObjectType):
name = 'application'
egg_protocols = ['paste.app_factory', 'paste.composite_factory',
'paste.composit_factory']
config_prefixes = [['app', 'application'], ['composite', 'composit'],
'pipeline', 'filter-app']
def
invoke(self, context):
if context.protocol in ('paste.composit_factory',
'paste.composite_factory'):
return fix_call(context.object, context.loader, context.global_conf,**context.local_conf)
elif context.protocol == 'paste.app_factory':
return
fix_call(context.object, context.global_conf, **context.local_conf)
else:
assert 0, "Protocol %r unknown" % context.protocol
APP = _App()
def loadapp(uri, name=None, **kw):
return
loadobj(
APP, uri, name=name, **kw)
def loadobj(object_type, uri, name=None, relative_to=None,global_conf=None):
context =
loadcontext(object_type, uri, name=name, relative_to=relative_to, global_conf=global_conf)
return
context.create()
def
loadcontext(object_type, uri, name=None, relative_to=None, global_conf=None):
if '#' in uri:
if name is None: //此处第一次调用时name=osapi_volume , uri=config:/etc/cinder/api-paste.ini
uri, name = uri.split('#', 1)
else:
# @@: Ignore fragment or error?
uri = uri.split('#', 1)[0]
if name is None:
name = 'main'
if ':' not in uri:
raise LookupError("URI has no scheme: %r" % uri)
scheme, path = uri.split(':', 1) //此处第一次得到的scheme 是config,path是/etc/cinder/api-paste.ini 因此reuturn 返回的是 _loacer['config'] ,具体指向看后面的部分
scheme = scheme.lower()
if scheme not in _loaders:
raise LookupError(
"URI scheme not known: %r (from %s)"
% (scheme, ', '.join(_loaders.keys())))
return _loaders[scheme](object_type, uri, path, name=name, relative_to=relative_to,global_conf=global_conf)
其中
def _loadegg(object_type, uri, spec, name, relative_to, global_conf):
loader = EggLoader(spec)
return loader.get_context(object_type, name, global_conf)
def _loadfunc(object_type, uri, spec, name, relative_to, global_conf):
loader =
FuncLoader(spec)
return loader.
get_context(object_type, name, global_conf) //返回了一个LoaderContext,包含了一个ConfigLoader(_Loader)对象
_loaders['config'] = _loadconfig
_loaders['egg'] = _loadegg
_loaders['call'] = _loadfunc
class
FuncLoader(_Loader): //继承自_Loader类
def __init__(self, spec):
self.spec = spec
def
get_context(...):
obj =
lookup_object(self.spec) //该方法来自paste/deploy/util.py
return
LoaderContext(obj,object_type,None, global_conf or {},{},self,)
class
LoaderContext(object):
def create(self):
return self.
object_type.
invoke(self) //此处的object_type为APP,所以调用的是class _APP的invoke方法,从上面的代码可以看到,该方法返回的是一个
fix_call的调用结果,下面具体分析
fix_call方法来自于paste/deploy/util.py
def
fix_call(callable, *args, **kw):
try:
val =
callable(*args, **kw)
//此处的callable指向api的一些功能入口api/__init__.py:root_app_factory api/middleware/auth.py:pipeline_factory 可以看出此处进行了接口的实际调用,其中root_app_factory实际出发了Paste/paste/urlmap.py:urlmap_factory的调用,后者调用了loader.get_app() ,laoder对象是一个PasteDeploy/paste/deploy/loadwsgi.py中的ConfigLoader(_Loader)类对象。urlmap_factory中的local_conf.items() 为
/: apiversions
/v1: openstack_volume_api_v1
/v2: openstack_volume_api_v2
参看/etc/cinder/api-paste.ini
因此loader.get_app()的解析是逐个加载下面几个字段,直到所有字段加载完成,后面的其它模块加载流程基本相同
[composite:openstack_volume_api_v2]
[composite:openstack_volume_api_v1]
[pipeline:apiversions]
参看/etc/cinder/api-paste.ini
except TypeError:
exc_info = fix_type_error(None, callable, args, kw)
reraise(*exc_info)
return val
def
lookup_object(spec): //此处的spec值类似于 cinder.api:root_app_factory、cinder.api.middleware.auth:pipeline_factory
parts, target = spec.split(':') if ':' in spec else (spec, None)
module = __import__(parts)
for part in parts.split('.')[1:] + ([target] if target else []):
module = getattr(module, part)
return module
Paste/paste/urlmap.py
def
urlmap_factory(loader, global_conf, **local_conf):
if 'not_found_app' in local_conf:
not_found_app = local_conf.pop('not_found_app')
else:
not_found_app = global_conf.get('not_found_app')
if not_found_app:
not_found_app = loader.get_app(not_found_app, global_conf=global_conf)
urlmap = URLMap(not_found_app=not_found_app)
for path, app_name in local_conf.items():
path = parse_path_expression(path)
app =
loader.get_app(app_name, global_conf=global_conf)
urlmap[path] = app
return urlmap
整个load_app加载了多个模块,最终返回的就是一个urlmap的列表,看下面的调用流程
****** PasteDeploy:paste:deploy:loadwsgi.py:loadcontext: uri:config:/etc/cinder/api-paste.ini name:osapi_volume scheme:config:
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name osapi_volume
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name call:cinder.api:root_app_factory
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name:call:cinder.api:root_app_factory have a sechema
****** PasteDeploy:paste:deploy:loadwsgi.py:loadcontext: uri:call:cinder.api:root_app_factory name:main scheme:call:
****** PasteDeploy:paste:deploy:util.py:lookup_object: module <module 'cinder.api' from '/usr/lib/python2.6/site-packages/cinder-2013.1.g3-py2.6.egg/cinder/api/__init__.pyc'>
****** PasteDeploy:paste:deploy:util.py:lookup_object: module <function root_app_factory at 0x2529e60>
****** PasteDeploy:paste:deploy:util.py:fix_call: callable: <function root_app_factory at 0x2529e60>
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name openstack_volume_api_v2
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name call:cinder.api.middleware.auth:pipeline_factory
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name:call:cinder.api.middleware.auth:pipeline_factory have a sechema
****** PasteDeploy:paste:deploy:loadwsgi.py:loadcontext: uri:call:cinder.api.middleware.auth:pipeline_factory name:main scheme:call:
****** PasteDeploy:paste:deploy:util.py:lookup_object: module <module 'cinder.api' from '/usr/lib/python2.6/site-packages/cinder-2013.1.g3-py2.6.egg/cinder/api/__init__.pyc'>
****** PasteDeploy:paste:deploy:util.py:lookup_object: module <module 'cinder.api.middleware' from '/usr/lib/python2.6/site-packages/cinder-2013.1.g3-py2.6.egg/cinder/api/middleware/__init__.pyc'>
****** PasteDeploy:paste:deploy:util.py:lookup_object: module <module 'cinder.api.middleware.auth' from '/usr/lib/python2.6/site-packages/cinder-2013.1.g3-py2.6.egg/cinder/api/middleware/auth.pyc'>
****** PasteDeploy:paste:deploy:util.py:lookup_object: module <function pipeline_factory at 0x2538e60>
****** PasteDeploy:paste:deploy:util.py:fix_call: callable: <function pipeline_factory at 0x2538e60>
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name faultwrap
****** PasteDeploy:paste:deploy:util.py:fix_call: callable: <bound method type.factory of <class 'cinder.api.middleware.fault.FaultWrapper'>>
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name sizelimit
****** PasteDeploy:paste:deploy:util.py:fix_call: callable: <bound method type.factory of <class 'cinder.api.middleware.sizelimit.RequestBodySizeLimiter'>>
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name authtoken
****** PasteDeploy:paste:deploy:util.py:fix_call: callable: <function filter_factory at 0x26d2668>
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name keystonecontext
****** PasteDeploy:paste:deploy:util.py:fix_call: callable: <bound method type.factory of <class 'cinder.api.middleware.auth.CinderKeystoneContext'>>
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name apiv2
****** PasteDeploy:paste:deploy:util.py:fix_call: callable: <bound method type.factory of <class 'cinder.api.v2.router.APIRouter'>>
2013-02-03 02:59:41.149 4294 AUDIT cinder.api.extensions [-] Initializing extension manager.
2013-02-03 02:59:41.151 4294 AUDIT cinder.api.extensions [-] Loaded extension: os-vol-image-meta
2013-02-03 02:59:41.152 4294 AUDIT cinder.api.extensions [-] Loaded extension: os-hosts
2013-02-03 02:59:41.153 4294 AUDIT cinder.api.extensions [-] Loaded extension: os-types-manage
2013-02-03 02:59:41.154 4294 AUDIT cinder.api.extensions [-] Loaded extension: os-volume-actions
2013-02-03 02:59:41.155 4294 AUDIT cinder.api.extensions [-] Loaded extension: os-vol-host-attr
2013-02-03 02:59:41.156 4294 AUDIT cinder.api.extensions [-] Loaded extension: os-admin-actions
2013-02-03 02:59:41.157 4294 AUDIT cinder.api.extensions [-] Loaded extension: os-extended-snapshot-attributes
2013-02-03 02:59:41.157 4294 AUDIT cinder.api.extensions [-] Loaded extension: os-services
2013-02-03 02:59:41.158 4294 AUDIT cinder.api.extensions [-] Loaded extension: os-image-create
2013-02-03 02:59:41.159 4294 AUDIT cinder.api.extensions [-] Loaded extension: os-vol-tenant-attr
2013-02-03 02:59:41.159 4294 AUDIT cinder.api.extensions [-] Loaded extension: os-quota-class-sets
2013-02-03 02:59:41.160 4294 AUDIT cinder.api.extensions [-] Loaded extension: os-types-extra-specs
2013-02-03 02:59:41.272 4294 AUDIT cinder.api.extensions [-] Loaded extension: os-quota-sets
2013-02-03 02:59:41.273 4294 AUDIT cinder.api.extensions [-] Loaded extension: backups
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name openstack_volume_api_v1
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name call:cinder.api.middleware.auth:pipeline_factory
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name:call:cinder.api.middleware.auth:pipeline_factory have a sechema
****** PasteDeploy:paste:deploy:loadwsgi.py:loadcontext: uri:call:cinder.api.middleware.auth:pipeline_factory name:main scheme:call:
****** PasteDeploy:paste:deploy:util.py:lookup_object: module <module 'cinder.api' from '/usr/lib/python2.6/site-packages/cinder-2013.1.g3-py2.6.egg/cinder/api/__init__.pyc'>
****** PasteDeploy:paste:deploy:util.py:lookup_object: module <module 'cinder.api.middleware' from '/usr/lib/python2.6/site-packages/cinder-2013.1.g3-py2.6.egg/cinder/api/middleware/__init__.pyc'>
****** PasteDeploy:paste:deploy:util.py:lookup_object: module <module 'cinder.api.middleware.auth' from '/usr/lib/python2.6/site-packages/cinder-2013.1.g3-py2.6.egg/cinder/api/middleware/auth.pyc'>
****** PasteDeploy:paste:deploy:util.py:lookup_object: module <function pipeline_factory at 0x2538e60>
****** PasteDeploy:paste:deploy:util.py:fix_call: callable: <function pipeline_factory at 0x2538e60>
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name faultwrap
****** PasteDeploy:paste:deploy:util.py:fix_call: callable: <bound method type.factory of <class 'cinder.api.middleware.fault.FaultWrapper'>>
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name sizelimit
****** PasteDeploy:paste:deploy:util.py:fix_call: callable: <bound method type.factory of <class 'cinder.api.middleware.sizelimit.RequestBodySizeLimiter'>>
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name authtoken
****** PasteDeploy:paste:deploy:util.py:fix_call: callable: <function filter_factory at 0x26d2668>
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name keystonecontext
****** PasteDeploy:paste:deploy:util.py:fix_call: callable: <bound method type.factory of <class 'cinder.api.middleware.auth.CinderKeystoneContext'>>
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name apiv1
****** PasteDeploy:paste:deploy:util.py:fix_call: callable: <bound method type.factory of <class 'cinder.api.v1.router.APIRouter'>>
2013-02-03 02:59:41.295 4294 AUDIT cinder.api.extensions [-] Initializing extension manager.
2013-02-03 02:59:41.296 4294 AUDIT cinder.api.extensions [-] Loaded extension: os-vol-image-meta
2013-02-03 02:59:41.297 4294 AUDIT cinder.api.extensions [-] Loaded extension: os-hosts
2013-02-03 02:59:41.297 4294 AUDIT cinder.api.extensions [-] Loaded extension: os-types-manage
2013-02-03 02:59:41.297 4294 AUDIT cinder.api.extensions [-] Loaded extension: os-volume-actions
2013-02-03 02:59:41.298 4294 AUDIT cinder.api.extensions [-] Loaded extension: os-vol-host-attr
2013-02-03 02:59:41.298 4294 AUDIT cinder.api.extensions [-] Loaded extension: os-admin-actions
2013-02-03 02:59:41.299 4294 AUDIT cinder.api.extensions [-] Loaded extension: os-extended-snapshot-attributes
2013-02-03 02:59:41.299 4294 AUDIT cinder.api.extensions [-] Loaded extension: os-services
2013-02-03 02:59:41.300 4294 AUDIT cinder.api.extensions [-] Loaded extension: os-image-create
2013-02-03 02:59:41.300 4294 AUDIT cinder.api.extensions [-] Loaded extension: os-vol-tenant-attr
2013-02-03 02:59:41.300 4294 AUDIT cinder.api.extensions [-] Loaded extension: os-quota-class-sets
2013-02-03 02:59:41.301 4294 AUDIT cinder.api.extensions [-] Loaded extension: os-types-extra-specs
2013-02-03 02:59:41.301 4294 AUDIT cinder.api.extensions [-] Loaded extension: os-quota-sets
2013-02-03 02:59:41.301 4294 AUDIT cinder.api.extensions [-] Loaded extension: backups
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name apiversions
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name osvolumeversionapp
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name faultwrap
****** PasteDeploy:paste:deploy:util.py:fix_call: callable: <bound method type.factory of <class 'cinder.api.versions.Versions'>>
****** PasteDeploy:paste:deploy:util.py:fix_call: callable: <bound method type.factory of <class 'cinder.api.middleware.fault.FaultWrapper'>>
****** Paste:paste:urlmap.py:urlmap_factory: app: <cinder.api.middleware.fault.FaultWrapper object at 0x3480fd0>
其中,在加载cinder.api.v1.router.APIRouter模块时,会有一个加载extension manager的过程,这个部分加载的模块是api服务的关键,完成实际服务的app来自于这些扩展,manager来进行管理,在有web请求时进过路由到对应的app
下面分析cinder.api.v2.router.APIRouter 模块的加载过程:
cinder/api/v2/router.py
class APIRouter(cinder.api.openstack.APIRouter):
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')
api/openstack/__init__.py
class
APIRouter(base_wsgi.Router):
def
__init__(self, ext_mgr=None):
if ext_mgr is None:
if self.ExtensionManager:
ext_mgr =
self.ExtensionManager() 此处触发一个 standard api extension 程序的加载过程
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)
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
mapper.resource(resource.collection, resource.collection, **kargs)
if resource.custom_routes_fn:
resource.custom_routes_fn(mapper, wsgi_resource)
def
_setup_extensions(self, ext_mgr):
for extension in ext_mgr.get_controller_extensions():
ext_name = extension.extension.name
collection = extension.collection
controller = extension.controller
if collection not in self.resources:
LOG.warning(_('Extension %(ext_name)s: Cannot extend '
'resource %(collection)s: No such resource') %
locals())
continue
LOG.debug(_('Extension %(ext_name)s extending resource: '
'%(collection)s') % locals())
resource = self.resources[collection]
resource.register_actions(controller)
resource.register_extensions(controller)
def
_setup_routes(self, mapper, ext_mgr):
raise NotImplementedError
cinder/api/extensions.py
class ExtensionManager(object):
def
__init__(self):
LOG.audit(_('Initializing extension manager.'))
self.cls_list = FLAGS.osapi_volume_extension 在flages.py配置文件中,osapi_volume_extension的配置值为cinder.api.contrib.standard_extensions
self.extensions = {}
self.
_load_extensions()
开始加载standard api extension类
def _load_extensions(self):
"""Load extensions specified on the command line."""
extensions = list(self.cls_list)
for ext_factory in extensions:
try:
self.
load_extension(ext_factory) 此处ext_factory 为cinder.api.contrib.standard_extensions
def load_extension(self, ext_factory):
factory = importutils.import_class(ext_factory) 此处ext_factory 为cinder.api.contrib.
standard_extensions,此处调用了cinder/api/contrib/__init__.py: standard_extensions(ext_mgr):
factory(self)
cinder/api/contrib/__init__.py
def
standard_extensions(ext_mgr):
extensions.
load_standard_extensions(ext_mgr, LOG, __path__, __package__) 调用的是extensions.py中的load_standard_extensions 方法.
cinder/api/extensions.py
def load_standard_extensions(ext_mgr, logger, path, package, ext_list=None):
"""Registers all standard API extensions."""
# Walk through all the modules in our directory...
our_dir = path[0]
for dirpath, dirnames, filenames in os.walk(our_dir):
# Compute the relative package name from the dirpath
relpath = os.path.relpath(dirpath, our_dir)
if relpath == '.':
relpkg = ''
else:
relpkg = '.%s' % '.'.join(relpath.split(os.sep))
# Now, consider each file in turn, only considering .py files
//下面的这个循环根据文件名遍历了standard API extension的.py文件
for fname in filenames:
root, ext = os.path.splitext(fname)
# Skip __init__ and anything that's not .py
if ext != '.py' or root == '__init__':
continue
# Try loading it
classname = "%s%s" % (root[0].upper(), root[1:]) //root第一个字符变为大写后作为类名
classpath = ("%s%s.%s.%s" % (package, relpkg, root, classname))
//根据文件名及路径信息构造类的路径信息,类似于cinder.api.contrib.volume_image_metadata.Volume_image_metadata
if ext_list is not None and classname not in ext_list:
logger.debug("Skipping extension: %s" % classpath)
continue
try:
ext_mgr.
load_extension(classpath) //根据给定的路径加载类
except Exception as exc:
logger.warn(_('Failed to load extension %(classpath)s: '
'%(exc)s') % locals())
# Now, let's consider any subdirectories we may have...
subdirs = []
for dname in dirnames: //子目录同样的加载过程
# Skip it if it does not have __init__.py
if not os.path.exists(os.path.join(dirpath, dname,
'__init__.py')):
continue
# If it has extension(), delegate...
ext_name = ("%s%s.%s.extension" %
(package, relpkg, dname))
try:
ext = importutils.import_class(ext_name)
except common_exception.NotFound:
# extension() doesn't exist on it, so we'll explore
# the directory for ourselves
subdirs.append(dname)
else:
try:
ext(ext_mgr)
except Exception as exc:
logger.warn(_('Failed to load extension %(ext_name)s: '
'%(exc)s') % locals())
# Update the list of directories we'll explore...
dirnames[:] = subdirs
在基本的standard api extension加载完成后,APIRouter的初始化并没有结束,还有如下的一个过程
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)
cinder/api/v2/router.py
class APIRouter(cinder.api.openstack.APIRouter):
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'})
以volumes.create_resource(ext_mgr) 为例创建一个resource
cinder/api/v2/volume.py
def create_resource(ext_mgr):
return
wsgi.Resource(
VolumeController(ext_mgr))
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 = volume.API() volume驱动API接口
self.ext_mgr = ext_mgr
super(VolumeController, self).__init__()
@wsgi.serializers(xml=VolumeTemplate)
def show(self, req, id):
"""Return data about the given volume."""
def delete(self, req, id):
"""Delete a volume."""
def index(self, req):
"""Returns a summary list of volumes."""
@wsgi.serializers(xml=VolumesTemplate)
def detail(self, req):
"""Returns a detailed list of volumes."""
可以看出该类包含了对卷的操作方法、show、delete等,最终都是调用的self.volume_api来进行操作的。
cinder/volume.py
API = cinder.openstack.common.importutils.import_class(cinder.flags.FLAGS.volume_api_class)
在flages.py中volume_api_class默认值为 cinder.volume.api.API
cinder/volume/api.py
from cinder.scheduler import rpcapi as scheduler_rpcapi
from cinder.volume import rpcapi as volume_rpcapi
class API(base.Base):
"""API for interacting with the volume manager."""
def __init__(self, db_driver=None, image_service=None):
self.image_service = (image_service or glance.get_default_image_service())
self.scheduler_rpcapi =
scheduler_rpcapi.
SchedulerAPI()
self.volume_rpcapi =
volume_rpcapi.
VolumeAPI()
super(API, self).__init__(db_driver)
def create(self, context, size, name, description, snapshot=None,
image_id=None, volume_type=None, metadata=None,
availability_zone=None, source_volume=None):
.....................................................
self._check_metadata_properties(context, metadata)
options = {'size': size,
'user_id': context.user_id,
'project_id': context.project_id,
'snapshot_id': snapshot_id,
'availability_zone': availability_zone,
'status': "creating",
'attach_status': "detached",
'display_name': name,
'display_description': description,
'volume_type_id': volume_type_id,
'metadata': metadata,
'source_volid': source_volid}
try:
volume = self.db.volume_create(context, options)
QUOTAS.commit(context, reservations)
request_spec = {'volume_properties': options,
'volume_type': volume_type,
'volume_id': volume['id'],
'snapshot_id': volume['snapshot_id'],
'image_id': image_id,
'source_volid': volume['source_volid']}
filter_properties = {}
self.
_cast_create_volume(context, request_spec, filter_properties)
return volume
def
_cast_create_volume(self, context, request_spec, filter_properties):
source_volume_ref = self.db.volume_get(context,source_volid)
now = timeutils.utcnow()
values = {'host': source_volume_ref['host'], 'scheduled_at': now}
volume_ref = self.db.volume_update(context, volume_id, values)
# bypass scheduler and send request directly to volume
self.
volume_rpcapi.create_volume(context,............
cinder/volume/rpcapi.py
class VolumeAPI(
cinder.openstack.common.rpc.proxy.RpcProxy):
BASE_RPC_API_VERSION = '1.0'
def __init__(self, topic=None):
super(VolumeAPI, self).__init__(topic=topic or FLAGS.volume_topic,default_version=self.BASE_RPC_API_VERSION)
def create_volume(self, ctxt, volume, host, request_spec, filter_properties,allow_reschedule=True, snapshot_id=None, image_id=None, source_volid=None):
self.
cast(ctxt,
self.make_msg('create_volume',volume_id=volume['id'], request_spec=request_spec,filter_properties=filter_properties,
allow_reschedule=allow_reschedule,snapshot_id=snapshot_id,image_id=image_id,source_volid=source_volid),
topic=rpc.queue_get_for(ctxt,self.topic,host),
version='1.4')
使用cast方法发送了一个methord:create_volume的消息到AMQP服务器,另外本类还提供了其它针对volume操作的方法。
再回过来看
wsgi.Resource
cinder/api/openstack/wsgi.py
class Resource(wsgi.Application):
def __init__(self, controller, action_peek=None, **deserializers):
self.controller = controller 从上面的调用wsgi.Resource(
VolumeController(ext_mgr))可以看出controller是一个
VolumeController控制对象
default_deserializers = dict(xml=XMLDeserializer,
.......
经过上面的这段代码流程的分析,可以看出,self._setup_routes(mapper, ext_mgr) 是对resource和controller进行初始化操作
下面继续
api/openstack/__init__.py
class
APIRouter(base_wsgi.Router):__init__初始化中的
self._setup_ext_routes(mapper, ext_mgr) 过程分析
//此方法主要是生成controller字典
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
mapper.resource(resource.collection, resource.collection, **kargs)
if resource.custom_routes_fn:
resource.custom_routes_fn(mapper, wsgi_resource)
//本方法主要是注册controller
def _setup_extensions(self, ext_mgr):
for extension in ext_mgr.get_controller_extensions():
ext_name = extension.extension.name
collection = extension.collection
controller = extension.controller
if collection not in self.resources:
LOG.warning(_('Extension %(ext_name)s: Cannot extend '
'resource %(collection)s: No such resource') %
locals())
continue
LOG.debug(_('Extension %(ext_name)s extending resource: '
'%(collection)s') % locals())
resource = self.resources[collection]
resource.register_actions(controller)
resource.register_extensions(controller)
最后一步:
cinder/wsgi.py
class Router(object):
def __init__(self, mapper):
"""Create a router for the given routes.Mapper.
self.map = mapper
self._router = routes.middleware.RoutesMiddleware(self._dispatch, self.map)
@webob.dec.wsgify(RequestClass=Request)
此处使用了“装饰者”模式,将__call__方法的返回值作为参数传给webob.dec.wsgify的同名方法__call__
def __call__(self, req):
"""Route the incoming request to a controller based on self.map.
If no match, return a 404.
"""
return self._router
@staticmethod
@webob.dec.wsgify(RequestClass=Request)
def _dispatch(req):
"""Dispatch the request to the appropriate controller.
Called by self._router after matching the incoming request to a route
and putting the information into req.environ. Either returns 404
or the routed WSGI app's response.
"""
match = req.environ['wsgiorg.routing_args'][1]
if not match:
return webob.exc.HTTPNotFound()
app = match['controller']
return app
当有web请求时,请求的入口就是__call__方法
最后在看WSGIService启动的最后一个步骤serve函数
cinder/service.py
def serve(*servers):
global _launcher
if not _launcher:
_launcher =
Launcher()
for server in servers:
_launcher.
launch_server(server)
class Launcher(object):
@staticmethod
def run_server(server):
"""Start and wait for a server to finish.
:param service: Server to run and wait for.
:returns: None
"""
server.start()
server.wait()
def launch_server(self, server):
"""Load and start the given server.
:param server: The server you would like to start.
:returns: None
"""
gt = eventlet.spawn(self.run_server, server)
self._services.append(gt)
可以看出最终是调用了wsgi.
Server的start方法
cinder/wsgi.py
def _start(self):
eventlet.wsgi.server(self._socket,
self.app, 这里的app就是经过多层包装的
APIRouter
protocol=self._protocol,
custom_pool=self._pool,
log=self._wsgi_logger)
def start(self, backlog=128):
"""Start serving a WSGI application.
"""
if backlog < 1:
raise exception.InvalidInput(
reason='The backlog must be more than 1')
self._socket = self._get_socket(self._host,
self._port,
backlog=backlog)
self._server = eventlet.spawn(self._start)
(self._host, self._port) = self._socket.getsockname()[0:2]
LOG.info(_("Started %(name)s on %(_host)s:%(_port)s") % self.__dict__)
****** PasteDeploy:paste:deploy:loadwsgi.py:loadcontext: uri:config:/etc/cinder/api-paste.ini name:osapi_volume scheme:config:
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name osapi_volume
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: section: composite:osapi_volume
****** PasteDeploy.paste.deploy.loadwsgi.get_context: section_start_with_use ******
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name call:cinder.api:root_app_factory
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name:call:cinder.api:root_app_factory have a sechema
****** PasteDeploy:paste:deploy:loadwsgi.py:loadcontext: uri:call:cinder.api:root_app_factory name:main scheme:call:
****** PasteDeploy:paste:deploy:util.py:lookup_object: module <module 'cinder.api' from '/usr/lib/python2.6/site-packages/cinder-2013.1.g3-py2.6.egg/cinder/api/__init__.pyc'>
****** PasteDeploy:paste:deploy:util.py:lookup_object: module <function root_app_factory at 0x20b0e60>
****** PasteDeploy:paste:deploy:util.py:fix_call: callable: <function root_app_factory at 0x20b0e60>
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name openstack_volume_api_v2
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: section: composite:openstack_volume_api_v2
****** PasteDeploy.paste.deploy.loadwsgi.get_context: section_start_with_use ******
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name call:cinder.api.middleware.auth:pipeline_factory
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name:call:cinder.api.middleware.auth:pipeline_factory have a sechema
****** PasteDeploy:paste:deploy:loadwsgi.py:loadcontext: uri:call:cinder.api.middleware.auth:pipeline_factory name:main scheme:call:
****** PasteDeploy:paste:deploy:util.py:lookup_object: module <module 'cinder.api' from '/usr/lib/python2.6/site-packages/cinder-2013.1.g3-py2.6.egg/cinder/api/__init__.pyc'>
****** PasteDeploy:paste:deploy:util.py:lookup_object: module <module 'cinder.api.middleware' from '/usr/lib/python2.6/site-packages/cinder-2013.1.g3-py2.6.egg/cinder/api/middleware/__init__.pyc'>
****** PasteDeploy:paste:deploy:util.py:lookup_object: module <module 'cinder.api.middleware.auth' from '/usr/lib/python2.6/site-packages/cinder-2013.1.g3-py2.6.egg/cinder/api/middleware/auth.py'>
****** PasteDeploy:paste:deploy:util.py:lookup_object: module <function pipeline_factory at 0x20bbed8>
****** PasteDeploy:paste:deploy:util.py:fix_call: callable: <function pipeline_factory at 0x20bbed8>
****** api.middleware.auth.pipeline_factory: call get_context method of loader for N times in pipeline: ['faultwrap', 'sizelimit', 'authtoken', 'keystonecontext', 'apiv2']
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name faultwrap
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: section: filter:faultwrap
****** PasteDeploy.paste.deploy.loadwsgi.get_context: section_start_with_filter ******
****** PasteDeploy:paste:deploy:util.py:fix_call: callable: <bound method type.factory of <class 'cinder.api.middleware.fault.FaultWrapper'>>
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name sizelimit
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: section: filter:sizelimit
****** PasteDeploy.paste.deploy.loadwsgi.get_context: section_start_with_filter ******
****** PasteDeploy:paste:deploy:util.py:fix_call: callable: <bound method type.factory of <class 'cinder.api.middleware.sizelimit.RequestBodySizeLimiter'>>
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name authtoken
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: section: filter:authtoken
****** PasteDeploy.paste.deploy.loadwsgi.get_context: section_start_with_filter ******
****** PasteDeploy:paste:deploy:util.py:fix_call: callable: <function filter_factory at 0x22536e0>
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name keystonecontext
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: section: filter:keystonecontext
****** PasteDeploy.paste.deploy.loadwsgi.get_context: section_start_with_filter ******
****** PasteDeploy:paste:deploy:util.py:fix_call: callable: <bound method type.factory of <class 'cinder.api.middleware.auth.CinderKeystoneContext'>>
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name apiv2
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: section: app:apiv2
****** PasteDeploy.paste.deploy.loadwsgi.get_context: section_start_with_filter ******
****** PasteDeploy:paste:deploy:util.py:fix_call: callable: <bound method type.factory of <class 'cinder.api.v2.router.APIRouter'>>
2013-02-08 05:28:49.000 39038 AUDIT cinder.api.extensions [-] Initializing extension manager.
2013-02-08 05:28:49.002 39038 AUDIT cinder.api.extensions [-] Loaded extension: os-vol-image-meta
2013-02-08 05:28:49.003 39038 AUDIT cinder.api.extensions [-] Loaded extension: os-hosts
2013-02-08 05:28:49.004 39038 AUDIT cinder.api.extensions [-] Loaded extension: os-types-manage
2013-02-08 05:28:49.005 39038 AUDIT cinder.api.extensions [-] Loaded extension: os-volume-actions
2013-02-08 05:28:49.006 39038 AUDIT cinder.api.extensions [-] Loaded extension: os-vol-host-attr
2013-02-08 05:28:49.007 39038 AUDIT cinder.api.extensions [-] Loaded extension: os-admin-actions
2013-02-08 05:28:49.007 39038 AUDIT cinder.api.extensions [-] Loaded extension: os-extended-snapshot-attributes
2013-02-08 05:28:49.008 39038 AUDIT cinder.api.extensions [-] Loaded extension: os-services
2013-02-08 05:28:49.008 39038 AUDIT cinder.api.extensions [-] Loaded extension: os-image-create
2013-02-08 05:28:49.009 39038 AUDIT cinder.api.extensions [-] Loaded extension: os-vol-tenant-attr
2013-02-08 05:28:49.010 39038 AUDIT cinder.api.extensions [-] Loaded extension: os-quota-class-sets
2013-02-08 05:28:49.011 39038 AUDIT cinder.api.extensions [-] Loaded extension: os-types-extra-specs
2013-02-08 05:28:49.122 39038 AUDIT cinder.api.extensions [-] Loaded extension: os-quota-sets
2013-02-08 05:28:49.123 39038 AUDIT cinder.api.extensions [-] Loaded extension: backups
****** api.middleware.auth.pipeline_factory: filter(app) : <cinder.api.middleware.auth.CinderKeystoneContext object at 0x2db0950>
****** api.middleware.auth.pipeline_factory: filter(app) : <keystoneclient.middleware.auth_token.AuthProtocol object at 0x2ed2810>
****** api.middleware.auth.pipeline_factory: filter(app) : <cinder.api.middleware.sizelimit.RequestBodySizeLimiter object at 0x2ed2c50>
****** api.middleware.auth.pipeline_factory: filter(app) : <cinder.api.middleware.fault.FaultWrapper object at 0x2ed2e50>
****** Paste:paste:urlmap.py:urlmap_factory: local_conf, app: {'/v2': 'openstack_volume_api_v2', '/v1': 'openstack_volume_api_v1', '/': 'apiversions'} <cinder.api.middleware.fault.FaultWrapper object at 0x2ed2e50>
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name openstack_volume_api_v1
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: section: composite:openstack_volume_api_v1
****** PasteDeploy.paste.deploy.loadwsgi.get_context: section_start_with_use ******
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name call:cinder.api.middleware.auth:pipeline_factory
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name:call:cinder.api.middleware.auth:pipeline_factory have a sechema
****** PasteDeploy:paste:deploy:loadwsgi.py:loadcontext: uri:call:cinder.api.middleware.auth:pipeline_factory name:main scheme:call:
****** PasteDeploy:paste:deploy:util.py:lookup_object: module <module 'cinder.api' from '/usr/lib/python2.6/site-packages/cinder-2013.1.g3-py2.6.egg/cinder/api/__init__.pyc'>
****** PasteDeploy:paste:deploy:util.py:lookup_object: module <module 'cinder.api.middleware' from '/usr/lib/python2.6/site-packages/cinder-2013.1.g3-py2.6.egg/cinder/api/middleware/__init__.pyc'>
****** PasteDeploy:paste:deploy:util.py:lookup_object: module <module 'cinder.api.middleware.auth' from '/usr/lib/python2.6/site-packages/cinder-2013.1.g3-py2.6.egg/cinder/api/middleware/auth.py'>
****** PasteDeploy:paste:deploy:util.py:lookup_object: module <function pipeline_factory at 0x20bbed8>
****** PasteDeploy:paste:deploy:util.py:fix_call: callable: <function pipeline_factory at 0x20bbed8>
****** api.middleware.auth.pipeline_factory: call get_context method of loader for N times in pipeline: ['faultwrap', 'sizelimit', 'authtoken', 'keystonecontext', 'apiv1']
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name faultwrap
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: section: filter:faultwrap
****** PasteDeploy.paste.deploy.loadwsgi.get_context: section_start_with_filter ******
****** PasteDeploy:paste:deploy:util.py:fix_call: callable: <bound method type.factory of <class 'cinder.api.middleware.fault.FaultWrapper'>>
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name sizelimit
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: section: filter:sizelimit
****** PasteDeploy.paste.deploy.loadwsgi.get_context: section_start_with_filter ******
****** PasteDeploy:paste:deploy:util.py:fix_call: callable: <bound method type.factory of <class 'cinder.api.middleware.sizelimit.RequestBodySizeLimiter'>>
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name authtoken
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: section: filter:authtoken
****** PasteDeploy.paste.deploy.loadwsgi.get_context: section_start_with_filter ******
****** PasteDeploy:paste:deploy:util.py:fix_call: callable: <function filter_factory at 0x22536e0>
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name keystonecontext
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: section: filter:keystonecontext
****** PasteDeploy.paste.deploy.loadwsgi.get_context: section_start_with_filter ******
****** PasteDeploy:paste:deploy:util.py:fix_call: callable: <bound method type.factory of <class 'cinder.api.middleware.auth.CinderKeystoneContext'>>
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name apiv1
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: section: app:apiv1
****** PasteDeploy.paste.deploy.loadwsgi.get_context: section_start_with_filter ******
****** PasteDeploy:paste:deploy:util.py:fix_call: callable: <bound method type.factory of <class 'cinder.api.v1.router.APIRouter'>>
2013-02-08 05:28:49.145 39038 AUDIT cinder.api.extensions [-] Initializing extension manager.
2013-02-08 05:28:49.146 39038 AUDIT cinder.api.extensions [-] Loaded extension: os-vol-image-meta
2013-02-08 05:28:49.146 39038 AUDIT cinder.api.extensions [-] Loaded extension: os-hosts
2013-02-08 05:28:49.147 39038 AUDIT cinder.api.extensions [-] Loaded extension: os-types-manage
2013-02-08 05:28:49.147 39038 AUDIT cinder.api.extensions [-] Loaded extension: os-volume-actions
2013-02-08 05:28:49.148 39038 AUDIT cinder.api.extensions [-] Loaded extension: os-vol-host-attr
2013-02-08 05:28:49.148 39038 AUDIT cinder.api.extensions [-] Loaded extension: os-admin-actions
2013-02-08 05:28:49.148 39038 AUDIT cinder.api.extensions [-] Loaded extension: os-extended-snapshot-attributes
2013-02-08 05:28:49.149 39038 AUDIT cinder.api.extensions [-] Loaded extension: os-services
2013-02-08 05:28:49.149 39038 AUDIT cinder.api.extensions [-] Loaded extension: os-image-create
2013-02-08 05:28:49.150 39038 AUDIT cinder.api.extensions [-] Loaded extension: os-vol-tenant-attr
2013-02-08 05:28:49.150 39038 AUDIT cinder.api.extensions [-] Loaded extension: os-quota-class-sets
2013-02-08 05:28:49.150 39038 AUDIT cinder.api.extensions [-] Loaded extension: os-types-extra-specs
2013-02-08 05:28:49.151 39038 AUDIT cinder.api.extensions [-] Loaded extension: os-quota-sets
2013-02-08 05:28:49.151 39038 AUDIT cinder.api.extensions [-] Loaded extension: backups
****** api.middleware.auth.pipeline_factory: filter(app) : <cinder.api.middleware.auth.CinderKeystoneContext object at 0x2fd3810>
****** api.middleware.auth.pipeline_factory: filter(app) : <keystoneclient.middleware.auth_token.AuthProtocol object at 0x30016d0>
****** api.middleware.auth.pipeline_factory: filter(app) : <cinder.api.middleware.sizelimit.RequestBodySizeLimiter object at 0x3001950>
****** api.middleware.auth.pipeline_factory: filter(app) : <cinder.api.middleware.fault.FaultWrapper object at 0x3001b10>
****** Paste:paste:urlmap.py:urlmap_factory: local_conf, app: {'/v2': 'openstack_volume_api_v2', '/v1': 'openstack_volume_api_v1', '/': 'apiversions'} <cinder.api.middleware.fault.FaultWrapper object at 0x3001b10>
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name apiversions
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: section: pipeline:apiversions
****** PasteDeploy.paste.deploy.loadwsgi.get_context: section_start_with_pipeline ******
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name osvolumeversionapp
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: section: app:osvolumeversionapp
****** PasteDeploy.paste.deploy.loadwsgi.get_context: section_start_with_filter ******
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: name faultwrap
****** PasteDeploy:paste:deploy:loadwsgi.py:ConfigLoader:get_context: section: filter:faultwrap
****** PasteDeploy.paste.deploy.loadwsgi.get_context: section_start_with_filter ******
****** PasteDeploy:paste:deploy:util.py:fix_call: callable: <bound method type.factory of <class 'cinder.api.versions.Versions'>>
****** PasteDeploy:paste:deploy:util.py:fix_call: callable: <bound method type.factory of <class 'cinder.api.middleware.fault.FaultWrapper'>>
****** Paste:paste:urlmap.py:urlmap_factory: local_conf, app: {'/v2': 'openstack_volume_api_v2', '/v1': 'openstack_volume_api_v1', '/': 'apiversions'} <cinder.api.middleware.fault.FaultWrapper object at 0x30090d0>