how to read openstack code: loading process

之前我们了解了neutron的结构,plugin 和 extension等信息。这一章我们看一下neutron如何加载这些plugin和extension。也就是neutron的启动过程。本文涉及的代码较多,而且调用过程复杂... 所以你手头最好有一份liberty版本的neutron代码,参考来看

回顾一下neutron的paste配置文件

[composite:neutron]
use = egg:Paste#urlmap
/: neutronversions
/v2.0: neutronapi_v2_0

[composite:neutronapi_v2_0]
use = call:neutron.auth:pipeline_factory
noauth = request_id catch_errors extensions neutronapiapp_v2_0
keystone = request_id catch_errors authtoken keystonecontext extensions neutronapiapp_v2_0

[filter:request_id]
paste.filter_factory = oslo_middleware:RequestId.factory

[filter:catch_errors]
paste.filter_factory = oslo_middleware:CatchErrors.factory

[filter:keystonecontext]
paste.filter_factory = neutron.auth:NeutronKeystoneContext.factory

[filter:authtoken]
paste.filter_factory = keystonemiddleware.auth_token:filter_factory

[filter:extensions]
paste.filter_factory = neutron.api.extensions:plugin_aware_extension_middleware_factory

[app:neutronversions]
paste.app_factory = neutron.api.versions:Versions.factory

[app:neutronapiapp_v2_0]
paste.app_factory = neutron.api.v2.router:APIRouter.factory

最重要的是extensions neutronapiapp_v2_0这两个组件。前一个是wsgi middleware, 后一个是wsgi app。 我们先看后一个,因为按照加载顺序,也必然是它先加载

neutronapiapp_v2_0

从配置文件不难定义到其代码在 neutron.api.v2.router.py 的 APIRouter类的factory函数。这是设计模式中的工厂函数。虽然python可能不太适用JAVA设计模式,但。。可能这些程序员之前是java程序员。该factory函数的作用就是构建APIRouter类的一个实例。我们看一下构造函数

    def __init__(self, **local_config):
        mapper = routes_mapper.Mapper()
        plugin = manager.NeutronManager.get_plugin()
        ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
        ext_mgr.extend_resources("2.0", attributes.RESOURCE_ATTRIBUTE_MAP)

        col_kwargs = dict(collection_actions=COLLECTION_ACTIONS,
                          member_actions=MEMBER_ACTIONS)

        def _map_resource(collection, resource, params, parent=None):
            allow_bulk = cfg.CONF.allow_bulk
            allow_pagination = cfg.CONF.allow_pagination
            allow_sorting = cfg.CONF.allow_sorting
            controller = base.create_resource(
                collection, resource, plugin, params, allow_bulk=allow_bulk,
                parent=parent, allow_pagination=allow_pagination,
                allow_sorting=allow_sorting)
            path_prefix = None
            if parent:
                path_prefix = "/%s/{%s_id}/%s" % (parent['collection_name'],
                                                  parent['member_name'],
                                                  collection)
            mapper_kwargs = dict(controller=controller,
                                 requirements=REQUIREMENTS,
                                 path_prefix=path_prefix,
                                 **col_kwargs)
            return mapper.collection(collection, resource,
                                     **mapper_kwargs)

        mapper.connect('index', '/', controller=Index(RESOURCES))
        for resource in RESOURCES:
            _map_resource(RESOURCES[resource], resource,
                          attributes.RESOURCE_ATTRIBUTE_MAP.get(
                              RESOURCES[resource], dict()))
            resource_registry.register_resource_by_name(resource)

        for resource in SUB_RESOURCES:
            _map_resource(SUB_RESOURCES[resource]['collection_name'], resource,
                          attributes.RESOURCE_ATTRIBUTE_MAP.get(
                              SUB_RESOURCES[resource]['collection_name'],
                              dict()),
                          SUB_RESOURCES[resource]['parent'])

        # Certain policy checks require that the extensions are loaded
        # and the RESOURCE_ATTRIBUTE_MAP populated before they can be
        # properly initialized. This can only be claimed with certainty
        # once this point in the code has been reached. In the event
        # that the policies have been initialized before this point,
        # calling reset will cause the next policy check to
        # re-initialize with all of the required data in place.
        policy.reset()
        super(APIRouter, self).__init__(mapper)

这部分代码调用若要展开详细说明会非常多,所以我们只挑重点部分,分为几个小节大概说明。

mapper是routes的mapper。它的后面会把url 和 逻辑处理模块映射起来。这里先创建,后面会增加映射。

