]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Add capability to wait for IPv6 address in ip_lib
authorKevin Benton <blak111@gmail.com>
Sat, 2 May 2015 12:08:26 +0000 (05:08 -0700)
committerKevin Benton <blak111@gmail.com>
Sun, 10 May 2015 03:49:40 +0000 (20:49 -0700)
When an IPv6 address is added to an interface, it
goes into a tentative state for a couple of seconds
for duplicate address detection. During this time,
use of the address will fail. This is an issue for
functional tests where they may add an address to
an interface and then immediately run a ping and
expect success.

This patch adds a new wait_until_address_ready function
to ip_lib that will poll the interface every 200 ms until
the status transitions off of tentative or until a time limit
is exceeded. If the time limit is exceeded, it will raise an
exception.

It also adds unit tests and updates a functional test to
make use of the new feature.

Change-Id: I2fa51e3f55847f7b5062bec0c1c666f5c11364d5

neutron/agent/linux/ip_lib.py
neutron/tests/functional/agent/test_ovs_flows.py
neutron/tests/unit/agent/linux/test_ip_lib.py

index 943fd543cb3523d7a97a49514743a25476f556fa..6df0094224c5b29419d506a3a9fcd61f0d56be1b 100644 (file)
@@ -38,6 +38,11 @@ LOOPBACK_DEVNAME = 'lo'
 SYS_NET_PATH = '/sys/class/net'
 
 
+class AddressNotReady(exceptions.NeutronException):
+    message = _("Failure waiting for address %(address)s to "
+                "become ready: %(reason)s")
+
+
 class SubProcessBase(object):
     def __init__(self, namespace=None,
                  log_fail_as_error=True):
@@ -383,9 +388,34 @@ class IpAddrCommand(IpDeviceCommandBase):
 
             retval.append(dict(cidr=parts[1],
                                scope=scope,
-                               dynamic=('dynamic' == parts[-1])))
+                               dynamic=('dynamic' == parts[-1]),
+                               tentative=('tentative' in line),
+                               dadfailed=('dadfailed' == parts[-1])))
         return retval
 
+    def wait_until_address_ready(self, address, wait_time=30):
+        """Wait until an address is no longer marked 'tentative'
+
+        raises AddressNotReady if times out or address not present on interface
+        """
+        def is_address_ready():
+            try:
+                addr_info = self.list(to=address)[0]
+            except IndexError:
+                raise AddressNotReady(
+                    address=address,
+                    reason=_LE('Address not present on interface'))
+            if not addr_info['tentative']:
+                return True
+            if addr_info['dadfailed']:
+                raise AddressNotReady(
+                    address=address, reason=_LE('Duplicate adddress detected'))
+        errmsg = _LE("Exceeded %s second limit waiting for "
+                     "address to leave the tentative state.") % wait_time
+        utils.utils.wait_until_true(
+            is_address_ready, timeout=wait_time, sleep=0.20,
+            exception=AddressNotReady(address=address, reason=errmsg))
+
 
 class IpRouteCommand(IpDeviceCommandBase):
     COMMAND = 'route'
index e1ccbeca6ad71b868c072529322567d8e8a7535e..410cfe186a83b7bb0c5a509ca4bdd6cda14f6dbf 100644 (file)
@@ -60,8 +60,9 @@ class ARPSpoofTestCase(test_ovs_lib.OVSBridgeTestBase,
         self._setup_arp_spoof_for_port(self.dst_p.name, [self.dst_addr])
         self.src_p.addr.add('%s/64' % self.src_addr)
         self.dst_p.addr.add('%s/64' % self.dst_addr)
-        # IPv6 addresses seem to take longer to initialize
-        self.pinger._max_attempts = 4
+        # make sure the IPv6 addresses are ready before pinging
+        self.src_p.addr.wait_until_address_ready(self.src_addr)
+        self.dst_p.addr.wait_until_address_ready(self.dst_addr)
         self.pinger.assert_ping(self.dst_addr)
 
     def test_arp_spoof_blocks_response(self):
index 54d703e6eac2681756835e375e04bab639f996ce..7b34d353742c9804dea8c888b59207e8e6115c65 100644 (file)
@@ -15,6 +15,7 @@
 
 import mock
 import netaddr
+import testtools
 
 from neutron.agent.common import utils  # noqa
 from neutron.agent.linux import ip_lib
@@ -80,6 +81,10 @@ ADDR_SAMPLE = ("""
     inet 172.16.77.240/24 brd 172.16.77.255 scope global eth0
     inet6 2001:470:9:1224:5595:dd51:6ba2:e788/64 scope global temporary dynamic
        valid_lft 14187sec preferred_lft 3387sec
+    inet6 fe80::3023:39ff:febc:22ae/64 scope link tentative
+        valid_lft forever preferred_lft forever
+    inet6 fe80::3023:39ff:febc:22af/64 scope link tentative dadfailed
+        valid_lft forever preferred_lft forever
     inet6 2001:470:9:1224:fd91:272:581e:3a32/64 scope global temporary """
                """deprecated dynamic
        valid_lft 14187sec preferred_lft 0sec
@@ -98,6 +103,10 @@ ADDR_SAMPLE2 = ("""
     inet 172.16.77.240/24 scope global eth0
     inet6 2001:470:9:1224:5595:dd51:6ba2:e788/64 scope global temporary dynamic
        valid_lft 14187sec preferred_lft 3387sec
+    inet6 fe80::3023:39ff:febc:22ae/64 scope link tentative
+        valid_lft forever preferred_lft forever
+    inet6 fe80::3023:39ff:febc:22af/64 scope link tentative dadfailed
+        valid_lft forever preferred_lft forever
     inet6 2001:470:9:1224:fd91:272:581e:3a32/64 scope global temporary """
                 """deprecated dynamic
        valid_lft 14187sec preferred_lft 0sec
@@ -687,29 +696,53 @@ class TestIpAddrCommand(TestIPCmdBase):
 
     def test_list(self):
         expected = [
-            dict(scope='global',
+            dict(scope='global', dadfailed=False, tentative=False,
                  dynamic=False, cidr='172.16.77.240/24'),
-            dict(scope='global',
+            dict(scope='global', dadfailed=False, tentative=False,
                  dynamic=True, cidr='2001:470:9:1224:5595:dd51:6ba2:e788/64'),
-            dict(scope='global',
+            dict(scope='link', dadfailed=False, tentative=True,
+                 dynamic=False, cidr='fe80::3023:39ff:febc:22ae/64'),
+            dict(scope='link', dadfailed=True, tentative=True,
+                 dynamic=False, cidr='fe80::3023:39ff:febc:22af/64'),
+            dict(scope='global', dadfailed=False, tentative=False,
                  dynamic=True, cidr='2001:470:9:1224:fd91:272:581e:3a32/64'),
-            dict(scope='global',
+            dict(scope='global', dadfailed=False, tentative=False,
                  dynamic=True, cidr='2001:470:9:1224:4508:b885:5fb:740b/64'),
-            dict(scope='global',
+            dict(scope='global', dadfailed=False, tentative=False,
                  dynamic=True, cidr='2001:470:9:1224:dfcc:aaff:feb9:76ce/64'),
-            dict(scope='link',
+            dict(scope='link', dadfailed=False, tentative=False,
                  dynamic=False, cidr='fe80::dfcc:aaff:feb9:76ce/64')]
 
         test_cases = [ADDR_SAMPLE, ADDR_SAMPLE2]
 
         for test_case in test_cases:
             self.parent._run = mock.Mock(return_value=test_case)
-            self.assertEqual(self.addr_cmd.list(), expected)
+            self.assertEqual(expected, self.addr_cmd.list())
             self._assert_call([], ('show', 'tap0'))
 
+    def test_wait_until_address_ready(self):
+        self.parent._run.return_value = ADDR_SAMPLE
+        # this address is not tentative or failed so it should return
+        self.assertIsNone(self.addr_cmd.wait_until_address_ready(
+            '2001:470:9:1224:fd91:272:581e:3a32'))
+
+    def test_wait_until_address_ready_non_existent_address(self):
+        self.addr_cmd.list = mock.Mock(return_value=[])
+        with testtools.ExpectedException(ip_lib.AddressNotReady):
+            self.addr_cmd.wait_until_address_ready('abcd::1234')
+
+    def test_wait_until_address_ready_timeout(self):
+        tentative_address = 'fe80::3023:39ff:febc:22ae'
+        self.addr_cmd.list = mock.Mock(return_value=[
+            dict(scope='link', dadfailed=False, tentative=True, dynamic=False,
+                 cidr=tentative_address + '/64')])
+        with testtools.ExpectedException(ip_lib.AddressNotReady):
+            self.addr_cmd.wait_until_address_ready(tentative_address,
+                                                   wait_time=1)
+
     def test_list_filtered(self):
         expected = [
-            dict(scope='global',
+            dict(scope='global', tentative=False, dadfailed=False,
                  dynamic=False, cidr='172.16.77.240/24')]
 
         test_cases = [ADDR_SAMPLE, ADDR_SAMPLE2]