• 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的过程

  • 相关阅读:
    模拟黑客入侵,恶搞小伙伴的利器
    牛客网算法竞赛难题
    ybt ——1346【例4-7】亲戚
    FBI树
    noi2020第二题
    noi2020第一题
    Happiness
    YiJuWuLiaoDeHua
    挂掉了一个u盘
    NOIp2020
  • 原文地址:https://www.cnblogs.com/kramer/p/6384629.html
Copyright © 2020-2023  润新知