NeutronManager

plugin = manager.NeutronManager.get_plugin() 比较关键。 所以我们这里单独用一个小节来说明。这里最关键的就是neutron manager这个类

"""Neutron's Manager class.

Neutron's Manager class is responsible for parsing a config file and
instantiating the correct plugin that concretely implements
neutron_plugin_base class.
The caller should make sure that NeutronManager is a singleton.
"""

上面是NeutronManager的注释,有几点内容注意:

singleton: 就是说在使用该类时要确保它是单实例的
instantiating plugin: 就是说该类是用来加载plugin的

这里说加载所有正确实施了neutron_plugin_base接口并且在配置文件中配置了的plugin,有点不太准确。因为core plugin 和service plugin的接口其实是不一样的。不过,我们只要知道它加载plugin的就可以了。这里plugin 包括core和service

单实例是确保这个class只实例化一次,这样plugin也就只会被加载一次。 下面我们看一下这个class 如何加载plugin。其构造函数代码如下

def __init__(self, options=None, config_file=None):
    # If no options have been provided, create an empty dict
    if not options:
        options = {}

    msg = validate_pre_plugin_load()
    if msg:
        LOG.critical(msg)
        raise Exception(msg)

    # NOTE(jkoelker) Testing for the subclass with the __subclasshook__
    #                breaks tach monitoring. It has been removed
    #                intentionally to allow v2 plugins to be monitored
    #                for performance metrics.
    plugin_provider = cfg.CONF.core_plugin
    LOG.info(_LI("Loading core plugin: %s"), plugin_provider)
    self.plugin = self._get_plugin_instance(CORE_PLUGINS_NAMESPACE,
                                            plugin_provider)
    msg = validate_post_plugin_load()
    if msg:
        LOG.critical(msg)
        raise Exception(msg)

    # core plugin as a part of plugin collection simplifies
    # checking extensions
    # TODO(enikanorov): make core plugin the same as
    # the rest of service plugins
    self.service_plugins = {constants.CORE: self.plugin}
    self._load_service_plugins()
    # Used by pecan WSGI
    self.resource_plugin_mappings = {}
    self.resource_controller_mappings = {}

plugin_provider = cfg.CONF.core_plugin就是配置文件/etc/neutron/neutron.conf中

[default]
core_plugin = 

的内容。通常这里是ml2。不管是什么,这里是core plugin注册在neutron.core_plugins下的entry point名字。如果你继续追踪self._get_plugin_instance的代码,就会发现是我们之前讲过的stevedore调用。
下面比较关键的地方是这段注释和代码

    # core plugin as a part of plugin collection simplifies
    # checking extensions
    # TODO(enikanorov): make core plugin the same as
    # the rest of service plugins
    self.service_plugins = {constants.CORE: self.plugin}
    self._load_service_plugins()

从注释中可知core plugin将会被当作service plugin处理。这是个趋势.

这个类的service_plugins属性是一个字典,字典的key是service type而value是service_plugin实例。 这里把core plugin当作一种service plugin加了进去。下面的self._load_service_plugins会继续填充这个字典,加载配置文件中配置的所有service plugin。

假设我们配置文件中配置了

core_plugin = ml2
service_plugin = router, zoo

那么现在这个字典的值就是

{‘CORE’: <ML2 instance>, 'ROUTER': <ROUTER instance>, 'ZOO': <ZOO instance>}

ok,说了那么多。我们现在总结一个重点就是,NeutronManager负责加载所有的配置在配置文件中的core plugin和service plugin。我们继续看

ExtensionManager

接着看ext_mgr = extensions.PluginAwareExtensionManager.get_instance()这行代码。这里最关键的自然是PluginAwareExtensionManager这个类。但为什么这一小节不是说它呢?因为它继承自ExtensionManager。

extensions.PluginAwareExtensionManager.get_instance()

这里的get_instance猜也可以猜到是获取PluginAwareExtensionManager的实例的。不过还是有一点玄机的:

@classmethod
def get_instance(cls):
    if cls._instance is None:
        service_plugins = manager.NeutronManager.get_service_plugins()
        cls._instance = cls(get_extensions_path(service_plugins),
                            service_plugins)
    return cls._instance

最初状态下,需要先获取service_plugins这个字典。然后调用get_extensions_path获取系统中存放所有extension的路径。再然后才是运行PluginAwareExtension的构造函数

