From 4df8d9a7016ab20fce235833d792b89309ec98a7 Mon Sep 17 00:00:00 2001 From: Kevin Benton Date: Mon, 22 Feb 2016 16:41:45 -0800 Subject: [PATCH] Make agent interface plugging utilize network MTU This changes the 'plug' and 'plug_new' interfaces of the LinuxInterfaceDriver to accept an MTU argument. It then updates the dhcp agent and l3 agent to pass the MTU that is set on the network that the port belongs to. This allows it to take into account the overhead calculations that are done for encapsulation types. It's necessary for the L3 agent to have the MTU because it must recognize when fragmentation is needed so it can fragment or generate an ICMP error. It's necessary for the DHCP agent to have the MTU so it doesn't interfere when it plugs into a bridge with a larger than 1500 MTU (the bridge would reduce its MTU to match the agent). If an operator sets 'network_device_mtu', the value of that will be used instead to preserve previous behavior. Closes-Bug: #1549470 Closes-Bug: #1542108 Closes-Bug: #1542475 DocImpact: Neutron agents now support arbitrary MTU configurations on each network (including jumbo frames). This is accomplished by checking the MTU value defined for each network on which it is wiring VIFs. Co-Authored-By: Matt Kassawara Change-Id: Ic091fa78dfd133179c71cbc847bf955a06cb248a --- neutron/agent/l3/dvr_edge_ha_router.py | 3 +- neutron/agent/l3/dvr_edge_router.py | 6 ++- neutron/agent/l3/dvr_fip_ns.py | 11 +++-- neutron/agent/l3/ha_router.py | 7 +-- neutron/agent/l3/router_info.py | 10 +++-- neutron/agent/linux/dhcp.py | 3 +- neutron/agent/linux/interface.py | 44 +++++++++++-------- .../functional/agent/l3/test_dvr_router.py | 19 +++++++- .../tests/functional/agent/test_dhcp_agent.py | 9 ++++ neutron/tests/unit/agent/dhcp/test_agent.py | 3 +- neutron/tests/unit/agent/l3/test_agent.py | 3 +- .../end-to-end-mtu-00345fc4282cb8fb.yaml | 20 +++++++++ 12 files changed, 101 insertions(+), 37 deletions(-) create mode 100644 releasenotes/notes/end-to-end-mtu-00345fc4282cb8fb.yaml diff --git a/neutron/agent/l3/dvr_edge_ha_router.py b/neutron/agent/l3/dvr_edge_ha_router.py index 377d51e4c44..da356682719 100644 --- a/neutron/agent/l3/dvr_edge_ha_router.py +++ b/neutron/agent/l3/dvr_edge_ha_router.py @@ -117,4 +117,5 @@ class DvrEdgeHaRouter(DvrEdgeRouter, HaRouter): self.driver.plug(port['network_id'], port['id'], interface_name, port['mac_address'], namespace=self.snat_namespace.name, - prefix=dvr_snat_ns.SNAT_INT_DEV_PREFIX) + prefix=dvr_snat_ns.SNAT_INT_DEV_PREFIX, + mtu=port.get('mtu')) diff --git a/neutron/agent/l3/dvr_edge_router.py b/neutron/agent/l3/dvr_edge_router.py index 078d3bc0b62..48c75213677 100644 --- a/neutron/agent/l3/dvr_edge_router.py +++ b/neutron/agent/l3/dvr_edge_router.py @@ -104,7 +104,8 @@ class DvrEdgeRouter(dvr_local_router.DvrLocalRouter): sn_port['fixed_ips'], sn_port['mac_address'], interface_name, - dvr_snat_ns.SNAT_INT_DEV_PREFIX) + dvr_snat_ns.SNAT_INT_DEV_PREFIX, + mtu=sn_port.get('mtu')) def _dvr_internal_network_removed(self, port): super(DvrEdgeRouter, self)._dvr_internal_network_removed(port) @@ -132,7 +133,8 @@ class DvrEdgeRouter(dvr_local_router.DvrLocalRouter): self.snat_namespace.name, port['network_id'], port['id'], port['fixed_ips'], port['mac_address'], interface_name, - dvr_snat_ns.SNAT_INT_DEV_PREFIX) + dvr_snat_ns.SNAT_INT_DEV_PREFIX, + mtu=port.get('mtu')) def _create_dvr_gateway(self, ex_gw_port, gw_interface_name): """Create SNAT namespace.""" diff --git a/neutron/agent/l3/dvr_fip_ns.py b/neutron/agent/l3/dvr_fip_ns.py index c5660386aca..30be9a2c3d5 100644 --- a/neutron/agent/l3/dvr_fip_ns.py +++ b/neutron/agent/l3/dvr_fip_ns.py @@ -106,7 +106,8 @@ class FipNamespace(namespaces.Namespace): ex_gw_port['mac_address'], bridge=self.agent_conf.external_network_bridge, namespace=ns_name, - prefix=FIP_EXT_DEV_PREFIX) + prefix=FIP_EXT_DEV_PREFIX, + mtu=ex_gw_port.get('mtu')) ip_cidrs = common_utils.fixed_ip_cidrs(ex_gw_port['fixed_ips']) self.driver.init_l3(interface_name, ip_cidrs, namespace=ns_name, @@ -232,9 +233,11 @@ class FipNamespace(namespaces.Namespace): self._internal_ns_interface_added(str(fip_2_rtr), fip_2_rtr_name, fip_ns_name) - if self.agent_conf.network_device_mtu: - int_dev[0].link.set_mtu(self.agent_conf.network_device_mtu) - int_dev[1].link.set_mtu(self.agent_conf.network_device_mtu) + mtu = (self.agent_conf.network_device_mtu or + ri.get_ex_gw_port().get('mtu')) + if mtu: + int_dev[0].link.set_mtu(mtu) + int_dev[1].link.set_mtu(mtu) int_dev[0].link.set_up() int_dev[1].link.set_up() diff --git a/neutron/agent/l3/ha_router.py b/neutron/agent/l3/ha_router.py index f1a248963de..b7d7031fd7c 100644 --- a/neutron/agent/l3/ha_router.py +++ b/neutron/agent/l3/ha_router.py @@ -144,7 +144,8 @@ class HaRouter(router.RouterInfo): interface_name, self.ha_port['mac_address'], namespace=self.ha_namespace, - prefix=HA_DEV_PREFIX) + prefix=HA_DEV_PREFIX, + mtu=self.ha_port.get('mtu')) ip_cidrs = common_utils.fixed_ip_cidrs(self.ha_port['fixed_ips']) self.driver.init_l3(interface_name, ip_cidrs, namespace=self.ha_namespace, @@ -268,13 +269,13 @@ class HaRouter(router.RouterInfo): def _plug_ha_router_port(self, port, name_getter, prefix): port_id = port['id'] interface_name = name_getter(port_id) - self.driver.plug(port['network_id'], port_id, interface_name, port['mac_address'], namespace=self.ha_namespace, - prefix=prefix) + prefix=prefix, + mtu=port.get('mtu')) self._disable_ipv6_addressing_on_interface(interface_name) self._add_vips(port, interface_name) diff --git a/neutron/agent/l3/router_info.py b/neutron/agent/l3/router_info.py index b4b7eb45930..c442aac4d3a 100644 --- a/neutron/agent/l3/router_info.py +++ b/neutron/agent/l3/router_info.py @@ -361,12 +361,12 @@ class RouterInfo(object): def _internal_network_added(self, ns_name, network_id, port_id, fixed_ips, mac_address, - interface_name, prefix): + interface_name, prefix, mtu=None): LOG.debug("adding internal network: prefix(%s), port(%s)", prefix, port_id) self.driver.plug(network_id, port_id, interface_name, mac_address, namespace=ns_name, - prefix=prefix) + prefix=prefix, mtu=mtu) ip_cidrs = common_utils.fixed_ip_cidrs(fixed_ips) self.driver.init_router_port( @@ -391,7 +391,8 @@ class RouterInfo(object): fixed_ips, mac_address, interface_name, - INTERNAL_DEV_PREFIX) + INTERNAL_DEV_PREFIX, + mtu=port.get('mtu')) def internal_network_removed(self, port): interface_name = self.get_internal_device_name(port['id']) @@ -557,7 +558,8 @@ class RouterInfo(object): ex_gw_port['mac_address'], bridge=self.agent_conf.external_network_bridge, namespace=ns_name, - prefix=EXTERNAL_DEV_PREFIX) + prefix=EXTERNAL_DEV_PREFIX, + mtu=ex_gw_port.get('mtu')) def _get_external_gw_ips(self, ex_gw_port): gateway_ips = [] diff --git a/neutron/agent/linux/dhcp.py b/neutron/agent/linux/dhcp.py index b4d04256ea6..e08a1265489 100644 --- a/neutron/agent/linux/dhcp.py +++ b/neutron/agent/linux/dhcp.py @@ -1230,7 +1230,8 @@ class DeviceManager(object): port.id, interface_name, port.mac_address, - namespace=network.namespace) + namespace=network.namespace, + mtu=network.get('mtu')) except Exception: with excutils.save_and_reraise_exception(): LOG.exception(_LE('Unable to plug DHCP port for ' diff --git a/neutron/agent/linux/interface.py b/neutron/agent/linux/interface.py index 29c1b352895..280385cd240 100644 --- a/neutron/agent/linux/interface.py +++ b/neutron/agent/linux/interface.py @@ -20,7 +20,7 @@ from oslo_config import cfg from oslo_log import log as logging import six -from neutron._i18n import _, _LE, _LI +from neutron._i18n import _, _LE, _LI, _LW from neutron.agent.common import ovs_lib from neutron.agent.linux import ip_lib from neutron.agent.linux import utils @@ -233,15 +233,15 @@ class LinuxInterfaceDriver(object): @abc.abstractmethod def plug_new(self, network_id, port_id, device_name, mac_address, - bridge=None, namespace=None, prefix=None): + bridge=None, namespace=None, prefix=None, mtu=None): """Plug in the interface only for new devices that don't exist yet.""" def plug(self, network_id, port_id, device_name, mac_address, - bridge=None, namespace=None, prefix=None): + bridge=None, namespace=None, prefix=None, mtu=None): if not ip_lib.device_exists(device_name, namespace=namespace): self.plug_new(network_id, port_id, device_name, mac_address, - bridge, namespace, prefix) + bridge, namespace, prefix, mtu) else: LOG.info(_LI("Device %s already exists"), device_name) @@ -269,7 +269,7 @@ class LinuxInterfaceDriver(object): class NullDriver(LinuxInterfaceDriver): def plug_new(self, network_id, port_id, device_name, mac_address, - bridge=None, namespace=None, prefix=None): + bridge=None, namespace=None, prefix=None, mtu=None): pass def unplug(self, device_name, bridge=None, namespace=None, prefix=None): @@ -304,7 +304,7 @@ class OVSInterfaceDriver(LinuxInterfaceDriver): ovs.replace_port(device_name, *attrs) def plug_new(self, network_id, port_id, device_name, mac_address, - bridge=None, namespace=None, prefix=None): + bridge=None, namespace=None, prefix=None, mtu=None): """Plug in the interface.""" if not bridge: bridge = self.conf.ovs_integration_bridge @@ -328,11 +328,13 @@ class OVSInterfaceDriver(LinuxInterfaceDriver): ns_dev.link.set_address(mac_address) - if self.conf.network_device_mtu: - ns_dev.link.set_mtu(self.conf.network_device_mtu) + mtu = self.conf.network_device_mtu or mtu + if mtu: + ns_dev.link.set_mtu(mtu) if self.conf.ovs_use_veth: - root_dev.link.set_mtu(self.conf.network_device_mtu) - + root_dev.link.set_mtu(mtu) + else: + LOG.warning(_LW("No MTU configured for port %s"), port_id) # Add an interface created by ovs to the namespace. if not self.conf.ovs_use_veth and namespace: namespace_obj = ip.ensure_namespace(namespace) @@ -381,7 +383,7 @@ class IVSInterfaceDriver(LinuxInterfaceDriver): utils.execute(cmd, run_as_root=True) def plug_new(self, network_id, port_id, device_name, mac_address, - bridge=None, namespace=None, prefix=None): + bridge=None, namespace=None, prefix=None, mtu=None): """Plug in the interface.""" ip = ip_lib.IPWrapper() tap_name = self._get_tap_name(device_name, prefix) @@ -393,9 +395,12 @@ class IVSInterfaceDriver(LinuxInterfaceDriver): ns_dev = ip.device(device_name) ns_dev.link.set_address(mac_address) - if self.conf.network_device_mtu: - ns_dev.link.set_mtu(self.conf.network_device_mtu) - root_dev.link.set_mtu(self.conf.network_device_mtu) + mtu = self.conf.network_device_mtu or mtu + if mtu: + ns_dev.link.set_mtu(mtu) + root_dev.link.set_mtu(mtu) + else: + LOG.warning(_LW("No MTU configured for port %s"), port_id) if namespace: namespace_obj = ip.ensure_namespace(namespace) @@ -424,7 +429,7 @@ class BridgeInterfaceDriver(LinuxInterfaceDriver): DEV_NAME_PREFIX = 'ns-' def plug_new(self, network_id, port_id, device_name, mac_address, - bridge=None, namespace=None, prefix=None): + bridge=None, namespace=None, prefix=None, mtu=None): """Plugin the interface.""" ip = ip_lib.IPWrapper() @@ -436,9 +441,12 @@ class BridgeInterfaceDriver(LinuxInterfaceDriver): namespace2=namespace) ns_veth.link.set_address(mac_address) - if self.conf.network_device_mtu: - root_veth.link.set_mtu(self.conf.network_device_mtu) - ns_veth.link.set_mtu(self.conf.network_device_mtu) + mtu = self.conf.network_device_mtu or mtu + if mtu: + root_veth.link.set_mtu(mtu) + ns_veth.link.set_mtu(mtu) + else: + LOG.warning(_LW("No MTU configured for port %s"), port_id) root_veth.link.set_up() ns_veth.link.set_up() diff --git a/neutron/tests/functional/agent/l3/test_dvr_router.py b/neutron/tests/functional/agent/l3/test_dvr_router.py index f6308e98ad1..4283f8736f7 100644 --- a/neutron/tests/functional/agent/l3/test_dvr_router.py +++ b/neutron/tests/functional/agent/l3/test_dvr_router.py @@ -51,6 +51,10 @@ class TestDvrRouter(framework.L3AgentTestFramework): self.agent.conf.agent_mode = 'dvr' self._test_update_floatingip_statuses(self.generate_dvr_router_info()) + def test_dvr_router_lifecycle_ha_with_snat_with_fips_nmtu(self): + self._dvr_router_lifecycle(enable_ha=True, enable_snat=True, + use_port_mtu=True) + def test_dvr_router_lifecycle_without_ha_without_snat_with_fips(self): self._dvr_router_lifecycle(enable_ha=False, enable_snat=False) @@ -101,7 +105,7 @@ class TestDvrRouter(framework.L3AgentTestFramework): self._validate_fips_for_external_network(router2, fip2_ns) def _dvr_router_lifecycle(self, enable_ha=False, enable_snat=False, - custom_mtu=2000, + custom_mtu=2000, use_port_mtu=False, ip_version=4, dual_stack=False): '''Test dvr router lifecycle @@ -115,11 +119,19 @@ class TestDvrRouter(framework.L3AgentTestFramework): # Since by definition this is a dvr (distributed = true) # only dvr and dvr_snat are applicable self.agent.conf.agent_mode = 'dvr_snat' if enable_snat else 'dvr' - self.agent.conf.network_device_mtu = custom_mtu # We get the router info particular to a dvr router router_info = self.generate_dvr_router_info( enable_ha, enable_snat, extra_routes=True) + if use_port_mtu: + for key in ('_interfaces', '_snat_router_interfaces', + '_floatingip_agent_interfaces'): + for port in router_info[key]: + port['mtu'] = custom_mtu + router_info['gw_port']['mtu'] = custom_mtu + router_info['_ha_interface']['mtu'] = custom_mtu + else: + self.agent.conf.network_device_mtu = custom_mtu # We need to mock the get_agent_gateway_port return value # because the whole L3PluginApi is mocked and we need the port @@ -156,6 +168,9 @@ class TestDvrRouter(framework.L3AgentTestFramework): router.get_internal_device_name, router.ns_name) utils.wait_until_true(device_exists) + name = router.get_internal_device_name(device['id']) + self.assertEqual(custom_mtu, + ip_lib.IPDevice(name, router.ns_name).link.mtu) ext_gateway_port = router_info['gw_port'] self.assertTrue(self._namespace_exists(router.ns_name)) diff --git a/neutron/tests/functional/agent/test_dhcp_agent.py b/neutron/tests/functional/agent/test_dhcp_agent.py index db86e1b89e8..041367c6c5b 100644 --- a/neutron/tests/functional/agent/test_dhcp_agent.py +++ b/neutron/tests/functional/agent/test_dhcp_agent.py @@ -253,6 +253,15 @@ class DHCPAgentOVSTestCase(DHCPAgentOVSTestFramework): dhcp_enabled=dhcp_enabled) self.assert_dhcp_resources(network, dhcp_enabled) + def test_agent_mtu_set_on_interface_driver(self): + network = self.network_dict_for_dhcp() + network["mtu"] = 789 + self.configure_dhcp_for_network(network=network) + port = network.ports[0] + iface_name = self.get_interface_name(network, port) + dev = ip_lib.IPDevice(iface_name, network.namespace) + self.assertEqual(789, dev.link.mtu) + def test_good_address_allocation(self): network, port = self._get_network_port_for_allocation_test() network.ports.append(port) diff --git a/neutron/tests/unit/agent/dhcp/test_agent.py b/neutron/tests/unit/agent/dhcp/test_agent.py index 6c8645fa20f..c3046c12136 100644 --- a/neutron/tests/unit/agent/dhcp/test_agent.py +++ b/neutron/tests/unit/agent/dhcp/test_agent.py @@ -1309,7 +1309,8 @@ class TestDeviceManager(base.BaseTestCase): port.id, 'tap12345678-12', 'aa:bb:cc:dd:ee:ff', - namespace=net.namespace)) + namespace=net.namespace, + mtu=None)) self.mock_driver.assert_has_calls(expected) dh._set_default_route.assert_called_once_with(net, 'tap12345678-12') diff --git a/neutron/tests/unit/agent/l3/test_agent.py b/neutron/tests/unit/agent/l3/test_agent.py index 48de8a90082..2ee744c8057 100644 --- a/neutron/tests/unit/agent/l3/test_agent.py +++ b/neutron/tests/unit/agent/l3/test_agent.py @@ -390,7 +390,8 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): sn_port['fixed_ips'], sn_port['mac_address'], ri._get_snat_int_device_name(sn_port['id']), - dvr_snat_ns.SNAT_INT_DEV_PREFIX) + dvr_snat_ns.SNAT_INT_DEV_PREFIX, + mtu=None) elif action == 'remove': self.device_exists.return_value = False ri.get_snat_port_for_internal_port = mock.Mock( diff --git a/releasenotes/notes/end-to-end-mtu-00345fc4282cb8fb.yaml b/releasenotes/notes/end-to-end-mtu-00345fc4282cb8fb.yaml new file mode 100644 index 00000000000..e5164c2dca8 --- /dev/null +++ b/releasenotes/notes/end-to-end-mtu-00345fc4282cb8fb.yaml @@ -0,0 +1,20 @@ +--- +features: + - Use the value of the network 'mtu' attribute for the MTU + of virtual network interfaces such as veth pairs, patch + ports, and tap devices involving a particular network. + - Enable end-to-end support for arbitrary MTUs including + jumbo frames between instances and provider networks by + moving MTU disparities between flat or VLAN networks and + overlay networks from layer-2 devices to layer-3 devices + that support path MTU discovery (PMTUD). +upgrade: + - Does not change MTU for existing virtual network interfaces. + - Actions that create virtual network interfaces on an existing + network with the 'mtu' attribute containing a value greater + than zero could cause issues for network traffic traversing + existing and new virtual network interfaces. +fixes: + - Explicitly configure MTU of virtual network interfaces + rather than using default values or incorrect values that + do not account for overlay protocol overhead.