Ocata Neutron代码分析(六)——APIRouter的初始化(1)加载core plugin和service plugin
在分析api-paste.ini时,曾分析到wsgi app neutronapiapp_v2_0是直接调用/neutron/api/v2/router.py中APIRouter的factory方法:
class APIRouter(base_wsgi.Router): @classmethod def factory(cls, global_config, **local_config): return cls(**local_config)
APIRouter的factory方法是一个classmethod,直接调用APIRouter的构造函数,对APIRouter进行初始化。下面重点分析一下APIRouter类的构造函数:
class APIRouter(base_wsgi.Router): def __init__(self, **local_config): mapper = routes_mapper.Mapper() # 获取routes.Mapper的实例。 manager.init() # 初始化NeutronManager来读取配置文件和加载plugin,该部分最关键的步骤。 plugin = directory.get_plugin() # 从_PLUGIN_DIRECTORY._plugin中获取core plugin。 ext_mgr = extensions.PluginAwareExtensionManager.get_instance() # 根据所有的plugins获取相应path并加载extensions。 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): ...... mapper.connect('index', '/', controller=Index(RESOURCES)) # http://controller:9696/v2.0/ 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)
APIRouter的内容比较复杂,主要分为以下三个部分:
- 初始化NeutronManager来加载core plugin和service plugin;
- 初始化PluginAwareExtensionManager;
- 调用_map_resource方法为四个顶级resource进行map。
下面分为三篇来逐个分析这三个部分。首先是NeutronManager,该部分在APIRouter的构造函数中只有一行代码:
manager.init()
调用/neutron/manager.py中的init方法:
def init(): """Call to load the plugins (core+services) machinery.""" # TODO(armax): use is_loaded() when available if not directory.get_plugins(): NeutronManager.get_instance()
首先,调用了neutron_lib库中的plugins.directory.py中的get_plugins方法。这里把相关的方法都贴出来,一起分析。
_PLUGIN_DIRECTORY = None @_synchronized("plugin-directory") def _create_plugin_directory(): global _PLUGIN_DIRECTORY if _PLUGIN_DIRECTORY is None: _PLUGIN_DIRECTORY = _PluginDirectory() return _PLUGIN_DIRECTORY def _get_plugin_directory(): if _PLUGIN_DIRECTORY is None: return _create_plugin_directory() return _PLUGIN_DIRECTORY def add_plugin(alias, plugin): _get_plugin_directory().add_plugin(alias, plugin) def get_plugin(alias=constants.CORE): return _get_plugin_directory().get_plugin(alias) def get_plugins(): return _get_plugin_directory().plugins def get_unique_plugins(): return _get_plugin_directory().unique_plugins def is_loaded(): return _get_plugin_directory().is_loaded
这里申请了一个全局变量_PLUGIN_DIRECTORY,通过_get_plugin_directory方法来获取这个变量,并且在第一次调用_get_plugin_directory方法(即_PLUGIN_DIRECTORY为None时)时,对_PLUGIN_DIRECTORY变量进行赋值。这里申明global是为了在函数体内对一个函数体外的变量进行赋值。可以看到_PLUGIN_DIRECTORY变量是_PluginDirectory类的实例,其构造函数稍后分析。
其余几个函数,均首先获取了_PLUGIN_DIRECTORY变量,再对其进行操作。具体含义如下:
add_plugin(alias, plugin):
向_PLUGIN_DIRECTORY添加名为alias的plugin。
get_plugin(alias=constants.CORE):
从_PLUGIN_DIRECTORY中获取名为alias(默认为core plugin)的plugin。
get_plugins():
获取_PLUGIN_DIRECTORY中所有的plugin。
get_unique_plugins():
获取_PLUGIN_DIRECTORY中不同类型的plugin。
is_loaded():
获取_PLUGIN_DIRECTORY是否为None。
_PLUGIN_DIRECTORY变量是_PluginDirectory类的实例,_PluginDirectory类中均有函数或属性与上述几个函数相对应。
class _PluginDirectory(object): """A directory of activated plugins in a Neutron Deployment. The directory is bootstrapped by a Neutron Manager running in the context of a Neutron Server process. """ def __init__(self): self._plugins = {} # 构造函数只是对_plugins属性进行了初始化。 def add_plugin(self, alias, plugin): """Add a plugin of type 'alias'.""" self._plugins[alias] = plugin def get_plugin(self, alias): """Get a plugin for a given alias or None if not present.""" p = self._plugins.get(alias) return weakref.proxy(p) if p else None # 如果没有名为alias的plugin,返回None。否则返回对应plugin的弱引用。 @property def plugins(self): # _PluginDirectory的plugins属性是一个函数,返回各个plugin的弱引用。 """The mapping alias -> weak reference to the plugin.""" return dict((x, weakref.proxy(y)) for x, y in self._plugins.items()) @property def unique_plugins(self): """A sequence of the unique plugins activated in the environments.""" return tuple(weakref.proxy(x) for x in set(self._plugins.values())) @property def is_loaded(self): """True if the directory is non empty.""" return len(self._plugins) > 0
回到/neutron/manager.py中的init方法:
def init(): """Call to load the plugins (core+services) machinery.""" # TODO(armax): use is_loaded() when available if not directory.get_plugins(): NeutronManager.get_instance()
第一次调用get_plugins方法,此时_PluginDirectory._plugin为空,返回None。接着,调用NeutronManager的get_instance方法来实际加载各个plugin。
有没有觉得get_instance这个函数名特别眼熟,这里NeutronManager也是一个单例模式的实现。
@six.add_metaclass(profiler.TracedMeta) class NeutronManager(object): """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. """ @classmethod @utils.synchronized("manager") def _create_instance(cls): if not cls.has_instance(): cls._instance = cls() @classmethod def has_instance(cls): return cls._instance is not None @classmethod def get_instance(cls): # double checked locking if not cls.has_instance(): cls._create_instance() return cls._instance
通过对NeutronManager的has_instance的两次判断,确定NeutronManager没有被实例化,这时调用其构造函数:
@six.add_metaclass(profiler.TracedMeta) class NeutronManager(object): _instance = None def __init__(self, options=None, config_file=None): # If no options have been provided, create an empty dict if not options: options = {} """ 检查配置文件中是否配置了core_plugin,否则抛出异常 """ 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 # 从配置文件中获取core_plugin,一般为ml2 LOG.info(_LI("Loading core plugin: %s"), plugin_provider) # server.log中打印 INFO neutron.manager [-] Loading core plugin: ml2 # NOTE(armax): keep hold of the actual plugin object plugin = self._get_plugin_instance(CORE_PLUGINS_NAMESPACE, # 通过namespace和plugin_provider获取plugin。 plugin_provider) directory.add_plugin(lib_const.CORE, plugin) # 添加core_plugin(Ml2Plugin的实例)到_PLUGIN_DIRECTORY._plugins中。 """ 检查配置文件中的dhcp_agents_per_network,否则抛出异常 """ msg = validate_post_plugin_load() if msg: LOG.critical(msg) raise Exception(msg) # load services from the core plugin first self._load_services_from_core_plugin(plugin) self._load_service_plugins() # 加载service_plugins,包括配置文件中指定的和默认的。 # Used by pecan WSGI self.resource_plugin_mappings = {} self.resource_controller_mappings = {} self.path_prefix_resource_mappings = defaultdict(list)
在构造函数中,分别对core plugin和service plugin进行了初始化,并调用directory.add_plugin将各个plugin的实例添加到_PLUGIN_DIRECTORY变量中,供之后调用。其中,core plugin是直接调用_get_plugin_instance方法来获取相应的plugin类(一般为Ml2Plugin)并初始化:
class NeutronManager(object): def _get_plugin_instance(self, namespace, plugin_provider): # 通过不同的namespace中的plugin_provider获取相应的plugin。 plugin_class = self.load_class_for_provider(namespace, plugin_provider) # 调用load_class_for_provider函数来返回Plugin类。 return plugin_class() # 实例化Plugin类,在此处完成各个plugin的__init__方法的调用。
而service plugin是直接调用_load_service_plugins方法来加载的,但这个方法也是通过_get_plugin_instance方法来加载各个service plugin。
通过_load_service_plugins方法加载的service plugin分为两种:一种为配置文件中指定的service_plugins,另一种为默认的service plugins,由函数_get_default_service_plugins获取。
总结:
各个plugin的加载均是通过NeutronManager以单例模式的方式在其构造函数中实现的,而各个plugin在初始化后的实例均以{名称:对应plugin类的实例}这样的字典方式保存在变量_PLUGIN_DIRECTORY中,供之后的各个流程调用。