Skip to content

Commit e2d9cf2

Browse files
authored
Merge pull request #77 from cloudscale-ch/denis/retry-dad
Automatically retry DAD once if it fails
2 parents 0da0bd4 + e51444f commit e2d9cf2

1 file changed

Lines changed: 48 additions & 1 deletion

File tree

resources.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@
3030
from util import RESOURCE_NAME_PREFIX
3131
from util import SERVER_START_TIMEOUT
3232
from uuid import uuid4
33+
from warnings import warn
34+
35+
36+
class DADFailed(Exception):
37+
pass
3338

3439

3540
class CloudscaleResource:
@@ -331,7 +336,36 @@ def create_host(self, timeout):
331336

332337
# Validate IPv6 if necessary
333338
if self.spec['use_ipv6'] and self.has_public_interface:
334-
self.wait_for_non_tentative_ipv6()
339+
timeout = 60
340+
341+
for attempt in range(2):
342+
343+
# There is an issue with DAD on recently used IPv6 addresses,
344+
# rarely observed during automated tests, which launch loads
345+
# of VMs in quick succession.
346+
#
347+
# When this happens, we force DAD to be repeated once, by
348+
# removing, then re-adding the IPV6 address on the public
349+
# interface.
350+
351+
try:
352+
self.wait_for_non_tentative_ipv6(timeout=timeout)
353+
except DADFailed as e:
354+
if attempt == 0:
355+
# Output a warning at the end of the test run
356+
warn(f"DAD failed on first try: {' '.join(e.args)}")
357+
358+
# Wait for the fabric cache to expire
359+
time.sleep(15)
360+
361+
# Re-add IPv6, to trigger DAD
362+
self.readd_public_ipv6()
363+
364+
# Use a lower timeout for the second try
365+
timeout = 30
366+
else:
367+
raise e
368+
335369
self.wait_for_ipv6_default_route()
336370

337371
# By default, `ndisc_notify` is turned off in most Linux
@@ -384,6 +418,9 @@ def wait_for_non_tentative_ipv6(self, timeout=60):
384418
while datetime.utcnow() <= until:
385419
output = self.output_of('sudo ip a')
386420

421+
if 'dadfailed' in output:
422+
raise DADFailed(f"DAD failed. Last output: {output}")
423+
387424
for line in output.splitlines():
388425
if preferred.match(line):
389426
return
@@ -393,6 +430,16 @@ def wait_for_non_tentative_ipv6(self, timeout=60):
393430
raise Timeout(
394431
f'Wait for non-tentative IPv6 timed-out. Last output: {output}')
395432

433+
@with_trigger('server.reset-public-ipv6')
434+
def readd_public_ipv6(self):
435+
""" Removes, then adds the public IPv6 address, to trigger DAD. """
436+
437+
address = self.ip('public', 6)
438+
interface = self.public_interface.name
439+
440+
self.assert_run(f'sudo ip -6 addr del {address} dev {interface}')
441+
self.assert_run(f'sudo ip -6 addr add {address} dev {interface}')
442+
396443
@with_trigger('server.wait-for-ipv6-default-route')
397444
def wait_for_ipv6_default_route(self, timeout=30):
398445
until = datetime.utcnow() + timedelta(seconds=timeout)

0 commit comments

Comments
 (0)