Set MTU on tap devices in Linux Bridge agent

Libvirt does not set the MTU of the tap device it creates when creating
a bridge interface. It also does not set the MTU of the bridge itself.
This cannot be fixed on the Nova side since libvirt doesn't appear to
have support for setting MTUs on bridges until version 3x.

This results in a situation where the first VM tap interface attached to
a bridge will always have an MTU of 1500. The Neutron agent will then
add in VLAN/VXLAN interfaces with the correct MTU; however, the bridge
MTU will still be pinned to the smallest interface MTU attached to it.
This breaks jumbo frames until all small MTU tap devices are removed
from the bridge.

This patch explicitly sets the MTU on tap devices to match the network
MTU when processing the device.

We will have to carry this workaround until we stop Nova from
plugging taps into bridges[1] or until we drop support for older
libvirts on the Nova side and have it set the MTU.

This bug was introduced by change
I53c0eb57da956b36f09731d25db989719e9bc9dc which reverted automatic
setting of tap MTUs to match those of the physical device.

1. I23c5faaeab69aede1fd038a36f4a0b8f928498ce
Closes-Bug: #1684038
Change-Id: Ia245a3e22339fce026901e24a82e836c8b27cc28
This commit is contained in:
Kevin Benton 2017-04-19 22:30:14 -07:00
parent 5f9b9c47cf
commit df320474c5
5 changed files with 37 additions and 16 deletions

View File

@ -21,10 +21,12 @@ import six
class NetworkSegment(object):
"""Represents a Neutron network segment"""
def __init__(self, network_type, physical_network, segmentation_id):
def __init__(self, network_type, physical_network, segmentation_id,
mtu=None):
self.network_type = network_type
self.physical_network = physical_network
self.segmentation_id = segmentation_id
self.mtu = mtu
@six.add_metaclass(abc.ABCMeta)

View File

@ -245,7 +245,8 @@ class CommonAgentLoop(service.Service):
segment = amb.NetworkSegment(
device_details.get('network_type'),
device_details['physical_network'],
device_details.get('segmentation_id')
device_details.get('segmentation_id'),
device_details.get('mtu')
)
network_id = device_details['network_id']
self.rpc_callbacks.add_network(network_id, segment)

View File

