]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
LBaaS VIP doesn't work after delete and re-add
authorLipingMao <limao@cisco.com>
Wed, 2 Apr 2014 07:34:31 +0000 (07:34 +0000)
committerLiping Mao <limao@cisco.com>
Mon, 12 May 2014 01:14:58 +0000 (01:14 +0000)
When delete and re-add the same vip, we need to send gratuitous ARP
to flush the ARP cache in the Router.

Change-Id: Id97088abb95f4433a100abdae8c8726b3be42ed2
Closes-Bug: 1301165

etc/lbaas_agent.ini
etc/neutron/rootwrap.d/lbaas-haproxy.filters
neutron/services/loadbalancer/drivers/haproxy/namespace_driver.py
neutron/tests/unit/services/loadbalancer/drivers/haproxy/test_namespace_driver.py

index 021a8ba227d673be547a340881fb75ef5f088879..68a2759e6d43ab2faad7e0d1baf1bfcbfb93d5da 100644 (file)
@@ -36,3 +36,7 @@
 
 # The user group
 # user_group = nogroup
+
+# When delete and re-add the same vip, send this many gratuitous ARPs to flush
+# the ARP cache in the Router. Set it below or equal to 0 to disable this feature.
+# send_gratuitous_arp = 3
index 1cbfe3378246b243d61c45e1e728838ff278254a..f807887e64ee2527a43211e19d72de1025a2a43b 100644 (file)
@@ -20,3 +20,6 @@ mm-ctl: CommandFilter, mm-ctl, root
 # ip_lib
 ip: IpFilter, ip, root
 ip_exec: IpNetnsExecFilter, ip, root
+
+# arping
+arping: CommandFilter, arping, root
index e07f814d47b6d49e6a08d86757393013c811de0c..b15f7d864827b233697858c9e6fe9cb6b8cc777a 100644 (file)
@@ -53,6 +53,13 @@ OPTS = [
         default=USER_GROUP_DEFAULT,
         help=_('The user group'),
         deprecated_opts=[cfg.DeprecatedOpt('user_group')],
+    ),
+    cfg.IntOpt(
+        'send_gratuitous_arp',
+        default=3,
+        help=_('When delete and re-add the same vip, send this many '
+               'gratuitous ARPs to flush the ARP cache in the Router. '
+               'Set it below or equal to 0 to disable this feature.'),
     )
 ]
 cfg.CONF.register_opts(OPTS, 'haproxy')
@@ -270,6 +277,16 @@ class HaproxyNSDriver(agent_device_driver.AgentDeviceDriver):
             ip_wrapper = ip_lib.IPWrapper(self.root_helper,
                                           namespace=namespace)
             ip_wrapper.netns.execute(cmd, check_exit_code=False)
+            # When delete and re-add the same vip, we need to
+            # send gratuitous ARP to flush the ARP cache in the Router.
+            gratuitous_arp = self.conf.haproxy.send_gratuitous_arp
+            if gratuitous_arp > 0:
+                for ip in port['fixed_ips']:
+                    cmd_arping = ['arping', '-U',
+                                  '-I', interface_name,
+                                  '-c', gratuitous_arp,
+                                  ip['ip_address']]
+                    ip_wrapper.netns.execute(cmd_arping, check_exit_code=False)
 
     def _unplug(self, namespace, port_id):
         port_stub = {'id': port_id}
index 7f436956afd567b52fb44edaf050c8039837c430..f3c3b39727574ea591815ef7d510187bbd52744e 100644 (file)
@@ -33,7 +33,9 @@ class TestHaproxyNSDriver(base.BaseTestCase):
         conf.haproxy.loadbalancer_state_path = '/the/path'
         conf.interface_driver = 'intdriver'
         conf.haproxy.user_group = 'test_group'
+        conf.haproxy.send_gratuitous_arp = 3
         conf.AGENT.root_helper = 'sudo_test'
+        self.conf = conf
         self.mock_importer = mock.patch.object(namespace_driver,
                                                'importutils').start()
 
@@ -278,15 +280,44 @@ class TestHaproxyNSDriver(base.BaseTestCase):
                                                             namespace=
                                                             'test_ns')
             cmd = ['route', 'add', 'default', 'gw', '10.0.0.1']
+            cmd_arping = ['arping', '-U', '-I',
+                          'test_interface', '-c',
+                          self.conf.haproxy.send_gratuitous_arp, '10.0.0.2']
             ip_wrap.assert_has_calls([
                 mock.call('sudo_test', namespace='test_ns'),
                 mock.call().netns.execute(cmd, check_exit_code=False),
+                mock.call().netns.execute(cmd_arping, check_exit_code=False),
             ])
 
             dev_exists.return_value = True
             self.assertRaises(exceptions.PreexistingDeviceFailure,
                               self.driver._plug, 'test_ns', test_port, False)
 
+    def test_plug_not_send_gratuitous_arp(self):
+        self.conf.haproxy.send_gratuitous_arp = 0
+        test_port = {'id': 'port_id',
+                     'network_id': 'net_id',
+                     'mac_address': 'mac_addr',
+                     'fixed_ips': [{'ip_address': '10.0.0.2',
+                                    'subnet': {'cidr': '10.0.0.0/24',
+                                               'gateway_ip': '10.0.0.1'}}]}
+        with contextlib.nested(
+                mock.patch('neutron.agent.linux.ip_lib.device_exists'),
+                mock.patch('netaddr.IPNetwork'),
+                mock.patch('neutron.agent.linux.ip_lib.IPWrapper'),
+        ) as (dev_exists, ip_net, ip_wrap):
+            self.vif_driver.get_device_name.return_value = 'test_interface'
+            dev_exists.return_value = False
+            ip_net.return_value = ip_net
+            ip_net.prefixlen = 24
+
+            self.driver._plug('test_ns', test_port)
+            cmd = ['route', 'add', 'default', 'gw', '10.0.0.1']
+            expected = [
+                mock.call('sudo_test', namespace='test_ns'),
+                mock.call().netns.execute(cmd, check_exit_code=False)]
+            self.assertEqual(expected, ip_wrap.mock_calls)
+
     def test_plug_no_gw(self):
         test_port = {'id': 'port_id',
                      'network_id': 'net_id',