def __init__(self, path, plugins):
    self.plugins = plugins
    super(PluginAwareExtensionManager, self).__init__(path)
    self.check_if_plugin_extensions_loaded()

而这个构造函数就三行。大部分的工作都是第二行,也就是我们这一小节的主角ExtensionManager的构造函数。 OK。。。我们这一小节的主题终于开始了。

def _load_all_extensions_from_path(self, path):
    # Sorting the extension list makes the order in which they
    # are loaded predictable across a cluster of load-balanced
    # Neutron Servers
    for f in sorted(os.listdir(path)):
        try:
            LOG.debug('Loading extension file: %s', f)
            mod_name, file_ext = os.path.splitext(os.path.split(f)[-1])
            ext_path = os.path.join(path, f)
            if file_ext.lower() == '.py' and not mod_name.startswith('_'):
                mod = imp.load_source(mod_name, ext_path)
                ext_name = mod_name[0].upper() + mod_name[1:]
                new_ext_class = getattr(mod, ext_name, None)
                if not new_ext_class:
                    LOG.warn(_LW('Did not find expected name '
                                 '"%(ext_name)s" in %(file)s'),
                             {'ext_name': ext_name,
                              'file': ext_path})
                    continue
                new_ext = new_ext_class()
                self.add_extension(new_ext)
        except Exception as exception:
            LOG.warn(_LW("Extension file %(f)s wasn't loaded due to "
                         "%(exception)s"),
                     {'f': f, 'exception': exception})

根据构造函数追踪,可以追踪到上面这个类,读代码可知,该类会加载系统中extension path,默认是neutron/extensions下所有的extension。加载的时候有几个条件

  • extension的名字必须是file名和class同名,不过class名字的首字母要大写
  • extension的文件名不能开头是_

这些也正符合我们之前说的写extension要符合的几个要求。

OK, 总结重点,ExtensionManager加载了所有的extension,存在它的extensions属性中。该属性是一个字典,key是extension的别名,value是extension的实例

PluginAwareExtensionManager

上面我们已经说了PluignAwareExtensionManager的构造函数是下面这样。

def __init__(self, path, plugins):
    self.plugins = plugins
    super(PluginAwareExtensionManager, self).__init__(path)
    self.check_if_plugin_extensions_loaded()

目前为止,我们知道了NeutronManager加载所有plugin, ExtensionManager加载extension,那么这个类是干嘛的呢?从名字看它说Plugin aware Extension,其实正是这样。

之前的blog中我们说extension有三种

  • resource extension 新增资源
  • action extension 新增action
  • request extension 扩充request

很多plugin独自不能实现所需的api功能,这些plugin就需要extension。plugin有一个supported_extension_alias记录了它需要的extension的列表。这个类就会根据这个列表检查plugin所需的extension是否加载了。

看这个构造函数,第一行获取所有的plugin,第二行获取所有的extension,第三行不言而喻,就是根据plugin来check extension是否加载

ExtensionManager.extend_resource

我们看看下面的代码。

    ext_mgr.extend_resources("2.0", attributes.RESOURCE_ATTRIBUTE_MAP)

目前为止我们加载了所有的plugin,所有的extension,并且确保了plugin所需的extension都加载了。这一行代码其实是跟我们之前说的request extension相关

我们知道request extension的作用是为request添加一些参数。这些参数落到RESTful的资源中其实就是资源的属性,既然是资源的属性,那么就需要在RESOURCE_ATTRIBUTE_MAP定义的资源中有相应的内容。比如

最初的RESOURCE_ATTRIBUTE_MAP定义可能如下:

