diff --git a/neutron/agent/linux/pd.py b/neutron/agent/linux/pd.py index e8b9ebfa419..74cce2d4fe0 100644 --- a/neutron/agent/linux/pd.py +++ b/neutron/agent/linux/pd.py @@ -27,6 +27,7 @@ from oslo_utils import netutils import six from stevedore import driver +from neutron.agent.linux import ip_lib from neutron.common import utils LOG = logging.getLogger(__name__) @@ -209,16 +210,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 fa7a347976c..a0e873eb3e3 100644 --- a/neutron/tests/unit/agent/l3/test_agent.py +++ b/neutron/tests/unit/agent/l3/test_agent.py @@ -3814,6 +3814,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")