From e7d7414548a29c6b362f5e890cd8f8cb31885b84 Mon Sep 17 00:00:00 2001 From: LipingMao Date: Wed, 2 Apr 2014 07:34:31 +0000 Subject: [PATCH] LBaaS VIP doesn't work after delete and re-add 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 | 4 +++ etc/neutron/rootwrap.d/lbaas-haproxy.filters | 3 ++ .../drivers/haproxy/namespace_driver.py | 17 ++++++++++ .../drivers/haproxy/test_namespace_driver.py | 31 +++++++++++++++++++ 4 files changed, 55 insertions(+) diff --git a/etc/lbaas_agent.ini b/etc/lbaas_agent.ini index 021a8ba227d..68a2759e6d4 100644 --- a/etc/lbaas_agent.ini +++ b/etc/lbaas_agent.ini @@ -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 diff --git a/etc/neutron/rootwrap.d/lbaas-haproxy.filters b/etc/neutron/rootwrap.d/lbaas-haproxy.filters index 1cbfe337824..f807887e64e 100644 --- a/etc/neutron/rootwrap.d/lbaas-haproxy.filters +++ b/etc/neutron/rootwrap.d/lbaas-haproxy.filters @@ -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 diff --git a/neutron/services/loadbalancer/drivers/haproxy/namespace_driver.py b/neutron/services/loadbalancer/drivers/haproxy/namespace_driver.py index e07f814d47b..b15f7d86482 100644 --- a/neutron/services/loadbalancer/drivers/haproxy/namespace_driver.py +++ b/neutron/services/loadbalancer/drivers/haproxy/namespace_driver.py @@ -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} diff --git a/neutron/tests/unit/services/loadbalancer/drivers/haproxy/test_namespace_driver.py b/neutron/tests/unit/services/loadbalancer/drivers/haproxy/test_namespace_driver.py index 7f436956afd..f3c3b397275 100644 --- a/neutron/tests/unit/services/loadbalancer/drivers/haproxy/test_namespace_driver.py +++ b/neutron/tests/unit/services/loadbalancer/drivers/haproxy/test_namespace_driver.py @@ -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',