From e1130a96740e1d3180b2aa5a2874d5598ea9320b Mon Sep 17 00:00:00 2001 From: justin ljj Date: Mon, 27 Aug 2012 16:27:23 +0800 Subject: [PATCH] support 'send_arp_for_ha' option in l3_agent Fixes bug 1042046 Sending gratuitous ARPs when a external router or a floating IP was added in order to facilitate HA. I am using iputils-arping package to send ARP packets. You will need to install iputils-arping on Ubuntu and iputils on Fedora/RHEL/CentOS. Arping locates in /usr/bin/arping on Ubuntu and locates in /sbin/arping on Fedora/RHEL/CentOS. Change-Id: I125dbc57b90027dc5e99ff0a5d6877843a0b02a5 --- etc/l3_agent.ini | 4 ++++ etc/quantum/rootwrap.d/l3.filters | 4 ++++ quantum/agent/l3_agent.py | 30 +++++++++++++++++++++++++---- quantum/tests/unit/test_l3_agent.py | 23 +++++++++++++++++++++- 4 files changed, 56 insertions(+), 5 deletions(-) diff --git a/etc/l3_agent.ini b/etc/l3_agent.ini index 586c6670447..c76b1af26b3 100644 --- a/etc/l3_agent.ini +++ b/etc/l3_agent.ini @@ -54,3 +54,7 @@ root_helper = sudo # The time in seconds between state poll requests # polling_interval = 3 + +# Send this many gratuitous ARPs for HA setup. Set it below or equal to 0 +# to disable this feature. +# send_arp_for_ha = 3 diff --git a/etc/quantum/rootwrap.d/l3.filters b/etc/quantum/rootwrap.d/l3.filters index 82b9ed57532..a84800067cc 100644 --- a/etc/quantum/rootwrap.d/l3.filters +++ b/etc/quantum/rootwrap.d/l3.filters @@ -8,6 +8,10 @@ [Filters] +# arping +arping: CommandFilter, /usr/bin/arping, root +arping_sbin: CommandFilter, /sbin/arping, root + # l3_agent sysctl: CommandFilter, /sbin/sysctl, root route: CommandFilter, /sbin/route, root diff --git a/quantum/agent/l3_agent.py b/quantum/agent/l3_agent.py index 26a56fc71f3..b60f5627d3c 100644 --- a/quantum/agent/l3_agent.py +++ b/quantum/agent/l3_agent.py @@ -83,10 +83,10 @@ class L3NATAgent(object): cfg.IntOpt('metadata_port', default=8775, help="TCP Port used by Nova metadata server."), - #FIXME(danwent): not currently used - cfg.BoolOpt('send_arp_for_ha', - default=True, - help="Send gratuitious ARP when router IP is configured"), + cfg.IntOpt('send_arp_for_ha', + default=3, + help="Send this many gratuitous ARPs for HA setup, " + "set it below or equal to 0 to disable this feature."), cfg.BoolOpt('use_namespaces', default=True, help="Allow overlapping IP."), cfg.StrOpt('router_id', default='', @@ -359,6 +359,23 @@ class L3NATAgent(object): LOG.error("Ignoring multiple gateway ports for router %s" % ri.router_id) + def _send_gratuitous_arp_packet(self, ri, interface_name, ip_address): + if self.conf.send_arp_for_ha > 0: + arping_cmd = ['arping', '-A', '-U', + '-I', interface_name, + '-c', self.conf.send_arp_for_ha, + ip_address] + try: + if self.conf.use_namespaces: + ip_wrapper = ip_lib.IPWrapper(self.conf.root_helper, + namespace=ri.ns_name()) + ip_wrapper.netns.execute(arping_cmd, check_exit_code=True) + else: + utils.execute(arping_cmd, check_exit_code=True, + root_helper=self.conf.root_helper) + except Exception as e: + LOG.error(_("Failed sending gratuitous ARP: %s") % str(e)) + def get_internal_device_name(self, port_id): return (INTERNAL_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN] @@ -380,6 +397,8 @@ class L3NATAgent(object): prefix=EXTERNAL_DEV_PREFIX) self.driver.init_l3(interface_name, [ex_gw_port['ip_cidr']], namespace=ri.ns_name()) + ip_address = ex_gw_port['ip_cidr'].split('/')[0] + self._send_gratuitous_arp_packet(ri, interface_name, ip_address) gw_ip = ex_gw_port['subnet']['gateway_ip'] if ex_gw_port['subnet']['gateway_ip']: @@ -454,6 +473,8 @@ class L3NATAgent(object): self.driver.init_l3(interface_name, [internal_cidr], namespace=ri.ns_name()) + ip_address = internal_cidr.split('/')[0] + self._send_gratuitous_arp_packet(ri, interface_name, ip_address) if ex_gw_port: ex_gw_ip = ex_gw_port['fixed_ips'][0]['ip_address'] @@ -494,6 +515,7 @@ class L3NATAgent(object): if not ip_cidr in [addr['cidr'] for addr in device.addr.list()]: net = netaddr.IPNetwork(ip_cidr) device.addr.add(net.version, ip_cidr, str(net.broadcast)) + self._send_gratuitous_arp_packet(ri, interface_name, floating_ip) for chain, rule in self.floating_forward_rules(floating_ip, fixed_ip): ri.iptables_manager.ipv4['nat'].add_rule(chain, rule) diff --git a/quantum/tests/unit/test_l3_agent.py b/quantum/tests/unit/test_l3_agent.py index f7383c3453b..5b45e0ed5cf 100644 --- a/quantum/tests/unit/test_l3_agent.py +++ b/quantum/tests/unit/test_l3_agent.py @@ -125,13 +125,23 @@ class TestBasicRouterOperations(unittest.TestCase): 'network_id': _uuid(), 'mac_address': 'ca:fe:de:ad:be:ef', 'ip_cidr': '20.0.0.30/24'} + interface_name = agent.get_external_device_name(ex_gw_port['id']) if action == 'add': self.device_exists.return_value = False agent.external_gateway_added(ri, ex_gw_port, internal_cidrs) self.assertEquals(self.mock_driver.plug.call_count, 1) self.assertEquals(self.mock_driver.init_l3.call_count, 1) - self.assertEquals(self.mock_ip.netns.execute.call_count, 1) + arping_cmd = ['arping', '-A', '-U', + '-I', interface_name, + '-c', self.conf.send_arp_for_ha, + '20.0.0.30'] + if self.conf.use_namespaces: + self.mock_ip.netns.execute.assert_any_call( + arping_cmd, check_exit_code=True) + else: + self.utils_exec.assert_any_call( + check_exit_code=True, root_helper=self.conf.root_helper) elif action == 'remove': self.device_exists.return_value = True @@ -159,10 +169,21 @@ class TestBasicRouterOperations(unittest.TestCase): 'id': _uuid(), 'mac_address': 'ca:fe:de:ad:be:ef', 'ip_cidr': '20.0.0.30/24'} + interface_name = agent.get_external_device_name(ex_gw_port['id']) if action == 'add': self.device_exists.return_value = False agent.floating_ip_added(ri, ex_gw_port, floating_ip, fixed_ip) + arping_cmd = ['arping', '-A', '-U', + '-I', interface_name, + '-c', self.conf.send_arp_for_ha, + floating_ip] + if self.conf.use_namespaces: + self.mock_ip.netns.execute.assert_any_call( + arping_cmd, check_exit_code=True) + else: + self.utils_exec.assert_any_call( + check_exit_code=True, root_helper=self.conf.root_helper) elif action == 'remove': self.device_exists.return_value = True