]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Implement local ARP responder onto OVS agent
authorÉdouard Thuleau <edouard.thuleau@cloudwatt.com>
Mon, 6 Jan 2014 12:58:39 +0000 (13:58 +0100)
committerÉdouard Thuleau <edouard.thuleau@cloudwatt.com>
Mon, 19 May 2014 19:42:11 +0000 (21:42 +0200)
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

etc/neutron/plugins/openvswitch/ovs_neutron_plugin.ini
neutron/agent/linux/ovs_lib.py
neutron/common/utils.py
neutron/plugins/ml2/drivers/l2pop/README [new file with mode: 0644]
neutron/plugins/openvswitch/agent/ovs_neutron_agent.py
neutron/plugins/openvswitch/common/config.py
neutron/plugins/openvswitch/common/constants.py
neutron/tests/unit/agent/linux/test_ovs_lib.py
neutron/tests/unit/openvswitch/test_ovs_defaults.py
neutron/tests/unit/openvswitch/test_ovs_neutron_agent.py
neutron/tests/unit/openvswitch/test_ovs_tunnel.py

index 50ec55657971022f5ecaf89754b88264ecc638d0..1718d4dd60ea460cdbb68df93404aaa0a57b9cfe 100644 (file)
 #
 # 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
index 5caa42045143c25fc3a310412b1b15bfc1e9593f..97e9af2cb5f6d279d8c2b7b5c2869d1e688e3ea9 100644 (file)
@@ -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
index d72a3ed1c3af8a27957ff6e1aee2903c45a18936..5b0d38a1d24d3d9461dab8c0dc1e4baac46a34d0 100644 (file)
 
 """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 (file)
index 0000000..46bb27e
--- /dev/null
@@ -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
index 70115217314b19aaed55ad231d4c38e5090e1a64..fd565bc09446f0ba9e3618a2cb17254c2f026a41 100644 (file)
@@ -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
index 038b3473117ddc555dfc1a7953db10bbe7876955..b3c332dce783c3d41e873a22f73b0a8afe6432a6 100644 (file)
@@ -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")),
 ]
 
 
index 3a5b4aaae93e811fe9942da84d05ea38b1f154d0..f6e748b53b33f262c1f8258fc7ee04cb679103f5 100644 (file)
@@ -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}
index 4b4231790d1c4ebe71cd3a8353520e46004489f4..a1daa22a01b08be16a3f6ce0cb72107806382ac0 100644 (file)
@@ -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)
index b2d30111df712cdf7715e15158e2a75c3926c48b..0d5c00f730d916326524f22662acbd229ad70a79 100644 (file)
@@ -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)
index 6df77160ea91e7306272435c13b690df9f337f48..b219ad5aa43f8cc4c9ece1dcc0188113199f2617 100644 (file)
@@ -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:
index 1940730fad152fce3f7c3ce8c917803dda05bcf9..8ba75085cbd1623990b651a84b0797d20e25faf0 100644 (file)
@@ -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: