首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 服务器 > 云计算 >

OpenStack源码分析之cinder-api服务起步

2013-03-16 
OpenStack源码分析之cinder-api服务启动版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处

OpenStack源码分析之cinder-api服务启动

版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处!


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-apiimport 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.pyclass 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.pyclass 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.pyclass _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.pydef 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.pydef 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.pyclass 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.pyclass 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__.pydef standard_extensions(ext_mgr):    extensions.load_standard_extensions(ext_mgr, LOG, __path__, __package__)   调用的是extensions.py中的load_standard_extensions 方法.

cinder/api/extensions.pydef 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.pyclass 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.pydef 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.pyAPI = cinder.openstack.common.importutils.import_class(cinder.flags.FLAGS.volume_api_class)
在flages.py中volume_api_class默认值为 cinder.volume.api.API
cinder/volume/api.pyfrom 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.pyclass 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.Resourcecinder/api/openstack/wsgi.pyclass 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.pydef 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__)

热点排行