Fix ipv6 interface configuration

When configuring an ipv6 interface, wait for the tentative flag to be
off. Waiting for this flag prevents haproxy to be restarted too early
and to use invalid source ip addresses for its healthchecks.

Story 2009847
Task 44467

Change-Id: Ie859e9911dfc54c327476ee9925d0c9046fed832
This commit is contained in:
Gregory Thiemonge 2022-02-09 20:18:27 +01:00
parent 76e731ad73
commit 2323797a7f
3 changed files with 79 additions and 2 deletions

View File

@ -22,6 +22,8 @@ import time
from oslo_config import cfg
from oslo_log import log as logging
import pyroute2
# pylint: disable=no-name-in-module
from pyroute2.netlink.rtnl import ifaddrmsg
from octavia.amphorae.backends.utils import interface_file
from octavia.common import constants as consts
@ -37,6 +39,9 @@ class InterfaceController(object):
DELETE = 'delete'
SET = 'set'
TENTATIVE_WAIT_INTERVAL = .2
TENTATIVE_WAIT_TIMEOUT = 30
def interface_file_list(self):
net_dir = interface_file.InterfaceFile.get_directory()
@ -134,6 +139,21 @@ class InterfaceController(object):
LOG.debug("Running '%s'", cmd)
subprocess.check_output(cmd, stderr=subprocess.STDOUT)
def _wait_tentative(self, ipr, idx):
start = time.time()
while time.time() - start < self.TENTATIVE_WAIT_TIMEOUT:
addrs = ipr.get_addr(idx)
has_tentative = [
True
for addr in addrs
if (addr['family'] == socket.AF_INET6 and
addr['flags'] & ifaddrmsg.IFA_F_TENTATIVE)]
if not has_tentative:
return
time.sleep(self.TENTATIVE_WAIT_INTERVAL)
LOG.warning("Some IPV6 addresses remain still in 'tentative' state "
"after %d seconds.", self.TENTATIVE_WAIT_TIMEOUT)
def up(self, interface):
LOG.info("Setting interface %s up", interface.name)
@ -158,6 +178,8 @@ class InterfaceController(object):
LOG.debug("%s: Adding address %s", interface.name, address)
self._ipr_command(ipr.addr, self.ADD, index=idx, **address)
self._wait_tentative(ipr, idx)
for route in interface.routes:
route[consts.FAMILY] = self._family(route[consts.DST])
LOG.debug("%s: Adding route %s", interface.name, route)

View File

@ -446,8 +446,11 @@ class TestInterface(base.TestCase):
@mock.patch('pyroute2.IPRoute.link')
@mock.patch('pyroute2.IPRoute.link_lookup')
@mock.patch('subprocess.check_output')
def test_up_auto(self, mock_check_output, mock_link_lookup, mock_link,
mock_addr, mock_route, mock_rule):
@mock.patch('octavia.amphorae.backends.utils.interface.'
'InterfaceController._wait_tentative')
def test_up_auto(self, mock_wait_tentative, mock_check_output,
mock_link_lookup, mock_link, mock_addr, mock_route,
mock_rule):
iface = interface_file.InterfaceFile(
name="eth1",
mtu=1450,
@ -900,3 +903,50 @@ class TestInterface(base.TestCase):
stderr=-2),
mock.call(["post-down", iface.name])
])
@mock.patch("time.time")
@mock.patch("time.sleep")
def test__wait_tentative(self, mock_time_sleep, mock_time_time):
mock_ipr = mock.MagicMock()
mock_ipr.get_addr.side_effect = [
({'family': socket.AF_INET,
'flags': 0},
{'family': socket.AF_INET6,
'flags': 0x40}, # tentative
{'family': socket.AF_INET6,
'flags': 0}),
({'family': socket.AF_INET,
'flags': 0},
{'family': socket.AF_INET6,
'flags': 0},
{'family': socket.AF_INET6,
'flags': 0})
]
mock_time_time.return_value = 0
controller = interface.InterfaceController()
idx = 4
controller._wait_tentative(mock_ipr, idx)
mock_time_sleep.assert_called_once()
@mock.patch("time.time")
@mock.patch("time.sleep")
def test__wait_tentative_timeout(self, mock_time_sleep,
mock_time_time):
mock_ipr = mock.MagicMock()
mock_ipr.get_addr.return_value = (
{'family': socket.AF_INET6,
'flags': 0x40}, # tentative
{'family': socket.AF_INET6,
'flags': 0}
)
mock_time_time.side_effect = [0, 0, 1, 2, 29, 30, 31]
controller = interface.InterfaceController()
idx = 4
controller._wait_tentative(mock_ipr, idx)
self.assertEqual(4, len(mock_time_sleep.mock_calls))

View File

@ -0,0 +1,5 @@
---
fixes:
- |
Fix an issue with IPv6 members that could have been set in operating_status
``ERROR`` just after being added.