]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Create arping helper in ip_lib
authorCarl Baldwin <carl.baldwin@hp.com>
Mon, 12 Jan 2015 16:36:40 +0000 (16:36 +0000)
committerCarl Baldwin <carl.baldwin@hp.com>
Thu, 15 Jan 2015 05:43:10 +0000 (05:43 +0000)
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

neutron/agent/l3/agent.py
neutron/agent/l3/dvr.py
neutron/agent/linux/ip_lib.py
neutron/tests/functional/agent/test_l3_agent.py
neutron/tests/unit/test_l3_agent.py
neutron/tests/unit/test_linux_ip_lib.py

index 6579566d7b242bb3f6ee92f0fd5d017b82ec244b..e07c82fcb7c236d859e654eb1ab39734d62fbd07 100644 (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']
index db88b5a84faa54f1ac2978b46282d82d3009caa9..a0fb9343261914752c1a86bcded908822d651f67 100644 (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
 
index 4ca7591df614e8311783bac69a3ea9e0624d4c76..dea22c466e95b1e6ea312f6ab5e312d32bba8f61 100644 (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)
index c8270d8d6c53a1d453911498ee5c1a5bedc23a79..48bf58e0091990b1192cf8bca924e7b13d4e400b 100644 (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
 
index c64a303912385d5d2a9c8ef1b68039e3d1fca6b4..ae7bc0dc891faebd62446ed5f49db79d1530dfe4 100644 (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)
index 27564821d7ace39bd6dc3d29620b8f69246f967e..954dd7666cec2a1f0d84ffe2d3cea255c6d47132 100644 (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)