Merge "Support MTU advertisement using IPv6 RAs" into stable/liberty
This commit is contained in:
commit
5a8d8a9caa
|
@ -41,6 +41,10 @@ CONFIG_TEMPLATE = jinja2.Template("""interface {{ interface_name }}
|
||||||
MinRtrAdvInterval 3;
|
MinRtrAdvInterval 3;
|
||||||
MaxRtrAdvInterval 10;
|
MaxRtrAdvInterval 10;
|
||||||
|
|
||||||
|
{% if network_mtu >= constants.IPV6_MIN_MTU %}
|
||||||
|
AdvLinkMTU {{network_mtu}};
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if constants.DHCPV6_STATELESS in ra_modes %}
|
{% if constants.DHCPV6_STATELESS in ra_modes %}
|
||||||
AdvOtherConfigFlag on;
|
AdvOtherConfigFlag on;
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -85,6 +89,7 @@ class DaemonMonitor(object):
|
||||||
'radvd.conf',
|
'radvd.conf',
|
||||||
True)
|
True)
|
||||||
buf = six.StringIO()
|
buf = six.StringIO()
|
||||||
|
network_mtu = 0
|
||||||
for p in router_ports:
|
for p in router_ports:
|
||||||
subnets = p.get('subnets', [])
|
subnets = p.get('subnets', [])
|
||||||
v6_subnets = [subnet for subnet in subnets if
|
v6_subnets = [subnet for subnet in subnets if
|
||||||
|
@ -98,12 +103,16 @@ class DaemonMonitor(object):
|
||||||
stateful_config_prefixes = [subnet['cidr'] for subnet in v6_subnets
|
stateful_config_prefixes = [subnet['cidr'] for subnet in v6_subnets
|
||||||
if subnet['ipv6_ra_mode'] == constants.DHCPV6_STATEFUL]
|
if subnet['ipv6_ra_mode'] == constants.DHCPV6_STATEFUL]
|
||||||
interface_name = self._dev_name_helper(p['id'])
|
interface_name = self._dev_name_helper(p['id'])
|
||||||
|
if self._agent_conf.advertise_mtu:
|
||||||
|
network_mtu = p.get('mtu', 0)
|
||||||
|
|
||||||
buf.write('%s' % CONFIG_TEMPLATE.render(
|
buf.write('%s' % CONFIG_TEMPLATE.render(
|
||||||
ra_modes=list(ra_modes),
|
ra_modes=list(ra_modes),
|
||||||
interface_name=interface_name,
|
interface_name=interface_name,
|
||||||
auto_config_prefixes=auto_config_prefixes,
|
auto_config_prefixes=auto_config_prefixes,
|
||||||
stateful_config_prefixes=stateful_config_prefixes,
|
stateful_config_prefixes=stateful_config_prefixes,
|
||||||
constants=constants))
|
constants=constants,
|
||||||
|
network_mtu=int(network_mtu)))
|
||||||
|
|
||||||
utils.replace_file(radvd_conf, buf.getvalue())
|
utils.replace_file(radvd_conf, buf.getvalue())
|
||||||
return radvd_conf
|
return radvd_conf
|
||||||
|
|
|
@ -1231,7 +1231,18 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
|
||||||
for rp in qry]
|
for rp in qry]
|
||||||
return interfaces
|
return interfaces
|
||||||
|
|
||||||
def _populate_subnets_for_ports(self, context, ports):
|
def _get_mtus_by_network_list(self, context, network_ids):
|
||||||
|
if not network_ids:
|
||||||
|
return {}
|
||||||
|
filters = {'network_id': network_ids}
|
||||||
|
fields = ['id', 'mtu']
|
||||||
|
networks = self._core_plugin.get_networks(context, filters=filters,
|
||||||
|
fields=fields)
|
||||||
|
mtus_by_network = dict((network['id'], network.get('mtu', 0))
|
||||||
|
for network in networks)
|
||||||
|
return mtus_by_network
|
||||||
|
|
||||||
|
def _populate_mtu_and_subnets_for_ports(self, context, ports):
|
||||||
"""Populate ports with subnets.
|
"""Populate ports with subnets.
|
||||||
|
|
||||||
These ports already have fixed_ips populated.
|
These ports already have fixed_ips populated.
|
||||||
|
@ -1257,6 +1268,7 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
|
||||||
fields = ['id', 'cidr', 'gateway_ip',
|
fields = ['id', 'cidr', 'gateway_ip',
|
||||||
'network_id', 'ipv6_ra_mode', 'subnetpool_id']
|
'network_id', 'ipv6_ra_mode', 'subnetpool_id']
|
||||||
|
|
||||||
|
mtus_by_network = self._get_mtus_by_network_list(context, network_ids)
|
||||||
subnets_by_network = dict((id, []) for id in network_ids)
|
subnets_by_network = dict((id, []) for id in network_ids)
|
||||||
for subnet in self._core_plugin.get_subnets(context, filters, fields):
|
for subnet in self._core_plugin.get_subnets(context, filters, fields):
|
||||||
subnets_by_network[subnet['network_id']].append(subnet)
|
subnets_by_network[subnet['network_id']].append(subnet)
|
||||||
|
@ -1286,6 +1298,8 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
|
||||||
# This subnet is not used by the port.
|
# This subnet is not used by the port.
|
||||||
port['extra_subnets'].append(subnet_info)
|
port['extra_subnets'].append(subnet_info)
|
||||||
|
|
||||||
|
port['mtu'] = mtus_by_network.get(port['network_id'], 0)
|
||||||
|
|
||||||
def _process_floating_ips(self, context, routers_dict, floating_ips):
|
def _process_floating_ips(self, context, routers_dict, floating_ips):
|
||||||
for floating_ip in floating_ips:
|
for floating_ip in floating_ips:
|
||||||
router = routers_dict.get(floating_ip['router_id'])
|
router = routers_dict.get(floating_ip['router_id'])
|
||||||
|
@ -1321,7 +1335,7 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
|
||||||
context, router_ids=router_ids, active=active)
|
context, router_ids=router_ids, active=active)
|
||||||
ports_to_populate = [router['gw_port'] for router in routers
|
ports_to_populate = [router['gw_port'] for router in routers
|
||||||
if router.get('gw_port')] + interfaces
|
if router.get('gw_port')] + interfaces
|
||||||
self._populate_subnets_for_ports(context, ports_to_populate)
|
self._populate_mtu_and_subnets_for_ports(context, ports_to_populate)
|
||||||
routers_dict = dict((router['id'], router) for router in routers)
|
routers_dict = dict((router['id'], router) for router in routers)
|
||||||
self._process_floating_ips(context, routers_dict, floating_ips)
|
self._process_floating_ips(context, routers_dict, floating_ips)
|
||||||
self._process_interfaces(routers_dict, interfaces)
|
self._process_interfaces(routers_dict, interfaces)
|
||||||
|
|
|
@ -478,7 +478,7 @@ class L3_NAT_with_dvr_db_mixin(l3_db.L3_NAT_db_mixin,
|
||||||
if router.get(l3_const.SNAT_ROUTER_INTF_KEY):
|
if router.get(l3_const.SNAT_ROUTER_INTF_KEY):
|
||||||
ports_to_populate += router[l3_const.SNAT_ROUTER_INTF_KEY]
|
ports_to_populate += router[l3_const.SNAT_ROUTER_INTF_KEY]
|
||||||
ports_to_populate += interfaces
|
ports_to_populate += interfaces
|
||||||
self._populate_subnets_for_ports(context, ports_to_populate)
|
self._populate_mtu_and_subnets_for_ports(context, ports_to_populate)
|
||||||
self._process_interfaces(routers_dict, interfaces)
|
self._process_interfaces(routers_dict, interfaces)
|
||||||
return list(routers_dict.values())
|
return list(routers_dict.values())
|
||||||
|
|
||||||
|
@ -553,12 +553,13 @@ class L3_NAT_with_dvr_db_mixin(l3_db.L3_NAT_db_mixin,
|
||||||
agent_port = p_utils.create_port(self._core_plugin, context,
|
agent_port = p_utils.create_port(self._core_plugin, context,
|
||||||
{'port': port_data})
|
{'port': port_data})
|
||||||
if agent_port:
|
if agent_port:
|
||||||
self._populate_subnets_for_ports(context, [agent_port])
|
self._populate_mtu_and_subnets_for_ports(context,
|
||||||
|
[agent_port])
|
||||||
return agent_port
|
return agent_port
|
||||||
msg = _("Unable to create the Agent Gateway Port")
|
msg = _("Unable to create the Agent Gateway Port")
|
||||||
raise n_exc.BadRequest(resource='router', msg=msg)
|
raise n_exc.BadRequest(resource='router', msg=msg)
|
||||||
else:
|
else:
|
||||||
self._populate_subnets_for_ports(context, [f_port])
|
self._populate_mtu_and_subnets_for_ports(context, [f_port])
|
||||||
return f_port
|
return f_port
|
||||||
|
|
||||||
def _get_snat_interface_ports_for_router(self, context, router_id):
|
def _get_snat_interface_ports_for_router(self, context, router_id):
|
||||||
|
@ -598,7 +599,8 @@ class L3_NAT_with_dvr_db_mixin(l3_db.L3_NAT_db_mixin,
|
||||||
context.session.add(router_port)
|
context.session.add(router_port)
|
||||||
|
|
||||||
if do_pop:
|
if do_pop:
|
||||||
return self._populate_subnets_for_ports(context, [snat_port])
|
return self._populate_mtu_and_subnets_for_ports(context,
|
||||||
|
[snat_port])
|
||||||
return snat_port
|
return snat_port
|
||||||
|
|
||||||
def _create_snat_intf_ports_if_not_exists(self, context, router):
|
def _create_snat_intf_ports_if_not_exists(self, context, router):
|
||||||
|
@ -611,7 +613,7 @@ class L3_NAT_with_dvr_db_mixin(l3_db.L3_NAT_db_mixin,
|
||||||
port_list = self._get_snat_interface_ports_for_router(
|
port_list = self._get_snat_interface_ports_for_router(
|
||||||
context, router.id)
|
context, router.id)
|
||||||
if port_list:
|
if port_list:
|
||||||
self._populate_subnets_for_ports(context, port_list)
|
self._populate_mtu_and_subnets_for_ports(context, port_list)
|
||||||
return port_list
|
return port_list
|
||||||
port_list = []
|
port_list = []
|
||||||
|
|
||||||
|
@ -633,7 +635,7 @@ class L3_NAT_with_dvr_db_mixin(l3_db.L3_NAT_db_mixin,
|
||||||
intf['fixed_ips'][0]['subnet_id'], do_pop=False)
|
intf['fixed_ips'][0]['subnet_id'], do_pop=False)
|
||||||
port_list.append(snat_port)
|
port_list.append(snat_port)
|
||||||
if port_list:
|
if port_list:
|
||||||
self._populate_subnets_for_ports(context, port_list)
|
self._populate_mtu_and_subnets_for_ports(context, port_list)
|
||||||
return port_list
|
return port_list
|
||||||
|
|
||||||
def _generate_arp_table_and_notify_agent(
|
def _generate_arp_table_and_notify_agent(
|
||||||
|
|
|
@ -601,7 +601,7 @@ class L3_HA_NAT_db_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin):
|
||||||
for router in routers_dict.values():
|
for router in routers_dict.values():
|
||||||
interface = router.get(constants.HA_INTERFACE_KEY)
|
interface = router.get(constants.HA_INTERFACE_KEY)
|
||||||
if interface:
|
if interface:
|
||||||
self._populate_subnets_for_ports(context, [interface])
|
self._populate_mtu_and_subnets_for_ports(context, [interface])
|
||||||
|
|
||||||
# we don't want to return HA routers without HA interfaces created yet
|
# we don't want to return HA routers without HA interfaces created yet
|
||||||
return [r for r in list(routers_dict.values())
|
return [r for r in list(routers_dict.values())
|
||||||
|
|
|
@ -175,7 +175,8 @@ def router_append_interface(router, count=1, ip_version=4, ra_mode=None,
|
||||||
|
|
||||||
|
|
||||||
def router_append_subnet(router, count=1, ip_version=4,
|
def router_append_subnet(router, count=1, ip_version=4,
|
||||||
ipv6_subnet_modes=None, interface_id=None):
|
ipv6_subnet_modes=None, interface_id=None,
|
||||||
|
network_mtu=0):
|
||||||
if ip_version == 6:
|
if ip_version == 6:
|
||||||
subnet_mode_none = {'ra_mode': None, 'address_mode': None}
|
subnet_mode_none = {'ra_mode': None, 'address_mode': None}
|
||||||
if not ipv6_subnet_modes:
|
if not ipv6_subnet_modes:
|
||||||
|
@ -235,6 +236,7 @@ def router_append_subnet(router, count=1, ip_version=4,
|
||||||
mac_address.dialect = netaddr.mac_unix
|
mac_address.dialect = netaddr.mac_unix
|
||||||
interfaces.append(
|
interfaces.append(
|
||||||
{'id': _uuid(),
|
{'id': _uuid(),
|
||||||
|
'mtu': network_mtu,
|
||||||
'network_id': _uuid(),
|
'network_id': _uuid(),
|
||||||
'admin_state_up': True,
|
'admin_state_up': True,
|
||||||
'mac_address': str(mac_address),
|
'mac_address': str(mac_address),
|
||||||
|
|
|
@ -1327,7 +1327,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
|
||||||
return expected_calls
|
return expected_calls
|
||||||
|
|
||||||
def _process_router_ipv6_subnet_added(
|
def _process_router_ipv6_subnet_added(
|
||||||
self, router, ipv6_subnet_modes=None):
|
self, router, ipv6_subnet_modes=None, network_mtu=0):
|
||||||
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
||||||
ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs)
|
ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs)
|
||||||
agent.external_gateway_added = mock.Mock()
|
agent.external_gateway_added = mock.Mock()
|
||||||
|
@ -1338,7 +1338,8 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
|
||||||
router,
|
router,
|
||||||
count=len(ipv6_subnet_modes),
|
count=len(ipv6_subnet_modes),
|
||||||
ip_version=6,
|
ip_version=6,
|
||||||
ipv6_subnet_modes=ipv6_subnet_modes)
|
ipv6_subnet_modes=ipv6_subnet_modes,
|
||||||
|
network_mtu=network_mtu)
|
||||||
# Reassign the router object to RouterInfo
|
# Reassign the router object to RouterInfo
|
||||||
self._process_router_instance_for_agent(agent, ri, router)
|
self._process_router_instance_for_agent(agent, ri, router)
|
||||||
return ri
|
return ri
|
||||||
|
@ -2333,6 +2334,24 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
|
||||||
self.assertIn(_join('-p', pidfile), cmd)
|
self.assertIn(_join('-p', pidfile), cmd)
|
||||||
self.assertIn(_join('-m', 'syslog'), cmd)
|
self.assertIn(_join('-m', 'syslog'), cmd)
|
||||||
|
|
||||||
|
def test_generate_radvd_mtu_conf(self):
|
||||||
|
router = l3_test_common.prepare_router_data()
|
||||||
|
ipv6_subnet_modes = [{'ra_mode': l3_constants.IPV6_SLAAC,
|
||||||
|
'address_mode': l3_constants.IPV6_SLAAC}]
|
||||||
|
network_mtu = '1446'
|
||||||
|
ri = self._process_router_ipv6_subnet_added(router,
|
||||||
|
ipv6_subnet_modes,
|
||||||
|
network_mtu)
|
||||||
|
expected = "AdvLinkMTU 1446"
|
||||||
|
ri.agent_conf.set_override('advertise_mtu', False)
|
||||||
|
ri.radvd._generate_radvd_conf(router[l3_constants.INTERFACE_KEY])
|
||||||
|
self.assertNotIn(expected, self.utils_replace_file.call_args[0][1])
|
||||||
|
|
||||||
|
# Verify that MTU is advertised when advertise_mtu is True
|
||||||
|
ri.agent_conf.set_override('advertise_mtu', True)
|
||||||
|
ri.radvd._generate_radvd_conf(router[l3_constants.INTERFACE_KEY])
|
||||||
|
self.assertIn(expected, self.utils_replace_file.call_args[0][1])
|
||||||
|
|
||||||
def test_generate_radvd_conf_other_and_managed_flag(self):
|
def test_generate_radvd_conf_other_and_managed_flag(self):
|
||||||
# expected = {ra_mode: (AdvOtherConfigFlag, AdvManagedFlag), ...}
|
# expected = {ra_mode: (AdvOtherConfigFlag, AdvManagedFlag), ...}
|
||||||
expected = {l3_constants.IPV6_SLAAC: (False, False),
|
expected = {l3_constants.IPV6_SLAAC: (False, False),
|
||||||
|
|
|
@ -79,3 +79,29 @@ class TestL3_NAT_dbonly_mixin(base.BaseTestCase):
|
||||||
self.db.get_floatingip = mock.Mock()
|
self.db.get_floatingip = mock.Mock()
|
||||||
with testtools.ExpectedException(n_exc.ServicePortInUse):
|
with testtools.ExpectedException(n_exc.ServicePortInUse):
|
||||||
self.db.prevent_l3_port_deletion(mock.Mock(), None)
|
self.db.prevent_l3_port_deletion(mock.Mock(), None)
|
||||||
|
|
||||||
|
def test__populate_ports_for_subnets_none(self):
|
||||||
|
"""Basic test that the method runs correctly with no ports"""
|
||||||
|
ports = []
|
||||||
|
with mock.patch.object(manager.NeutronManager, 'get_plugin') as get_p:
|
||||||
|
get_p().get_networks.return_value = []
|
||||||
|
self.db._populate_mtu_and_subnets_for_ports(mock.sentinel.context,
|
||||||
|
ports)
|
||||||
|
self.assertEqual([], ports)
|
||||||
|
|
||||||
|
def test__populate_ports_for_subnets(self):
|
||||||
|
ports = [{'network_id': 'net_id',
|
||||||
|
'id': 'port_id',
|
||||||
|
'fixed_ips': [{'subnet_id': mock.sentinel.subnet_id}]}]
|
||||||
|
with mock.patch.object(manager.NeutronManager, 'get_plugin') as get_p:
|
||||||
|
get_p().get_networks.return_value = [{'id': 'net_id', 'mtu': 1446}]
|
||||||
|
self.db._populate_mtu_and_subnets_for_ports(mock.sentinel.context,
|
||||||
|
ports)
|
||||||
|
self.assertEqual([{'extra_subnets': [],
|
||||||
|
'fixed_ips': [{'subnet_id':
|
||||||
|
mock.sentinel.subnet_id}],
|
||||||
|
'id': 'port_id',
|
||||||
|
'mtu': 1446,
|
||||||
|
'network_id': 'net_id',
|
||||||
|
'subnets': []}],
|
||||||
|
ports)
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
prelude: >
|
||||||
|
Support for MTU selection and advertisement.
|
||||||
|
features:
|
||||||
|
- When advertise_mtu is set in the config, Neutron supports
|
||||||
|
advertising the LinkMTU using Router Advertisements.
|
||||||
|
other:
|
||||||
|
- For details please read `Blueprint mtu-selection-and-advertisement
|
||||||
|
<https://specs.openstack.org/openstack/neutron-specs/specs/kilo/mtu-selection-and-advertisement.html>`_.
|
Loading…
Reference in New Issue