diff --git a/etc/neutron/rootwrap.d/dibbler.filters b/etc/neutron/rootwrap.d/dibbler.filters index eea55252f35..7ba7015c25e 100644 --- a/etc/neutron/rootwrap.d/dibbler.filters +++ b/etc/neutron/rootwrap.d/dibbler.filters @@ -14,3 +14,4 @@ # prefix_delegation_agent dibbler-client: CommandFilter, dibbler-client, root +kill_dibbler-client: KillFilter, root, dibbler-client, -9 diff --git a/neutron/agent/l3/ha.py b/neutron/agent/l3/ha.py index c9fef216d0b..038e167053b 100644 --- a/neutron/agent/l3/ha.py +++ b/neutron/agent/l3/ha.py @@ -125,6 +125,7 @@ class AgentMixin(object): if self.conf.enable_metadata_proxy: self._update_metadata_proxy(ri, router_id, state) self._update_radvd_daemon(ri, state) + self.pd.process_ha_state(router_id, state == 'master') self.state_change_notifier.queue_event((router_id, state)) def _configure_ipv6_ra_on_ext_gw_port_if_necessary(self, ri, state): diff --git a/neutron/agent/l3/ha_router.py b/neutron/agent/l3/ha_router.py index 426f2c5733d..b9fba67e10a 100644 --- a/neutron/agent/l3/ha_router.py +++ b/neutron/agent/l3/ha_router.py @@ -95,6 +95,15 @@ class HaRouter(router.RouterInfo): def ha_namespace(self): return self.ns_name + def is_router_master(self): + """this method is normally called before the ha_router object is fully + initialized + """ + if self.router.get('_ha_state') == 'active': + return True + else: + return False + def initialize(self, process_monitor): super(HaRouter, self).initialize(process_monitor) ha_port = self.router.get(n_consts.HA_INTERFACE_KEY) diff --git a/neutron/agent/l3/router_info.py b/neutron/agent/l3/router_info.py index 956de45009d..f91b94c5f43 100644 --- a/neutron/agent/l3/router_info.py +++ b/neutron/agent/l3/router_info.py @@ -116,6 +116,9 @@ class RouterInfo(object): # enable_snat by default if it wasn't specified by plugin self._snat_enabled = self._router.get('enable_snat', True) + def is_router_master(self): + return True + def get_internal_device_name(self, port_id): return (INTERNAL_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN] diff --git a/neutron/agent/linux/dibbler.py b/neutron/agent/linux/dibbler.py index 71f34999613..86e2742878b 100644 --- a/neutron/agent/linux/dibbler.py +++ b/neutron/agent/linux/dibbler.py @@ -51,7 +51,13 @@ iface {{ interface_name }} { # Bind to generated LLA bind-to-address {{ bind_address }} # ask for address + {% if hint_prefix != None %} + pd 1 { + prefix {{ hint_prefix }} + } + {% else %} pd 1 + {% endif %} } """) @@ -77,7 +83,7 @@ class PDDibbler(pd_driver.PDDriverBase): def _is_dibbler_client_running(self): return utils.get_value_from_file(self.pid_path) - def _generate_dibbler_conf(self, ex_gw_ifname, lla): + def _generate_dibbler_conf(self, ex_gw_ifname, lla, hint_prefix): dcwa = self.dibbler_client_working_area script_path = utils.get_conf_file_name(dcwa, 'notify', 'sh', True) buf = six.StringIO() @@ -94,7 +100,8 @@ class PDDibbler(pd_driver.PDDriverBase): va_id='0x%s' % self.converted_subnet_id, script_path='"%s/notify.sh"' % dcwa, interface_name='"%s"' % ex_gw_ifname, - bind_address='%s' % lla)) + bind_address='%s' % lla, + hint_prefix=hint_prefix)) file_utils.replace_file(dibbler_conf, buf.getvalue()) return dcwa @@ -118,17 +125,18 @@ class PDDibbler(pd_driver.PDDriverBase): service_name=PD_SERVICE_NAME, monitored_process=pm) - def enable(self, pmon, router_ns, ex_gw_ifname, lla): + def enable(self, pmon, router_ns, ex_gw_ifname, lla, prefix=None): LOG.debug("Enable IPv6 PD for router %s subnet %s ri_ifname %s", self.router_id, self.subnet_id, self.ri_ifname) if not self._is_dibbler_client_running(): - dibbler_conf = self._generate_dibbler_conf(ex_gw_ifname, lla) + dibbler_conf = self._generate_dibbler_conf(ex_gw_ifname, + lla, prefix) self._spawn_dibbler(pmon, router_ns, dibbler_conf) LOG.debug("dibbler client enabled for router %s subnet %s" " ri_ifname %s", self.router_id, self.subnet_id, self.ri_ifname) - def disable(self, pmon, router_ns): + def disable(self, pmon, router_ns, switch_over=False): LOG.debug("Disable IPv6 PD for router %s subnet %s ri_ifname %s", self.router_id, self.subnet_id, self.ri_ifname) dcwa = self.dibbler_client_working_area @@ -147,7 +155,10 @@ class PDDibbler(pd_driver.PDDriverBase): service=PD_SERVICE_NAME, conf=cfg.CONF, pid_file=self.pid_path) - pm.disable(get_stop_command=callback) + if switch_over: + pm.disable() + else: + pm.disable(get_stop_command=callback) shutil.rmtree(dcwa, ignore_errors=True) LOG.debug("dibbler client disabled for router %s subnet %s " "ri_ifname %s", diff --git a/neutron/agent/linux/pd.py b/neutron/agent/linux/pd.py index 7ec9c4244f7..0e6f6b71854 100644 --- a/neutron/agent/linux/pd.py +++ b/neutron/agent/linux/pd.py @@ -65,6 +65,9 @@ class PrefixDelegation(object): events.AFTER_DELETE) self._get_sync_data() + def _is_pd_master_router(self, router): + return router['master'] + @utils.synchronized("l3-agent-pd") def enable_subnet(self, router_id, subnet_id, prefix, ri_ifname, mac): router = self.routers.get(router_id) @@ -80,10 +83,12 @@ class PrefixDelegation(object): if pd_info.sync: pd_info.mac = mac pd_info.old_prefix = prefix - else: + elif self._is_pd_master_router(router): self._add_lla(router, pd_info.get_bind_lla_with_mask()) def _delete_pd(self, router, pd_info): + if not self._is_pd_master_router(router): + return self._delete_lla(router, pd_info.get_bind_lla_with_mask()) if pd_info.client_started: pd_info.driver.disable(self.pmon, router['ns_name']) @@ -98,10 +103,11 @@ class PrefixDelegation(object): if not pd_info: return self._delete_pd(router, pd_info) - prefix_update[subnet_id] = l3_constants.PROVISIONAL_IPV6_PD_PREFIX + if self._is_pd_master_router(router): + prefix_update[subnet_id] = l3_constants.PROVISIONAL_IPV6_PD_PREFIX + LOG.debug("Update server with prefixes: %s", prefix_update) + self.notifier(self.context, prefix_update) del router['subnets'][subnet_id] - LOG.debug("Update server with prefixes: %s", prefix_update) - self.notifier(self.context, prefix_update) @utils.synchronized("l3-agent-pd") def update_subnet(self, router_id, subnet_id, prefix): @@ -111,16 +117,19 @@ class PrefixDelegation(object): if pd_info and pd_info.old_prefix != prefix: old_prefix = pd_info.old_prefix pd_info.old_prefix = prefix + pd_info.prefix = prefix return old_prefix @utils.synchronized("l3-agent-pd") def add_gw_interface(self, router_id, gw_ifname): router = self.routers.get(router_id) - prefix_update = {} if not router: return router['gw_interface'] = gw_ifname - for subnet_id, pd_info in six.iteritems(router['subnets']): + if not self._is_pd_master_router(router): + return + prefix_update = {} + for pd_info in six.itervalues(router['subnets']): # gateway is added after internal router ports. # If a PD is being synced, and if the prefix is available, # send update if prefix out of sync; If not available, @@ -141,6 +150,8 @@ class PrefixDelegation(object): self.notifier(self.context, prefix_update) def delete_router_pd(self, router): + if not self._is_pd_master_router(router): + return prefix_update = {} for subnet_id, pd_info in six.iteritems(router['subnets']): self._delete_lla(router, pd_info.get_bind_lla_with_mask()) @@ -166,7 +177,7 @@ class PrefixDelegation(object): preserve_ips = [] router = self.routers.get(router_id) if router is not None: - for subnet_id, pd_info in router['subnets'].items(): + for pd_info in six.itervalues(router['subnets']): preserve_ips.append(pd_info.get_bind_lla_with_mask()) return preserve_ips @@ -180,7 +191,7 @@ class PrefixDelegation(object): def remove_stale_ri_ifname(self, router_id, stale_ifname): router = self.routers.get(router_id) if router is not None: - for subnet_id, pd_info in router['subnets'].items(): + for subnet_id, pd_info in six.iteriterms(router['subnets']): if pd_info.ri_ifname == stale_ifname: self._delete_pd(router, pd_info) del router['subnets'][subnet_id] @@ -253,13 +264,34 @@ class PrefixDelegation(object): return not lla['tentative'] return False + @utils.synchronized("l3-agent-pd") + def process_ha_state(self, router_id, master): + router = self.routers.get(router_id) + if router is None or router['master'] == master: + return + + router['master'] = master + if master: + for pd_info in six.itervalues(router['subnets']): + bind_lla_with_mask = pd_info.get_bind_lla_with_mask() + self._add_lla(router, bind_lla_with_mask) + else: + for pd_info in six.itervalues(router['subnets']): + self._delete_lla(router, pd_info.get_bind_lla_with_mask()) + if pd_info.client_started: + pd_info.driver.disable(self.pmon, + router['ns_name'], + switch_over=True) + pd_info.client_started = False + @utils.synchronized("l3-agent-pd") def process_prefix_update(self): LOG.debug("Processing IPv6 PD Prefix Update") prefix_update = {} for router_id, router in six.iteritems(self.routers): - if not router['gw_interface']: + if not (self._is_pd_master_router(router) and + router['gw_interface']): continue llas = None @@ -279,9 +311,15 @@ class PrefixDelegation(object): if not pd_info.driver: pd_info.driver = self.pd_dhcp_driver( router_id, subnet_id, pd_info.ri_ifname) + prefix = None + if (pd_info.prefix != + l3_constants.PROVISIONAL_IPV6_PD_PREFIX): + prefix = pd_info.prefix + pd_info.driver.enable(self.pmon, router['ns_name'], router['gw_interface'], - pd_info.bind_lla) + pd_info.bind_lla, + prefix) pd_info.client_started = True if prefix_update: @@ -305,7 +343,8 @@ class PrefixDelegation(object): for pd_info in sync_data: router_id = pd_info.router_id if not self.routers.get(router_id): - self.routers[router_id] = {'gw_interface': None, + self.routers[router_id] = {'master': True, + 'gw_interface': None, 'ns_name': None, 'subnets': {}} new_pd_info = PDInfo(pd_info=pd_info) @@ -322,8 +361,9 @@ def remove_router(resource, event, l3_agent, **kwargs): del l3_agent.pd.routers[router_id] -def get_router_entry(ns_name): - return {'gw_interface': None, +def get_router_entry(ns_name, master): + return {'master': master, + 'gw_interface': None, 'ns_name': ns_name, 'subnets': {}} @@ -333,12 +373,14 @@ def add_router(resource, event, l3_agent, **kwargs): added_router = kwargs['router'] router = l3_agent.pd.routers.get(added_router.router_id) gw_ns_name = added_router.get_gw_ns_name() + master = added_router.is_router_master() if not router: l3_agent.pd.routers[added_router.router_id] = ( - get_router_entry(gw_ns_name)) + get_router_entry(gw_ns_name, master)) else: # This will happen during l3 agent restart router['ns_name'] = gw_ns_name + router['master'] = master @utils.synchronized("l3-agent-pd") diff --git a/neutron/tests/common/l3_test_common.py b/neutron/tests/common/l3_test_common.py index 6cdeee28539..748c0f464aa 100644 --- a/neutron/tests/common/l3_test_common.py +++ b/neutron/tests/common/l3_test_common.py @@ -20,6 +20,7 @@ from oslo_utils import uuidutils from six import moves from neutron.common import constants as n_const +from neutron.common import ipv6_utils _uuid = uuidutils.generate_uuid @@ -287,6 +288,26 @@ def router_append_pd_enabled_subnet(router, count=1): interfaces.append(intf) pd_intfs.append(intf) mac_address.value += 1 + + +def get_unassigned_pd_interfaces(router): + pd_intfs = [] + for intf in router[lib_constants.INTERFACE_KEY]: + for subnet in intf['subnets']: + if (ipv6_utils.is_ipv6_pd_enabled(subnet) and + subnet['cidr'] == n_const.PROVISIONAL_IPV6_PD_PREFIX): + pd_intfs.append(intf) + return pd_intfs + + +def assign_prefix_for_pd_interfaces(router): + pd_intfs = [] + for ifno, intf in enumerate(router[lib_constants.INTERFACE_KEY]): + for subnet in intf['subnets']: + if (ipv6_utils.is_ipv6_pd_enabled(subnet) and + subnet['cidr'] == n_const.PROVISIONAL_IPV6_PD_PREFIX): + subnet['cidr'] = "2001:db8:%d::/64" % ifno + pd_intfs.append(intf) return pd_intfs diff --git a/neutron/tests/unit/agent/l3/test_agent.py b/neutron/tests/unit/agent/l3/test_agent.py index dce225ae8a8..eecb42a5180 100644 --- a/neutron/tests/unit/agent/l3/test_agent.py +++ b/neutron/tests/unit/agent/l3/test_agent.py @@ -2550,7 +2550,8 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): expected += "%s " % dns self.assertIn(expected, self.utils_replace_file.call_args[0][1]) - def _pd_expected_call_external_process(self, requestor, ri, enable=True): + def _pd_expected_call_external_process(self, requestor, ri, + enable=True, ha=False): expected_calls = [] if enable: expected_calls.append(mock.call(uuid=requestor, @@ -2566,34 +2567,37 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): namespace=ri.ns_name, conf=mock.ANY, pid_file=mock.ANY)) - expected_calls.append(mock.call().disable( - get_stop_command=mock.ANY)) + # in the HA switchover case, disable is called without arguments + if ha: + expected_calls.append(mock.call().disable()) + else: + expected_calls.append(mock.call().disable( + get_stop_command=mock.ANY)) return expected_calls - def _pd_setup_agent_router(self): + def _pd_setup_agent_router(self, enable_ha=False): router = l3_test_common.prepare_router_data() agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) - ri = l3router.RouterInfo(agent, router['id'], - router, **self.ri_kwargs) - ri.iptables_manager.ipv6['mangle'] = mock.MagicMock() - ri._process_pd_iptables_rules = mock.MagicMock() - agent.external_gateway_added = mock.Mock() - ri.process() agent._router_added(router['id'], router) # Make sure radvd monitor is created + ri = agent.router_info[router['id']] + ri.iptables_manager.ipv6['mangle'] = mock.MagicMock() + ri._process_pd_iptables_rules = mock.MagicMock() if not ri.radvd: ri.radvd = ra.DaemonMonitor(router['id'], ri.ns_name, agent.process_monitor, ri.get_internal_device_name, self.conf) + if enable_ha: + agent.pd.routers[router['id']]['master'] = False return agent, router, ri - def _pd_remove_gw_interface(self, intfs, agent, router, ri): + def _pd_remove_gw_interface(self, intfs, agent, ri): expected_pd_update = {} expected_calls = [] for intf in intfs: - requestor_id = self._pd_get_requestor_id(intf, router, ri) + requestor_id = self._pd_get_requestor_id(intf, ri) expected_calls += (self._pd_expected_call_external_process( requestor_id, ri, False)) for subnet in intf['subnets']: @@ -2616,19 +2620,19 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): # Remove the gateway interface agent.pd.notifier = pd_notifier - agent.pd.remove_gw_interface(router['id']) + agent.pd.remove_gw_interface(ri.router['id']) self._pd_assert_dibbler_calls(expected_calls, self.external_process.mock_calls[-len(expected_calls):]) self.assertEqual(expected_pd_update, self.pd_update) - def _pd_remove_interfaces(self, intfs, agent, router, ri): + def _pd_remove_interfaces(self, intfs, agent, ri): expected_pd_update = [] expected_calls = [] for intf in intfs: # Remove the router interface - router[lib_constants.INTERFACE_KEY].remove(intf) - requestor_id = self._pd_get_requestor_id(intf, router, ri) + ri.router[lib_constants.INTERFACE_KEY].remove(intf) + requestor_id = self._pd_get_requestor_id(intf, ri) expected_calls += (self._pd_expected_call_external_process( requestor_id, ri, False)) for subnet in intf['subnets']: @@ -2659,10 +2663,10 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): self._pd_assert_radvd_calls(ri, False) self.assertEqual(expected_pd_update, self.pd_update) - def _pd_get_requestor_id(self, intf, router, ri): + def _pd_get_requestor_id(self, intf, ri): ifname = ri.get_internal_device_name(intf['id']) for subnet in intf['subnets']: - return dibbler.PDDibbler(router['id'], + return dibbler.PDDibbler(ri.router['id'], subnet['id'], ifname).requestor_id def _pd_assert_dibbler_calls(self, expected, actual): @@ -2698,7 +2702,14 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): self.assertEqual(exp_calls, self.external_process.mock_calls[-len(exp_calls):]) - def _pd_get_prefixes(self, agent, router, ri, + def _pd_assert_update_subnet_calls(self, router_id, intfs, + mock_pd_update_subnet): + for intf in intfs: + mock_pd_update_subnet.assert_any_call(router_id, + intf['subnets'][0]['id'], + intf['subnets'][0]['cidr']) + + def _pd_get_prefixes(self, agent, ri, existing_intfs, new_intfs, mock_get_prefix): # First generate the prefixes that will be used for each interface prefixes = {} @@ -2706,8 +2717,8 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): expected_calls = [] last_prefix = '' for ifno, intf in enumerate(existing_intfs + new_intfs): - requestor_id = self._pd_get_requestor_id(intf, router, ri) - prefixes[requestor_id] = "2001:cafe:cafe:%d::/64" % ifno + requestor_id = self._pd_get_requestor_id(intf, ri) + prefixes[requestor_id] = "2001:db8:%d::/64" % ifno last_prefix = prefixes[requestor_id] if intf in new_intfs: subnet_id = (intf['subnets'][0]['id'] if intf['subnets'] @@ -2723,11 +2734,16 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): def pd_notifier(context, prefix_update): self.pd_update = prefix_update for subnet_id, prefix in six.iteritems(prefix_update): + gateway_ip = '%s1' % netaddr.IPNetwork(prefix).network for intf in new_intfs: + for fip in intf['fixed_ips']: + if fip['subnet_id'] == subnet_id: + fip['ip_address'] = gateway_ip for subnet in intf['subnets']: if subnet['id'] == subnet_id: # Update the prefix subnet['cidr'] = prefix + subnet['gateway_ip'] = gateway_ip break # Start the dibbler client @@ -2748,10 +2764,26 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): return last_prefix - def _pd_add_gw_interface(self, agent, router, ri): - gw_ifname = ri.get_external_device_name(router['gw_port']['id']) - agent.pd.add_gw_interface(router['id'], gw_ifname) + def _pd_verify_update_results(self, ri, pd_intfs, mock_pd_update_subnet): + # verify router port initialized + for intf in pd_intfs: + self.mock_driver.init_router_port.assert_any_call( + ri.get_internal_device_name(intf['id']), + ip_cidrs=l3router.common_utils.fixed_ip_cidrs( + intf['fixed_ips']), + namespace=ri.ns_name) + # verify that subnet is updated in PD + self._pd_assert_update_subnet_calls(ri.router['id'], pd_intfs, + mock_pd_update_subnet) + # Check that radvd is started + self._pd_assert_radvd_calls(ri) + + def _pd_add_gw_interface(self, agent, ri): + gw_ifname = ri.get_external_device_name(ri.router['gw_port']['id']) + agent.pd.add_gw_interface(ri.router['id'], gw_ifname) + + @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', @@ -2760,7 +2792,8 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): @mock.patch.object(dibbler.shutil, 'rmtree') @mock.patch.object(pd.PrefixDelegation, '_get_sync_data') def test_pd_add_remove_subnet(self, mock1, mock2, mock3, mock4, - mock_getpid, mock_get_prefix): + mock_getpid, mock_get_prefix, + mock_pd_update_subnet): '''Add and remove one pd-enabled subnet Remove the interface by deleting it from the router ''' @@ -2768,8 +2801,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): agent, router, ri = self._pd_setup_agent_router() # Create one pd-enabled subnet and add router interface - intfs = l3_test_common.router_append_pd_enabled_subnet(router) - subnet_id = intfs[0]['subnets'][0]['id'] + l3_test_common.router_append_pd_enabled_subnet(router) ri.process() # No client should be started since there is no gateway port @@ -2777,18 +2809,21 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): self.assertFalse(mock_get_prefix.call_count) # Add the gateway interface - self._pd_add_gw_interface(agent, router, ri) + self._pd_add_gw_interface(agent, ri) + + update_router = copy.deepcopy(router) + pd_intfs = l3_test_common.get_unassigned_pd_interfaces(update_router) + subnet_id = pd_intfs[0]['subnets'][0]['id'] # Get one prefix - prefix = self._pd_get_prefixes(agent, router, ri, [], - intfs, mock_get_prefix) + prefix = self._pd_get_prefixes(agent, ri, [], + pd_intfs, mock_get_prefix) # Update the router with the new prefix + ri.router = update_router ri.process() - # Check that radvd is started and the router port is configured - # with the new prefix - self._pd_assert_radvd_calls(ri) + self._pd_verify_update_results(ri, pd_intfs, mock_pd_update_subnet) # Check that _process_pd_iptables_rules() is called correctly self.assertEqual({subnet_id: prefix}, ri.pd_subnets) @@ -2796,9 +2831,10 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): subnet_id) # Now remove the interface - self._pd_remove_interfaces(intfs, agent, router, ri) + self._pd_remove_interfaces(pd_intfs, agent, ri) self.assertEqual({}, ri.pd_subnets) + @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', @@ -2807,7 +2843,8 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): @mock.patch.object(dibbler.shutil, 'rmtree') @mock.patch.object(pd.PrefixDelegation, '_get_sync_data') def test_pd_remove_gateway(self, mock1, mock2, mock3, mock4, - mock_getpid, mock_get_prefix): + mock_getpid, mock_get_prefix, + mock_pd_update_subnet): '''Add one pd-enabled subnet and remove the gateway port Remove the gateway port and check the prefix is removed ''' @@ -2815,27 +2852,28 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): agent, router, ri = self._pd_setup_agent_router() # Create one pd-enabled subnet and add router interface - intfs = l3_test_common.router_append_pd_enabled_subnet(router) + l3_test_common.router_append_pd_enabled_subnet(router) ri.process() # Add the gateway interface - self._pd_add_gw_interface(agent, router, ri) + self._pd_add_gw_interface(agent, ri) + + update_router = copy.deepcopy(router) + pd_intfs = l3_test_common.get_unassigned_pd_interfaces(update_router) # Get one prefix - self._pd_get_prefixes(agent, router, ri, [], intfs, mock_get_prefix) + self._pd_get_prefixes(agent, ri, [], pd_intfs, mock_get_prefix) # Update the router with the new prefix + ri.router = update_router ri.process() - # Check that radvd is started - self._pd_assert_radvd_calls(ri) + self._pd_verify_update_results(ri, pd_intfs, mock_pd_update_subnet) # Now remove the gw interface - self._pd_remove_gw_interface(intfs, agent, router, ri) - - # There will be a router update - ri.process() + self._pd_remove_gw_interface(pd_intfs, agent, ri) + @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', @@ -2844,7 +2882,8 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): @mock.patch.object(dibbler.shutil, 'rmtree') @mock.patch.object(pd.PrefixDelegation, '_get_sync_data') def test_pd_add_remove_2_subnets(self, mock1, mock2, mock3, mock4, - mock_getpid, mock_get_prefix): + mock_getpid, mock_get_prefix, + mock_pd_update_subnet): '''Add and remove two pd-enabled subnets Remove the interfaces by deleting them from the router ''' @@ -2852,7 +2891,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): agent, router, ri = self._pd_setup_agent_router() # Create 2 pd-enabled subnets and add router interfaces - intfs = l3_test_common.router_append_pd_enabled_subnet(router, count=2) + l3_test_common.router_append_pd_enabled_subnet(router, count=2) ri.process() # No client should be started @@ -2860,21 +2899,24 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): self.assertFalse(mock_get_prefix.call_count) # Add the gateway interface - self._pd_add_gw_interface(agent, router, ri) + self._pd_add_gw_interface(agent, ri) + + update_router = copy.deepcopy(router) + pd_intfs = l3_test_common.get_unassigned_pd_interfaces(update_router) # Get prefixes - self._pd_get_prefixes(agent, router, ri, [], intfs, mock_get_prefix) + self._pd_get_prefixes(agent, ri, [], pd_intfs, mock_get_prefix) # Update the router with the new prefix + ri.router = update_router ri.process() - # Check that radvd is started and the router port is configured - # with the new prefix - self._pd_assert_radvd_calls(ri) + self._pd_verify_update_results(ri, pd_intfs, mock_pd_update_subnet) # Now remove the interface - self._pd_remove_interfaces(intfs, agent, router, ri) + self._pd_remove_interfaces(pd_intfs, agent, ri) + @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', @@ -2883,7 +2925,8 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): @mock.patch.object(dibbler.shutil, 'rmtree') @mock.patch.object(pd.PrefixDelegation, '_get_sync_data') def test_pd_remove_gateway_2_subnets(self, mock1, mock2, mock3, mock4, - mock_getpid, mock_get_prefix): + mock_getpid, mock_get_prefix, + mock_pd_update_subnet): '''Add one pd-enabled subnet, followed by adding another one Remove the gateway port and check the prefix is removed ''' @@ -2891,42 +2934,209 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): agent, router, ri = self._pd_setup_agent_router() # Add the gateway interface - self._pd_add_gw_interface(agent, router, ri) + self._pd_add_gw_interface(agent, ri) # Create 1 pd-enabled subnet and add router interface - intfs = l3_test_common.router_append_pd_enabled_subnet(router, count=1) + l3_test_common.router_append_pd_enabled_subnet(router, count=1) ri.process() + update_router = copy.deepcopy(router) + pd_intfs = l3_test_common.get_unassigned_pd_interfaces(update_router) + # Get prefixes - self._pd_get_prefixes(agent, router, ri, [], intfs, mock_get_prefix) + self._pd_get_prefixes(agent, ri, [], pd_intfs, mock_get_prefix) # Update the router with the new prefix + ri.router = update_router ri.process() - # Check that radvd is started - self._pd_assert_radvd_calls(ri) + self._pd_verify_update_results(ri, pd_intfs, mock_pd_update_subnet) # Now add another interface # Create one pd-enabled subnet and add router interface - intfs1 = l3_test_common.router_append_pd_enabled_subnet(router, - count=1) + l3_test_common.router_append_pd_enabled_subnet(update_router, count=1) ri.process() + update_router_2 = copy.deepcopy(update_router) + pd_intfs1 = l3_test_common.get_unassigned_pd_interfaces( + update_router_2) + # Get prefixes - self._pd_get_prefixes(agent, router, ri, intfs, - intfs1, mock_get_prefix) + self._pd_get_prefixes(agent, ri, pd_intfs, pd_intfs1, mock_get_prefix) # Update the router with the new prefix + ri.router = update_router_2 ri.process() - # Check that radvd is notified for the new prefix - self._pd_assert_radvd_calls(ri) + self._pd_verify_update_results(ri, pd_intfs1, mock_pd_update_subnet) # Now remove the gw interface - self._pd_remove_gw_interface(intfs + intfs1, agent, router, ri) + self._pd_remove_gw_interface(pd_intfs + pd_intfs1, agent, ri) + @mock.patch.object(l3router.RouterInfo, 'enable_radvd') + @mock.patch.object(pd.PrefixDelegation, '_add_lla') + @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_ha_standby(self, mock1, mock2, mock3, mock4, + mock_getpid, mock_get_prefix, + mock_pd_update_subnet, + mock_add_lla, mock_enable_radvd): + '''Test HA in the standby 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) + + # 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() + self.assertFalse(mock_add_lla.called) + + # 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.assign_prefix_for_pd_interfaces( + update_router) + + # Update the router with the new prefix + ri.router = update_router + ri.process() + + self._pd_assert_update_subnet_calls(router['id'], pd_intfs, + mock_pd_update_subnet) + + # 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) + + @mock.patch.object(pd.PrefixDelegation, '_add_lla') + @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_ha_active(self, mock1, mock2, mock3, mock4, + mock_getpid, mock_get_prefix, + mock_pd_update_subnet, + mock_add_lla): + '''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) + + # 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() + + self.assertFalse(mock_add_lla.called) + + # 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(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_ha_switchover(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) + + # Turn the router to be active + agent.pd.process_ha_state(router['id'], True) + + # 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() + + update_router = copy.deepcopy(router) + pd_intfs = l3_test_common.get_unassigned_pd_interfaces(update_router) + + # 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) + + # Turn the router to be standby + agent.pd.process_ha_state(router['id'], False) + + expected_calls = [] + for intf in pd_intfs: + requestor_id = self._pd_get_requestor_id(intf, ri) + expected_calls += (self._pd_expected_call_external_process( + requestor_id, ri, False, ha=True)) + + self._pd_assert_dibbler_calls(expected_calls, + self.external_process.mock_calls[-len(expected_calls):]) + + @mock.patch.object(dibbler.os, 'chmod') + def test_pd_generate_dibbler_conf(self, mock_chmod): + pddib = dibbler.PDDibbler("router_id", "subnet-id", "ifname") + + pddib._generate_dibbler_conf("ex_gw_ifname", + "fe80::f816:3eff:fef5:a04e", None) + expected = 'bind-to-address fe80::f816:3eff:fef5:a04e\n'\ + '# ask for address\n \n pd 1\n \n}' + self.assertIn(expected, self.utils_replace_file.call_args[0][1]) + + pddib._generate_dibbler_conf("ex_gw_ifname", + "fe80::f816:3eff:fef5:a04e", + "2001:db8:2c50:2026::/64") + expected = 'bind-to-address fe80::f816:3eff:fef5:a04e\n'\ + '# ask for address\n \n pd 1 '\ + '{\n prefix 2001:db8:2c50:2026::/64\n }\n \n}' + self.assertIn(expected, self.utils_replace_file.call_args[0][1]) + def _verify_address_scopes_iptables_rule(self, mock_iptables_manager): filter_calls = [mock.call.add_chain('scope'), mock.call.add_rule('FORWARD', '-j $scope')] diff --git a/neutron/tests/unit/agent/linux/test_pd.py b/neutron/tests/unit/agent/linux/test_pd.py index 2b4a5d444c0..e4b4c336ed1 100644 --- a/neutron/tests/unit/agent/linux/test_pd.py +++ b/neutron/tests/unit/agent/linux/test_pd.py @@ -27,8 +27,9 @@ class FakeRouter(object): class TestPrefixDelegation(tests_base.DietTestCase): def test_remove_router(self): l3_agent = mock.Mock() - router_id = '1' - l3_agent.pd.routers = {router_id: pd.get_router_entry(None)} + router_id = 1 + l3_agent.pd.routers = {router_id: + pd.get_router_entry(None, True)} pd.remove_router(None, None, l3_agent, router=FakeRouter(router_id)) self.assertTrue(l3_agent.pd.delete_router_pd.called) self.assertEqual({}, l3_agent.pd.routers)