nova添加neutron安全组代码流程分析




依赖知识点

  • neutron-server启动流程(包含处理HTTP请求的core plugin和extensions注册流程)
  • neutron-openvswitch-agent启动流程
  • neutron openvswitch(或Linux bridge)firewall安全组配置方案
  • neutron安全组创建、安全组规则创建流程
  • neutron rpc及callback机制
  • nova client(或openstack client)命令行及代码流程
  • nova-api启动流程(包含处理HTTP请求的controller注册流程)
  • nova-compute启动流程
  • nova云主机创建流程
  • WSGI路由规则
  • paste deploy WSGI框架

我们可以使用nova命令行给一台云主机(或者叫虚拟机、实例,下面统一叫云主机)添加安全组(前提是云主机已经成功创建,neutron安全组和规则也都创建完毕),其命令行如下:

本文的目的就是分析一下“nova add-secgroup”这个操作在nova项目和neutron项目中,具体执行到的代码流程,分析的相关项目的OpenStack版本是Mitaka。

本人水平有限,如有谬误,请不吝指正!

Nova项目代码流程

nova部分的代码流程比较简单,nova add-secgroup命令会通过client封装并发送HTTP请求到nova-api服务,对应的curl命令发送方式为(可以通过nova –debug add-secgroup $UUID $SGID获得):

nova-api接受到请求后,会交给nova.api.openstack.compute.security_groups.SecurityGroupActionController进行实际的处理,对应的处理方法为:

这段代码比较简单,_invoke没什么好说的,看一眼就明白,主要是self.security_group_api这个是什么要搞清楚,因为它影响接下来的代码流程。一般来讲,类里面的属性都是在__init__里面初始化的,我们首先去那边找找看:

是的,就是在这里初始化的。这里又涉及到openstack_driver是什么从哪儿来的问题(我看代码的三问:是什么?从哪儿来?到哪儿去?)。在nova\network\security_group\openstack_driver.py找到get_openstack_security_group_driver:

可以看到,_get_openstack_security_group_driver这个方法会import对应的类,这里还有一个is_neutron_security_groups方法,CONF.security_group_api.lower() == ‘neutron’这半个条件容易迷惑人,因为这个配置项的默认值是’nova’,因此实际上是后面的nova.network.is_neutron()条件才有效,找到nova\network\__init__.py中的is_neutron:

可以看到network_api_class、use_neutron这两个配置项,根据我们实际环境的配置(/etc/nova/nova.conf),network_api_class未配置为默认值,use_neutron=True,因此is_neutron()返回True。再回到openstack_driver.py和SecurityGroupActionController,最终确定security_group_api是NEUTRON_DRIVER = (‘nova.network.security_group.neutron_driver.SecurityGroupAPI’)。因此self.security_group_api.add_to_instance调用的是nova.network.security_group.neutron_driver.SecurityGroupAPI#add_to_instance方法:

这段代码也比较简单清晰,先根据安全组的名称或id找到安全组,之后根据instance uuid找到云主机上的ports,最后遍历ports调用update_port接口将安全组更新到每个port上。

nova项目代码流程到此结束。

Neutron项目代码流程

使用nova命令行添加安全组到云主机的主要流程实际是在neutron项目中,neutron client会封装update_port并发送HTTP请求给neutron-server,neutron-server收到后转发给base controller,然后根据之前注册好的controller和url映射关系,以及HTTP method(method这里是PUT,url是/port,映射过来就是update_port),再动态找到处理请求的core plugin和extensions(这段代码流程需要分析neutron-server启动流程),我们配置的core plugin是ml2,因此首先转到neutron.plugins.ml2.plugin.Ml2Plugin#update_port:

这个流程就比较复杂了,但我们要谨记一点,nova调用的update_port方法,传入的参数只有2个,一个是port_id,一个是包含security_groups属性的port信息:

知道这些信息后,代码流程就可以忽略很多无关的分支,整理后的相关代码分支如下:

check_and_notify_security_group_member_changed这个方法是关键流程,它是在基类neutron.db.securitygroups_rpc_base.SecurityGroupServerRpcMixin实现的,用途是通过rpc发送port安全组更新通知给neutron-openvswitch-agent服务,我们单独分析下:

