Neutron做为Openstack的网络组件,其内部所有功能均是以plugin形式实现的,其中极具代表性的plugin就是ml2和l3两个插件,下面我将从neutron启动的源码来简单介绍neutron加载组件和扩展的流程。
1 目录结构
2 启动
setup.cfg--entrypoint 配置
console_scripts =
neutron-l3-agent = neutron.cmd.eventlet.agents.l3:main
neutron-server = neutron.cmd.eventlet.server:main
neutron.core_plugins =
ml2 = neutron.plugins.ml2.plugin:Ml2Plugin
neutron.service_plugins =
router = neutron.services.l3_router.l3_router_plugin:L3RouterPlugin
neutron没有api进程,它的所有的api操作都是有neutron-server来处理的,包括初始资源的载入,插件的载入,还有所有的数据库操作以及与各个agent之间的通信。
# neutron/cmd/eventlet/server/__init__.py
def main():
# 启动服务,并调用 _main_neutron_server
server.boot_server(_main_neutron_server)
def _main_neutron_server():
# neutron有两种api方式,一种是pecan一种是原来的legacy
if cfg.CONF.web_framework == 'legacy':
# 这里我们主要讲legacy 下面发析这一步
wsgi_eventlet.eventlet_wsgi_server()
else:
wsgi_pecan.pecan_wsgi_server()
def main_rpc_eventlet():
server.boot_server(rpc_eventlet.eventlet_rpc_server)
# neutron/server/wsgi_eventlet.py
def eventlet_wsgi_server():
# 程序从这里开始进入第二步
# 这是注册neutron-api 关键步骤,下面我们将首先分析这一过程
neutron_api = service.serve_wsgi(service.NeutronApiService)
# 这一步是利用eventlet的线程池启动api和rpc
start_api_and_rpc_workers(neutron_api)
def start_api_and_rpc_workers(neutron_api):
pool = eventlet.GreenPool()
# 这是关键的一步,下面我们进入neutron_api.wait进行分析
api_thread = pool.spawn(neutron_api.wait)
try:
# 注册rpc server
neutron_rpc = service.serve_rpc()
except NotImplementedError:
LOG.info(_LI("RPC was already started in parent process by " "plugin."))
else:
#启动rpc
rpc_thread = pool.spawn(neutron_rpc.wait)
# 启动rpc plugin works
plugin_workers = service.start_plugin_workers()
for worker in plugin_workers:
pool.spawn(worker.wait)
# api and rpc should die together. When one dies, kill the other.
rpc_thread.link(lambda gt: api_thread.kill())
api_thread.link(lambda gt: rpc_thread.kill())
pool.waitall()
分析api的注册和wait过程
# neutron_api = service.serve_wsgi(service.NeutronApiService)
# neutron/service.py
class NeutronApiService(WsgiService):
"""Class for neutron-api service."""
@classmethod
def create(cls, app_name='neutron'):
# Setup logging early, supplying both the CLI options and the
# configuration mapping from the config file
# We only update the conf dict for the verbose and debug
# flags. Everything else must be set up in the conf file...
# Log the options used when starting if we're in debug mode...
# 建立log日志
config.setup_logging()
# 这一步是关键,返回一个当前类的实例
service = cls(app_name)
return service
def serve_wsgi(cls):
try:
# cls = NeutronApiService
service = cls.create()
# 启动api server实例 下面我们分析这一步
service.start()
except Exception:
with excutils.save_and_reraise_exception():
LOG.exception(_LE('Unrecoverable error: please check log ' 'for details.'))
return service
分析service.start()
# service.start()
# neutron/service.py
class WsgiService(object):
"""Base class for WSGI based services.
For each api you define, you must also define these flags:
:_listen: The address on which to listen
:_listen_port: The port on which to listen
"""
def __init__(self, app_name):
self.app_name = app_name
self.wsgi_app = None
def start(self):
# 启动时 调用_run_wsgi加载app
self.wsgi_app = _run_wsgi(self.app_name)
def wait(self):
self.wsgi_app.wait()
def _run_wsgi(app_name):
# app_name 默认是neutron
# 这一步比较关键,利用了develop paste
# 先从配置文件api-paste.ini读取出要加载的app
# 默认配置: [app:neutronapiapp_v2_0]
# paste.app_factory = neutron.api.v2.router:APIRouter.factory
# 所以这一步实际调用的是 neutron.api.v2.router ApiRouter实例的工厂方法factory生成一个router实例,
# 这里的router不是neutron中路由的router,而是wsgi中路由的概念,即请求转发的规则
# 从这一步开始,neutron从正式开始启动,并加载各种资源。
app = config.load_paste_app(app_name)
if not app:
LOG.error(_LE('No known API applications configured.'))
return
return run_wsgi_app(app)
3、加载资源
这一步骤主要是neutron加载配置的插件,并通过载入载入插件的资源和扩展
# neutron.api.v2.router:APIRouter.factory
# neutron/api/v2/router.py
class APIRouter(base_wsgi.Router):
@classmethod
def factory(cls, global_config, **local_config):
return cls(**local_config)
def __init__(self, **local_config):
# 调用routes包获取mapper对象,不多说,有需求的可以自己进去看看
mapper = routes_mapper.Mapper()
# 加载 插件 3.1会对其中具体步骤进行分析
plugin = manager.NeutronManager.get_plugin()
# 加载 扩展 3.2会对其中具体步骤进行分析
ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
# 加载 扩展资源 3.3会对其中具体步骤进行分析
ext_mgr.extend_resources("2.0", attributes.RESOURCE_ATTRIBUTE_MAP)
# 生成方法字典,即请求中restful的请求对应的方法
col_kwargs = dict(collection_actions=COLLECTION_ACTIONS,
member_actions=MEMBER_ACTIONS)
def _map_resource(collection, resource, params, parent=None):
"""这里面主要是遍历生成资源图(router)的过程,不列代码了"""
3.1 载入插件机制分析
# manager.NeutronManager.get_plugin()
# neutron/manager.py
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.
The caller should make sure that NeutronManager is a singleton.
"""
_instance = None
@classmethod
def get_plugin(cls):
# Return a weakref to minimize gc-preventing references.
# weakref不多说,说另外两步,
# 第一步获取manager实例cls.get_instance()
# 第二步返回当前的self.plugin
return weakref.proxy(cls.get_instance().plugin)
@classmethoddef
def get_instance(cls):
# double checked locking
# 检测是否有实例
if not cls.has_instance():
# 没有则创建
cls._create_instance()
return cls._instance
@classmethod
def has_instance(cls):
return cls._instance is not None
@classmethod
@utils.synchronized("manager")
def _create_instance(cls):
if not cls.has_instance():
# 创建实例即调用__init__
cls._instance = cls()
def __init__(self, options=None, config_file=None):
# If no options have been provided, create an empty dict
if not options:
options = {}
# 验证是否配置了cor_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
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
# 加载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 = {}
def _get_plugin_instance(self, namespace, plugin_provider):
# 利用stevdore加载插件,获取其实例
plugin_class = self.load_class_for_provider(namespace, plugin_provider)
return plugin_class()
def _load_service_plugins(self):
"""Loads service plugins.
Starts from the core plugin and checks if it supports
advanced services then loads classes provided in configuration.
"""
# load services from the core plugin first
self._load_services_from_core_plugin()
plugin_providers = cfg.CONF.service_plugins
plugin_providers.extend(self._get_default_service_plugins())
LOG.debug("Loading service plugins: %s", plugin_providers)
for provider in plugin_providers:
if provider == '':
continue
LOG.info(_LI("Loading Plugin: %s"), provider)
plugin_inst = self._get_plugin_instance('neutron.service_plugins',
provider)
# only one implementation of svc_type allowed
# specifying more than one plugin
# for the same type is a fatal exception
if plugin_inst.get_plugin_type() in self.service_plugins:
raise ValueError(_("Multiple plugins for service "
"%s were configured") %
plugin_inst.get_plugin_type())
self.service_plugins[plugin_inst.get_plugin_type()] = plugin_inst
# search for possible agent notifiers declared in service plugin
# (needed by agent management extension)
if (hasattr(self.plugin, 'agent_notifiers') and
hasattr(plugin_inst, 'agent_notifiers')):
self.plugin.agent_notifiers.update(plugin_inst.agent_notifiers)
LOG.debug("Successfully loaded %(type)s plugin. "
"Description: %(desc)s",
{"type": plugin_inst.get_plugin_type(),
"desc": plugin_inst.get_plugin_description()})
3.2 载入扩展机制分析
# plugin = manager.NeutronManager.get_plugin()
# neutron/api/extensions.py
class PluginAwareExtensionManager(ExtensionManager):
_instance = None
@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
def __init__(self, path, plugins):
self.plugins = plugins
# 调用父类初始化 注入路径 这个路径是neutron路径+所有service_plugin的路径
# 父类的初始化方法进行了扩展的载入
super(PluginAwareExtensionManager, self).__init__(path)
# 检测插件扩展是否载入
self.check_if_plugin_extensions_loaded()
def check_if_plugin_extensions_loaded(self):
"""根据别名aliase判断哪些扩展没有载入并raise异常"""
def _check_extension(self, extension):
"""Check if an extension is supported by any plugin."""
extension_is_valid = super(PluginAwareExtensionManager,
self)._check_extension(extension)
return (extension_is_valid and
self._plugins_support(extension) and
self._plugins_implement_interface(extension))
class ExtensionManager(object):
"""Load extensions from the configured extension path.
See tests/unit/extensions/foxinsocks.py for an
example extension implementation.
"""
def __init__(self, path):
LOG.info(_LI('Initializing extension manager.'))
# 子类注入路径
# 这个路径是neutron路径+所有service_plugin的路径
self.path = path
self.extensions = {}
# 载入所有扩展
self._load_all_extensions()
def _load_all_extensions(self):
"""Load extensions from the configured path.
The extension name is constructed from the module_name. If your
extension module is named widgets.py, the extension class within that
module should be 'Widgets'.
See tests/unit/extensions/foxinsocks.py for an example extension
implementation.
"""
for path in self.path.split(':'):
if os.path.exists(path):
# 这个路径是neutron路径+所有service_plugin的路径
# 到这我们就基本明白如果你要给neutron写扩展需要放在什么位置了
self._load_all_extensions_from_path(path)
else:
LOG.error(_LE("Extension path '%s' doesn't exist!"), path)
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)
# 载入扩展文件 文件需要以.py结尾,并且不能是_开头
if file_ext.lower() == '.py' and not mod_name.startswith('_'):
# 利用python的系统调用载入这个文件
# mod_name是文件名字,路径是文件路径
mod = imp.load_source(mod_name, ext_path)
# 生成扩展名,这一步很重要,扩展类的名称一定要是文件名的首字母大写
# 例如扩展l3.py 那么文件中的扩展类一定要是L3
ext_name = mod_name[0].upper() + mod_name[1:]
# 获取这个扩展文件中的扩展类
new_ext_class = getattr(mod, ext_name, None)
if not new_ext_class:
LOG.warning(_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.warning(_LW("Extension file %(f)s wasn't loaded due to "
"%(exception)s"),
{'f': f, 'exception': exception})
def add_extension(self, ext):
# Do nothing if the extension doesn't check out
# 检测扩展类
if not self._check_extension(ext):
return
# 获取扩展类的别名
alias = ext.get_alias()
LOG.info(_LI('Loaded extension: %s'), alias)
if alias in self.extensions:
raise exceptions.DuplicatedExtension(alias=alias)
# 将扩展加入到扩展字典中
self.extensions[alias] = ext
def _check_extension(self, extension):
"""Checks for required methods in extension objects."""
# 检测扩展类 分别进行了get_name get_alias get_description get_updated检测
# 意味着你写的扩展类一定要有这些方法,才能被成功载入
try:
LOG.debug('Ext name: %s', extension.get_name())
LOG.debug('Ext alias: %s', extension.get_alias())
LOG.debug('Ext description: %s', extension.get_description())
LOG.debug('Ext updated: %s', extension.get_updated())
except AttributeError:
LOG.exception(_LE("Exception loading extension"))
return False
return isinstance(extension, ExtensionDescriptor)
3.3 加载扩展资源
# ext_mgr.extend_resources("2.0", attributes.RESOURCE_ATTRIBUTE_MAP)
# neutron/api/extensions.py
class ExtensionManager(object):
"""Load extensions from the configured extension path.
See tests/unit/extensions/foxinsocks.py for an
example extension implementation.
"""
def extend_resources(self, version, attr_map):
"""Extend resources with additional resources or attributes.
:param attr_map: the existing mapping from resource name to
attrs definition.
After this function, we will extend the attr_map if an extension
wants to extend this map.
"""
processed_exts = {}
# 获取3.3中载入的扩展的副本
exts_to_process = self.extensions.copy()
check_optionals = True
# Iterate until there are unprocessed extensions or if no progress
# is made in a whole iteration
# 因为扩展与扩展之间有依赖,而程序并不知道加载的顺序,所以这里用了多个循环来
# 按照正确的顺序加载扩展
while exts_to_process:
processed_ext_count = len(processed_exts)
# 遍历扩展
for ext_name, ext in list(exts_to_process.items()):
# Process extension only if all required extensions
# have been processed already
# 如果所有依赖都被加载就继续运行,否则continue跳过
required_exts_set = set(ext.get_required_extensions())
if required_exts_set - set(processed_exts):
continue
# 检测可选扩展
optional_exts_set = set(ext.get_optional_extensions())
if check_optionals and optional_exts_set - set(processed_exts):
continue
# 获取对应版本的扩展属性图
extended_attrs = ext.get_extended_resources(version)
# 遍历扩展图 res:资源,resource_attr 资源拥有的属性
for res, resource_attrs in six.iteritems(extended_attrs):
attr_map.setdefault(res, {}).update(resource_attrs)
processed_exts[ext_name] = ext
# 加载成功后删除该扩展
del exts_to_process[ext_name]
if len(processed_exts) == processed_ext_count:
# if we hit here, it means there are unsatisfied
# dependencies. try again without optionals since optionals
# are only necessary to set order if they are present.
if check_optionals:
check_optionals = False
continue
# Exit loop as no progress was made
break
# 如果有扩展没有被成功加载就抛出异常
if exts_to_process:
unloadable_extensions = set(exts_to_process.keys())
LOG.error(_LE("Unable to process extensions (%s) because "
"the configured plugins do not satisfy "
"their requirements. Some features will not "
"work as expected."),
', '.join(unloadable_extensions))
# Fail gracefully for default extensions, just in case some out
# of tree plugins are not entirely up to speed
default_extensions = set(const.DEFAULT_SERVICE_PLUGINS.values())
if not unloadable_extensions <= default_extensions:
raise exceptions.ExtensionsNotFound(
extensions=list(unloadable_extensions))
# 扩展 每个扩展的属性图,调用每个扩展的update_attributes_map方法.
for ext in processed_exts.values():
ext.update_attributes_map(attr_map)
到这里server程序启动成功,还有部分代码未分析,持续更新