之前我们了解了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的过程

how to read openstack code: loading process的更多相关文章

  1. hot code loading in nodejs

    Hot Code Loading in Node.js Node.js Web应用代码热更新的另类思路 Reading through Fever today, this post by Jack M ...

  2. how to read openstack code : routes

    When coding a web system, you have to think about an important problem, how to map urls to logic. Op ...

  3. how to read openstack code : wsgi

    要读懂本篇,你至少得写过一个python的web程序,并且把它部署到web服务器上过. 什么是wsgi 假设你写了一个python的web程序,并部署到了nginx上,那么一个http request ...

  4. 敏捷软件开发实践-Code Review Process(转)

    介绍: 在敏捷软件开发中,从代码的产生速度上来看,要比 传统Waterfall产生速度高很多.因为我们把时间安排的更加紧凑了.那么这么多的代码,如何能保证这些代码质量呢?很多人可能直接想到静态代码检测 ...

  5. how to read openstack code: request extension

    We have learned resource extension and action extension. This post we will write a request extension ...

  6. how to read openstack code: action extension

    之前我们看过了core plugin, service plugin 还有resource extension. resource extension的作用是定义新的资源.而我们说过还有两种exten ...

  7. how to read openstack code: service plugin

    We have learned core plugin, service plugin and extension in last post. Now let`s review: Core Plugi ...

  8. how to read openstack code: Core plugin and resource extension

    本章我们将写一个自己的core plugin 和一个resource extension来加深理解.(阅读本文的前提是你已经理解了restful以及stevedore等内容) 什么是 core plu ...

  9. how to read openstack code: Neutron architecture

    今天这一章节非常重要.我们知道neutron是一个非常复杂的系统,由很多组件构成.研究这样一个复杂的系统,正确的顺序应该是现在宏观上对其整体结构有所了解,然后再由针对性的对其组件进行深入了解.本章要做 ...

随机推荐

  1. Android(java)学习笔记172:服务(service)之绑定服务调用服务里面的方法 (采用接口隐藏代码内部实现)

    1. 接口 接口可以隐藏代码内部的细节,只暴露程序员想暴露的方法 2. 利用上面的思想优化之前的案例:服务(service)之绑定服务调用服务里面的方法,如下: (1)这里MainActivity.j ...

  2. java项目部署jar包

    1. 先将打包成jar包 2. 查看所有的java进程   pgrep java 3. 杀死进程 kill   -9 程序号 4.执行命令  nohup java -jar admin.jar > ...

  3. TensorFlow低阶API(二)—— 张量

    简介 正如名字所示,TensorFlow这一框架定义和运行涉及张量的计算.张量是对矢量和矩阵向潜在的更高维度的泛化.TensorFlow在内部将张量表示为基本数据类型的n维数组. 在编写TensorF ...

  4. Java集合系列之HashMap

    概要 第1部分 HashMap介绍 HashMap简介 HashMap 是一个散列表,它存储的内容是键值对(key-value)映射.HashMap 继承于AbstractMap,实现了Map.Clo ...

  5. Spring Data Redis入门示例:程序配置(五)

    单机配置 redis.properties配置 #redis的服务器地址 redis.host=127.0.0.1 #redis的服务端口 redis.port=6379 #客户端超时时间单位是毫秒 ...

  6. 从零实现一个http服务器

    我始终觉得,天生的出身很重要,但后天的努力更加重要,所以如今的很多“科班”往往不如后天努力的“非科班”.所以,我们需要重新给“专业”和“专家”下一个定义:所谓专业,就是别人搞你不搞,这就是你的“专业” ...

  7. AHOI2014/JSOI2014 奇怪的计算器

    题目描述 题解: 考虑到经过一系列变化后小数不可能比大数大,我们可以用线段树维护区间修改. 重点是,每个节点都可以通过$a[i]=a[i]*t1+a0[i]*t2+t3$这个函数来表示,我们就可以把三 ...

  8. Hibernate的核心配置

    Hibernate的设计思路 Hibernate是一种全自动化管理持久化对象的ORM框架,既提供了完全面向对象的封装完整的对象持久化接口(屏蔽db层的差异化,提升代码可移植性),也提供了操作HQL和S ...

  9. python3.6以上 asyncio模块的异步编程模型 async await语法

    这是python3.6以上版本的用法,本例是python3.7.2编写使用asyncio模块的异步编程模型,生产这消费者,异步生产,用sleep来代替IO等待使用async和await语法来进行描述a ...

  10. python_正则_re模块

    正则表达式元字符: 字符匹配 : . :除换行符以外的任意单个字符 [] :指定范围内字符 [^] :指定范围外字符 次数匹配: * :任意次,0,1,多次 .* :任意字符 任意次 ? :至多1次或 ...