Create arping helper in ip_lib

In trying to restructure the L3 agent in to more modules, some helpers
like arping will be used by several modules.  It is better to relocate
it to a common module which all of them will import and use.

Since there is only one spot which passed 'distributed=True', I chose
to break the utility in to two.  Also, 'distributed' doesn't really
describe what that argument is for.  So, I named the second utility
differently to indicate that it is for sending garps when proxyarp is
in use for the address on the interface.

Change-Id: Icfdf41917e9e2e0dcd2be19297aee5ac89e96e94
Partially-Implements: blueprint restructure-l3-agent
This commit is contained in:
Carl Baldwin 2015-01-12 16:36:40 +00:00
parent 6df7f5abf1
commit 7333678c13
6 changed files with 156 additions and 67 deletions

View File

@ -714,8 +714,11 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
else:
# As GARP is processed in a distinct thread the call below
# won't raise an exception to be handled.
self._send_gratuitous_arp_packet(
ri.ns_name, interface_name, fip_ip)
ip_lib.send_gratuitous_arp(ri.ns_name,
interface_name,
fip_ip,
self.conf.send_arp_for_ha,
self.root_helper)
return l3_constants.FLOATINGIP_STATUS_ACTIVE
def _remove_floating_ip(self, ri, device, ip_cidr):
@ -770,33 +773,6 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
def _get_ex_gw_port(self, ri):
return ri.router.get('gw_port')
def _arping(self, ns_name, interface_name, ip_address, distributed=False):
if distributed:
device = ip_lib.IPDevice(interface_name, self.root_helper,
namespace=ns_name)
ip_cidr = str(ip_address) + FLOATING_IP_CIDR_SUFFIX
net = netaddr.IPNetwork(ip_cidr)
device.addr.add(net.version, ip_cidr, str(net.broadcast))
arping_cmd = ['arping', '-A',
'-I', interface_name,
'-c', self.conf.send_arp_for_ha,
ip_address]
try:
ip_wrapper = ip_lib.IPWrapper(self.root_helper,
namespace=ns_name)
ip_wrapper.netns.execute(arping_cmd, check_exit_code=True)
except Exception:
LOG.exception(_LE("Failed sending gratuitous ARP."))
if distributed:
device.addr.delete(net.version, ip_cidr)
def _send_gratuitous_arp_packet(self, ns_name, interface_name, ip_address,
distributed=False):
if self.conf.send_arp_for_ha > 0:
eventlet.spawn_n(self._arping, ns_name, interface_name, ip_address,
distributed)
def get_internal_device_name(self, port_id):
return (INTERNAL_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN]
@ -894,8 +870,11 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
extra_subnets=ex_gw_port.get('extra_subnets', []),
preserve_ips=preserve_ips)
ip_address = ex_gw_port['ip_cidr'].split('/')[0]
self._send_gratuitous_arp_packet(ns_name,
interface_name, ip_address)
ip_lib.send_gratuitous_arp(ns_name,
interface_name,
ip_address,
self.conf.send_arp_for_ha,
self.root_helper)
def external_gateway_removed(self, ri, ex_gw_port, interface_name):
if ri.router['distributed']:
@ -948,8 +927,11 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback,
self.driver.init_l3(interface_name, [internal_cidr],
namespace=ns_name)
ip_address = internal_cidr.split('/')[0]
self._send_gratuitous_arp_packet(ns_name, interface_name,
ip_address)
ip_lib.send_gratuitous_arp(ns_name,
interface_name,
ip_address,
self.conf.send_arp_for_ha,
self.root_helper)
def internal_network_added(self, ri, port):
network_id = port['network_id']

View File

