1 代码结构
主要分为如下几个部分:
1)Plugin
- 安全组模块并没有独立的一个Plugin,它是从属于ML2 Plugin。在neutron/plugins/ml2/plugin.py源码文件中,类class Ml2Plugin通过继承neutron/db/securitygroups_rpc_base.py源码文件中的类class SecurityGroupServerRpcMixin来执行安全组资源的CRUD操作。
- neutron/extensions/securitygroup.py。该源码文件主要定义了安全组、安全组规则各个数据字段对应在restapi接口中的操作属性。即:数据字段是否允许创建、是否允许更新、是否返回给用户呈现等。同时还定义了Plugin基类class SecurityGroupPluginBase,该基类定义了安全组、安全组规则CRUD的抽象方法。
2)DB
定义了安全组资源的数据库表格及其相应的数据库CRUD操作。同时发出相应的资源本地通告事件。
- neutron/db/models/securitygroup.py。定义了几个数据库表,包含:SecurityGroup(安全组)、DefaultSecurityGroup(默认安全组)、SecurityGroupPortBinding(Neutron Port与SecurityGroup关联)、SecurityGroupRule(安全组规则)。
- neutron/db/port_security/models.py。该源码文件与安全组有强关联,因此这里也提出来进行阐述。主要定义了几个数据表,包含:PortSecurityBinding(主要记录了Nuetron Port是否开启了安全组特性)、NetworkSecurityBinding(主要记录了Neutron Network是否开启了安全组特性,如果开启了安全组特性,则在该Network上创建的Neutron Port都自动开启安全组特性)。
- neutron/db/securitygroups_db.py。该源码文件是安全组资源操作数据库的核心文件。这里定义了安全组、安全组规则、默认安全组资源的CRUD方法。实现了从Plugin接收资源请求、执行校验、操作数据库、发出资源本地通告等流程。
- neutron/objects/securitygroup.py。该源码文件用于构建安全组、安全组规则、默认安全组资源对象。
3)Config
在源码文件neutron/conf/agent/securitygroups_rpc.py定义了安全组相关的配置选项。如果想进一步增加其他新配置选项,可以在这个文件里进行添加即可。
4)RPC
RPC主要是用于Plugin和Agent之间的交互。对于安全组模块而言,其RPC交互代码较为隐藏,对于Linuxbridge Agent和Openvswitch Agent而言,对应的RPC交互又存在不同。因此安全组的RPC交互也是本文的重点分析之一。
- neutron/api/rpc/handlers/securitygroup_rpc.py
class SecurityGroupServerRpcApi:RPC客户端(对应的服务端为class SecurityGroupServerRpcCallback),用在Agent端。在neutron/plugins/ml2/drivers/agent/_common_agent.py源码文件中setup_rpc方法中,创建了该类的对象,对象传递给了SecurityGroupAgentRpc,用来向Plugin请求安全组相关的信息。
class SecurityGroupServerRpcCallback:RPC服务端(对应的客户端为class SecurityGroupServerRpcApi),用在Plugin端。
class SecurityGroupAgentRpcApiMixin:RPC客户端(对应的服务端为class SecurityGroupAgentRpcCallbackMixin),用在Plugin端。在neutron/plugins/ml2/plugin.py源码文件中的_start_rpc_notifiers方法中,创建了class AgentNotifierApi,其继承了class SecurityGroupAgentRpcApiMixin。在neutron/db/securitygroups_rpc_base.py源码文件中class SecurityGroupServerNotifierRpcMixin中会使用class AgentNotifierApi对象调用class SecurityGroupAgentRpcApiMixin里的方法,进而通过RPC通知到服务端。
class SecurityGroupAgentRpcCallbackMixin:RPC服务端(对应的客户端为class SecurityGroupAgentRpcApiMixin),用在Agent端。在neutron/plugins/ml2/drivers/linuxbridge/agent/linuxbridge_neutron_agent.py源码文件中的class LinuxBridgeRpcCallbacks继承了此类。class LinuxBridgeRpcCallbacks对象会作为rpc topic=’q-agent-notifier-security_group-update’的endpoints。因此Plugin通过RPC通告安全组资源操作最终会回调class SecurityGroupAgentRpcApiMixin里的方法。
class SecurityGroupServerAPIShim:Agent端。用来取代class SecurityGroupServerRpcApi。在class SecurityGroupServerAPIShim类中并看不出哪里用到了RPC机制。这个类中有个成员rcache,该成员是class RemoteResourceCache类对象(neutron/agent/resource_cache.py)。要分析清楚这里的RPC流程,需要回到Openvswitch agent的初始化阶段:在neutron/plugin/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py源码文件中的类class OVSNeutronAgent的setup_rpc方法,创建了class OVSPluginApi对象,创建此对象的过程中会创建class RemoteResourceCache类对象,之后调用了class RemoteResourceCache类的start_watcher方法。start_watcher方法中会创建class RemoteResourceWatcher(neutron/agent/resource_cache.py),class RemoteResourceWatcher类方法_init_rpc_listeners会监听topic=’neutron-vo-%(resource_type)s-%(version)s’的rpc通知。对于安全组资源而言,其中的resource_type为SecurityGroup或SecurityGroupRule。最终会回调到class SecurityGroupServerAPIShim类中注册的函数。具体流程参见第6节。
- neutron/plugins/ml2/ovo_rpc.py
class OVOServerRpcInterface:该类是class _ObjectChangeHandler的封装类,仅此而已。
class _ObjectChangeHandler:该类会针对多类资源(网络、子网、端口、安全组、安全组规则)订阅各类事件的本地通告(本地,非RPC)回调函数。事件涉及有AFTER_CREATE, AFTER_UPDATE, AFTER_DELETE。当收到本地事件通告后,会调用class ResourcePushRpcApi(下面会介绍)将事件通过RPC推送到Agent端。对于安全组资源而言,在neutron/db/securitygroups_db.py中CRUD安全组或安全组规则资源后,都会发出对应的事件通告。class _ObjectChangeHandler收到本地通告后,再通过RPC推送到Agent端,其中rpc topic= topic=’neutron-vo-%(resource_type)s-%(version)s’。
- neutron/api/rpc/handlers/resources_rpc.py
class ResourcesPullRpcApi:Agent端,RPC客户端(对应服务端为class ResourcesPullRpcCallback)。用于主动从Neutron Server拉取资源信息。在class RemoteResourceCache(neutron/agent/resource_cache.py)有用到。
class ResourcesPullRpcCallback:Plugin端,RPC服务端(对应客户端为class ResourcesPullRpcApi)。
class ResourcesPullRpcApi:Plugin端,RPC客户端(对应服务端为class ResourcesPullRpcCallback)。用于从Plugin端推送资源更新到Agent端。在class OVOServerRpcInterface(neutron/plugins/ml2/ovo_rpc.py)有用到。
class ResourcesPullRpcCallback:Agent端,RPC服务端(对应客户端为class ResourcesPullRpcApi)。在class RemoteResourceWatcher(neutron/agent/resource_cache.py)有用到。
- Agent
和Plugin一样,安全组模块也没有独立的一个Agent,它是从属于Neutron L2 Agent。例如Neutron Linuxbridge Agent或Neutron Openvswitch Agent。这篇文章我们重点分析Neutron Linuxbridge Agent和Neutron Openvswitch Agent。其他L2 Agent应该也是类似的。
Neutron Openvswitch Agent:在neutron/plugin/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py源码文件中的类class OVSNeutronAgent,在其__init__方法中创建了类class SecurityGroupAgentRpc(在neutron/agent/securitygroups_rpc.py源码文件)对象,该对象我们可以理解为安全组模块的Agent。
Neutron Linuxbridge Agent:在neutron/plugins/ml2/drivers/agent/_common_agent.py源码文件中的类class CommonAgentLoop(这个类对象会是在linuxbridge agent main函数中创建的,会被linuxbridge agent引用的)setup_rpc方法中创建了类class SecurityGroupAgentRpc(在neutron/agent/securitygroups_rpc.py源码文件)对象,该对象我们可以理解为安全组模块的Agent。
- Driver
在Agent中,class SecurityGroupAgentRpc的__init__方法中会根据我们的配置文件加载对应的安全组driver。主要有四个driver,如下:
iptables:此driver对应在neutron/agent/linux/iptables_firewall.py源码文件中的类class IptablesFirewallDriver。该driver主要用在linuxbridge作为ml2机制驱动的时候,底层是用iptables+connection track来实现的。
iptables_hybrid:此driver对应在neutron/agent/linux/iptables_firewall.py源码文件中的类class OVSHybridIptablesFirewallDriver。该类继承了IptablesFirewallDriver。该driver主要用在openvswitch作为ml2机制驱动的时候,底层是用iptables+connection track来实现的。
noop:此driver对应在neutron/agent/firewall.py源码文件中的类class NoopFirewallDriver。该driver实现的是一个空驱动,即没有任何的具体实现。因此,如果想不要安全组的功能特性,可以将driver配置为noop。
openvswitch:此driver对应在neutron/agent/linux/openvswitch_firewall.py源码文件中的类class OVSFirewallDriver。该driver主要用在openvswitch作为ml2机制驱动的时候,底层是用openflow+connection track来实现的。
2 总体框架
如上图所示,ML2 Plugin收到用户发来的安全组资源请求时,校验通过后会将资源信息存储到数据库(DB)上。接着通过RPC通道将资源更新信息通知到其他节点(计算节点、网络节点)的Neutron L2 Agent(Neutron Linuxbridge Agent或Neutron Openvswitch Agent)。通过配置好的driver将安全组资源信息转换为iptables规则或openflow流表并下发至相关的Linuxbridge或Openvswitch桥。
3 类关系
这里的类设计主要分析几个比较重要的类。下面图中的类图关系箭头,除了红色箭头之外,其他箭头含义都是遵循标准UML类图进行建模。
3.1 Plugin/DB类
3.2 RPC类
下面这个类关系图主要是针对Linuxbridge Agent的场景。
下面这个类关系图主要是针对Openvswitch Agent的场景。
3.3 Agent类
下面这个类关系图主要是针对Linuxbridge Agent的场景。
下面这个类关系图主要是针对Openvswitch Agent的场景。
3.4 Driver类
4 本地回调
回顾上面第4节的内容,有部分类图关系中标注了”本地通告”字样,这个”本地通告”的作用就是本地进程注册监听某类资源事件回调的过程。
关于本地回调,源码上对应有两处,如下:
- neutron/api/rpc/callbacks:此目录下,对于安全组模块,涉及重要的源码文件有如下:
resource_manager.py:此源码文件中,首先定义了一个基础类ResourceCallbacksManager。class ProducerResourceCallbacksManager和class ConsumerResourceCallbacksManager分别继承了此基类。这两个子类中都有一个重要的成员_callbacks,用于记录下对应资源的回调函数。因此这个源码文件的作用是用来维护管理各类资源类型对应的回调函数,包含:注册回调函数、解注册回调函数、获取回调函数信息以及清除回调函数信息。
consumer/registry.py:此源码文件主要针对被动消费者。消费者通过调用注册函数register或subscribe提前注册对某类资源的回调函数,这个时候消费者并不会去主动获取资源信息,而是由生产者主动通过函数push回调消费者之前注册的函数(允许一类资源注册多个回调函数),从而将资源推送给消费者。此源码文件在安全组模块中的class RemoteResourceWatcher(neutron/agent/resource_cache.py)类中用到,在class RemoteResourceWatcher类中用于注册各类资源(含网络、子网、端口、安全组、安全组规则等)的回调,提供的回调方法是resource_change_handler,对应的生产者为class ResourcesPushRpcCallback(neutron/api/rpc/handlers/resources_rpc.py),class ResourcesPushRpcCallback类为RPC服务端的endpoint,通过rpc调用push函数,将rpc的消息转换为本地通告推送给class RemoteResourceWatcher。
producer/registry.py:此源码文件主要针对主动消费者。生产者首先调用provide函数注册对某类资源的回调函数,当消费者需要某类资源时,主动调用pull函数获取回调函数(只允许一类资源注册一个回调函数,因为资源的提供者只能有一个)进而调用回调函数获取具体的资源信息。此源码文件在安全组模块中暂未看到有哪个地方有使用。
- neutron/callbacks
manager.py:此源码文件提供了class CallbackManager类,该类有一个重要的成员_callbacks,用于记录某类资源某类事件对应注册的回调函数。一个回调函数支持注册多类资源多个事件,一类资源的某类事件支持接受多个回调函数的注册。该类的重要成员记录回调函数的方法为:self._callbacks[resource][event][callback_id] = callback。class CallbackManager类不仅提供了注册函数、解注册函数,还提供了通告/发布函数(publish/notify)。通告/发布函数会根据某类资源的具体事件获取到已经注册的回调函数(可能有多个),遍历每个回调函数并调用,将资源的事件通告信息推送给消费者。
registry.py:对class CallbackManager类的封装。上层应用主要通过此源码文件提供的方法进行操作。此源码文件还提供了一个函数receives,该方法在neutron的源码中大量使用,作用就是通过注解的方法来完成回调函数的注册,如下面给出的示例(如下图所示):通过添加注解,调用receives函数,实现对PORT资源,多个事件的回调函数注册,对应的回调函数即为notify_sg_on_port_change方法。此源码文件在安全组模块中大量被使用,如在class SecurityGroupServerAPIShim中的__init__和register_legacy_sg_notification_callbacks方法中就注册了对安全组、安全组规则更新和删除事件的回调。对应的生产者则在class RemoteResourceCache中的record_resource_update和record_resource_delete方法。又如在class _ObjectChangeHandler中__init__方法针对多类资源(含网络、子网、端口、安全组、安全组规则等)多个事件注册了回调函数(handle_event),对应的生产者为class SecurityGroupDbMixin中的_registry_notify函数。
5 业务流
这节主要分析安全组资源从ML2 CRUD操作到Agent处理的整个业务流程。主要分两个场景阐述,即:Linuxbridge Agent场景和Openvswitch Agent场景。
5.1 Linuxbridge Agent场景
5.1.1 Plugin端
5.1.2 Agent端
下面是LinuxbridgeAgent在处理Neutron Port的新增、更新及删除时与安全组Agent及Driver之间的交互流程:
5.2 Openvswitch Agent场景
5.2.1 Plugin端
5.2.2 Agent端
上图中涉及的”接收到本地PORT通过,事件为xxx”,这个流程是这样的:ovs agent从ovsdb监控到端口信息,在处理的过程中会通过rpc从neutron server端拉取端口信息(用的是RemoteResourceCache类中的get_resource_by_id或get_resources方法,如果本地缓存没有则通过rpc从neutron server端拉取,拉取到端口信息后,会进一步触发本地通告。而此时,SecurityGroupServerAPIShim类注册了监听回调,进一步处理端口关联的安全组信息)
(security_groups_member_update--->当port更新或删除时,提取出此port所关联的sg,假设为sg1,接着找出ports(这些ports关联的安全组规则中,有通过remote_group_id引用了sg1)),则说明port的更新或删除事件,需要通知这些ports去刷新他们的安全组规则(即:刷新ipset里面的ip集合---增加或删除IP)
下面是OVS Agent在处理Neutron Port的新增、更新及删除时与安全组Agent及Driver之间的交互流程:
上面附件所示的图中(如上面截图所示),涉及根据端口信息计算关联的安全组、安全组规则以及remote安全组信息的过程。这个过程涉及RPC的交互过程,下面给出具体的交互流程图。
6 总结
本文主要从代码结构、总体框架、类关系、本地回调以及业务流程对openstack安全组模块进行了详细的阐述。通过分析能够更加清楚openstack安全组模块背后的实现细节。希望能够对想深入理解openstack安全组的朋友有较好的帮助。同时对文章所述内容及观点,如有不恰当或不正确之处,还希望各位指正,万分感谢!