问题导读
1.资源的加载是如何实现的?
2.cinder模块功能的扩展和加载过程的具体实现是怎样的?
在OpenStack Cinder服务启动的过程中,对/cinder/api/contrib/和/cinder/api/v1/和/cinder/api/v2/下定义的资源功能进行了加载。
在前面的博客cinder服务启动源码分析 中,我们知道在服务类cinder.service.WSGIService的初始化过程中,有这样的源码实现:
class WSGIService(object):
"""
Provides ability to launch API from a 'paste' configuration.
实现启动API的类;
"""
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
"""
# 给定的要加载的服务的名称;
# osapi_volume
self.name = name
# 根据给定的服务名称导入对应的管理类;
self.manager = self._get_manager()
self.loader = loader or wsgi.Loader()
self.app = self.loader.load_app(name)
# 获取主机host;
self.host = getattr(CONF, '%s_listen' % name, "0.0.0.0")
# 获取主机端口;
self.port = getattr(CONF, '%s_listen_port' % name, 0)
# Server:管理WSGI服务的类;
self.server = wsgi.Server(name,
self.app,
host=self.host,
port=self.port) 复制代码
对于语句:
self.loader = loader or wsgi.Loader()
self.app = self.loader.load_app(name)
之前我们没有进行深入的分析,实际上,这两条语句就实现了模块功能的扩展和加载过程。我们来看源码:
class Loader(object):
"""
Used to load WSGI applications from paste configurations.
"""
def __init__(self, config_path=None):
"""Initialize the loader, and attempt to find the config.
:param config_path: Full or relative path to the paste config.
:returns: None
"""
# 此处指向/etc/cinder/api-paste.ini
config_path = config_path or CONF.api_paste_config
# 获取config_path的全路径;
self.config_path = utils.find_config(config_path)def load_app(self, name):
"""
Return the paste URLMap wrapped WSGI application.
:param name: Name of the application to load.
:returns: Paste URLMap object wrapping the requested application.
:raises: `cinder.exception.PasteAppNotFound`
加载osapi_volume程序;
"""
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) 复制代码
我们可以看到config_path指向/etc/cinder/api-paste.ini,之前我们在博客Paste Deployment简介以及cinder-api-paste.ini的解析(1) 和Paste
Deployment简介以及cinder-api-paste.ini的解析(2) 中,已经进行过解析,cinder通过api-paste.ini中的配置信息来进行指定WSGI Application的实现。这里可以看到,在方法load_app中,调用了模块库deploy来实现对api-paste.ini中配置信息的解析和指定WSGI
Application的实现。
而通过上述两篇博客我们也可以知道,通过对api-paste.ini中配置信息的解析,最终就调用了WSGI Applicationapiv1的实现,具体就是cinder.api.v1.router:APIRouter.factory,这个WSGI
Application的具体功能就是实现模块功能的扩展和加载过程,所以这里我们就深入分析cinder.api.v1.router:APIRouter.factory的实现过程。
这里调用的方法是/cinder/api/openstack/__init__.py----class APIRouter(base_wsgi.Router)----def factory:
<font face="Consolas, Courier New, Courier, mono, serif" color="#000000"><span style="font-size: 12px; line-height: 18px;">def factory(cls, global_config, **local_config): </span></font>
return cls() 复制代码
输出示例:
cls =
global_config = {'__file__': '/etc/cinder/api-paste.ini', 'here': '/etc/cinder'}
所以我们来看类/cinder/api/v1/router.py----class APIRouter的具体实现:
class APIRouter(cinder.api.openstack.APIRouter):
"""
Routes requests on the OpenStack API to the appropriate controller
and method.
"""
ExtensionManager = extensions.ExtensionManager
def _setup_routes(self, mapper, ext_mgr):
...... 复制代码
进而来看其父类/cinder/api/openstack/__init__.py----class APIRouter的初始化方法:
class APIRouter(base_wsgi.Router):
def __init__(self, ext_mgr=None):
if ext_mgr is None:
if self.ExtensionManager:
ext_mgr = self.ExtensionManager() (1)
else:
raise Exception(_("Must specify an ExtensionManager class"))
mapper = ProjectMapper()
self.resources = {}
self._setup_routes(mapper, ext_mgr) (2)
self._setup_ext_routes(mapper, ext_mgr) (3)
self._setup_extensions(ext_mgr) (4)
super(APIRouter, self).__init__(mapper) (5) 复制代码
在这个类的初始化方法中,就实现了cinder模块功能的扩展和加载过程,这里我们就对其中比较重要的5条语句进行详细的解析,来看看cinder模块功能的扩展和加载过程的具体实现过程。
(1).ext_mgr = self.ExtensionManager()
这里实现的具体语句是类/cinder/api/v1/router.py----class APIRouter中的语句:
ExtensionManager = extensions.ExtensionManager
具体来看类ExtensionManager的初始化方法:
class ExtensionManager(object):
"""
Load extensions from the configured extension path.
See cinder/tests/api/extensions/foxinsocks/extension.py for an
example extension implementation.
"""
def __init__(self):
LOG.audit(_('Initializing extension manager.'))
self.extensions = {}
self._load_extensions() 复制代码
继续来看方法_load_extensions的实现:
def _load_extensions(self):
"""
Load extensions specified on the command line.
"""
# extensions=['cinder.api.contrib.standard_extensions']
extensions = list(self.cls_list)
old_contrib_path = ('cinder.api.openstack.volume.contrib.'
'standard_extensions')
new_contrib_path = 'cinder.api.contrib.standard_extensions'
if old_contrib_path in extensions:
extensions = [e.replace(old_contrib_path, new_contrib_path)
for e in extensions]
for ext_factory in extensions:
try:
self.load_extension(ext_factory)
except Exception as exc:
LOG.warn(......) 复制代码
这里最重要的语句就是:
for ext_factory in extensions:
try:
self.load_extension(ext_factory)
except Exception as exc:
LOG.warn(......)
输出示例:
extensions=['cinder.api.contrib.standard_extensions']
继续来看方法load_extension的实现源码:
def load_extension(self, ext_factory):
"""
ext_factory=['cinder.api.contrib.standard_extensions']
"""
# ext_factory = cinder.api.contrib.standard_extensions
factory = importutils.import_class(ext_factory)
factory(self) 复制代码
这里ext_factory = cinder.api.contrib.standard_extensions,所以调用的是方法/cinder/api/contrib/__init__.py----def standard_extensions;
我们继续来看方法:
def standard_extensions(ext_mgr):
extensions.load_standard_extensions(ext_mgr, LOG, __path__, __package__) 复制代码
def load_standard_extensions(ext_mgr, logger, path, package, ext_list=None):
"""
Registers all standard API extensions.
ext_mgr =
logger =
path = ['/usr/lib/python2.6/site-packages/cinder/api/contrib']
package = cinder.api.contrib
ext_list = None
"""
# our_dir = path[0] = /usr/lib/python2.6/site-packages/cinder/api/contrib
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
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:])
classpath = ("%s%s.%s.%s" % (package, relpkg, root, classname))
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'),
{'classpath': classpath, 'exc': exc})
# 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
ext_name = ("%s%s.%s.extension" %
(package, relpkg, dname))
try:
ext = importutils.import_class(ext_name)
except ImportError:
# 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), {'ext_name': ext_name, 'exc': exc})
# Update the list of directories we'll explore...
dirnames[:] = subdirs 复制代码
我们来看这个方法的实现,首先来看两个变量的输出示例:
our_dir = path[0] = /usr/lib/python2.6/site-packages/cinder/api/contrib
filenames = [
'volume_tenant_attribute.pyc', 'volume_type_encryption.py', 'volume_host_attribute.pyc', 'snapshot_actions.py', 'types_extra_specs.pyo', 'volume_actions.pyc', 'getname.pyc', 'volume_type_encryption.pyo', 'snapshot_actions.pyo', 'quota_classes.pyc', 'getname.py',
'services.pyc', 'image_create.py', 'snapshot_actions.pyc', 'services.pyo', 'image_create.pyo', 'volume_encryption_metadata.py', 'qos_specs_manage.pyc', 'availability_zones.pyc', 'quota_classes.py', 'quotas.pyc', 'volume_transfer.pyo', 'volume_tenant_attribute.pyo',
'quota_classes.pyo', 'image_create.pyc', 'volume_actions.py~', 'scheduler_hints.py~', 'snapshot_actions.py~', 'hosts.py', 'volume_host_attribute.pyo', 'hosts.pyo', 'extended_snapshot_attributes.py~', 'volume_encryption_metadata.py~', 'quotas.pyo', 'types_extra_specs.py',
'volume_image_metadata.pyc', 'volume_type_encryption.pyc', 'volume_image_metadata.pyo', 'volume_mig_status_attribute.py', 'scheduler_hints.py', 'volume_mig_status_attribute.pyo', 'qos_specs_manage.py~', 'services.py', 'volume_host_attribute.py', 'volume_encryption_metadata.pyc',
'hosts.pyc', 'scheduler_hints.pyo', '__init__.py', 'volume_encryption_metadata.pyo', 'volume_mig_status_attribute.pyc', 'backups.py~', 'volume_host_attribute.py~', 'quota_classes.py~', 'extended_snapshot_attributes.pyc', 'availability_zones.pyo', 'quotas.py',
'volume_type_encryption.py~', 'volume_transfer.pyc', 'volume_mig_status_attribute.py~', 'scheduler_hints.pyc', 'backups.pyc', 'volume_tenant_attribute.py~', '__init__.pyc', 'hosts.py~', 'backups.pyo', 'types_extra_specs.pyc', 'qos_specs_manage.py', 'types_manage.py~',
'admin_actions.pyo', 'volume_tenant_attribute.py', 'volume_actions.pyo', 'types_extra_specs.py~', 'admin_actions.pyc', 'extended_snapshot_attributes.pyo', 'qos_specs_manage.pyo', 'volume_image_metadata.py', '__init__.py~', 'types_manage.pyo', 'availability_zones.py',
'backups.py', 'volume_actions.py', 'types_manage.py', 'types_manage.pyc', 'extended_snapshot_attributes.py', '__init__.pyo', 'services.py~', 'volume_image_metadata.py~', 'availability_zones.py~', 'quotas.py~', 'volume_transfer.py~', 'admin_actions.py', 'volume_transfer.py']
从而我们可以知道,这个方法中会遍历路径/usr/lib/python2.6/site-packages/cinder/api/contrib,调用方法ext_mgr.load_extension加载路径/usr/lib/python2.6/site-packages/cinder/api/contrib下每个以.py后缀结尾的文件对应的类,如:
cinder.api.contrib.volume_type_encryption.Volume_type_encryption
cinder.api.contrib.snapshot_actions.Snapshot_actions
cinder.api.contrib.getname.Getname
cinder.api.contrib.image_create.Image_create
cinder.api.contrib.volume_encryption_metadata.Volume_encryption_metadata
cinder.api.contrib.quota_classes.Quota_classes
cinder.api.contrib.hosts.Hosts
cinder.api.contrib.types_extra_specs.Types_extra_specs
cinder.api.contrib.volume_mig_status_attribute.Volume_mig_status_attribute
cinder.api.contrib.scheduler_hints.Scheduler_hints
cinder.api.contrib.services.Services
cinder.api.contrib.volume_host_attribute.Volume_host_attribute
cinder.api.contrib.quotas.Quotas
cinder.api.contrib.qos_specs_manage.Qos_specs_manage
cinder.api.contrib.volume_tenant_attribute.Volume_tenant_attribute
cinder.api.contrib.volume_image_metadata.Volume_image_metadata
cinder.api.contrib.availability_zones.Availability_zones
cinder.api.contrib.backups.Backups
cinder.api.contrib.volume_actions.Volume_actions
cinder.api.contrib.types_manage.Types_manage
cinder.api.contrib.extended_snapshot_attributes.Extended_snapshot_attributes
cinder.api.contrib.admin_actions.Admin_actions
cinder.api.contrib.volume_transfer.Volume_transfer
从源码中可以看到,这里再次调用了方法load_extension:
def load_extension(self, ext_factory):
"""
Execute an extension factory.
"""
LOG.debug(_("Loading extension %s"), ext_factory)
# Load the factory
factory = importutils.import_class(ext_factory)
# Call it
LOG.debug(_("Calling extension factory %s"), ext_factory)
factory(self) 复制代码
这个方法中factory(self)会调用上述各个类,进一步看源码,发现上述各个类中都没有实现def __init__方法,都要调用其父类中的def __init__方法;
以cinder.api.contrib.backups.Backups为例:
class Backups(extensions.ExtensionDescriptor):
"""Backups support."""
name = 'Backups'
alias = 'backups'
namespace = 'http://docs.openstack.org/volume/ext/backups/api/v1'
updated = '2012-12-12T00:00:00+00:00'
def get_resources(self):
resources = []
res = extensions.ResourceExtension(
Backups.alias,
BackupsController(),
collection_actions={'detail': 'GET'},
member_actions={'restore': 'POST'})
resources.append(res)
return resources 复制代码
这里先会实现对变量name,alias,namespace,updated的赋值,然后会调用父类ExtensionDescriptor中的__init__方法。
class ExtensionDescriptor(object):
"""
Base class that defines the contract for extensions.
Note that you don't have to derive from this class to have a valid
extension; it is purely a convenience.
API扩展描述的基类;
"""
name = None
alias = None
namespace = None
updated = None
def __init__(self, ext_mgr):
"""
Register extension with the extension manager.
"""
ext_mgr.register(self) 复制代码
def register(self, ext):
# Do nothing if the extension doesn't check out
if not self._check_extension(ext):
return
alias = ext.alias
LOG.audit(_('Loaded extension: %s'), alias)
if alias in self.extensions:
raise exception.Error("Found duplicate extension: %s" % alias)
self.extensions[alias] = ext 复制代码
以cinder.api.contrib.volume_image_metadata.Volume_image_metadata为例,这里就是把变量alias = os-vol-image-meta和类cinder.api.contrib.volume_image_metadata.Volume_image_metadata一一对应起来;
再以cinder.api.contrib.backups.Backups为例,这里就是把变量alias = backups和类cinder.api.contrib.backups.Backups一一对应起来;
相关文章
OpenStack Cinder服务启动过程中的资源加载和扩展源码解析之二
http://www.aboutyun.com/thread-10223-1-1.html
OpenStack Cinder服务启动过程中的资源加载和扩展源码解析之三
http://www.aboutyun.com/thread-10224-1-1.html
原文链接:http://blog.csdn.net/gaoxingnengjisuan/article/details/21722111