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.

Conflicts:
	neutron/agent/l3/dvr_edge_ha_router.py
	neutron/agent/l3/dvr_edge_router.py
	neutron/agent/l3/ha_router.py
	neutron/agent/linux/interface.py
	neutron/tests/functional/agent/l3/test_dvr_router.py
	neutron/tests/functional/agent/test_dhcp_agent.py

Additional modifications for Liberty:
- test_dvr_router_lifecycle_ha_with_snat_with_fips_nmtu renamed into
  test_dvr_router_lifecycle_without_ha_with_snat_with_fips_nmtu,
- the test validates DVR without HA.

Reason for the change: Liberty does not support DVR + HA routers (the
test raises DvrHaRouterNotSupported without those modifications).

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>
(cherry picked from commit 4df8d9a701)

===

Also squashing in the following fix to pass unit tests for midonet
interface driver:

Support interface drivers that don't support mtu parameter for plug_new

The method signature before Mitaka did not have the mtu= parameter. We
should continue supporting the old signature, since it can be used in
out of tree interface drivers. The class is part of public neutron API,
so we should make an effort to not break out of tree code.

Local modifications:
- don't issue a deprecation warning in minor release update.

Change-Id: I8e0c07c76fd0b4c55b66c20ebe29cdb7c07d6f27
Closes-Bug: #1570392
(cherry picked from commit 8a86ba1d01)

===

Change-Id: Ic091fa78dfd133179c71cbc847bf955a06cb248a
This commit is contained in:
Kevin Benton 2016-02-22 16:41:45 -08:00 committed by Ihar Hrachyshka
parent 36864b6a70
commit c55aba1dba
12 changed files with 132 additions and 37 deletions

View File

@ -99,7 +99,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):
snat_ns.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'))
self._external_gateway_added(ex_gw_port, gw_interface_name,
snat_ns.name, preserve_ips=[])
self.snat_iptables_manager = iptables_manager.IptablesManager(

View File

@ -105,7 +105,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,
@ -231,9 +232,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()

View File

@ -144,7 +144,8 @@ class HaRouter(router.RouterInfo):
interface_name,
self.ha_port['mac_address'],
namespace=self.ns_name,
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.ns_name,
@ -263,13 +264,13 @@ class HaRouter(router.RouterInfo):
def internal_network_added(self, port):
port_id = port['id']
interface_name = self.get_internal_device_name(port_id)
self.driver.plug(port['network_id'],
port_id,
interface_name,
port['mac_address'],
namespace=self.ns_name,
prefix=router.INTERNAL_DEV_PREFIX)
prefix=router.INTERNAL_DEV_PREFIX,
mtu=port.get('mtu'))
self._disable_ipv6_addressing_on_interface(interface_name)
for ip_cidr in common_utils.fixed_ip_cidrs(port['fixed_ips']):

View File

@ -289,12 +289,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(
@ -319,7 +319,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'])
@ -468,7 +469,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 = []

View File

@ -1221,7 +1221,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 '

View File

@ -26,7 +26,7 @@ from neutron.agent.linux import utils
from neutron.common import constants as n_const
from neutron.common import exceptions
from neutron.common import ipv6_utils
from neutron.i18n import _LE, _LI
from neutron.i18n import _LE, _LI, _LW
LOG = logging.getLogger(__name__)
@ -242,15 +242,19 @@ 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)
try:
self.plug_new(network_id, port_id, device_name, mac_address,
bridge, namespace, prefix, mtu)
except TypeError:
self.plug_new(network_id, port_id, device_name, mac_address,
bridge, namespace, prefix)
else:
LOG.info(_LI("Device %s already exists"), device_name)
@ -278,7 +282,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):
@ -313,7 +317,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
@ -338,11 +342,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)
@ -430,7 +436,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)
@ -443,9 +449,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)
@ -474,7 +483,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()
@ -487,9 +496,12 @@ class BridgeInterfaceDriver(LinuxInterfaceDriver):
root_veth.disable_ipv6()
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()

View File

@ -244,6 +244,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)

View File

@ -1051,6 +1051,10 @@ class UnprivilegedUserGroupMetadataL3AgentTestCase(MetadataL3AgentTestCase):
class TestDvrRouter(L3AgentTestFramework):
def test_dvr_router_lifecycle_without_ha_with_snat_with_fips_nmtu(self):
self._dvr_router_lifecycle(enable_ha=False, 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)
@ -1102,7 +1106,7 @@ class TestDvrRouter(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):
'''Test dvr router lifecycle
:param enable_ha: sets the ha value for the router.
@ -1114,11 +1118,18 @@ class TestDvrRouter(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
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
@ -1137,6 +1148,11 @@ class TestDvrRouter(L3AgentTestFramework):
# manage the router (create it, create namespaces,
# attach interfaces, etc...)
router = self.manage_router(self.agent, router_info)
if enable_ha:
device = router.router[l3_constants.INTERFACE_KEY][-1]
name = router.get_internal_device_name(device['id'])
self.assertEqual(custom_mtu,
ip_lib.IPDevice(name, router.ns_name).link.mtu)
self.assertTrue(self._namespace_exists(router.ns_name))
self.assertTrue(self._metadata_proxy_exists(self.agent.conf, router))

View File

@ -1287,7 +1287,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')

View File

@ -380,7 +380,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(

View File

@ -56,6 +56,23 @@ class FakePort(object):
network_id = network.id
class FakeInterfaceDriverNoMtu(interface.LinuxInterfaceDriver):
# NOTE(ihrachys) this method intentially omit mtu= parameter, since that
# was the method signature before Mitaka. We should make sure the old
# signature still works.
def __init__(self, *args, **kwargs):
super(FakeInterfaceDriverNoMtu, self).__init__(*args, **kwargs)
self.plug_called = False
def plug_new(self, network_id, port_id, device_name, mac_address,
bridge=None, namespace=None, prefix=None):
self.plug_called = True
def unplug(self, device_name, bridge=None, namespace=None, prefix=None):
pass
class TestBase(base.BaseTestCase):
def setUp(self):
super(TestBase, self).setUp()
@ -69,6 +86,16 @@ class TestBase(base.BaseTestCase):
self.device_exists = self.device_exists_p.start()
class TestABCDriverNoMtu(TestBase):
def test_plug_with_no_mtu_works(self):
driver = FakeInterfaceDriverNoMtu(self.conf)
self.device_exists.return_value = False
driver.plug(
mock.Mock(), mock.Mock(), mock.Mock(), mock.Mock(), mtu=9000)
self.assertTrue(driver.plug_called)
class TestABCDriver(TestBase):
def setUp(self):
super(TestABCDriver, self).setUp()

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.