走到这里,就又遇到了代码三问,notifier是什么?从哪儿来?security_groups_member_updated到哪儿去找?因为上面的代码流程有很多基类和派生类之间的跳转,还是得回到最初调用过来的地方,然后一路再找回来,我们目前分析过的类的跳转关系如下:

  1. neutron.plugins.ml2.plugin.Ml2Plugin#update_port
  2. neutron.db.securitygroups_rpc_base.SecurityGroupServerRpcMixin#check_and_notify_security_group_member_changed

其中SecurityGroupServerRpcMixin是Ml2Plugin的基类,因此self.notifier不在SecurityGroupServerRpcMixin这里就肯定在Ml2Plugin里,我们在Ml2Plugin的__init__方法里没有直接找到self.notifier的初始化代码,但看到了self._start_rpc_notifiers()这个方法,里面有self.notifier初始化过程(其实我是直接搜索字符串”self.notifier = “找到的_start_rpc_notifiers,然后再找_start_rpc_notifiers的调用方,就找到了__init__,这也是我常用的·猥琐·查找代码方法,个人感觉比较快速有效):

我们要找的security_groups_member_updated方法在AgentNotifierApi类没有实现,只能从基类里面查找:

上面的代码是发送rpc请求到agent端(neutron-openvswitch-agent端的rpc consumer注册流程,需要分析agent的启动流程),agent端的回调方法在:

接下来是self.sg_agent三问:是什么?从哪儿来?到哪里去?分析neutron-openvswitch-agent启动流程可以得知,self.sg_agent是在neutron.plugins.ml2.drivers.openvswitch.agent.ovs_neutron_agent.OVSNeutronAgent#__init__方法中定义并初始化的:

因此只能等轮询过程调用refresh_firewall了,分析neutron-openvswitch-agent启动流程,可以知道轮询执行流程如下:

  1. neutron.plugins.ml2.drivers.openvswitch.agent.ovs_neutron_agent.main
  2. neutron.plugins.ml2.drivers.openvswitch.agent.ovs_neutron_agent.OVSNeutronAgent#daemon_loop
  3. neutron.plugins.ml2.drivers.openvswitch.agent.ovs_neutron_agent.OVSNeutronAgent#rpc_loop
  4. neutron.plugins.ml2.drivers.openvswitch.agent.ovs_neutron_agent.OVSNeutronAgent#process_network_ports
  5. neutron.agent.securitygroups_rpc.SecurityGroupAgentRpc#setup_port_filters
  6. neutron.agent.securitygroups_rpc.SecurityGroupAgentRpc#refresh_firewall

2017-09-20补充:ipdb加断点调试后确认之前分析的流程是正确的。

上述流程没有仔细分析,但应该差不多就是这样了。

self.delete_all_port_flows(of_port) 、self.initialize_port_flows(of_port) 、self.add_flows_from_rules(of_port)这三个方法分别执行清空port上的openflow流表、初始化流表、根据安全组规则添加流,具体执行流程不再分析,最终是通过ovs-ofctl命令进行br-int网桥上的流表规则的下发,这其中还牵涉到安全组规则到openflow流规则的转换过程。

这部分也没有来得及仔细分析,感觉应该是在启动过程中注册的br-int网桥的driver(neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py:__init__方法有self.int_br = self.br_int_cls(ovs_conf.integration_bridge)初始化)。

关于rpc类的命名和用途的联系,我参考这些RPC类的注释分析相关类的用途后,得到到结论如下(不知道是否正确):

  • SecurityGroupServerRpcApi:注意里面的”ServerRpc”字段,表明这是发送到neutron-server的rpc client,因此调用方(client端)是agent或者extension,neutron-server是server端
  • SecurityGroupServerRpcCallback:对应上面rpc client发送请求的rpc server端的回调方法,用来处理rpc请求并返回结果(返回结果仅限call类型的同步的rpc请求,cast异步请求不返回)
  • SecurityGroupAgentRpcApiMixin:”AgentRpc”表明是发送到agent或者extension的rpc client,因此调用方(rpc client端)neutron-server,rpc server端是neutron-openvswitch-agent或其他extension
  • SecurityGroupAgentRpcCallbackMixin:对应上面rpc client请求的rpc server端的回调处理方法

其它rpc类的命名和用途的联系也是类似情况,知道这个规则,可以更简单的理解rpc调用关系以及代码是在哪个服务里执行的。