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’: , 'ROUTER': , 'ZOO': }

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  阅读(341)  评论(0)    收藏  举报

导航