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: I17df98128c7a88e72e31251687f30f569df6b860changes/37/413137/15
parent
a457949bf7
commit
bb3c0e8285
|
@ -14,3 +14,4 @@
|
|||
|
||||
# prefix_delegation_agent
|
||||
dibbler-client: CommandFilter, dibbler-client, root
|
||||
kill_dibbler-client: KillFilter, root, dibbler-client, -9
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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')]
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue