Liberty nova-api load app过程跟踪

本博客欢迎转载,但请注明出处 http://blog.csdn.net/ringoshen/article/details/51334973

由于能力与时间有限,文章内容难免错漏,望大家多加指正,相互进步!

之前的文章中已经分析了nova-api的整体启动流程,但是其中一些重要的细节并没有深入看,今天这篇文章主要看一下nova-api中的loadapp的过程。至于URLMap、APIRouter以及各个Filter的功能流程之后在解析http请求的时候会做一下分析。
接下来还是直接看一下代码。

# nova/service.py

class WSGIService(service.Service):
    def __init__(self, name, loader=None, use_ssl=False, max_url_len=None):
        ... ...
        ... ...
        # 接下来2步加载app,app的信息在"/etc/nova/api-paste.ini",
        # loader = <nova.wsgi.Loader object at 0x7f6f2b5b1ed0>
        self.loader = loader or wsgi.Loader()
        # app = <nova.api.openstack.urlmap.URLMap object at 0x7f6f2b541790>
        # 这边主要返回一个URLMap对象,从名字上就知道是用来根据url路由到app
        self.app = self.loader.load_app(name)
        ... ...
        ... ...

加载app代码跟踪

这边简单跟踪一下整体的代码调用过程,不做细节的介绍。

# nova/wsgi.py

class Loader(object):
    ... ...
    def load_app(self, name):
        try:
            LOG.debug("Loading app %(name)s from %(path)s",
                      {'name': name, 'path': self.config_path})
            # deploy.loadapp("config:'/etc/nova/api-paste.ini'", name='osapi_compute')
            return deploy.loadapp("config:%s" % self.config_path, name=name)
        except LookupError:
            LOG.exception(_LE("Couldn't lookup app: %s"), name)
            raise exception.PasteAppNotFound(name=name, path=self.config_path)

之后的流程就开始在paste.deploy中执行。

# paste/deploy/loadwsgi.py

# uri = 'config:/etc/nova/api-paste.ini'
# name = 'osapi_compute'
# kw = {}
# 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):
    # object_type = <application protocols=[['paste.app_factory'], ['paste.composite_factory'], ['paste.composit_factory']] prefixes=[['app', 'application'], ['composite', 'composit'], ['pipeline'], ['filter-app']]>
    # uri = 'config:/etc/nova/api-paste.ini'
    # name = 'osapi_compute'
    # 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):
    ... ...
    ... ...
    # scheme = 'config'
    # _loaders['config'] = _loadconfig
    return _loaders[scheme](
        object_type,
        uri, path, name=name, relative_to=relative_to,
        global_conf=global_conf)
def _loadconfig(object_type, uri, path, name, relative_to, global_conf):
    ... ...
    ... ...
    # path = '/etc/nova/api-paste.ini'
    path = unquote(path)
    # loader = <paste.deploy.loadwsgi.ConfigLoader object at 0x7fe4c77f2e50>
    loader = ConfigLoader(path)
    if global_conf:
        loader.update_defaults(global_conf, overwrite=False)
    return loader.get_context(object_type, name, global_conf)
