OpenStack Cinder服务启动过程中的资源加载和扩展源码解析之一

来源:互联网 发布:c语言中system 编辑:程序博客网 时间:2024/05/14 09:28

感谢朋友支持本博客,欢迎共同探讨交流,由于能力和时间有限,错误之处在所难免,欢迎指正!
如果转载,请保留作者信息。
博客地址:http://blog.csdn.net/gaoxingnengjisuan
邮箱地址:dong.liu@siat.ac.cn


在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:

def factory(cls, global_config, **local_config):    return cls()
输出示例:

cls = <class 'cinder.api.v1.router.APIRouter'>
global_config = {'__file__': '/etc/cinder/api-paste.ini', 'here': '/etc/cinder'}
local_config = {}

所以我们来看类/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 = <cinder.api.extensions.ExtensionManager object at 0x1724190>    logger = <cinder.openstack.common.log.ContextAdapter instance at 0x172c830>    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一一对应起来;

0 0
原创粉丝点击