Merge "Support MTU advertisement using IPv6 RAs"

This commit is contained in:
Jenkins 2016-02-10 23:15:12 +00:00 committed by Gerrit Code Review
commit 5d3fd438b7
9 changed files with 90 additions and 29 deletions

View File

@ -53,6 +53,10 @@ CONFIG_TEMPLATE = jinja2.Template("""interface {{ interface_name }}
MinRtrAdvInterval {{ min_rtr_adv_interval }}; MinRtrAdvInterval {{ min_rtr_adv_interval }};
MaxRtrAdvInterval {{ max_rtr_adv_interval }}; MaxRtrAdvInterval {{ max_rtr_adv_interval }};
{% 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 %}
@ -101,6 +105,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
@ -118,6 +123,9 @@ class DaemonMonitor(object):
subnet['ipv6_ra_mode'] == constants.IPV6_SLAAC] subnet['ipv6_ra_mode'] == constants.IPV6_SLAAC]
dns_servers = list(iter_chain(*[subnet['dns_nameservers'] for dns_servers = list(iter_chain(*[subnet['dns_nameservers'] for
subnet in slaac_subnets if subnet.get('dns_nameservers')])) subnet in slaac_subnets if subnet.get('dns_nameservers')]))
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,
@ -126,7 +134,8 @@ class DaemonMonitor(object):
dns_servers=dns_servers[0:MAX_RDNSS_ENTRIES], dns_servers=dns_servers[0:MAX_RDNSS_ENTRIES],
constants=constants, constants=constants,
min_rtr_adv_interval=self._agent_conf.min_rtr_adv_interval, min_rtr_adv_interval=self._agent_conf.min_rtr_adv_interval,
max_rtr_adv_interval=self._agent_conf.max_rtr_adv_interval)) max_rtr_adv_interval=self._agent_conf.max_rtr_adv_interval,
network_mtu=int(network_mtu)))
common_utils.replace_file(radvd_conf, buf.getvalue()) common_utils.replace_file(radvd_conf, buf.getvalue())
return radvd_conf return radvd_conf

View File

@ -157,9 +157,8 @@ core_opts = [
'there are any events to send.')), 'there are any events to send.')),
cfg.BoolOpt('advertise_mtu', default=True, cfg.BoolOpt('advertise_mtu', default=True,
help=_('If True, advertise network MTU values if core plugin ' help=_('If True, advertise network MTU values if core plugin '
'calculates them. Currently, the only way to advertise ' 'calculates them. MTU is advertised to running '
'MTU to running instances is using corresponding DHCP ' 'instances via DHCP and RA MTU options.')),
'option.')),
cfg.StrOpt('ipam_driver', cfg.StrOpt('ipam_driver',
help=_("Neutron IPAM (IP address management) driver to use. " help=_("Neutron IPAM (IP address management) driver to use. "
"If ipam_driver is not set (default behavior), no IPAM " "If ipam_driver is not set (default behavior), no IPAM "

View File

@ -1424,7 +1424,18 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
subnets_by_network[subnet['network_id']].append(subnet) subnets_by_network[subnet['network_id']].append(subnet)
return subnets_by_network return subnets_by_network
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.
@ -1432,6 +1443,7 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
network_ids = [p['network_id'] network_ids = [p['network_id']
for p in self._each_port_having_fixed_ips(ports)] for p in self._each_port_having_fixed_ips(ports)]
mtus_by_network = self._get_mtus_by_network_list(context, network_ids)
subnets_by_network = self._get_subnets_by_network_list( subnets_by_network = self._get_subnets_by_network_list(
context, network_ids) context, network_ids)
@ -1469,6 +1481,7 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
port['extra_subnets'].append(subnet_info) port['extra_subnets'].append(subnet_info)
port['address_scopes'].update(scopes) port['address_scopes'].update(scopes)
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:
@ -1505,7 +1518,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)

View File

@ -513,7 +513,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())
@ -583,12 +583,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):
@ -628,7 +629,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):
@ -641,7 +643,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 = []
@ -663,7 +665,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(

View File

@ -635,7 +635,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])
return list(routers_dict.values()) return list(routers_dict.values())

View File

@ -177,7 +177,7 @@ 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,
dns_nameservers=None): dns_nameservers=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:
@ -238,6 +238,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),

View File

@ -1306,8 +1306,8 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
expected_calls.append(mock.call().disable()) expected_calls.append(mock.call().disable())
return expected_calls return expected_calls
def _process_router_ipv6_subnet_added( def _process_router_ipv6_subnet_added(self, router,
self, router, ipv6_subnet_modes=None, dns_nameservers=None): ipv6_subnet_modes=None, dns_nameservers=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()
@ -1319,7 +1319,8 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
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,
dns_nameservers=dns_nameservers) dns_nameservers=dns_nameservers,
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
@ -2299,6 +2300,25 @@ 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,
None,
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),

View File

@ -72,7 +72,10 @@ class TestL3_NAT_dbonly_mixin(base.BaseTestCase):
def test__populate_ports_for_subnets_none(self): def test__populate_ports_for_subnets_none(self):
"""Basic test that the method runs correctly with no ports""" """Basic test that the method runs correctly with no ports"""
ports = [] ports = []
self.db._populate_subnets_for_ports(mock.sentinel.context, 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) self.assertEqual([], ports)
@mock.patch.object(l3_db.L3_NAT_dbonly_mixin, @mock.patch.object(l3_db.L3_NAT_dbonly_mixin,
@ -91,17 +94,22 @@ class TestL3_NAT_dbonly_mixin(base.BaseTestCase):
ports = [{'network_id': 'net_id', ports = [{'network_id': 'net_id',
'id': 'port_id', 'id': 'port_id',
'fixed_ips': [{'subnet_id': mock.sentinel.subnet_id}]}] 'fixed_ips': [{'subnet_id': mock.sentinel.subnet_id}]}]
self.db._populate_subnets_for_ports(mock.sentinel.context, ports) with mock.patch.object(manager.NeutronManager, 'get_plugin') as get_p:
keys = ('id', 'cidr', 'gateway_ip', 'ipv6_ra_mode', 'subnetpool_id', get_p().get_networks.return_value = [{'id': 'net_id', 'mtu': 1446}]
'dns_nameservers') self.db._populate_mtu_and_subnets_for_ports(mock.sentinel.context,
address_scopes = {4: None, 6: mock.sentinel.address_scope_id} ports)
self.assertEqual([{'extra_subnets': [], keys = ('id', 'cidr', 'gateway_ip', 'ipv6_ra_mode',
'fixed_ips': [{'subnet_id': mock.sentinel.subnet_id, 'subnetpool_id', 'dns_nameservers')
'prefixlen': 64}], address_scopes = {4: None, 6: mock.sentinel.address_scope_id}
'id': 'port_id', self.assertEqual([{'extra_subnets': [],
'network_id': 'net_id', 'fixed_ips': [{'subnet_id':
'subnets': [{k: subnet[k] for k in keys}], mock.sentinel.subnet_id,
'address_scopes': address_scopes}], ports) 'prefixlen': 64}],
'id': 'port_id',
'mtu': 1446,
'network_id': 'net_id',
'subnets': [{k: subnet[k] for k in keys}],
'address_scopes': address_scopes}], ports)
def test__get_sync_floating_ips_no_query(self): def test__get_sync_floating_ips_no_query(self):
"""Basic test that no query is performed if no router ids are passed""" """Basic test that no query is performed if no router ids are passed"""

View File

@ -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>`_.