@ -445,12 +445,12 @@ class LinuxBridgeManager(amb.CommonAgentManagerBase):
network_id: network_id})
def add_tap_interface(self, network_id, network_type, physical_network,
segmentation_id, tap_device_name, device_owner):
segmentation_id, tap_device_name, device_owner, mtu):
"""Add tap interface and handle interface missing exceptions."""
try:
return self._add_tap_interface(network_id, network_type,
physical_network, segmentation_id,
tap_device_name, device_owner)
tap_device_name, device_owner, mtu)
except Exception:
with excutils.save_and_reraise_exception() as ctx:
if not ip_lib.device_exists(tap_device_name):
@ -461,7 +461,7 @@ class LinuxBridgeManager(amb.CommonAgentManagerBase):
return False
def _add_tap_interface(self, network_id, network_type, physical_network,
segmentation_id, tap_device_name, device_owner):
segmentation_id, tap_device_name, device_owner, mtu):
"""Add tap interface.
If a VIF has been plugged into a network, this function will
@ -483,6 +483,12 @@ class LinuxBridgeManager(amb.CommonAgentManagerBase):
physical_network,
segmentation_id):
return False
if mtu: # <-None with device_details from older neutron servers.
# we ensure the MTU here because libvirt does not set the
# MTU of a bridge it creates and the tap device it creates will
# inherit from the bridge its plugged into, which will be 1500
# at the time. See bug/1684326 for details.
self._set_tap_mtu(tap_device_name, mtu)
# Avoid messing with plugging devices into a bridge that the agent
# does not own
if not device_owner.startswith(constants.DEVICE_OWNER_COMPUTE_PREFIX):
@ -504,12 +510,16 @@ class LinuxBridgeManager(amb.CommonAgentManagerBase):
"thus added elsewhere.", data)
return True
def _set_tap_mtu(self, tap_device_name, mtu):
ip_lib.IPDevice(tap_device_name).link.set_mtu(mtu)
def plug_interface(self, network_id, network_segment, tap_name,
device_owner):
return self.add_tap_interface(network_id, network_segment.network_type,
network_segment.physical_network,
network_segment.segmentation_id,
tap_name, device_owner)
tap_name, device_owner,
network_segment.mtu)
def delete_bridge(self, bridge_name):
bridge_device = bridge_lib.BridgeDevice(bridge_name)

View File

@ -120,6 +120,7 @@ class RpcCallbacks(type_tunnel.TunnelRpcCallbackMixin):
'network_type': segment[api.NETWORK_TYPE],
'segmentation_id': segment[api.SEGMENTATION_ID],
'physical_network': segment[api.PHYSICAL_NETWORK],
'mtu': port_context.network._network.get('mtu'),
'fixed_ips': port['fixed_ips'],
'device_owner': port['device_owner'],
'allowed_address_pairs': port['allowed_address_pairs'],

View File

@ -478,7 +478,7 @@ class TestLinuxBridgeManager(base.BaseTestCase):
self.assertFalse(self.lbm.add_tap_interface("123",
p_const.TYPE_VLAN,
"physnet1", None,
"tap1", "foo"))
"tap1", "foo", None))
@mock.patch.object(ip_lib, "device_exists", return_value=True)
def test_add_tap_interface_with_other_error(self, exists):
@ -486,7 +486,7 @@ class TestLinuxBridgeManager(base.BaseTestCase):
side_effect=RuntimeError("No more fuel")):
self.assertRaises(RuntimeError, self.lbm.add_tap_interface, "123",
p_const.TYPE_VLAN, "physnet1", None, "tap1",
"foo")
"foo", None)
def test_add_tap_interface_owner_compute(self):
with mock.patch.object(ip_lib, "device_exists"):
@ -495,7 +495,7 @@ class TestLinuxBridgeManager(base.BaseTestCase):
p_const.TYPE_LOCAL,
"physnet1", None,
"tap1",
"compute:1"))
"compute:1", None))
def _test_add_tap_interface(self, dev_owner_prefix):
with mock.patch.object(ip_lib, "device_exists") as de_fn:
@ -503,13 +503,14 @@ class TestLinuxBridgeManager(base.BaseTestCase):
self.assertFalse(
self.lbm.add_tap_interface("123", p_const.TYPE_VLAN,
"physnet1", "1", "tap1",
dev_owner_prefix))
dev_owner_prefix, None))
de_fn.return_value = True
bridge_device = mock.Mock()
with mock.patch.object(self.lbm, "ensure_local_bridge") as en_fn,\
mock.patch.object(bridge_lib, "BridgeDevice",
return_value=bridge_device), \
mock.patch.object(self.lbm, '_set_tap_mtu') as set_tap, \
mock.patch.object(bridge_lib.BridgeDevice,
"get_interface_bridge") as get_br:
bridge_device.addif.retun_value = False
@ -518,7 +519,8 @@ class TestLinuxBridgeManager(base.BaseTestCase):
p_const.TYPE_LOCAL,
"physnet1", None,
"tap1",
dev_owner_prefix))
dev_owner_prefix,
None))
en_fn.assert_called_with("123", "brq123")
self.lbm.bridge_mappings = {"physnet1": "brq999"}
@ -526,7 +528,9 @@ class TestLinuxBridgeManager(base.BaseTestCase):
p_const.TYPE_LOCAL,
"physnet1", None,
"tap1",
dev_owner_prefix))
dev_owner_prefix,
8765))
set_tap.assert_called_with('tap1', 8765)
en_fn.assert_called_with("123", "brq999")
get_br.return_value = False
@ -535,7 +539,8 @@ class TestLinuxBridgeManager(base.BaseTestCase):
p_const.TYPE_LOCAL,
"physnet1", None,
"tap1",
dev_owner_prefix))
dev_owner_prefix,
None))
with mock.patch.object(self.lbm,
"ensure_physical_in_bridge") as ens_fn:
ens_fn.return_value = False
@ -543,7 +548,8 @@ class TestLinuxBridgeManager(base.BaseTestCase):
p_const.TYPE_VLAN,
"physnet1", "1",
"tap1",
dev_owner_prefix))
dev_owner_prefix,
None))
def test_add_tap_interface_owner_network(self):
self._test_add_tap_interface(constants.DEVICE_OWNER_NETWORK_PREFIX)
@ -552,13 +558,14 @@ class TestLinuxBridgeManager(base.BaseTestCase):
self._test_add_tap_interface(constants.DEVICE_OWNER_NEUTRON_PREFIX)
def test_plug_interface(self):
segment = amb.NetworkSegment(p_const.TYPE_VLAN, "physnet-1", "1")
segment = amb.NetworkSegment(p_const.TYPE_VLAN, "physnet-1", "1", 1777)
with mock.patch.object(self.lbm, "add_tap_interface") as add_tap:
self.lbm.plug_interface("123", segment, "tap234",
constants.DEVICE_OWNER_NETWORK_PREFIX)
add_tap.assert_called_with("123", p_const.TYPE_VLAN, "physnet-1",
"1", "tap234",
constants.DEVICE_OWNER_NETWORK_PREFIX)
constants.DEVICE_OWNER_NETWORK_PREFIX,
1777)
def test_delete_bridge(self):
with mock.patch.object(ip_lib.IPDevice, "exists") as de_fn,\