From 2323797a7f8eff16b8d2ce90f41adc428438b869 Mon Sep 17 00:00:00 2001 From: Gregory Thiemonge Date: Wed, 9 Feb 2022 20:18:27 +0100 Subject: [PATCH] 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 --- octavia/amphorae/backends/utils/interface.py | 22 ++++++++ .../amphorae/backends/utils/test_interface.py | 54 ++++++++++++++++++- ...erface-configuration-61b1bd7d2c962cea.yaml | 5 ++ 3 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/fix-ipv6-interface-configuration-61b1bd7d2c962cea.yaml diff --git a/octavia/amphorae/backends/utils/interface.py b/octavia/amphorae/backends/utils/interface.py index 04090dbdef..098e94ea89 100644 --- a/octavia/amphorae/backends/utils/interface.py +++ b/octavia/amphorae/backends/utils/interface.py @@ -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) diff --git a/octavia/tests/unit/amphorae/backends/utils/test_interface.py b/octavia/tests/unit/amphorae/backends/utils/test_interface.py index ae5d8157c0..e2b56d866e 100644 --- a/octavia/tests/unit/amphorae/backends/utils/test_interface.py +++ b/octavia/tests/unit/amphorae/backends/utils/test_interface.py @@ -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)) diff --git a/releasenotes/notes/fix-ipv6-interface-configuration-61b1bd7d2c962cea.yaml b/releasenotes/notes/fix-ipv6-interface-configuration-61b1bd7d2c962cea.yaml new file mode 100644 index 0000000000..718655ef60 --- /dev/null +++ b/releasenotes/notes/fix-ipv6-interface-configuration-61b1bd7d2c962cea.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fix an issue with IPv6 members that could have been set in operating_status + ``ERROR`` just after being added.