RESOURCE_ATTRIBUTE_MAP = {
	NETWORKS: {
		'id': {'allow_post': False, 'allow_put': False,
			   'validate': {'type:uuid': None},
			   'is_visible': True,
			   'primary_key': True},
		'name': {'allow_post': True, 'allow_put': True,
				 'validate': {'type:string': NAME_MAX_LEN},
				 'default': '', 'is_visible': True},
		'subnets': {'allow_post': False, 'allow_put': False,
					'default': [],
					'is_visible': True},
		'admin_state_up': {'allow_post': True, 'allow_put': True,
						   'default': True,
						   'convert_to': convert_to_boolean,
						   'is_visible': True},
		'status': {'allow_post': False, 'allow_put': False,
				   'is_visible': True},
		'tenant_id': {'allow_post': True, 'allow_put': False,
					  'validate': {'type:string': TENANT_ID_MAX_LEN},
					  'required_by_policy': True,
					  'is_visible': True},
		SHARED: {'allow_post': True,
				 'allow_put': True,
				 'default': False,
				 'convert_to': convert_to_boolean,
				 'is_visible': True,
				 'required_by_policy': True,
				 'enforce_policy': True},
	},

这里并没有 some_attr这个属性,所以你在创建网络的时候如果带了这个属性就会报错。而resource extension会update这个字典,添加这个属性。

ext_mgr是ExtensionManager的实例,它拥有所有的extension,因此通过遍历自己的extension,找到所有的request extension并用其update RESOURCE_ATTRIBUTE_MAP,可以把API需要的属性注册到系统中。换句话说,到了这里,所有的resource extension 的最主要任务完成了。

_map_resource

接下来最重要的是这个函数。该函数的作用是把resource的URL 和 controller映射起来。 Resource是作为参数传入的,重点是controller。它是由

        controller = base.create_resource(
            collection, resource, plugin, params, allow_bulk=allow_bulk,
            parent=parent, allow_pagination=allow_pagination,
            allow_sorting=allow_sorting)

这段话创建的。这个controller最后会包装成一个wsgi的资源。这一过程中 POST/GET 等HTTP method 被翻译成create 和 get等action,而controller由通过这些action去调用plugin中对应的方法,进而完成api操作。

重要的是这里处理的只有核心资源

到这里APIRouter的主要内容就分析完了。接下来看 extension middleware

ExtensionMiddleware

从paste的文件首先找到这个工厂函数

def plugin_aware_extension_middleware_factory(global_config, **local_config):
    """Paste factory."""
    def _factory(app):
        ext_mgr = PluginAwareExtensionManager.get_instance()
        return ExtensionMiddleware(app, ext_mgr=ext_mgr)
    return _factory

它返回了一个PluginAwareExtensionManager的实例。这个实例我们之前说过,没什么特殊的。重要的是它拥有所有的配置的plugin和extension。重点看ExtensionMiddleware。其构造函数如下

def __init__(self, application, ext_mgr=None):
    self.ext_mgr = (ext_mgr or ExtensionManager(get_extensions_path()))
    mapper = routes.Mapper()

    # extended resources
    for resource in self.ext_mgr.get_resources():
        path_prefix = resource.path_prefix
        if resource.parent:
            path_prefix = (resource.path_prefix +
                           "/%s/{%s_id}" %
                           (resource.parent["collection_name"],
                            resource.parent["member_name"]))

        LOG.debug('Extended resource: %s',
                  resource.collection)
        for action, method in six.iteritems(resource.collection_actions):
            conditions = dict(method=[method])
            path = "/%s/%s" % (resource.collection, action)
            with mapper.submapper(controller=resource.controller,
                                  action=action,
                                  path_prefix=path_prefix,
                                  conditions=conditions) as submap:
                submap.connect(path_prefix + path, path)
                submap.connect(path_prefix + path + "_format",
                               "%s.:(format)" % path)

        mapper.resource(resource.collection, resource.collection,
                        controller=resource.controller,
                        member=resource.member_actions,
                        parent_resource=resource.parent,
                        path_prefix=path_prefix)

    # extended actions
    action_controllers = self._action_ext_controllers(application,
                                                      self.ext_mgr, mapper)
    for action in self.ext_mgr.get_actions():
        LOG.debug('Extended action: %s', action.action_name)
        controller = action_controllers[action.collection]
        controller.add_action(action.action_name, action.handler)

    # extended requests
    req_controllers = self._request_ext_controllers(application,
                                                    self.ext_mgr, mapper)
    for request_ext in self.ext_mgr.get_request_extensions():
        LOG.debug('Extended request: %s', request_ext.key)
        controller = req_controllers[request_ext.key]
        controller.add_handler(request_ext.handler)

    self._router = routes.middleware.RoutesMiddleware(self._dispatch,
                                                      mapper)
    super(ExtensionMiddleware, self).__init__(application)

我们挑重点说。这里仅有的三个注释其实已经说明了这个middleware的重点。extension有三种,这里对应着这三条extension的处理

首先是resource extension

self.ext_mgr.get_resources是extension manager的函数,它遍历所有的resource extension,获取它们定义的所有resource,返回一个resource列表。接下来的内容就简单了,生成controller,映射url。

其次是action extension

同上。。

接着request extension

同上。。

OK 上面就是neutron启动加载所有plugin和extension的过程

posted on 2017-02-09 23:30  kramer  阅读(311)  评论(0编辑  收藏  举报

导航