在我们正式开始之前,我们先来看一下博文两张图总结 Neutron 架构总结的 Neutron 架构图(特别简明易懂)。
这里我们关注的是 Neutron Server 那一部分的初始化。跟以前 Nova 源码分析一样,我们主要还是会用 UML 来进行代码的分析。
PasteDeploy 特点
这个特点其实在博文 PasteDeploy 其实有说过,就是 PasteDeploy 中初始化 pipeline 和调用 pipeline 的执行顺序是相反的
。以 Neutron 的 api-paste.ini 为例:
1 | [composite:neutronapi_v2_0] |
初始化该 pipeline(composite) 的顺序是 ...extensions.factory(neutronapiapp_v2_0.factory),先执行的是(括号里)最后一个应用 neutronapiapp_v2_0 的函数 factory;而调用该 pipeline 就不一样了,执行顺序是 neutronapiapp_v2_0.__call__(extensions.__call__(...)),先执行的也是括号里的函数,最后执行的才是 neutronapiapp_v2_0 的函数 __call__。
注:上边的 __call__
只是用来表示函数或类调用,实际不一定如此。
Neutron Server 启动概览
其实,Neutron Server 就是一个 WSGI Service,向外接收 REST API 请求,向内路由请求到具体的处理函数。
根据 setup.cfg (neutron-server) 及 api-paste.ini 文件,我们可以得到 Neutron Server 的启动概览:
Neutron API Service 初始化
下文就分为 APIRouter
及 Extensions Routes
两部分进行分析。
在此之前,我们先来看一个 api-paste.ini 文件:
1 | [composite:neutronapi_v2_0] |
按照 PasteDeploy 中初始化 pipeline 和调用 pipeline 的执行顺序是相反的
的原则,neutronapiapp_v2_0 app 的初始化要早于 extensions app。我们也先从前者(APIRouter)开始说起。
APIRouter
APIRouter 初始化核心代码如下:
1 | class APIRouter(base_wsgi.Router): |
代码执行路径可用 UML 表示如下:
NeutronManager
Core Plugin
这里我们所指的 Core Plugin 是 ML2:
1 | # /etc/neutron/neutron.conf |
ML2 简介
博文 OpenStack Neutron ML2 Deep Dive 对 ML2 有一个很简要的介绍:
OpenStack Neutron 作为一种 SDN(Software Defined Network),在其内部使用 ML2 模块来管理 Layer2。ML2 全称是 Modular Layer 2。它是一个可以同时管理多种 Layer 2 技术的框架。在 OpenStack Neutron 的项目代码中,ML2 目前支持 Open vSwitch,linux bridge,SR-IOV 等虚拟化 Layer 2 技术。
它解决了 Neutron 老版本(ML2 之前)存在的两个问题:
- 每支持一种 Layer 2 技术,对 OpenStack Neutron 中的 L2 resource(例如 Network/Subnet/Port)的逻辑需要进行一次重写,这大大增加了相应的工作量;
- OpenStack Neutron 最多只支持一种 Layer 2 技术,也就是说如果配置使用了 Open vSwitch,那么整个 OpenStack 环境都只能使用 neutron-openvswitch-agent 作为 Layer 2 的管理服务与 Open vSwitch 交互。
ML2 的提出解决了上面两个问题。ML2 之前的 Layer 2 plugin 代码相同的部分被提取到了 ML2 plugin 中。这样,当一个新的 Layer 2 需要被 Neutron 支持时,只需要实现其特殊部分的代码,需要的代码工作大大减少,开发人员甚至不需要详细了解 Neutron 的具体实现机制,只需要实现对应的接口。
这样,ML2 通过其中的 Mechanism drivers 可以同时管理多种 Layer 2 技术,如下图:
图片来源:OpenStack Neutron ML2 Deep Dive
ML2 的框架如下图所示:
图片来源:OpenStack Neutron ML2 Deep Dive
其中,该博客还对框架中各模块有一个简介(简单汇总了下):
- ML2 plugin:所有对 Neutron 中 L2 resource 操作的入口,实现文件是 neutron/plugins/ml2/plugin.py。
- Extensions drivers:extensions drivers 就是建立 Neutron 中 L2 resource 与其他 resource 之间的联系。当创建、更新或删除 Neutron 中 L2 resource 时,对应的 extensions driver 会被执行,并更新对应的其他 resource。同时 extensions drivers 还会将其他 resource 与 Neutron L2 resource 的关联报告给 ML2 plugin,这样,用户在查看 Port 信息的时候,就能看到对应的 Security Group。
- Types drivers:物理环境中的 L2 网络类型有很多种,而在虚拟网络 OpenStack Neutron 中,也支持多种网络类型。这些网络类型的支持由 ML2 的 types drivers 来完成。
- Mechanism drivers:这部分是对各种 L2 技术的支持,例如 Open vSwitch,linux bridge 等等。最近流行的 OVN 也是作为一个 mechanism driver,通过 ML2 与 OpenStack Neutron 工作。
- RPC:这是 ML2 与 L2 agents 通讯的部分,是基于 AMQP 的实现。例如,删除 Network,需要通过 rpc 通知 L2 agents 也删除相应的流表,虚拟端口等等。
初始化概览
ML2 初始化部分概览如下:
上图中重要的除了一些 driver 的加载,还有一点值得说的是 Neutron 所用的 Callback System。这部分因为在 Service Plugin 也会涉及,所以我们放到本文最后一起说。
DHCP
这里很重要的一点就是初始化 DHCP,导入 network_scheduler_driver:
1 | # neutron/plugins/ml2/plugin.py |
RPC Notifiers
在前边初始化分析 Core Plugin 的时候(UML)的时候就已经提到初始化 Core Plugin 的时候就会初始化 RPC Notifiers:
1 | # neutron/plugins/ml2/plugin.py |
Service Plugins
Service Plugins 比较多,如我们在 setup.cfg 文件就可以看到声明的诸多 plugin:
1 | # setup.cfg |
但实际只加载 cfg.CONF.service_plugins 及 DEFAULT_SERVICE_PLUGINS 所限定的。
下边我们以 L3RouterPlugin 为例来做一个简要说明。
L3 Router Plugin
一个粗略的加载图如下:
Service Plugin 的详细加载就不细说了,我们会在后续博文中单位为一些重要的 plguin 做分析,那时再做详细解析。
RPC Notifiers
如 L3 Router Plugin 定义的 RPC Notifiers 如下:
1 | class L3RouterPlugin(...): |
Extension Manager
在 APIRouter 初始化完 Core Plugin 和 Service Plugins 之后就是加载 PluginAwareExtensionManager(Extension Manager),进一步又会把 neutron/extensions
目录下的 Extension(API
) 都加载,具体如下:
Core Plugin Routes
加载完 Core Plugin 及 Service Plugins 之后,接下来一步就是建立路由映射了,不过这里的路由映射只做了 Core Plugin 的路由映射(network/subnet/subnetpool/port)
。具体看代码:
1 | # neutron/api/v2/router.py |
关于路由映射可参考之前博文 routes。不过这里想重点说一下 create_resource
这个函数,它返回的是一个 (WSGI) Controller。这个函数很重要,Core Plugin、Extensions 路由映射都会调用它。下边我们来重点看下:
1 | # neutron/api/v2/base.py |
如果我们看一下 Controller 这个类,我们很容易就可以看出它究竟做的是什么事情了:
1 | # neutron/api/v2/base.py |
也就是说,它定义了一些资源的常见操作(CRUD),然后再根据具体资源及 action 将这些操作映射到对 plugin (Core Plguin or Service Plugin) 的真正操作上,如 show 函数:
1 | # neutron/api/v2/base.py |
这样确实能够统一处理所有 plugin 的路由映射了,只要它们的 action 符合这个 Controller 定义的标准即可。但是也正因为路由构造如此高度抽象,一开始看很容易看晕…
不过,这个 Controller 还不是一个 WSGI 应用,需要继续进一步包装
:
1 | # neutron/api/v2/base.py |
一看就很熟悉对不对?我们只要比较一下 OpenStack 其他模块(如 Nova)常用的路由 Controller 构造方法就很清楚了(样例):
1 | import webob.dec |
** Extensions Routes**
虽然前边 Service Plugins 已经加载完毕了,但它们的路由还没建立…
本文一开始就在提到 PasteDeploy 中初始化 pipeline 和调用 pipeline 的执行顺序是相反的
,这也就是为什么在 api-paste.ini 中 extensions app 的初始化要在 neutronapiapp_v2_0 app 之后。且看详细::
1 | [composite:neutronapi_v2_0] |
下边就直接以代码来说明。
1 | # neutron/api/extensions.py |
也就是说,这里分成了两步,第 1 步是加载扩展 Extension Manager,第 2 步才是做路由映射。第 1 步已经在 APIRouter 中完成了,接下来我们要看的是第 2 步。
直接上代码:
1 | # neutron/api/extensions.py |
self.ext_mgr.get_resources() 实际调用的是每个 extension 的 get_resources 函数:
1 | # neutron/api/extensions.py |
下边我们以 L3 extension 为例来说明实际的路由映射是怎么做的。
1 | # neutron/extensions/l3.py |
上边有两个地方很重:
- 获取 plugin,这里具体获取的是 neutron.services.l3_router.l3_router_plugin.L3RouterPlugin,它的初始化在前文说过了;
- extension 的路由映射和 Core Plugin 一样最终都会调用
create_resource
这个函数。接下来的步骤参考 Core Plugin 的就行了。
注:如果我们仔细看,有些 Extension(如 timestamp) 的 get_resources 返回的是一个空列表,也就是说,这些 Extension 不会做路由映射。
Extension 与 Service Plugin 关系
对于 Core Plugin 来说,它的路由映射是单独做的,不容易误解;但对 Service Plugin 来说,它的路由是由 Extension 来做的。把 Extension 成为 Extension API 更为合适。
API 与 Plugin 之间的关系可参考陈沙克老师画的一张图:
服务启动
这里的服务指的是 NeutronAPIServie、RPC 以及 Plugins(Core Plugin & Service Plugin)
。服务启动部分我们在 "Neutron Server 启动概览"
部分的 UML 图中均有体现出来(_run_wsgi
及 start_api_and_rpc_workers
函数)。涉及代码如下:
1 | # neutron/server/wsgi_eventlet.py |
NeutronApiService
NeutronApiService 的启动在 _run_wsgi
函数中:
1 | # neutron/service.py |
它做的第 1 件事初始化 NeutronApiService 前文已经分析过,接下来我们要来看一下它如何启动 NeutronApiService:
下边我们就以启动 NeutronApiService 为例来说明启动详细过程(不包括 evenlet 部分):
Plugins RPC Listeners & Workers
这里的 Plugins 包括了 Core Plugin 及 Service Plugins。它们的 RPC Listeners & Workers 启动在 start_api_and_rpc_workers
的函数中:
1 | # neutron/server/wsgi_eventlet.py |
详细分析见如下的 UML 图:
RPC Listeners(Workers)
这里我们以 Core Plugin 为例()。Core Plugin 的 start_rpc_listeners
、start_rpc_state_reports_listener (只有 Core Plugin 才有)
定义如下:
1 | # neutron/plugins/ml2/plugin.py |
Plugin Workers
而关于 Core & Service Plugins 的 Workers 的获取则是调用每个 Plugin 的 get_workers 函数,具体代码如下:
1 | # neutron/service.py |
启动 RPC & Plugins Workers
获取到 RPC & Plugins Workers 之后就是要启动这些 Workers,具体代码在 _start_workers
函数:
1 | # neutron/service.py |
这个启动方式我们在 NeutronAPIService 服务启动里分析过,请参考之。
Wait for NeutronApiService/RPC/Plugins Workers
当 NeutronApiService/RPC/Plugins Workers (Services) 都启动起来后,就会用 eventlet thread 来调用这些 Workers (Services) 的 wait 函数(如 oslo_service.service::ProcessLauncher、neutron.wsgi::WorkerService 的 wait 函数):
1 | # neutron/server/wsgi_eventlet.py |
附:Callback System
简介
不同于 Nova,Neutron 多了回调系统(Callback System)和消息回调系统(Messaging Callback System)。这两种系统的区别在于用途及实现方式:
Neutron already has a callback system for in-process resource callbacks where publishers and subscribers are able to publish and subscribe for resource events.
This system (Messaging Callback System) is different, and is intended to be used for inter-process callbacks, via the messaging fanout mechanisms.
也就是说,回调系统用于进程内通信(如 Neutron Server 进程里的 core plugin 与 service plugin 通信);消息回调系统用于进程间通信,通过消息队列的广播机制实现。前者相对于后者来说比较简单,无需通过复杂机制来实现。
这里其实还有一个疑问,就是既然是进程内通信了,那还有必要弄一个回调系统吗?应该说,不用该系统也是可以实现进程内各服务之间的通信的,但考虑到 Neutron 的复杂性(多 service plugin),不用回调系统会使得各服务之间强耦合,而且不便于 service 独立开发:
In Neutron, core and service components may need to cooperate during the execution of certain operations, or they may need to react upon the occurrence of certain events. For instance, when a Neutron resource is associated to multiple services, the components in charge of these services may need to play an active role in determining what the right state of the resource needs to be.
The cooperation may be achieved by making each object aware of each other, but this leads to tight coupling, or alternatively it can be achieved by using a callback-based system, where the same objects are allowed to cooperate in a loose manner.
This is particularly important since the spin off of the advanced services like VPN, Firewall and Load Balancer, where each service’s codebase lives independently from the core and from one another. This means that the tight coupling is no longer a practical solution for object cooperation. In addition to this, if more services are developed independently, there is no viable integration between them and the Neutron core.
A callback system, and its registry, tries to address these issues.
这样使用回调系统的好处就是解耦,各个 service 只需知道作为通信的中介(intermediary)即可:
The intermediary is then in charge of keeping track of who is interested in the messages and in delivering the messages forth and back, when required. As mentioned earlier, the use of an intermediary has the benefit of decoupling the parties involved in the communications, as now they only need to know about the intermediary; the other benefit is that the use of an intermediary opens up the possibility of multiple party communication: more than one object can express interest in receiving the same message, and the same message can be delivered to more than one object.
对于一个回调系统而言,我们需要考虑以下三个问题:
- how to become consumer of messages (i.e. how to be on the receiving end of the message);
- how to become producer of messages (i.e. how to be on the sending end of the message);
- how to consume/produce messages selectively;
针对 Neutron 而言,该系统主要用于资源的生命周期事件方面:
Translate and narrow this down to Neutron needs, and this means the design of a callback system where messages are about
lifecycle events (e.g. before creation, before deletion, etc.)
of Neutronresources (e.g. networks, routers, ports, etc.)
.
源码解析
回调系统的代码还挺简单,很容易看懂。代码路径:neutron.callbacks.manager.py::CallbacksManager。
subscribe & unsubscribe
订阅与取消订阅实际就是注册或注销回调函数:
1 | # neutron.callbacks.manager.py |
notify
通知也挺简单的,就是遍历,然后一个一个调用订阅某一事件的回调函数:
1 | # neutron.callbacks.manager.py |