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 commit4df8d9a701
) === 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 commit8a86ba1d01
) === Change-Id: Ic091fa78dfd133179c71cbc847bf955a06cb248a
This commit is contained in:
parent
36864b6a70
commit
c55aba1dba
|
@ -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(
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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']):
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -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 '
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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.
|
Loading…
Reference in New Issue