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