class ConfigLoader(_Loader):

    def __init__(self, filename):
        # filename = '/etc/nova/api-paste.ini'
        self.filename = filename = filename.strip()
        # defaults = {'__file__': '/etc/nova/api-paste.ini', 'here': '/etc/nova'}
        defaults = {
            'here': os.path.dirname(os.path.abspath(filename)),
            '__file__': os.path.abspath(filename)
            }
        self.parser = NicerConfigParser(filename, defaults=defaults)
        self.parser.optionxform = str  # Don't lower-case keys
        with open(filename) as f:
            self.parser.read_file(f)

    def get_context(self, object_type, name=None, global_conf=None):
        if self.absolute_name(name):
            return loadcontext(object_type, name,
                               relative_to=os.path.dirname(self.filename),
                               global_conf=global_conf)
        # section = 'composite:osapi_compute'
        section = self.find_config_section(
            object_type, name=name)
        ... ...
        ... ...
        # 循环读取section成local_conf和global_conf
        for option in self.parser.options(section):
            if option.startswith('set '):
                name = option[4:].strip()
                global_additions[name] = global_conf[name] = (
                    self.parser.get(section, option))
            elif option.startswith('get '):
                name = option[4:].strip()
                get_from_globals[name] = self.parser.get(section, option)
            else:
                if option in defaults:
                    continue
                local_conf[option] = self.parser.get(section, option)
        # local_conf = {'/v2': 'openstack_compute_api_v21_legacy_v2_compatible', '/v2.1': 'openstack_compute_api_v21', 'use': 'call:nova.api.openstack.urlmap:urlmap_factory', '/': 'oscomputeversions'}
        # global_conf = {'__file__': '/etc/nova/api-paste.ini', 'here': '/etc/nova'}
        ... ...
        ... ...
        if section.startswith('filter-app:'):
            context = self._filter_app_context(
                object_type, section, name=name,
                global_conf=global_conf, local_conf=local_conf,
                global_additions=global_additions)
        elif section.startswith('pipeline:'):
            context = self._pipeline_app_context(
                object_type, section, name=name,
                global_conf=global_conf, local_conf=local_conf,
                global_additions=global_additions)
        elif 'use' in local_conf:
            context = self._context_from_use(
                object_type, local_conf, global_conf, global_additions,
                section)
        else:
            context = self._context_from_explicit(
                object_type, local_conf, global_conf, global_additions,
                section)
        if filter_with is not None:
            filter_with_context = LoaderContext(
                obj=None,
                object_type=FILTER_WITH,
                protocol=None,
                global_conf=global_conf, local_conf=local_conf,
                loader=self)
            filter_with_context.filter_context = self.filter_context(
                name=filter_with, global_conf=global_conf)
            filter_with_context.next_context = context
            return filter_with_context
        return context

    def _context_from_use(self, object_type, local_conf, global_conf, global_additions, section):
        # 这边真正取到urlmap_factory
        # use = 'call:nova.api.openstack.urlmap:urlmap_factory'
        use = local_conf.pop('use')
        context = self.get_context(
            object_type, name=use, global_conf=global_conf)
        context.global_conf.update(global_additions)
        context.local_conf.update(local_conf)
        if '__file__' in global_conf:
            context.global_conf['__file__'] = global_conf['__file__']
        context.loader = self

        if context.protocol is None:
            section_protocol = section.split(':', 1)[0]
            if section_protocol in ('application', 'app'):
                context.protocol = 'paste.app_factory'
            elif section_protocol in ('composit', 'composite'):
                context.protocol = 'paste.composit_factory'
            else:
                context.protocol = 'paste.%s_factory' % section_protocol

        return context

之后又调用回loadcontext方法。

def loadcontext(object_type, uri, name=None, relative_to=None, global_conf=None):
    ... ...
    ... ...
    # 此处开始加载urlmap的步骤
    # scheme = 'call'
    # _loaders['call'] = _loadfunc
    return _loaders[scheme](
        object_type,
        uri, path, name=name, relative_to=relative_to,
        global_conf=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)
class FuncLoader(_Loader):
    def __init__(self, spec):
        self.spec = spec
        if not ':' in spec:
            raise LookupError("Configuration not in format module:function")

    def get_context(self, object_type, name=None, global_conf=None):
        # 从uri -> module的过程主要是通过这个方法实现,实现方式主要是动态加载
        # module = __import__(parts)
        obj = lookup_object(self.spec)
        return LoaderContext(obj, object_type, None, global_conf or {}, {}, self,)
class LoaderContext(object):

    def __init__(self, obj, object_type, protocol, global_conf, local_conf, loader, distribution=None, entry_point_name=None):
        self.object = obj
        self.object_type = object_type
        self.protocol = protocol
        self.global_conf = global_conf
        self.local_conf = local_conf
        self.loader = loader
        self.distribution = distribution
        self.entry_point_name = entry_point_name

    # 之前的create方法的定义在此
    def create(self):
        return self.object_type.invoke(self)

这是之前APP = _App()的定义

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

之后调用了公共模块

# paste/deploy/util.py

def fix_call(callable, *args, **kw):
    """ Call ``callable(*args, **kw)`` fixing any type errors that come out. """
    try:
        # ###### 此处真正调用urlmap_factory工厂方法 ######
        val = callable(*args, **kw)
    except TypeError:
        exc_info = fix_type_error(None, callable, args, kw)
        reraise(*exc_info)
    return val
# nova/api/openstack/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 = paste.urlmap.parse_path_expression(path)
        # 类似的方法加载section里面的各类app
        app = loader.get_app(app_name, global_conf=global_conf)
        urlmap[path] = app
    return urlmap

至此,加载app的方法调用过程就走完一遍了,已经加载到我们需要的urlmap_factory,至于urlmap里面的app的加载方式跟上述方式整体上是大致一样的,这里不继续跟踪,不过可以看一下filter包裹app的过程。

# nova/api/auth.py

# 在Liberty版本中采用的openstack compute api是v2.1
def pipeline_factory_v21(loader, global_conf, **local_conf):
    """A paste pipeline replica that keys off of auth_strategy."""
    return _load_pipeline(loader, local_conf[CONF.auth_strategy].split())
# nova/api/auth.py

def _load_pipeline(loader, pipeline):
    # 加载pipeline中各个filter
    filters = [loader.get_filter(n) for n in pipeline[:-1]]
    # 加载app(osapi_compute_app_v21)
    app = loader.get_app(pipeline[-1])
    # 倒序filter
    filters.reverse()
    # 反复使用filter包装app
    for filter in filters:
        app = filter(app)
    return app

你可能感兴趣的:(load-app)