Add capability to wait for IPv6 address in ip_lib

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
This commit is contained in:
Kevin Benton 2015-05-02 05:08:26 -07:00
parent fc22eab8d0
commit e833d33db1
3 changed files with 75 additions and 11 deletions

View 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'

View 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):

View 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]