我们知道neutron在处理路由时,是交个Controller,而Controller又会交给plugin去处理,要想入新功能,就得知道plugin怎么加载的。下面我们就捋一下neutron如何加载的core_plugins的。
neutron的好多资源都是由NeutronManager管理的。plugin也是由它加载的。
我们从入口开始
neutron/pecan_wsgi/startup.py
def initialize_all():
manager.init()
我们进入manager.init()会发现,就是去看NeutronManager的一个单例对象存在不存在,不存在就实例化一个。也就是我们下一步需要看下NeutronManager的构造函数。
进入构造NeutronManager单例对象
def __init__(self, options=None, config_file=None):
# Store instances of already loaded plugins to avoid instantiate same
# plugin more than once
self._loaded_plugins = {}
# 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("Loading core plugin: %s", plugin_provider)
# NOTE(armax): keep hold of the actual plugin object
plugin = self._get_plugin_instance(CORE_PLUGINS_NAMESPACE,
plugin_provider)
directory.add_plugin(lib_const.CORE, plugin)
...
我们能看出来plugin = self._get_plugin_instance(CORE_PLUGINS_NAMESPACE, plugin_provider)就是去加载plugin。
我们可以轻松的知道CORE_PLUGINS_NAMESPACE=‘neutron.core_plugins’,plugin_provider这个值是从配置文件里读的。
因此我们也知道plugin_provider的值为ml2。
去发现如何返回的一个plugin对象
我们通过pdb或者其他调试工具,可以知道plugin最终是一个neutron.plugins.ml2.plugin:Ml2Plugin对象。
我们会发现再一步一步进入
runtime.load_class_by_alias_or_classname(namespace,
plugin_provider)
def load_class_by_alias_or_classname(namespace, name):
...
try:
# Try to resolve class by alias
mgr = driver.DriverManager(
namespace, name, warn_on_missing_entrypoint=False)
class_to_load = mgr.driver
except RuntimeError:
e1_info = sys.exc_info()
# Fallback to class name
....
从这代码看,mgr.driver是我们最终想要的,也就是driver.DriverManager生成的对象的driver属性
再进入driver.DriverManager的构造函数,以及driver的定义
def __init__(self, namespace, name,
invoke_on_load=False, invoke_args=(), invoke_kwds={},
on_load_failure_callback=None,
verify_requirements=False,
warn_on_missing_entrypoint=True):
on_load_failure_callback = on_load_failure_callback \
or self._default_on_load_failure
super(DriverManager, self).__init__(
namespace=namespace,
names=[name],
invoke_on_load=invoke_on_load,
invoke_args=invoke_args,
invoke_kwds=invoke_kwds,
on_load_failure_callback=on_load_failure_callback,
verify_requirements=verify_requirements,
warn_on_missing_entrypoint=warn_on_missing_entrypoint
)
...
@property
def driver(self):
"""Returns the driver being used by this manager.
"""
ext = self.extensions[0]
return ext.obj if ext.obj else ext.plugin
...
也就是我们需要关注extensions值是怎么赋进去的。
再进入DriverManager父类NamedExtensionManager的构造函数
会发现有
extensions = self._load_plugins(invoke_on_load,
invoke_args,
invoke_kwds,
verify_requirements)
def _load_plugins(self, invoke_on_load, invoke_args, invoke_kwds,
verify_requirements):
extensions = []
for ep in self.list_entry_points():
LOG.debug('found extension %r', ep)
try:
ext = self._load_one_plugin(ep,
invoke_on_load,
invoke_args,
invoke_kwds,
verify_requirements,
)
if ext:
extensions.append(ext)
...
也即是需要看一下ep是是个啥。也就是需要进入self.list_entry_points()
def list_entry_points(self):
"""Return the list of entry points for this namespace.
The entry points are not actually loaded, their list is just read and
returned.
"""
if self.namespace not in self.ENTRY_POINT_CACHE:
eps = list(pkg_resources.iter_entry_points(self.namespace))
self.ENTRY_POINT_CACHE[self.namespace] = eps
return self.ENTRY_POINT_CACHE[self.namespace]
这是在干嘛呢,就是判断namesapce在不在缓存字典了,在的话就返回,不在加载一下。这时namespace是我们传过来的neutron.core_plugins。我们看到了pkg_resources.iter_entry_points(self.namesapce),具体如何加载,我没去研究,它会去你模块的setup.cfg就发现入口。
我们去看一下neutron的setup.cfg。就可以发现
...
neutron.core_plugins =
ml2 = neutron.plugins.ml2.plugin:Ml2Plugin
neutron.service_plugins =
dummy = neutron.tests.unit.dummy_plugin:DummyServicePlugin
router = neutron.services.l3_router.l3_router_plugin:L3RouterPlugin
...
我们发现了neutron.core_plugins这个命名空间的定义,也发现了ml2的定义,正好是neutron.plugins.ml2.plugin:Ml2Plugin。
总结一下
饶了很大一圈,看了很多代码,runtime.load_class_by_alias_or_classname(namespace,
plugin_provider),可以实现的一个功能就是从neutron setup.cfg里定义的入口,加载成一个具体类。然后自己可以去实例化一个对象。