From 81d375d39ab1affe4b0a29437eaabc387fb1c570 Mon Sep 17 00:00:00 2001 From: Slawek Kaplonski Date: Fri, 21 Aug 2020 13:15:34 +0200 Subject: [PATCH] Handle properly existing LLA address during l3 agent restart In case when L3 agent is hosting routers which have got subnets with Prefix Delegation enabled, agent couldn't properly handle IpAddressAlreadyExists exception raised when pd module tries to configure link local IPv6 addresses. Now this is fixed and L3 agent can restart without problems in such case. Change-Id: Icc995f7b2b465921e41342711d17539f16ead0ce Closes-Bug: #1892362 --- neutron/agent/linux/pd.py | 25 +++++++----- neutron/tests/unit/agent/l3/test_agent.py | 47 +++++++++++++++++++++++ 2 files changed, 62 insertions(+), 10 deletions(-) diff --git a/neutron/agent/linux/pd.py b/neutron/agent/linux/pd.py index e710d998ef7..2a4558f7559 100644 --- a/neutron/agent/linux/pd.py +++ b/neutron/agent/linux/pd.py @@ -26,6 +26,7 @@ from oslo_log import log as logging from oslo_utils import netutils from stevedore import driver +from neutron.agent.linux import ip_lib from neutron.common import utils LOG = logging.getLogger(__name__) @@ -208,16 +209,20 @@ class PrefixDelegation(object): def _add_lla(self, router, lla_with_mask): if router['gw_interface']: - self.intf_driver.add_ipv6_addr(router['gw_interface'], - lla_with_mask, - router['ns_name'], - 'link') - # There is a delay before the LLA becomes active. - # This is because the kernel runs DAD to make sure LLA uniqueness - # Spawn a thread to wait for the interface to be ready - self._spawn_lla_thread(router['gw_interface'], - router['ns_name'], - lla_with_mask) + try: + self.intf_driver.add_ipv6_addr(router['gw_interface'], + lla_with_mask, + router['ns_name'], + 'link') + # There is a delay before the LLA becomes active. + # This is because the kernel runs DAD to make sure LLA + # uniqueness + # Spawn a thread to wait for the interface to be ready + self._spawn_lla_thread(router['gw_interface'], + router['ns_name'], + lla_with_mask) + except ip_lib.IpAddressAlreadyExists: + pass def _spawn_lla_thread(self, gw_ifname, ns_name, lla_with_mask): eventlet.spawn_n(self._ensure_lla_task, diff --git a/neutron/tests/unit/agent/l3/test_agent.py b/neutron/tests/unit/agent/l3/test_agent.py index 26cab2eebfd..779d02063ae 100644 --- a/neutron/tests/unit/agent/l3/test_agent.py +++ b/neutron/tests/unit/agent/l3/test_agent.py @@ -3837,6 +3837,53 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): self._pd_assert_dibbler_calls(expected_calls, self.external_process.mock_calls[-len(expected_calls):]) + @mock.patch.object(pd.PrefixDelegation, 'update_subnet') + @mock.patch.object(dibbler.PDDibbler, 'get_prefix', autospec=True) + @mock.patch.object(dibbler.os, 'getpid', return_value=1234) + @mock.patch.object(pd.PrefixDelegation, '_is_lla_active', + return_value=True) + @mock.patch.object(dibbler.os, 'chmod') + @mock.patch.object(dibbler.shutil, 'rmtree') + @mock.patch.object(pd.PrefixDelegation, '_get_sync_data') + def test_pd_lla_already_exists(self, mock1, mock2, mock3, mock4, + mock_getpid, mock_get_prefix, + mock_pd_update_subnet): + '''Test HA in the active router + The intent is to test the PD code with HA. To avoid unnecessary + complexities, use the regular router. + ''' + # Initial setup + agent, router, ri = self._pd_setup_agent_router(enable_ha=True) + + agent.pd.intf_driver = mock.MagicMock() + agent.pd.intf_driver.add_ipv6_addr.side_effect = ( + ip_lib.IpAddressAlreadyExists()) + + # Create one pd-enabled subnet and add router interface + l3_test_common.router_append_pd_enabled_subnet(router) + self._pd_add_gw_interface(agent, ri) + ri.process() + + # No client should be started since it's standby router + agent.pd.process_prefix_update() + self.assertFalse(self.external_process.called) + self.assertFalse(mock_get_prefix.called) + + update_router = copy.deepcopy(router) + pd_intfs = l3_test_common.get_unassigned_pd_interfaces(update_router) + + # Turn the router to be active + agent.pd.process_ha_state(router['id'], True) + + # Get prefixes + self._pd_get_prefixes(agent, ri, [], pd_intfs, mock_get_prefix) + + # Update the router with the new prefix + ri.router = update_router + ri.process() + + self._pd_verify_update_results(ri, pd_intfs, mock_pd_update_subnet) + @mock.patch.object(dibbler.os, 'chmod') def test_pd_generate_dibbler_conf(self, mock_chmod): pddib = dibbler.PDDibbler("router_id", "subnet-id", "ifname")