From 47713f58701af9e27792230dd64daa3e20a4260b Mon Sep 17 00:00:00 2001 From: sridhargaddam Date: Thu, 12 Nov 2015 15:49:15 +0000 Subject: [PATCH] Support MTU advertisement using IPv6 RAs RFC4861 allows us to specify the Link MTU using IPv6 RAs. When advertise_mtu is set in the config, this patch supports advertising the LinkMTU using Router Advertisements. Partially Implements: blueprint mtu-selection-and-advertisement Closes-Bug: #1495444 Change-Id: I50d40cd3b8eabf1899461a80e729d5bd1e727f28 --- neutron/agent/linux/ra.py | 11 ++++++- neutron/common/config.py | 5 ++- neutron/db/l3_db.py | 17 ++++++++-- neutron/db/l3_dvr_db.py | 14 ++++---- neutron/db/l3_hamode_db.py | 2 +- neutron/tests/common/l3_test_common.py | 3 +- neutron/tests/unit/agent/l3/test_agent.py | 26 +++++++++++++-- neutron/tests/unit/db/test_l3_db.py | 32 ++++++++++++------- ...on-and-advertisement-ab29f9ec43140224.yaml | 9 ++++++ 9 files changed, 90 insertions(+), 29 deletions(-) create mode 100644 releasenotes/notes/mtu-selection-and-advertisement-ab29f9ec43140224.yaml diff --git a/neutron/agent/linux/ra.py b/neutron/agent/linux/ra.py index bf6113ac655..304d344edbe 100644 --- a/neutron/agent/linux/ra.py +++ b/neutron/agent/linux/ra.py @@ -53,6 +53,10 @@ CONFIG_TEMPLATE = jinja2.Template("""interface {{ interface_name }} MinRtrAdvInterval {{ min_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 %} AdvOtherConfigFlag on; {% endif %} @@ -101,6 +105,7 @@ class DaemonMonitor(object): 'radvd.conf', True) buf = six.StringIO() + network_mtu = 0 for p in router_ports: subnets = p.get('subnets', []) v6_subnets = [subnet for subnet in subnets if @@ -118,6 +123,9 @@ class DaemonMonitor(object): subnet['ipv6_ra_mode'] == constants.IPV6_SLAAC] dns_servers = list(iter_chain(*[subnet['dns_nameservers'] for 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( ra_modes=list(ra_modes), interface_name=interface_name, @@ -126,7 +134,8 @@ class DaemonMonitor(object): dns_servers=dns_servers[0:MAX_RDNSS_ENTRIES], constants=constants, 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()) return radvd_conf diff --git a/neutron/common/config.py b/neutron/common/config.py index f3ca14a9166..a0f52f07661 100644 --- a/neutron/common/config.py +++ b/neutron/common/config.py @@ -157,9 +157,8 @@ core_opts = [ 'there are any events to send.')), cfg.BoolOpt('advertise_mtu', default=True, help=_('If True, advertise network MTU values if core plugin ' - 'calculates them. Currently, the only way to advertise ' - 'MTU to running instances is using corresponding DHCP ' - 'option.')), + 'calculates them. MTU is advertised to running ' + 'instances via DHCP and RA MTU options.')), cfg.StrOpt('ipam_driver', help=_("Neutron IPAM (IP address management) driver to use. " "If ipam_driver is not set (default behavior), no IPAM " diff --git a/neutron/db/l3_db.py b/neutron/db/l3_db.py index 8853e53d028..51d2b784411 100644 --- a/neutron/db/l3_db.py +++ b/neutron/db/l3_db.py @@ -1381,7 +1381,18 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase): subnets_by_network[subnet['network_id']].append(subnet) 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. These ports already have fixed_ips populated. @@ -1389,6 +1400,7 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase): network_ids = [p['network_id'] 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( context, network_ids) @@ -1426,6 +1438,7 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase): port['extra_subnets'].append(subnet_info) 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): for floating_ip in floating_ips: @@ -1462,7 +1475,7 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase): context, router_ids=router_ids, active=active) ports_to_populate = [router['gw_port'] for router in routers 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) self._process_floating_ips(context, routers_dict, floating_ips) self._process_interfaces(routers_dict, interfaces) diff --git a/neutron/db/l3_dvr_db.py b/neutron/db/l3_dvr_db.py index 61747ec8699..f4a62c073c2 100644 --- a/neutron/db/l3_dvr_db.py +++ b/neutron/db/l3_dvr_db.py @@ -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): ports_to_populate += router[l3_const.SNAT_ROUTER_INTF_KEY] 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) 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, {'port': port_data}) if agent_port: - self._populate_subnets_for_ports(context, [agent_port]) + self._populate_mtu_and_subnets_for_ports(context, + [agent_port]) return agent_port msg = _("Unable to create the Agent Gateway Port") raise n_exc.BadRequest(resource='router', msg=msg) else: - self._populate_subnets_for_ports(context, [f_port]) + self._populate_mtu_and_subnets_for_ports(context, [f_port]) return f_port 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) 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 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( context, router.id) if port_list: - self._populate_subnets_for_ports(context, port_list) + self._populate_mtu_and_subnets_for_ports(context, port_list) return 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) port_list.append(snat_port) if port_list: - self._populate_subnets_for_ports(context, port_list) + self._populate_mtu_and_subnets_for_ports(context, port_list) return port_list def _generate_arp_table_and_notify_agent( diff --git a/neutron/db/l3_hamode_db.py b/neutron/db/l3_hamode_db.py index 9201117b56f..373aabc9a6a 100644 --- a/neutron/db/l3_hamode_db.py +++ b/neutron/db/l3_hamode_db.py @@ -613,7 +613,7 @@ class L3_HA_NAT_db_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin, for router in routers_dict.values(): interface = router.get(constants.HA_INTERFACE_KEY) if interface: - self._populate_subnets_for_ports(context, [interface]) + self._populate_mtu_and_subnets_for_ports(context, [interface]) return list(routers_dict.values()) diff --git a/neutron/tests/common/l3_test_common.py b/neutron/tests/common/l3_test_common.py index c4c6c1e5b90..8345b334ed6 100644 --- a/neutron/tests/common/l3_test_common.py +++ b/neutron/tests/common/l3_test_common.py @@ -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, ipv6_subnet_modes=None, interface_id=None, - dns_nameservers=None): + dns_nameservers=None, network_mtu=0): if ip_version == 6: subnet_mode_none = {'ra_mode': None, 'address_mode': None} 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 interfaces.append( {'id': _uuid(), + 'mtu': network_mtu, 'network_id': _uuid(), 'admin_state_up': True, 'mac_address': str(mac_address), diff --git a/neutron/tests/unit/agent/l3/test_agent.py b/neutron/tests/unit/agent/l3/test_agent.py index b0737a66641..06f9faf368e 100644 --- a/neutron/tests/unit/agent/l3/test_agent.py +++ b/neutron/tests/unit/agent/l3/test_agent.py @@ -1299,8 +1299,8 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): expected_calls.append(mock.call().disable()) return expected_calls - def _process_router_ipv6_subnet_added( - self, router, ipv6_subnet_modes=None, dns_nameservers=None): + def _process_router_ipv6_subnet_added(self, router, + ipv6_subnet_modes=None, dns_nameservers=None, network_mtu=0): agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs) agent.external_gateway_added = mock.Mock() @@ -1312,7 +1312,8 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): count=len(ipv6_subnet_modes), ip_version=6, ipv6_subnet_modes=ipv6_subnet_modes, - dns_nameservers=dns_nameservers) + dns_nameservers=dns_nameservers, + network_mtu=network_mtu) # Reassign the router object to RouterInfo self._process_router_instance_for_agent(agent, ri, router) return ri @@ -2292,6 +2293,25 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): self.assertIn(_join('-p', pidfile), 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): # expected = {ra_mode: (AdvOtherConfigFlag, AdvManagedFlag), ...} expected = {l3_constants.IPV6_SLAAC: (False, False), diff --git a/neutron/tests/unit/db/test_l3_db.py b/neutron/tests/unit/db/test_l3_db.py index 5694e6808e2..14a67005d37 100644 --- a/neutron/tests/unit/db/test_l3_db.py +++ b/neutron/tests/unit/db/test_l3_db.py @@ -66,7 +66,10 @@ class TestL3_NAT_dbonly_mixin(base.BaseTestCase): def test__populate_ports_for_subnets_none(self): """Basic test that the method runs correctly with no 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) @mock.patch.object(l3_db.L3_NAT_dbonly_mixin, @@ -85,17 +88,22 @@ class TestL3_NAT_dbonly_mixin(base.BaseTestCase): ports = [{'network_id': 'net_id', 'id': 'port_id', 'fixed_ips': [{'subnet_id': mock.sentinel.subnet_id}]}] - self.db._populate_subnets_for_ports(mock.sentinel.context, ports) - keys = ('id', 'cidr', 'gateway_ip', 'ipv6_ra_mode', 'subnetpool_id', - 'dns_nameservers') - address_scopes = {4: None, 6: mock.sentinel.address_scope_id} - self.assertEqual([{'extra_subnets': [], - 'fixed_ips': [{'subnet_id': mock.sentinel.subnet_id, - 'prefixlen': 64}], - 'id': 'port_id', - 'network_id': 'net_id', - 'subnets': [{k: subnet[k] for k in keys}], - 'address_scopes': address_scopes}], ports) + 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) + keys = ('id', 'cidr', 'gateway_ip', 'ipv6_ra_mode', + 'subnetpool_id', 'dns_nameservers') + address_scopes = {4: None, 6: mock.sentinel.address_scope_id} + self.assertEqual([{'extra_subnets': [], + 'fixed_ips': [{'subnet_id': + mock.sentinel.subnet_id, + '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): """Basic test that no query is performed if no router ids are passed""" diff --git a/releasenotes/notes/mtu-selection-and-advertisement-ab29f9ec43140224.yaml b/releasenotes/notes/mtu-selection-and-advertisement-ab29f9ec43140224.yaml new file mode 100644 index 00000000000..1f85d14d3d7 --- /dev/null +++ b/releasenotes/notes/mtu-selection-and-advertisement-ab29f9ec43140224.yaml @@ -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 + `_.