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 <mkassawara@gmail.com>
Change-Id: Ic091fa78dfd133179c71cbc847bf955a06cb248a
This commit is contained in:
Kevin Benton 2016-02-22 16:41:45 -08:00
parent 3fb153b15b
commit 4df8d9a701
12 changed files with 101 additions and 37 deletions

View File

@ -117,4 +117,5 @@ class DvrEdgeHaRouter(DvrEdgeRouter, HaRouter):
self.driver.plug(port['network_id'], port['id'], self.driver.plug(port['network_id'], port['id'],
interface_name, port['mac_address'], interface_name, port['mac_address'],
namespace=self.snat_namespace.name, 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'))

View File

@ -104,7 +104,8 @@ class DvrEdgeRouter(dvr_local_router.DvrLocalRouter):
sn_port['fixed_ips'], sn_port['fixed_ips'],
sn_port['mac_address'], sn_port['mac_address'],
interface_name, 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): def _dvr_internal_network_removed(self, port):
super(DvrEdgeRouter, self)._dvr_internal_network_removed(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'], self.snat_namespace.name, port['network_id'],
port['id'], port['fixed_ips'], port['id'], port['fixed_ips'],
port['mac_address'], interface_name, 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): def _create_dvr_gateway(self, ex_gw_port, gw_interface_name):
"""Create SNAT namespace.""" """Create SNAT namespace."""

View File

@ -106,7 +106,8 @@ class FipNamespace(namespaces.Namespace):
ex_gw_port['mac_address'], ex_gw_port['mac_address'],
bridge=self.agent_conf.external_network_bridge, bridge=self.agent_conf.external_network_bridge,
namespace=ns_name, 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']) ip_cidrs = common_utils.fixed_ip_cidrs(ex_gw_port['fixed_ips'])
self.driver.init_l3(interface_name, ip_cidrs, namespace=ns_name, 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), self._internal_ns_interface_added(str(fip_2_rtr),
fip_2_rtr_name, fip_2_rtr_name,
fip_ns_name) fip_ns_name)
if self.agent_conf.network_device_mtu: mtu = (self.agent_conf.network_device_mtu or
int_dev[0].link.set_mtu(self.agent_conf.network_device_mtu) ri.get_ex_gw_port().get('mtu'))
int_dev[1].link.set_mtu(self.agent_conf.network_device_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[0].link.set_up()
int_dev[1].link.set_up() int_dev[1].link.set_up()

View File

@ -144,7 +144,8 @@ class HaRouter(router.RouterInfo):
interface_name, interface_name,
self.ha_port['mac_address'], self.ha_port['mac_address'],
namespace=self.ha_namespace, 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']) ip_cidrs = common_utils.fixed_ip_cidrs(self.ha_port['fixed_ips'])
self.driver.init_l3(interface_name, ip_cidrs, self.driver.init_l3(interface_name, ip_cidrs,
namespace=self.ha_namespace, namespace=self.ha_namespace,
@ -268,13 +269,13 @@ class HaRouter(router.RouterInfo):
def _plug_ha_router_port(self, port, name_getter, prefix): def _plug_ha_router_port(self, port, name_getter, prefix):
port_id = port['id'] port_id = port['id']
interface_name = name_getter(port_id) interface_name = name_getter(port_id)
self.driver.plug(port['network_id'], self.driver.plug(port['network_id'],
port_id, port_id,
interface_name, interface_name,
port['mac_address'], port['mac_address'],
namespace=self.ha_namespace, namespace=self.ha_namespace,
prefix=prefix) prefix=prefix,
mtu=port.get('mtu'))
self._disable_ipv6_addressing_on_interface(interface_name) self._disable_ipv6_addressing_on_interface(interface_name)
self._add_vips(port, interface_name) self._add_vips(port, interface_name)

View File

@ -361,12 +361,12 @@ class RouterInfo(object):
def _internal_network_added(self, ns_name, network_id, port_id, def _internal_network_added(self, ns_name, network_id, port_id,
fixed_ips, mac_address, fixed_ips, mac_address,
interface_name, prefix): interface_name, prefix, mtu=None):
LOG.debug("adding internal network: prefix(%s), port(%s)", LOG.debug("adding internal network: prefix(%s), port(%s)",
prefix, port_id) prefix, port_id)
self.driver.plug(network_id, port_id, interface_name, mac_address, self.driver.plug(network_id, port_id, interface_name, mac_address,
namespace=ns_name, namespace=ns_name,
prefix=prefix) prefix=prefix, mtu=mtu)
ip_cidrs = common_utils.fixed_ip_cidrs(fixed_ips) ip_cidrs = common_utils.fixed_ip_cidrs(fixed_ips)
self.driver.init_router_port( self.driver.init_router_port(
@ -391,7 +391,8 @@ class RouterInfo(object):
fixed_ips, fixed_ips,
mac_address, mac_address,
interface_name, interface_name,
INTERNAL_DEV_PREFIX) INTERNAL_DEV_PREFIX,
mtu=port.get('mtu'))
def internal_network_removed(self, port): def internal_network_removed(self, port):
interface_name = self.get_internal_device_name(port['id']) interface_name = self.get_internal_device_name(port['id'])
@ -557,7 +558,8 @@ class RouterInfo(object):
ex_gw_port['mac_address'], ex_gw_port['mac_address'],
bridge=self.agent_conf.external_network_bridge, bridge=self.agent_conf.external_network_bridge,
namespace=ns_name, 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): def _get_external_gw_ips(self, ex_gw_port):
gateway_ips = [] gateway_ips = []

View File

@ -1230,7 +1230,8 @@ class DeviceManager(object):
port.id, port.id,
interface_name, interface_name,
port.mac_address, port.mac_address,
namespace=network.namespace) namespace=network.namespace,
mtu=network.get('mtu'))
except Exception: except Exception:
with excutils.save_and_reraise_exception(): with excutils.save_and_reraise_exception():
LOG.exception(_LE('Unable to plug DHCP port for ' LOG.exception(_LE('Unable to plug DHCP port for '

View File

@ -20,7 +20,7 @@ from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
import six 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.common import ovs_lib
from neutron.agent.linux import ip_lib from neutron.agent.linux import ip_lib
from neutron.agent.linux import utils from neutron.agent.linux import utils
@ -233,15 +233,15 @@ class LinuxInterfaceDriver(object):
@abc.abstractmethod @abc.abstractmethod
def plug_new(self, network_id, port_id, device_name, mac_address, 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.""" """Plug in the interface only for new devices that don't exist yet."""
def plug(self, network_id, port_id, device_name, mac_address, 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, if not ip_lib.device_exists(device_name,
namespace=namespace): namespace=namespace):
self.plug_new(network_id, port_id, device_name, mac_address, self.plug_new(network_id, port_id, device_name, mac_address,
bridge, namespace, prefix) bridge, namespace, prefix, mtu)
else: else:
LOG.info(_LI("Device %s already exists"), device_name) LOG.info(_LI("Device %s already exists"), device_name)
@ -269,7 +269,7 @@ class LinuxInterfaceDriver(object):
class NullDriver(LinuxInterfaceDriver): class NullDriver(LinuxInterfaceDriver):
def plug_new(self, network_id, port_id, device_name, mac_address, 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 pass
def unplug(self, device_name, bridge=None, namespace=None, prefix=None): def unplug(self, device_name, bridge=None, namespace=None, prefix=None):
@ -304,7 +304,7 @@ class OVSInterfaceDriver(LinuxInterfaceDriver):
ovs.replace_port(device_name, *attrs) ovs.replace_port(device_name, *attrs)
def plug_new(self, network_id, port_id, device_name, mac_address, 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.""" """Plug in the interface."""
if not bridge: if not bridge:
bridge = self.conf.ovs_integration_bridge bridge = self.conf.ovs_integration_bridge
@ -328,11 +328,13 @@ class OVSInterfaceDriver(LinuxInterfaceDriver):
ns_dev.link.set_address(mac_address) ns_dev.link.set_address(mac_address)
if self.conf.network_device_mtu: mtu = self.conf.network_device_mtu or mtu
ns_dev.link.set_mtu(self.conf.network_device_mtu) if mtu:
ns_dev.link.set_mtu(mtu)
if self.conf.ovs_use_veth: 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. # Add an interface created by ovs to the namespace.
if not self.conf.ovs_use_veth and namespace: if not self.conf.ovs_use_veth and namespace:
namespace_obj = ip.ensure_namespace(namespace) namespace_obj = ip.ensure_namespace(namespace)
@ -381,7 +383,7 @@ class IVSInterfaceDriver(LinuxInterfaceDriver):
utils.execute(cmd, run_as_root=True) utils.execute(cmd, run_as_root=True)
def plug_new(self, network_id, port_id, device_name, mac_address, 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.""" """Plug in the interface."""
ip = ip_lib.IPWrapper() ip = ip_lib.IPWrapper()
tap_name = self._get_tap_name(device_name, prefix) tap_name = self._get_tap_name(device_name, prefix)
@ -393,9 +395,12 @@ class IVSInterfaceDriver(LinuxInterfaceDriver):
ns_dev = ip.device(device_name) ns_dev = ip.device(device_name)
ns_dev.link.set_address(mac_address) ns_dev.link.set_address(mac_address)
if self.conf.network_device_mtu: mtu = self.conf.network_device_mtu or mtu
ns_dev.link.set_mtu(self.conf.network_device_mtu) if mtu:
root_dev.link.set_mtu(self.conf.network_device_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: if namespace:
namespace_obj = ip.ensure_namespace(namespace) namespace_obj = ip.ensure_namespace(namespace)
@ -424,7 +429,7 @@ class BridgeInterfaceDriver(LinuxInterfaceDriver):
DEV_NAME_PREFIX = 'ns-' DEV_NAME_PREFIX = 'ns-'
def plug_new(self, network_id, port_id, device_name, mac_address, 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.""" """Plugin the interface."""
ip = ip_lib.IPWrapper() ip = ip_lib.IPWrapper()
@ -436,9 +441,12 @@ class BridgeInterfaceDriver(LinuxInterfaceDriver):
namespace2=namespace) namespace2=namespace)
ns_veth.link.set_address(mac_address) ns_veth.link.set_address(mac_address)
if self.conf.network_device_mtu: mtu = self.conf.network_device_mtu or mtu
root_veth.link.set_mtu(self.conf.network_device_mtu) if mtu:
ns_veth.link.set_mtu(self.conf.network_device_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() root_veth.link.set_up()
ns_veth.link.set_up() ns_veth.link.set_up()

View File

@ -51,6 +51,10 @@ class TestDvrRouter(framework.L3AgentTestFramework):
self.agent.conf.agent_mode = 'dvr' self.agent.conf.agent_mode = 'dvr'
self._test_update_floatingip_statuses(self.generate_dvr_router_info()) 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): def test_dvr_router_lifecycle_without_ha_without_snat_with_fips(self):
self._dvr_router_lifecycle(enable_ha=False, enable_snat=False) 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) self._validate_fips_for_external_network(router2, fip2_ns)
def _dvr_router_lifecycle(self, enable_ha=False, enable_snat=False, def _dvr_router_lifecycle(self, enable_ha=False, enable_snat=False,
custom_mtu=2000, custom_mtu=2000, use_port_mtu=False,
ip_version=4, ip_version=4,
dual_stack=False): dual_stack=False):
'''Test dvr router lifecycle '''Test dvr router lifecycle
@ -115,11 +119,19 @@ class TestDvrRouter(framework.L3AgentTestFramework):
# Since by definition this is a dvr (distributed = true) # Since by definition this is a dvr (distributed = true)
# only dvr and dvr_snat are applicable # only dvr and dvr_snat are applicable
self.agent.conf.agent_mode = 'dvr_snat' if enable_snat else 'dvr' 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 # We get the router info particular to a dvr router
router_info = self.generate_dvr_router_info( router_info = self.generate_dvr_router_info(
enable_ha, enable_snat, extra_routes=True) 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 # We need to mock the get_agent_gateway_port return value
# because the whole L3PluginApi is mocked and we need the port # 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.get_internal_device_name,
router.ns_name) router.ns_name)
utils.wait_until_true(device_exists) 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'] ext_gateway_port = router_info['gw_port']
self.assertTrue(self._namespace_exists(router.ns_name)) self.assertTrue(self._namespace_exists(router.ns_name))

View File

@ -253,6 +253,15 @@ class DHCPAgentOVSTestCase(DHCPAgentOVSTestFramework):
dhcp_enabled=dhcp_enabled) dhcp_enabled=dhcp_enabled)
self.assert_dhcp_resources(network, 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): def test_good_address_allocation(self):
network, port = self._get_network_port_for_allocation_test() network, port = self._get_network_port_for_allocation_test()
network.ports.append(port) network.ports.append(port)

View File

@ -1309,7 +1309,8 @@ class TestDeviceManager(base.BaseTestCase):
port.id, port.id,
'tap12345678-12', 'tap12345678-12',
'aa:bb:cc:dd:ee:ff', 'aa:bb:cc:dd:ee:ff',
namespace=net.namespace)) namespace=net.namespace,
mtu=None))
self.mock_driver.assert_has_calls(expected) self.mock_driver.assert_has_calls(expected)
dh._set_default_route.assert_called_once_with(net, 'tap12345678-12') dh._set_default_route.assert_called_once_with(net, 'tap12345678-12')

View File

@ -390,7 +390,8 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
sn_port['fixed_ips'], sn_port['fixed_ips'],
sn_port['mac_address'], sn_port['mac_address'],
ri._get_snat_int_device_name(sn_port['id']), 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': elif action == 'remove':
self.device_exists.return_value = False self.device_exists.return_value = False
ri.get_snat_port_for_internal_port = mock.Mock( ri.get_snat_port_for_internal_port = mock.Mock(

View File

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