From befa0b9184eb0c6248d06efa5b02be8217f1722e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89douard=20Thuleau?= Date: Mon, 6 Jan 2014 13:58:39 +0100 Subject: [PATCH] Implement local ARP responder onto OVS agent With ML2 plugin and l2-pop mechanism driver, it's possible to locally answer to the ARP request of the VM and avoid ARP broadcasting emulation on overlay which is costly. When this functionality is enabled, the OVS flows logic evolves to [1]. This functionality was introduce in 2.1 OVS branch [2]. A README is added to describe l2-pop mechanism driver and the agents particularities. [1] https://wiki.openstack.org/wiki/Ovs-flow-logic#OVS_flows_logic_with_local_ARP_responder [2] http://git.openvswitch.org/cgi-bin/gitweb.cgi?p=openvswitch;a=commitdiff;h=f6c8a6b163af343c66aea54953553d84863835f7 DocImpact: New OVS agent flag 'arp_responder' set to false by default Closes-Bug: #1237427 Change-Id: Ic28610faf2df6566d8d876fcd7aed333647970e2 --- .../openvswitch/ovs_neutron_plugin.ini | 5 + neutron/agent/linux/ovs_lib.py | 24 ++++ neutron/common/utils.py | 17 +++ neutron/plugins/ml2/drivers/l2pop/README | 41 ++++++ .../openvswitch/agent/ovs_neutron_agent.py | 135 +++++++++++++++++- neutron/plugins/openvswitch/common/config.py | 2 + .../plugins/openvswitch/common/constants.py | 4 +- .../tests/unit/agent/linux/test_ovs_lib.py | 32 +++++ .../unit/openvswitch/test_ovs_defaults.py | 2 + .../openvswitch/test_ovs_neutron_agent.py | 101 ++++++++++--- .../tests/unit/openvswitch/test_ovs_tunnel.py | 47 +++++- 11 files changed, 386 insertions(+), 24 deletions(-) create mode 100644 neutron/plugins/ml2/drivers/l2pop/README diff --git a/etc/neutron/plugins/openvswitch/ovs_neutron_plugin.ini b/etc/neutron/plugins/openvswitch/ovs_neutron_plugin.ini index 50ec556579..1718d4dd60 100644 --- a/etc/neutron/plugins/openvswitch/ovs_neutron_plugin.ini +++ b/etc/neutron/plugins/openvswitch/ovs_neutron_plugin.ini @@ -128,6 +128,11 @@ # # l2_population = False +# Enable local ARP responder. Requires OVS 2.1. This is only used by the l2 +# population ML2 MechanismDriver. +# +# arp_responder = False + [securitygroup] # Firewall driver for realizing neutron security group function. # firewall_driver = neutron.agent.firewall.NoopFirewallDriver diff --git a/neutron/agent/linux/ovs_lib.py b/neutron/agent/linux/ovs_lib.py index 5caa420451..97e9af2cb5 100644 --- a/neutron/agent/linux/ovs_lib.py +++ b/neutron/agent/linux/ovs_lib.py @@ -22,6 +22,7 @@ from oslo.config import cfg from neutron.agent.linux import ip_lib from neutron.agent.linux import utils from neutron.common import exceptions +from neutron.common import utils as common_utils from neutron.openstack.common import excutils from neutron.openstack.common import jsonutils from neutron.openstack.common import log as logging @@ -586,3 +587,26 @@ def _build_flow_expr_str(flow_dict, cmd): flow_expr_arr.append(actions) return ','.join(flow_expr_arr) + + +def ofctl_arg_supported(root_helper, cmd, args): + '''Verify if ovs-ofctl binary supports command with specific args. + + :param root_helper: utility to use when running shell cmds. + :param cmd: ovs-vsctl command to use for test. + :param args: arguments to test with command. + :returns: a boolean if the args supported. + ''' + supported = True + br_name = 'br-test-%s' % common_utils.get_random_string(6) + test_br = OVSBridge(br_name, root_helper) + test_br.reset_bridge() + + full_args = ["ovs-ofctl", cmd, test_br.br_name] + args + try: + utils.execute(full_args, root_helper=root_helper) + except Exception: + supported = False + + test_br.destroy() + return supported diff --git a/neutron/common/utils.py b/neutron/common/utils.py index d72a3ed1c3..5b0d38a1d2 100644 --- a/neutron/common/utils.py +++ b/neutron/common/utils.py @@ -18,8 +18,11 @@ """Utilities and helper functions.""" +import datetime +import hashlib import logging as std_logging import os +import random import signal import socket @@ -199,3 +202,17 @@ def log_opt_values(log): def is_valid_vlan_tag(vlan): return q_const.MIN_VLAN_TAG <= vlan <= q_const.MAX_VLAN_TAG + + +def get_random_string(length): + """Get a random hex string of the specified length. + + based on Cinder library + cinder/transfer/api.py + """ + rndstr = "" + random.seed(datetime.datetime.now().microsecond) + while len(rndstr) < length: + rndstr += hashlib.sha224(str(random.random())).hexdigest() + + return rndstr[0:length] diff --git a/neutron/plugins/ml2/drivers/l2pop/README b/neutron/plugins/ml2/drivers/l2pop/README new file mode 100644 index 0000000000..46bb27e541 --- /dev/null +++ b/neutron/plugins/ml2/drivers/l2pop/README @@ -0,0 +1,41 @@ +Neutron ML2 l2 population Mechanism Drivers + +l2 population (l2pop) mechanism drivers implements the ML2 driver to improve +open source plugins overlay implementations (VXLAN with Linux bridge and +GRE/VXLAN with OVS). This mechanism driver is implemented in ML2 to propagate +the forwarding information among agents using a common RPC API. + +More informations could be found on the wiki page [1]. + +VXLAN Linux kernel: +------------------- +The VXLAN Linux kernel module provide all necessary functionalities to populate +the forwarding table and local ARP responder tables. This module appears on +release 3.7 of the vanilla Linux kernel in experimental: +- 3.8: first stable release, no edge replication (multicast necessary), +- 3.9: edge replication only for the broadcasted packets, +- 3.11: edge replication for broadcast, multicast and unknown packets. + +Note: Some distributions (like RHEL) have backported this module on precedent + kernel version. + +OpenvSwitch: +------------ +The OVS OpenFlow tables provide all of the necessary functionality to populate +the forwarding table and local ARP responder tables. +A wiki page describe how the flow tables did evolve on OVS agents: +- [2] without local ARP responder +- [3] with local ARP responder. /!\ This functionality is only available since + the development branch 2.1. It's possible + to disable (enable by default) it through + the flag 'arp_responder'. /!\ + + +Note: A difference persists between the LB and OVS agents when they are used + with the l2-pop mechanism driver (and local ARP responder available). The + LB agent will drop unknown unicast (VXLAN bridge mode), whereas the OVS + agent will flood it. + +[1] https://wiki.openstack.org/wiki/L2population_blueprint +[2] https://wiki.openstack.org/wiki/Ovs-flow-logic#OVS_flows_logic +[3] https://wiki.openstack.org/wiki/Ovs-flow-logic#OVS_flows_logic_with_local_ARP_responder \ No newline at end of file diff --git a/neutron/plugins/openvswitch/agent/ovs_neutron_agent.py b/neutron/plugins/openvswitch/agent/ovs_neutron_agent.py index 7011521731..fd565bc094 100644 --- a/neutron/plugins/openvswitch/agent/ovs_neutron_agent.py +++ b/neutron/plugins/openvswitch/agent/ovs_neutron_agent.py @@ -152,7 +152,8 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, veth_mtu=None, l2_population=False, minimize_polling=False, ovsdb_monitor_respawn_interval=( - constants.DEFAULT_OVSDBMON_RESPAWN)): + constants.DEFAULT_OVSDBMON_RESPAWN), + arp_responder=False): '''Constructor. :param integ_br: name of the integration bridge. @@ -170,6 +171,8 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, :param ovsdb_monitor_respawn_interval: Optional, when using polling minimization, the number of seconds to wait before respawning the ovsdb monitor. + :param arp_responder: Optional, enable local ARP responder if it is + supported. ''' self.veth_mtu = veth_mtu self.root_helper = root_helper @@ -177,6 +180,11 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, q_const.MAX_VLAN_TAG)) self.tunnel_types = tunnel_types or [] self.l2_pop = l2_population + # TODO(ethuleau): Initially, local ARP responder is be dependent to the + # ML2 l2 population mechanism driver. + self.arp_responder_enabled = (arp_responder and + self._check_arp_responder_support() and + self.l2_pop) self.agent_state = { 'binary': 'neutron-openvswitch-agent', 'host': cfg.CONF.host, @@ -184,7 +192,9 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, 'configurations': {'bridge_mappings': bridge_mappings, 'tunnel_types': self.tunnel_types, 'tunneling_ip': local_ip, - 'l2_population': self.l2_pop}, + 'l2_population': self.l2_pop, + 'arp_responder_enabled': + self.arp_responder_enabled}, 'agent_type': q_const.AGENT_TYPE_OVS, 'start_flag': True} @@ -233,6 +243,20 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, LOG.exception(_("Agent terminated")) raise SystemExit(1) + def _check_arp_responder_support(self): + '''Check if OVS supports to modify ARP headers. + + This functionality is only available since the development branch 2.1. + ''' + args = ['arp,action=load:0x2->NXM_OF_ARP_OP[],' + 'move:NXM_NX_ARP_SHA[]->NXM_NX_ARP_THA[],' + 'move:NXM_OF_ARP_SPA[]->NXM_OF_ARP_TPA[]'] + supported = ovs_lib.ofctl_arg_supported(self.root_helper, 'add-flow', + args) + if not supported: + LOG.warning(_('OVS version can not support ARP responder.')) + return supported + def _report_state(self): # How many devices are likely used by a VM self.agent_state.get('configurations')['devices'] = ( @@ -375,7 +399,8 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, actions="strip_vlan,set_tunnel:%s," "output:%s" % (lvm.segmentation_id, ofports)) else: - # TODO(feleouet): add ARP responder entry + self._set_arp_responder('add', lvm.vlan, port_info[0], + port_info[1]) self.tun_br.add_flow(table=constants.UCAST_TO_TUN, priority=2, dl_vlan=lvm.vlan, @@ -400,11 +425,53 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, # Check if this tunnel port is still used self.cleanup_tunnel_port(ofport, lvm.network_type) else: - #TODO(feleouet): remove ARP responder entry + self._set_arp_responder('remove', lvm.vlan, port_info[0], + port_info[1]) self.tun_br.delete_flows(table=constants.UCAST_TO_TUN, dl_vlan=lvm.vlan, dl_dst=port_info[0]) + def _fdb_chg_ip(self, context, fdb_entries): + '''fdb update when an IP of a port is updated. + + The ML2 l2-pop mechanism driver send an fdb update rpc message when an + IP of a port is updated. + + :param context: RPC context. + :param fdb_entries: fdb dicts that contain all mac/IP informations per + agent and network. + {'net1': + {'agent_ip': + {'before': [[mac, ip]], + 'after': [[mac, ip]] + } + } + 'net2': + ... + } + ''' + LOG.debug(_("update chg_ip received")) + + # TODO(ethuleau): Use OVS defer apply flows for all rules will be an + # interesting improvement here. But actually, OVS lib defer apply flows + # methods doesn't ensure the add flows will be applied before delete. + for network_id, agent_ports in fdb_entries.items(): + lvm = self.local_vlan_map.get(network_id) + if not lvm: + continue + + for agent_ip, state in agent_ports.items(): + if agent_ip == self.local_ip: + continue + + after = state.get('after') + for mac, ip in after: + self._set_arp_responder('add', lvm.vlan, mac, ip) + + before = state.get('before') + for mac, ip in before: + self._set_arp_responder('remove', lvm.vlan, mac, ip) + def fdb_update(self, context, fdb_entries): LOG.debug(_("fdb_update received")) for action, values in fdb_entries.items(): @@ -414,6 +481,47 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, getattr(self, method)(context, values) + def _set_arp_responder(self, action, lvid, mac_str, ip_str): + '''Set the ARP respond entry. + + When the l2 population mechanism driver and OVS supports to edit ARP + fields, a table (ARP_RESPONDER) to resolve ARP locally is added to the + tunnel bridge. + + :param action: add or remove ARP entry. + :param lvid: local VLAN map of network's ARP entry. + :param mac_str: MAC string value. + :param ip_str: IP string value. + ''' + if not self.arp_responder_enabled: + return + + mac = netaddr.EUI(mac_str, dialect=netaddr.mac_unix) + ip = netaddr.IPAddress(ip_str) + + if action == 'add': + actions = ('move:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[],' + 'mod_dl_src:%(mac)s,' + 'load:0x2->NXM_OF_ARP_OP[],' + 'move:NXM_NX_ARP_SHA[]->NXM_NX_ARP_THA[],' + 'move:NXM_OF_ARP_SPA[]->NXM_OF_ARP_TPA[],' + 'load:%(mac)#x->NXM_NX_ARP_SHA[],' + 'load:%(ip)#x->NXM_OF_ARP_SPA[],' + 'in_port' % {'mac': mac, 'ip': ip}) + self.tun_br.add_flow(table=constants.ARP_RESPONDER, + priority=1, + proto='arp', + dl_vlan=lvid, + nw_dst='%s' % ip, + actions=actions) + elif action == 'remove': + self.tun_br.delete_flows(table=constants.ARP_RESPONDER, + proto='arp', + dl_vlan=lvid, + nw_dst='%s' % ip) + else: + LOG.warning(_('Action %s not supported'), action) + def create_rpc_dispatcher(self): '''Get the rpc dispatcher for this manager. @@ -701,13 +809,24 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, actions="resubmit(,%s)" % constants.PATCH_LV_TO_TUN) self.tun_br.add_flow(priority=0, actions="drop") + if self.arp_responder_enabled: + # ARP broadcast-ed request go to the local ARP_RESPONDER table to + # be locally resolved + self.tun_br.add_flow(table=constants.PATCH_LV_TO_TUN, + priority=1, + proto='arp', + dl_dst="ff:ff:ff:ff:ff:ff", + actions=("resubmit(,%s)" % + constants.ARP_RESPONDER)) # PATCH_LV_TO_TUN table will handle packets coming from patch_int # unicasts go to table UCAST_TO_TUN where remote adresses are learnt self.tun_br.add_flow(table=constants.PATCH_LV_TO_TUN, + priority=0, dl_dst="00:00:00:00:00:00/01:00:00:00:00:00", actions="resubmit(,%s)" % constants.UCAST_TO_TUN) # Broadcasts/multicasts go to table FLOOD_TO_TUN that handles flooding self.tun_br.add_flow(table=constants.PATCH_LV_TO_TUN, + priority=0, dl_dst="01:00:00:00:00:00/01:00:00:00:00:00", actions="resubmit(,%s)" % constants.FLOOD_TO_TUN) # Tables [tunnel_type]_TUN_TO_LV will set lvid depending on tun_id @@ -742,6 +861,13 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, priority=0, actions="resubmit(,%s)" % constants.FLOOD_TO_TUN) + if self.arp_responder_enabled: + # If none of the ARP entries correspond to the requested IP, the + # broadcast-ed packet is resubmitted to the flooding table + self.tun_br.add_flow(table=constants.ARP_RESPONDER, + priority=0, + actions="resubmit(,%s)" % + constants.FLOOD_TO_TUN) # FLOOD_TO_TUN will handle flooding in tunnels based on lvid, # for now, add a default drop action self.tun_br.add_flow(table=constants.FLOOD_TO_TUN, @@ -1318,6 +1444,7 @@ def create_agent_config_map(config): tunnel_types=config.AGENT.tunnel_types, veth_mtu=config.AGENT.veth_mtu, l2_population=config.AGENT.l2_population, + arp_responder=config.AGENT.arp_responder, ) # If enable_tunneling is TRUE, set tunnel_type to default to GRE diff --git a/neutron/plugins/openvswitch/common/config.py b/neutron/plugins/openvswitch/common/config.py index 038b347311..b3c332dce7 100644 --- a/neutron/plugins/openvswitch/common/config.py +++ b/neutron/plugins/openvswitch/common/config.py @@ -80,6 +80,8 @@ agent_opts = [ cfg.BoolOpt('l2_population', default=False, help=_("Use ml2 l2population mechanism driver to learn " "remote mac and IPs and improve tunnel scalability")), + cfg.BoolOpt('arp_responder', default=False, + help=_("Enable local ARP responder if it is supported")), ] diff --git a/neutron/plugins/openvswitch/common/constants.py b/neutron/plugins/openvswitch/common/constants.py index 3a5b4aaae9..f6e748b53b 100644 --- a/neutron/plugins/openvswitch/common/constants.py +++ b/neutron/plugins/openvswitch/common/constants.py @@ -45,7 +45,9 @@ GRE_TUN_TO_LV = 2 VXLAN_TUN_TO_LV = 3 LEARN_FROM_TUN = 10 UCAST_TO_TUN = 20 -FLOOD_TO_TUN = 21 +ARP_RESPONDER = 21 +FLOOD_TO_TUN = 22 + # Map tunnel types to tables number TUN_TABLE = {p_const.TYPE_GRE: GRE_TUN_TO_LV, p_const.TYPE_VXLAN: VXLAN_TUN_TO_LV} diff --git a/neutron/tests/unit/agent/linux/test_ovs_lib.py b/neutron/tests/unit/agent/linux/test_ovs_lib.py index 4b4231790d..a1daa22a01 100644 --- a/neutron/tests/unit/agent/linux/test_ovs_lib.py +++ b/neutron/tests/unit/agent/linux/test_ovs_lib.py @@ -915,3 +915,35 @@ class OVS_Lib_Test(base.BaseTestCase): min_kernel_ver = constants.MINIMUM_LINUX_KERNEL_OVS_VXLAN self._check_ovs_vxlan_version(min_vxlan_ver, min_vxlan_ver, min_kernel_ver, expecting_ok=True) + + def test_ofctl_arg_supported(self): + with mock.patch('neutron.common.utils.get_random_string') as utils: + utils.return_value = 'test' + supported = ovs_lib.ofctl_arg_supported(self.root_helper, 'cmd', + ['args']) + self.execute.assert_has_calls([ + mock.call(['ovs-vsctl', self.TO, '--', '--if-exists', 'del-br', + 'br-test-test'], root_helper=self.root_helper), + mock.call(['ovs-vsctl', self.TO, '--', '--may-exist', 'add-br', + 'br-test-test'], root_helper=self.root_helper), + mock.call(['ovs-ofctl', 'cmd', 'br-test-test', 'args'], + root_helper=self.root_helper), + mock.call(['ovs-vsctl', self.TO, '--', '--if-exists', 'del-br', + 'br-test-test'], root_helper=self.root_helper) + ]) + self.assertTrue(supported) + + self.execute.side_effect = Exception + supported = ovs_lib.ofctl_arg_supported(self.root_helper, 'cmd', + ['args']) + self.execute.assert_has_calls([ + mock.call(['ovs-vsctl', self.TO, '--', '--if-exists', 'del-br', + 'br-test-test'], root_helper=self.root_helper), + mock.call(['ovs-vsctl', self.TO, '--', '--may-exist', 'add-br', + 'br-test-test'], root_helper=self.root_helper), + mock.call(['ovs-ofctl', 'cmd', 'br-test-test', 'args'], + root_helper=self.root_helper), + mock.call(['ovs-vsctl', self.TO, '--', '--if-exists', 'del-br', + 'br-test-test'], root_helper=self.root_helper) + ]) + self.assertFalse(supported) diff --git a/neutron/tests/unit/openvswitch/test_ovs_defaults.py b/neutron/tests/unit/openvswitch/test_ovs_defaults.py index b2d30111df..0d5c00f730 100644 --- a/neutron/tests/unit/openvswitch/test_ovs_defaults.py +++ b/neutron/tests/unit/openvswitch/test_ovs_defaults.py @@ -31,3 +31,5 @@ class ConfigurationTest(base.BaseTestCase): self.assertEqual(0, len(cfg.CONF.OVS.bridge_mappings)) self.assertEqual(0, len(cfg.CONF.OVS.network_vlan_ranges)) self.assertEqual(0, len(cfg.CONF.OVS.tunnel_id_ranges)) + self.assertFalse(cfg.CONF.AGENT.l2_population) + self.assertFalse(cfg.CONF.AGENT.arp_responder) diff --git a/neutron/tests/unit/openvswitch/test_ovs_neutron_agent.py b/neutron/tests/unit/openvswitch/test_ovs_neutron_agent.py index 6df77160ea..b219ad5aa4 100644 --- a/neutron/tests/unit/openvswitch/test_ovs_neutron_agent.py +++ b/neutron/tests/unit/openvswitch/test_ovs_neutron_agent.py @@ -18,6 +18,7 @@ import contextlib import sys import mock +import netaddr from oslo.config import cfg import testtools @@ -35,6 +36,10 @@ NOTIFIER = ('neutron.plugins.openvswitch.' 'ovs_neutron_plugin.AgentNotifierApi') OVS_LINUX_KERN_VERS_WITHOUT_VXLAN = "3.12.0" +FAKE_MAC = '00:11:22:33:44:55' +FAKE_IP1 = '10.0.0.1' +FAKE_IP2 = '10.0.0.2' + class CreateAgentConfigMap(base.BaseTestCase): @@ -118,7 +123,10 @@ class TestOvsNeutronAgent(base.BaseTestCase): return_value='00:00:00:00:00:01'), mock.patch('neutron.openstack.common.loopingcall.' 'FixedIntervalLoopingCall', - new=MockFixedIntervalLoopingCall)): + new=MockFixedIntervalLoopingCall), + mock.patch('neutron.plugins.openvswitch.agent.ovs_neutron_agent.' + 'OVSNeutronAgent._check_arp_responder_support', + return_value=True)): self.agent = ovs_neutron_agent.OVSNeutronAgent(**kwargs) self.agent.tun_br = mock.Mock() self.agent.sg_agent = mock.Mock() @@ -503,6 +511,7 @@ class TestOvsNeutronAgent(base.BaseTestCase): self.agent.local_vlan_map = {'net1': lvm1, 'net2': lvm2} self.agent.tun_br_ofports = {'gre': {'1.1.1.1': '1', '2.2.2.2': '2'}} + self.agent.arp_responder_enabled = True def test_fdb_ignore_network(self): self._prepare_l2_pop_ofports() @@ -528,7 +537,7 @@ class TestOvsNeutronAgent(base.BaseTestCase): 'segment_id': 'tun2', 'ports': {'agent_ip': - [['mac', 'ip'], + [[FAKE_MAC, FAKE_IP1], n_const.FLOODING_ENTRY]}}} with mock.patch.object(self.agent.tun_br, "defer_apply_on") as defer_fn: @@ -545,7 +554,7 @@ class TestOvsNeutronAgent(base.BaseTestCase): 'segment_id': 'tun1', 'ports': {'2.2.2.2': - [['mac', 'ip'], + [[FAKE_MAC, FAKE_IP1], n_const.FLOODING_ENTRY]}}} with contextlib.nested( mock.patch.object(self.agent.tun_br, 'add_flow'), @@ -554,12 +563,30 @@ class TestOvsNeutronAgent(base.BaseTestCase): ) as (add_flow_fn, mod_flow_fn, add_tun_fn): self.agent.fdb_add(None, fdb_entry) self.assertFalse(add_tun_fn.called) - add_flow_fn.assert_called_with(table=constants.UCAST_TO_TUN, - priority=2, - dl_vlan='vlan1', - dl_dst='mac', - actions='strip_vlan,' - 'set_tunnel:seg1,output:2') + actions = ('move:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[],' + 'mod_dl_src:%(mac)s,' + 'load:0x2->NXM_OF_ARP_OP[],' + 'move:NXM_NX_ARP_SHA[]->NXM_NX_ARP_THA[],' + 'move:NXM_OF_ARP_SPA[]->NXM_OF_ARP_TPA[],' + 'load:%(mac)#x->NXM_NX_ARP_SHA[],' + 'load:%(ip)#x->NXM_OF_ARP_SPA[],' + 'in_port' % + {'mac': netaddr.EUI(FAKE_MAC, dialect=netaddr.mac_unix), + 'ip': netaddr.IPAddress(FAKE_IP1)}) + add_flow_fn.assert_has_calls([ + mock.call(table=constants.ARP_RESPONDER, + priority=1, + proto='arp', + dl_vlan='vlan1', + nw_dst=FAKE_IP1, + actions=actions), + mock.call(table=constants.UCAST_TO_TUN, + priority=2, + dl_vlan='vlan1', + dl_dst=FAKE_MAC, + actions='strip_vlan,' + 'set_tunnel:seg1,output:2') + ]) mod_flow_fn.assert_called_with(table=constants.FLOOD_TO_TUN, dl_vlan='vlan1', actions='strip_vlan,' @@ -572,16 +599,22 @@ class TestOvsNeutronAgent(base.BaseTestCase): 'segment_id': 'tun2', 'ports': {'2.2.2.2': - [['mac', 'ip'], + [[FAKE_MAC, FAKE_IP1], n_const.FLOODING_ENTRY]}}} with contextlib.nested( mock.patch.object(self.agent.tun_br, 'mod_flow'), mock.patch.object(self.agent.tun_br, 'delete_flows'), ) as (mod_flow_fn, del_flow_fn): self.agent.fdb_remove(None, fdb_entry) - del_flow_fn.assert_called_with(table=constants.UCAST_TO_TUN, - dl_vlan='vlan2', - dl_dst='mac') + del_flow_fn.assert_has_calls([ + mock.call(table=constants.ARP_RESPONDER, + proto='arp', + dl_vlan='vlan2', + nw_dst=FAKE_IP1), + mock.call(table=constants.UCAST_TO_TUN, + dl_vlan='vlan2', + dl_dst=FAKE_MAC) + ]) mod_flow_fn.assert_called_with(table=constants.FLOOD_TO_TUN, dl_vlan='vlan2', actions='strip_vlan,' @@ -592,7 +625,7 @@ class TestOvsNeutronAgent(base.BaseTestCase): fdb_entry = {'net1': {'network_type': 'gre', 'segment_id': 'tun1', - 'ports': {'1.1.1.1': [['mac', 'ip']]}}} + 'ports': {'1.1.1.1': [[FAKE_MAC, FAKE_IP1]]}}} with contextlib.nested( mock.patch.object(self.agent.tun_br, 'add_flow'), mock.patch.object(self.agent.tun_br, 'mod_flow'), @@ -600,7 +633,7 @@ class TestOvsNeutronAgent(base.BaseTestCase): ) as (add_flow_fn, mod_flow_fn, add_tun_fn): self.agent.fdb_add(None, fdb_entry) self.assertFalse(add_tun_fn.called) - fdb_entry['net1']['ports']['10.10.10.10'] = [['mac', 'ip']] + fdb_entry['net1']['ports']['10.10.10.10'] = [[FAKE_MAC, FAKE_IP1]] self.agent.fdb_add(None, fdb_entry) add_tun_fn.assert_called_with('gre-0a0a0a0a', '10.10.10.10', 'gre') @@ -617,6 +650,39 @@ class TestOvsNeutronAgent(base.BaseTestCase): self.agent.fdb_remove(None, fdb_entry) del_port_fn.assert_called_once_with('gre-02020202') + def test_fdb_update_chg_ip(self): + self._prepare_l2_pop_ofports() + fdb_entries = {'chg_ip': + {'net1': + {'agent_ip': + {'before': [[FAKE_MAC, FAKE_IP1]], + 'after': [[FAKE_MAC, FAKE_IP2]]}}}} + with contextlib.nested( + mock.patch.object(self.agent.tun_br, 'add_flow'), + mock.patch.object(self.agent.tun_br, 'delete_flows') + ) as (add_flow_fn, del_flow_fn): + self.agent.fdb_update(None, fdb_entries) + actions = ('move:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[],' + 'mod_dl_src:%(mac)s,' + 'load:0x2->NXM_OF_ARP_OP[],' + 'move:NXM_NX_ARP_SHA[]->NXM_NX_ARP_THA[],' + 'move:NXM_OF_ARP_SPA[]->NXM_OF_ARP_TPA[],' + 'load:%(mac)#x->NXM_NX_ARP_SHA[],' + 'load:%(ip)#x->NXM_OF_ARP_SPA[],' + 'in_port' % + {'mac': netaddr.EUI(FAKE_MAC, dialect=netaddr.mac_unix), + 'ip': netaddr.IPAddress(FAKE_IP2)}) + add_flow_fn.assert_called_once_with(table=constants.ARP_RESPONDER, + priority=1, + proto='arp', + dl_vlan='vlan1', + nw_dst=FAKE_IP2, + actions=actions) + del_flow_fn.assert_called_once_with(table=constants.ARP_RESPONDER, + proto='arp', + dl_vlan='vlan1', + nw_dst=FAKE_IP1) + def test_recl_lv_port_to_preserve(self): self._prepare_l2_pop_ofports() self.agent.l2_pop = True @@ -772,7 +838,10 @@ class AncillaryBridgesTest(base.BaseTestCase): return_value=bridges), mock.patch( 'neutron.agent.linux.ovs_lib.get_bridge_external_bridge_id', - side_effect=pullup_side_effect)): + side_effect=pullup_side_effect), + mock.patch('neutron.plugins.openvswitch.agent.ovs_neutron_agent.' + 'OVSNeutronAgent._check_arp_responder_support', + return_value=True)): self.agent = ovs_neutron_agent.OVSNeutronAgent(**self.kwargs) self.assertEqual(len(ancillary), len(self.agent.ancillary_brs)) if ancillary: diff --git a/neutron/tests/unit/openvswitch/test_ovs_tunnel.py b/neutron/tests/unit/openvswitch/test_ovs_tunnel.py index 1940730fad..8ba75085cb 100644 --- a/neutron/tests/unit/openvswitch/test_ovs_tunnel.py +++ b/neutron/tests/unit/openvswitch/test_ovs_tunnel.py @@ -73,6 +73,12 @@ class TunnelTest(base.BaseTestCase): 'neutron.openstack.common.rpc.impl_fake') cfg.CONF.set_override('report_interval', 0, 'AGENT') + check_arp_responder_str = ('neutron.plugins.openvswitch.agent.' + 'ovs_neutron_agent.OVSNeutronAgent.' + '_check_arp_responder_support') + self.mock_check_arp_resp = mock.patch(check_arp_responder_str).start() + self.mock_check_arp_resp.return_value = True + self.INT_BRIDGE = 'integration_bridge' self.TUN_BRIDGE = 'tunnel_bridge' self.MAP_TUN_BRIDGE = 'tunnel_bridge_mapping' @@ -148,12 +154,12 @@ class TunnelTest(base.BaseTestCase): in_port=self.INT_OFPORT, actions="resubmit(,%s)" % constants.PATCH_LV_TO_TUN), - mock.call.add_flow(priority=0, actions='drop'), - mock.call.add_flow(table=constants.PATCH_LV_TO_TUN, + mock.call.add_flow(priority=0, actions="drop"), + mock.call.add_flow(priority=0, table=constants.PATCH_LV_TO_TUN, dl_dst=UCAST_MAC, actions="resubmit(,%s)" % constants.UCAST_TO_TUN), - mock.call.add_flow(table=constants.PATCH_LV_TO_TUN, + mock.call.add_flow(priority=0, table=constants.PATCH_LV_TO_TUN, dl_dst=BCAST_MAC, actions="resubmit(,%s)" % constants.FLOOD_TO_TUN), @@ -247,6 +253,41 @@ class TunnelTest(base.BaseTestCase): self.VETH_MTU) self._verify_mock_calls() + # TODO(ethuleau): Initially, local ARP responder is be dependent to the + # ML2 l2 population mechanism driver. + # The next two tests use l2_pop flag to test ARP responder + def test_construct_with_arp_responder(self): + ovs_neutron_agent.OVSNeutronAgent(self.INT_BRIDGE, + self.TUN_BRIDGE, + '10.0.0.1', self.NET_MAPPING, + 'sudo', 2, ['gre'], + self.VETH_MTU, l2_population=True, + arp_responder=True) + self.mock_tun_bridge_expected.insert( + 5, mock.call.add_flow(table=constants.PATCH_LV_TO_TUN, + priority=1, + proto="arp", + dl_dst="ff:ff:ff:ff:ff:ff", + actions="resubmit(,%s)" % + constants.ARP_RESPONDER) + ) + self.mock_tun_bridge_expected.insert( + 12, mock.call.add_flow(table=constants.ARP_RESPONDER, + priority=0, + actions="resubmit(,%s)" % + constants.FLOOD_TO_TUN) + ) + self._verify_mock_calls() + + def test_construct_without_arp_responder(self): + ovs_neutron_agent.OVSNeutronAgent(self.INT_BRIDGE, + self.TUN_BRIDGE, + '10.0.0.1', self.NET_MAPPING, + 'sudo', 2, ['gre'], + self.VETH_MTU, l2_population=False, + arp_responder=True) + self._verify_mock_calls() + def test_construct_vxlan(self): with mock.patch.object(ovs_lib, 'get_installed_ovs_klm_version', return_value="1.10") as klm_ver: