diff --git a/neutron/agent/linux/ip_lib.py b/neutron/agent/linux/ip_lib.py index 943fd543cb3..6df0094224c 100644 --- a/neutron/agent/linux/ip_lib.py +++ b/neutron/agent/linux/ip_lib.py @@ -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' diff --git a/neutron/tests/functional/agent/test_ovs_flows.py b/neutron/tests/functional/agent/test_ovs_flows.py index e1ccbeca6ad..410cfe186a8 100644 --- a/neutron/tests/functional/agent/test_ovs_flows.py +++ b/neutron/tests/functional/agent/test_ovs_flows.py @@ -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): diff --git a/neutron/tests/unit/agent/linux/test_ip_lib.py b/neutron/tests/unit/agent/linux/test_ip_lib.py index 54d703e6eac..7b34d353742 100644 --- a/neutron/tests/unit/agent/linux/test_ip_lib.py +++ b/neutron/tests/unit/agent/linux/test_ip_lib.py @@ -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]