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
This commit is contained in:
justin ljj 2012-08-27 16:27:23 +08:00 committed by Jiajun Liu
parent 3e0afd8d11
commit e1130a9674
4 changed files with 56 additions and 5 deletions

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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