@ -242,7 +242,11 @@ class AgentMixin(object):
self.driver.init_l3(interface_name, [ex_gw_port['ip_cidr']],
namespace=ns_name)
ip_address = ex_gw_port['ip_cidr'].split('/')[0]
self._send_gratuitous_arp_packet(ns_name, interface_name, ip_address)
ip_lib.send_gratuitous_arp(ns_name,
interface_name,
ip_address,
self.conf.send_arp_for_ha,
self.root_helper)
gw_ip = ex_gw_port['subnet']['gateway_ip']
if gw_ip:
@ -332,9 +336,11 @@ class AgentMixin(object):
device.route.add_route(fip_cidr, str(rtr_2_fip.ip))
interface_name = (
self.get_fip_ext_device_name(self.agent_gateway_port['id']))
self._send_gratuitous_arp_packet(fip_ns_name,
interface_name, floating_ip,
distributed=True)
ip_lib.send_garp_for_proxyarp(fip_ns_name,
interface_name,
floating_ip,
self.conf.send_arp_for_ha,
self.root_helper)
# update internal structures
ri.dist_fip_count = ri.dist_fip_count + 1

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import eventlet
import os
import netaddr
@ -20,7 +21,10 @@ from oslo.config import cfg
from neutron.agent.linux import utils
from neutron.common import exceptions
from neutron.i18n import _LE
from neutron.openstack.common import log as logging
LOG = logging.getLogger(__name__)
OPTS = [
cfg.BoolOpt('ip_lib_force_root',
@ -610,3 +614,50 @@ def iproute_arg_supported(command, arg, root_helper=None):
stdout, stderr = utils.execute(command, root_helper=root_helper,
check_exit_code=False, return_stderr=True)
return any(arg in line for line in stderr.split('\n'))
def _arping(ns_name, iface_name, address, count, root_helper):
arping_cmd = ['arping', '-A', '-I', iface_name, '-c', count, address]
try:
ip_wrapper = IPWrapper(root_helper, namespace=ns_name)
ip_wrapper.netns.execute(arping_cmd, check_exit_code=True)
except Exception:
msg = _LE("Failed sending gratuitous ARP "
"to %(addr)s on %(iface)s in namespace %(ns)s")
LOG.exception(msg, {'addr': address,
'iface': iface_name,
'ns': ns_name})
def send_gratuitous_arp(ns_name, iface_name, address, count, root_helper):
"""Send a gratuitous arp using given namespace, interface, and address"""
def arping():
_arping(ns_name, iface_name, address, count, root_helper)
if count > 0:
eventlet.spawn_n(arping)
def send_garp_for_proxyarp(ns_name, iface_name, address, count, root_helper):
"""
Send a gratuitous arp using given namespace, interface, and address
This version should be used when proxy arp is in use since the interface
won't actually have the address configured. We actually need to configure
the address on the interface and then remove it when the proxy arp has been
sent.
"""
def arping_with_temporary_address():
# Configure the address on the interface
device = IPDevice(iface_name, root_helper, namespace=ns_name)
net = netaddr.IPNetwork(str(address))
device.addr.add(net.version, str(net), str(net.broadcast))
_arping(ns_name, iface_name, address, count, root_helper)
# Delete the address from the interface
device.addr.delete(net.version, str(net))
if count > 0:
eventlet.spawn_n(arping_with_temporary_address)

View File

@ -88,7 +88,7 @@ class L3AgentTestFramework(base.BaseOVSLinuxTestCase):
'%s/external/pids' % temp_dir)
conf.set_override('host', host)
agent = l3_test_agent.TestL3NATAgent(host, conf)
mock.patch.object(agent, '_arping').start()
mock.patch.object(ip_lib, 'send_gratuitous_arp').start()
return agent

View File

@ -203,9 +203,13 @@ class TestBasicRouterOperations(base.BaseTestCase):
self.external_process = self.external_process_p.start()
self.send_arp_p = mock.patch(
'neutron.agent.l3.agent.L3NATAgent._send_gratuitous_arp_packet')
'neutron.agent.linux.ip_lib.send_gratuitous_arp')
self.send_arp = self.send_arp_p.start()
self.send_arp_proxyarp_p = mock.patch(
'neutron.agent.linux.ip_lib.send_garp_for_proxyarp')
self.send_arp_proxyarp = self.send_arp_proxyarp_p.start()
self.dvr_cls_p = mock.patch('neutron.agent.linux.interface.NullDriver')
driver_cls = self.dvr_cls_p.start()
self.mock_driver = mock.MagicMock()
@ -328,7 +332,8 @@ class TestBasicRouterOperations(base.BaseTestCase):
self.assertEqual(self.mock_driver.plug.call_count, 1)
self.assertEqual(self.mock_driver.init_l3.call_count, 1)
self.send_arp.assert_called_once_with(ri.ns_name, interface_name,
'99.0.1.9')
'99.0.1.9',
mock.ANY, mock.ANY)
elif action == 'remove':
self.device_exists.return_value = True
agent.internal_network_removed(ri, port)
@ -415,7 +420,8 @@ class TestBasicRouterOperations(base.BaseTestCase):
self.assertEqual(self.mock_driver.init_l3.call_count, 1)
self.send_arp.assert_called_once_with(ri.ns_name,
interface_name,
'20.0.0.30')
'20.0.0.30',
mock.ANY, mock.ANY)
kwargs = {'preserve_ips': ['192.168.1.34/32'],
'namespace': 'qrouter-' + router['id'],
'gateway': '20.0.0.1',
@ -468,7 +474,7 @@ class TestBasicRouterOperations(base.BaseTestCase):
self.assertEqual(self.mock_driver.plug.call_count, 0)
self.assertEqual(self.mock_driver.init_l3.call_count, 1)
self.send_arp.assert_called_once_with(ri.ns_name, interface_name,
'20.0.0.30')
'20.0.0.30', mock.ANY, mock.ANY)
kwargs = {'preserve_ips': ['192.168.1.34/32'],
'namespace': 'qrouter-' + router['id'],
'gateway': '20.0.0.1',
@ -520,30 +526,6 @@ class TestBasicRouterOperations(base.BaseTestCase):
router['gw_port_host'] = HOSTNAME
self._test_external_gateway_action('add', router)
def _test_arping(self, namespace):
if not namespace:
self.conf.set_override('use_namespaces', False)
router_id = _uuid()
ri = l3router.RouterInfo(router_id, self.conf.root_helper, {})
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
floating_ip = '20.0.0.101'
interface_name = agent.get_external_device_name(router_id)
agent._arping(ri, interface_name, floating_ip)
arping_cmd = ['arping', '-A',
'-I', interface_name,
'-c', self.conf.send_arp_for_ha,
floating_ip]
self.mock_ip.netns.execute.assert_any_call(
arping_cmd, check_exit_code=True)
def test_arping_namespace(self):
self._test_arping(namespace=True)
def test_arping_no_namespace(self):
self._test_arping(namespace=False)
def test_agent_remove_external_gateway(self):
router = prepare_router_data(num_internal_ports=2)
self._test_external_gateway_action('remove', router)
@ -1856,7 +1838,8 @@ class TestBasicRouterOperations(base.BaseTestCase):
self.assertEqual(self.mock_driver.init_l3.call_count, 1)
if self.conf.use_namespaces:
self.send_arp.assert_called_once_with(fip_ns_name, interface_name,
'20.0.0.30')
'20.0.0.30',
mock.ANY, mock.ANY)
else:
self.utils_exec.assert_any_call(
check_exit_code=True, root_helper=self.conf.root_helper)

