Add PD support in HA router

The following enhancements are added:
  -- PD keeps track of status of neutron routers: active or
     standalone (master), or standby (not master),
  -- PD DHCP clients are only spawned in the active router. In the
     standby router, PD keeps track of the assigned prefixes, but
     doesn't spawn DHCP clients.
  -- When switchover occurs, on the router becoming standby, PD
     clients are "killed" so that they don't send prefix withdrawals
     to the DHCP server. On the router becoming active, PD spawns DHCP
     clients with the assigned prefixes configured as hints in the
     DHCP client's configuration

Closes-Bug: #1651465
Change-Id: I17df98128c7a88e72e31251687f30f569df6b860
This commit is contained in:
Robert Li 2016-12-20 10:21:21 -05:00 committed by Ihar Hrachyshka
parent a457949bf7
commit bb3c0e8285
9 changed files with 386 additions and 87 deletions

View File

@ -14,3 +14,4 @@
# prefix_delegation_agent
dibbler-client: CommandFilter, dibbler-client, root
kill_dibbler-client: KillFilter, root, dibbler-client, -9

View File

@ -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):

View File

@ -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)

View File

@ -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]

View File

@ -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",

View File

@ -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")

View File

@ -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

View File

@ -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')]

View File

@ -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)