View File

@ -917,3 +917,70 @@ class TestIpNeighCommand(TestIPCmdBase):
self.neigh_cmd.delete(4, '192.168.45.100', 'cc:dd:ee:ff:ab:cd')
self._assert_sudo([4], ('del', '192.168.45.100', 'lladdr',
'cc:dd:ee:ff:ab:cd', 'dev', 'tap0'))
class TestArpPing(TestIPCmdBase):
def _test_arping(self, function, address, spawn_n, mIPWrapper):
spawn_n.side_effect = lambda f: f()
function(mock.sentinel.ns_name,
mock.sentinel.iface_name,
address,
mock.sentinel.count,
mock.sentinel.root_helper)
self.assertTrue(spawn_n.called)
mIPWrapper.assert_called_once_with(mock.sentinel.root_helper,
namespace=mock.sentinel.ns_name)
ip_wrapper = mIPWrapper(mock.sentinel.root_helper,
mock.sentinel.ns_name)
# Just test that arping is called with the right arguments
arping_cmd = ['arping', '-A',
'-I', mock.sentinel.iface_name,
'-c', mock.sentinel.count,
address]
ip_wrapper.netns.execute.assert_any_call(arping_cmd,
check_exit_code=True)
@mock.patch.object(ip_lib, 'IPWrapper')
@mock.patch('eventlet.spawn_n')
def test_send_gratuitous_arp(self, spawn_n, mIPWrapper):
self._test_arping(
ip_lib.send_gratuitous_arp, '20.0.0.1', spawn_n, mIPWrapper)
@mock.patch.object(ip_lib, 'IPDevice')
@mock.patch.object(ip_lib, 'IPWrapper')
@mock.patch('eventlet.spawn_n')
def test_send_garp_for_proxy_arp(self, spawn_n, mIPWrapper, mIPDevice):
addr = '20.0.0.1'
ip_wrapper = mIPWrapper(mock.sentinel.root_helper,
mock.sentinel.ns_name)
mIPWrapper.reset_mock()
device = mIPDevice(mock.sentinel.iface_name,
mock.sentinel.root_helper,
namespace=mock.sentinel.ns_name)
mIPDevice.reset_mock()
# Check that the address was added to the interface before arping
def check_added_address(*args, **kwargs):
mIPDevice.assert_called_once_with(mock.sentinel.iface_name,
mock.sentinel.root_helper,
namespace=mock.sentinel.ns_name)
device.addr.add.assert_called_once_with(4, addr + '/32', addr)
self.assertFalse(device.addr.delete.called)
device.addr.reset_mock()
ip_wrapper.netns.execute.side_effect = check_added_address
self._test_arping(
ip_lib.send_garp_for_proxyarp, addr, spawn_n, mIPWrapper)
# Test that the address was removed after arping
device = mIPDevice(mock.sentinel.iface_name,
mock.sentinel.root_helper,
namespace=mock.sentinel.ns_name)
device.addr.delete.assert_called_once_with(4, addr + '/32')
# If this was called then check_added_address probably had a assert
self.assertFalse(device.addr.add.called)