Apply network MTU changes to l3 ports

This patch makes L3 agent to update its ports' MTU when it's changed on
core plugin side.

Related-Bug: #1671634
Change-Id: I4444da6358e8b8420a3a365e1107b02f5bb1161d
This commit is contained in:
Ihar Hrachyshka 2017-08-07 10:19:57 -07:00
parent d225b86738
commit cc69828ff0
9 changed files with 95 additions and 12 deletions

View File

@ -34,9 +34,6 @@ architectures should avoid cases 2 and 3.
networks that need a new MTU. (Network MTU update is available for all core networks that need a new MTU. (Network MTU update is available for all core
plugins that implement the ``net-mtu-writable`` API extension.) plugins that implement the ``net-mtu-writable`` API extension.)
When using the Open vSwitch or Linux bridge drivers, new MTU calculations
will be propogated automatically after restarting the ``l3-agent`` service.
Case 1 Case 1
------ ------

View File

@ -13,6 +13,8 @@
# under the License. # under the License.
# #
import itertools
import eventlet import eventlet
import netaddr import netaddr
from neutron_lib.callbacks import events from neutron_lib.callbacks import events
@ -29,6 +31,7 @@ from oslo_service import loopingcall
from oslo_service import periodic_task from oslo_service import periodic_task
from oslo_utils import excutils from oslo_utils import excutils
from oslo_utils import timeutils from oslo_utils import timeutils
from osprofiler import profiler
from neutron._i18n import _, _LE, _LI, _LW from neutron._i18n import _, _LE, _LI, _LW
from neutron.agent.common import utils as common_utils from neutron.agent.common import utils as common_utils
@ -167,6 +170,7 @@ class L3PluginApi(object):
host=self.host, network_id=fip_net) host=self.host, network_id=fip_net)
@profiler.trace_cls("l3-agent")
class L3NATAgent(ha.AgentMixin, class L3NATAgent(ha.AgentMixin,
dvr.AgentMixin, dvr.AgentMixin,
manager.Manager): manager.Manager):
@ -185,8 +189,9 @@ class L3NATAgent(ha.AgentMixin,
1.3 - fipnamespace_delete_on_ext_net - to delete fipnamespace 1.3 - fipnamespace_delete_on_ext_net - to delete fipnamespace
after the external network is removed after the external network is removed
Needed by the L3 service when dealing with DVR Needed by the L3 service when dealing with DVR
1.4 - support network_update to get MTU updates
""" """
target = oslo_messaging.Target(version='1.3') target = oslo_messaging.Target(version='1.4')
def __init__(self, host, conf=None): def __init__(self, host, conf=None):
if conf: if conf:
@ -256,6 +261,10 @@ class L3NATAgent(ha.AgentMixin,
self.create_pd_router_update, self.create_pd_router_update,
self.conf) self.conf)
# Consume network updates to trigger router resync
consumers = [[topics.NETWORK, topics.UPDATE]]
agent_rpc.create_consumers([self], topics.AGENT, consumers)
def _check_config_params(self): def _check_config_params(self):
"""Check items in configuration files. """Check items in configuration files.
@ -429,6 +438,16 @@ class L3NATAgent(ha.AgentMixin,
LOG.debug('Got router added to agent :%r', payload) LOG.debug('Got router added to agent :%r', payload)
self.routers_updated(context, payload) self.routers_updated(context, payload)
def network_update(self, context, **kwargs):
network_id = kwargs['network']['id']
for ri in self.router_info.values():
ports = itertools.chain(ri.internal_ports, [ri.ex_gw_port])
port_belongs = lambda p: p['network_id'] == network_id
if any(port_belongs(p) for p in ports):
update = queue.RouterUpdate(
ri.router_id, queue.PRIORITY_SYNC_ROUTERS_TASK)
self._resync_router(update)
def _process_router_if_compatible(self, router): def _process_router_if_compatible(self, router):
if (self.conf.external_network_bridge and if (self.conf.external_network_bridge and
not ip_lib.device_exists(self.conf.external_network_bridge)): not ip_lib.device_exists(self.conf.external_network_bridge)):

View File

@ -297,7 +297,9 @@ class HaRouter(router.RouterInfo):
if device.addr.list(to=ip_cidr): if device.addr.list(to=ip_cidr):
super(HaRouter, self).remove_floating_ip(device, ip_cidr) super(HaRouter, self).remove_floating_ip(device, ip_cidr)
def internal_network_updated(self, interface_name, ip_cidrs): def internal_network_updated(self, interface_name, ip_cidrs, mtu):
self.driver.set_mtu(interface_name, mtu, namespace=self.ns_name,
prefix=router.INTERNAL_DEV_PREFIX)
self._clear_vips(interface_name) self._clear_vips(interface_name)
self._disable_ipv6_addressing_on_interface(interface_name) self._disable_ipv6_addressing_on_interface(interface_name)
for ip_cidr in ip_cidrs: for ip_cidr in ip_cidrs:

View File

@ -476,10 +476,13 @@ class RouterInfo(object):
for existing_port in existing_ports: for existing_port in existing_ports:
current_port = current_ports_dict.get(existing_port['id']) current_port = current_ports_dict.get(existing_port['id'])
if current_port: if current_port:
if (sorted(existing_port['fixed_ips'], fixed_ips_changed = (
sorted(existing_port['fixed_ips'],
key=helpers.safe_sort_key) != key=helpers.safe_sort_key) !=
sorted(current_port['fixed_ips'], sorted(current_port['fixed_ips'],
key=helpers.safe_sort_key)): key=helpers.safe_sort_key))
mtu_changed = existing_port['mtu'] != current_port['mtu']
if fixed_ips_changed or mtu_changed:
updated_ports[current_port['id']] = current_port updated_ports[current_port['id']] = current_port
return updated_ports return updated_ports
@ -502,7 +505,9 @@ class RouterInfo(object):
self.router_id) self.router_id)
self.radvd.disable() self.radvd.disable()
def internal_network_updated(self, interface_name, ip_cidrs): def internal_network_updated(self, interface_name, ip_cidrs, mtu):
self.driver.set_mtu(interface_name, mtu, namespace=self.ns_name,
prefix=INTERNAL_DEV_PREFIX)
self.driver.init_router_port( self.driver.init_router_port(
interface_name, interface_name,
ip_cidrs=ip_cidrs, ip_cidrs=ip_cidrs,
@ -562,7 +567,8 @@ class RouterInfo(object):
ip_cidrs = common_utils.fixed_ip_cidrs(p['fixed_ips']) ip_cidrs = common_utils.fixed_ip_cidrs(p['fixed_ips'])
LOG.debug("updating internal network for port %s", p) LOG.debug("updating internal network for port %s", p)
updated_cidrs += ip_cidrs updated_cidrs += ip_cidrs
self.internal_network_updated(interface_name, ip_cidrs) self.internal_network_updated(
interface_name, ip_cidrs, p['mtu'])
enable_ra = enable_ra or self._port_has_ipv6_subnet(p) enable_ra = enable_ra or self._port_has_ipv6_subnet(p)
# Check if there is any pd prefix update # Check if there is any pd prefix update

View File

@ -858,6 +858,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
def update_network(self, context, id, network): def update_network(self, context, id, network):
net_data = network[net_def.RESOURCE_NAME] net_data = network[net_def.RESOURCE_NAME]
provider._raise_if_updates_provider_attributes(net_data) provider._raise_if_updates_provider_attributes(net_data)
need_network_update_notify = False
with db_api.context_manager.writer.using(context): with db_api.context_manager.writer.using(context):
original_network = super(Ml2Plugin, self).get_network(context, id) original_network = super(Ml2Plugin, self).get_network(context, id)
@ -883,6 +884,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
db_network.mtu is None): db_network.mtu is None):
db_network.mtu = self._get_network_mtu(db_network, db_network.mtu = self._get_network_mtu(db_network,
validate=False) validate=False)
# agents should now update all ports to reflect new MTU
need_network_update_notify = True
updated_network = self._make_network_dict( updated_network = self._make_network_dict(
db_network, context=context) db_network, context=context)
@ -896,7 +899,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
resources.NETWORK, events.PRECOMMIT_UPDATE, self, **kwargs) resources.NETWORK, events.PRECOMMIT_UPDATE, self, **kwargs)
# TODO(QoS): Move out to the extension framework somehow. # TODO(QoS): Move out to the extension framework somehow.
need_network_update_notify = ( need_network_update_notify |= (
qos_consts.QOS_POLICY_ID in net_data and qos_consts.QOS_POLICY_ID in net_data and
original_network[qos_consts.QOS_POLICY_ID] != original_network[qos_consts.QOS_POLICY_ID] !=
updated_network[qos_consts.QOS_POLICY_ID]) updated_network[qos_consts.QOS_POLICY_ID])

View File

@ -41,6 +41,7 @@ def get_ha_interface(ip='169.254.192.1', mac='12:34:56:78:2b:5d'):
'id': _uuid(), 'id': _uuid(),
'mac_address': mac, 'mac_address': mac,
'name': u'L3 HA Admin port 0', 'name': u'L3 HA Admin port 0',
'mtu': 1500,
'network_id': _uuid(), 'network_id': _uuid(),
'status': u'ACTIVE', 'status': u'ACTIVE',
'subnets': [{'cidr': '169.254.192.0/18', 'subnets': [{'cidr': '169.254.192.0/18',
@ -92,6 +93,7 @@ def prepare_router_data(ip_version=4, enable_snat=None, num_internal_ports=1,
if enable_gw: if enable_gw:
ex_gw_port = {'id': _uuid(), ex_gw_port = {'id': _uuid(),
'mac_address': gateway_mac, 'mac_address': gateway_mac,
'mtu': 1500,
'network_id': _uuid(), 'network_id': _uuid(),
'fixed_ips': fixed_ips, 'fixed_ips': fixed_ips,
'subnets': subnets, 'subnets': subnets,
@ -182,6 +184,7 @@ def router_append_interface(router, count=1, ip_version=4, ra_mode=None,
interfaces.append( interfaces.append(
{'id': _uuid(), {'id': _uuid(),
'mtu': 1500,
'network_id': _uuid(), 'network_id': _uuid(),
'admin_state_up': True, 'admin_state_up': True,
'fixed_ips': fixed_ips, 'fixed_ips': fixed_ips,
@ -274,6 +277,7 @@ def router_append_pd_enabled_subnet(router, count=1):
for i in range(current, current + count): for i in range(current, current + count):
subnet_id = _uuid() subnet_id = _uuid()
intf = {'id': _uuid(), intf = {'id': _uuid(),
'mtu': 1500,
'network_id': _uuid(), 'network_id': _uuid(),
'admin_state_up': True, 'admin_state_up': True,
'fixed_ips': [{'ip_address': '::1', 'fixed_ips': [{'ip_address': '::1',
@ -331,6 +335,7 @@ def prepare_ext_gw_test(context, ri, dual_stack=False):
'subnets': subnets, 'subnets': subnets,
'extra_subnets': [{'cidr': '172.16.0.0/24'}], 'extra_subnets': [{'cidr': '172.16.0.0/24'}],
'id': _uuid(), 'id': _uuid(),
'mtu': 1500,
'network_id': _uuid(), 'network_id': _uuid(),
'mac_address': 'ca:fe:de:ad:be:ef'} 'mac_address': 'ca:fe:de:ad:be:ef'}
interface_name = ri.get_external_device_name(ex_gw_port['id']) interface_name = ri.get_external_device_name(ex_gw_port['id'])

View File

@ -50,6 +50,13 @@ class ClientFixture(fixtures.Fixture):
self.addCleanup(_safe_method(delete), data['id']) self.addCleanup(_safe_method(delete), data['id'])
return data return data
def _update_resource(self, resource_type, id, spec):
update = getattr(self.client, 'update_%s' % resource_type)
body = {resource_type: spec}
resp = update(id, body=body)
return resp[resource_type]
def create_router(self, tenant_id, name=None, ha=False, def create_router(self, tenant_id, name=None, ha=False,
external_network=None): external_network=None):
resource_type = 'router' resource_type = 'router'
@ -79,6 +86,9 @@ class ClientFixture(fixtures.Fixture):
return self._create_resource(resource_type, spec) return self._create_resource(resource_type, spec)
def update_network(self, id, **kwargs):
return self._update_resource('network', id, kwargs)
def create_subnet(self, tenant_id, network_id, def create_subnet(self, tenant_id, network_id,
cidr, gateway_ip=None, name=None, enable_dhcp=True, cidr, gateway_ip=None, name=None, enable_dhcp=True,
ipv6_address_mode='slaac', ipv6_ra_mode='slaac'): ipv6_address_mode='slaac', ipv6_ra_mode='slaac'):

View File

@ -110,6 +110,34 @@ class TestLegacyL3Agent(TestL3Agent):
self.environment.hosts[0].l3_agent.get_namespace_suffix(), ) self.environment.hosts[0].l3_agent.get_namespace_suffix(), )
self._assert_namespace_exists(namespace) self._assert_namespace_exists(namespace)
def test_mtu_update(self):
tenant_id = uuidutils.generate_uuid()
router = self.safe_client.create_router(tenant_id)
network = self.safe_client.create_network(tenant_id)
subnet = self.safe_client.create_subnet(
tenant_id, network['id'], '20.0.0.0/24', gateway_ip='20.0.0.1')
self.safe_client.add_router_interface(router['id'], subnet['id'])
namespace = "%s@%s" % (
self._get_namespace(router['id']),
self.environment.hosts[0].l3_agent.get_namespace_suffix(), )
self._assert_namespace_exists(namespace)
ip = ip_lib.IPWrapper(namespace)
common_utils.wait_until_true(lambda: ip.get_devices())
devices = ip.get_devices()
self.assertEqual(1, len(devices))
ri_dev = devices[0]
mtu = ri_dev.link.mtu
self.assertEqual(1500, mtu)
mtu -= 1
network = self.safe_client.update_network(network['id'], mtu=mtu)
common_utils.wait_until_true(lambda: ri_dev.link.mtu == mtu)
def test_east_west_traffic(self): def test_east_west_traffic(self):
tenant_id = uuidutils.generate_uuid() tenant_id = uuidutils.generate_uuid()
router = self.safe_client.create_router(tenant_id) router = self.safe_client.create_router(tenant_id)

View File

@ -149,6 +149,7 @@ class BasicRouterOperationsFramework(base.BaseTestCase):
self.snat_ports = [{'subnets': [{'cidr': '152.2.0.0/16', self.snat_ports = [{'subnets': [{'cidr': '152.2.0.0/16',
'gateway_ip': '152.2.0.1', 'gateway_ip': '152.2.0.1',
'id': subnet_id_1}], 'id': subnet_id_1}],
'mtu': 1500,
'network_id': _uuid(), 'network_id': _uuid(),
'device_owner': 'device_owner':
lib_constants.DEVICE_OWNER_ROUTER_SNAT, lib_constants.DEVICE_OWNER_ROUTER_SNAT,
@ -160,6 +161,7 @@ class BasicRouterOperationsFramework(base.BaseTestCase):
{'subnets': [{'cidr': '152.10.0.0/16', {'subnets': [{'cidr': '152.10.0.0/16',
'gateway_ip': '152.10.0.1', 'gateway_ip': '152.10.0.1',
'id': subnet_id_2}], 'id': subnet_id_2}],
'mtu': 1450,
'network_id': _uuid(), 'network_id': _uuid(),
'device_owner': 'device_owner':
lib_constants.DEVICE_OWNER_ROUTER_SNAT, lib_constants.DEVICE_OWNER_ROUTER_SNAT,
@ -425,6 +427,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
port = {'network_id': _uuid(), port = {'network_id': _uuid(),
'id': _uuid(), 'id': _uuid(),
'mac_address': 'ca:fe:de:ad:be:ef', 'mac_address': 'ca:fe:de:ad:be:ef',
'mtu': 1500,
'fixed_ips': [{'subnet_id': _uuid(), 'fixed_ips': [{'subnet_id': _uuid(),
'ip_address': '99.0.1.9', 'ip_address': '99.0.1.9',
'prefixlen': 24}]} 'prefixlen': 24}]}
@ -459,6 +462,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
port = {'network_id': _uuid(), port = {'network_id': _uuid(),
'id': _uuid(), 'id': _uuid(),
'mac_address': 'ca:fe:de:ad:be:ef', 'mac_address': 'ca:fe:de:ad:be:ef',
'mtu': 1500,
'fixed_ips': [{'subnet_id': subnet_id, 'fixed_ips': [{'subnet_id': subnet_id,
'ip_address': '99.0.1.9', 'ip_address': '99.0.1.9',
'prefixlen': 24}], 'prefixlen': 24}],
@ -473,6 +477,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
'extra_subnets': [{'cidr': '172.16.0.0/24'}], 'extra_subnets': [{'cidr': '172.16.0.0/24'}],
'id': _uuid(), 'id': _uuid(),
'network_id': _uuid(), 'network_id': _uuid(),
'mtu': 1500,
'mac_address': 'ca:fe:de:ad:be:ef'} 'mac_address': 'ca:fe:de:ad:be:ef'}
ex_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30', ex_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30',
'prefixlen': 24, 'prefixlen': 24,
@ -482,6 +487,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
'id': _uuid(), 'id': _uuid(),
portbindings.HOST_ID: HOSTNAME, portbindings.HOST_ID: HOSTNAME,
'network_id': _uuid(), 'network_id': _uuid(),
'mtu': 1500,
'mac_address': 'ca:fe:de:ad:be:ef'} 'mac_address': 'ca:fe:de:ad:be:ef'}
ri.snat_ports = sn_port ri.snat_ports = sn_port
ri.ex_gw_port = ex_gw_port ri.ex_gw_port = ex_gw_port
@ -513,7 +519,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
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']),
lib_constants.SNAT_INT_DEV_PREFIX, lib_constants.SNAT_INT_DEV_PREFIX,
mtu=None) mtu=1500)
self.assertTrue(ri._check_if_address_scopes_match.called) self.assertTrue(ri._check_if_address_scopes_match.called)
if scope_match: if scope_match:
self.assertTrue( self.assertTrue(
@ -667,10 +673,12 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
'extra_subnets': [{'cidr': '172.16.0.0/24'}], 'extra_subnets': [{'cidr': '172.16.0.0/24'}],
'id': _uuid(), 'id': _uuid(),
'network_id': ex_net_id, 'network_id': ex_net_id,
'mtu': 1500,
'mac_address': 'ca:fe:de:ad:be:ef'} 'mac_address': 'ca:fe:de:ad:be:ef'}
ex_gw_port_no_sub = {'fixed_ips': [], ex_gw_port_no_sub = {'fixed_ips': [],
'id': _uuid(), 'id': _uuid(),
'network_id': ex_net_id, 'network_id': ex_net_id,
'mtu': 1500,
'mac_address': 'ca:fe:de:ad:be:ef'} 'mac_address': 'ca:fe:de:ad:be:ef'}
interface_name = ri.get_external_device_name(ex_gw_port['id']) interface_name = ri.get_external_device_name(ex_gw_port['id'])
@ -1159,6 +1167,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
'gateway_ip': '20.0.0.1'}], 'gateway_ip': '20.0.0.1'}],
'id': _uuid(), 'id': _uuid(),
'network_id': fake_network_id, 'network_id': fake_network_id,
'mtu': 1500,
'mac_address': 'ca:fe:de:ad:be:ef'}] 'mac_address': 'ca:fe:de:ad:be:ef'}]
) )
@ -1290,6 +1299,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
'gateway_ip': '20.0.0.1'}], 'gateway_ip': '20.0.0.1'}],
'id': _uuid(), 'id': _uuid(),
'network_id': fake_network_id, 'network_id': fake_network_id,
'mtu': 1500,
'mac_address': 'ca:fe:de:ad:be:ef'} 'mac_address': 'ca:fe:de:ad:be:ef'}
) )
@ -1336,6 +1346,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
'gateway_ip': '20.0.0.1'}], 'gateway_ip': '20.0.0.1'}],
'id': _uuid(), 'id': _uuid(),
'network_id': fake_network_id, 'network_id': fake_network_id,
'mtu': 1500,
'mac_address': 'ca:fe:de:ad:be:ef'}] 'mac_address': 'ca:fe:de:ad:be:ef'}]
) )
@ -1388,6 +1399,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
'gateway_ip': '20.0.0.1'}], 'gateway_ip': '20.0.0.1'}],
'id': _uuid(), 'id': _uuid(),
'network_id': 'fake_network_id', 'network_id': 'fake_network_id',
'mtu': 1500,
'mac_address': 'ca:fe:de:ad:be:ef'}] 'mac_address': 'ca:fe:de:ad:be:ef'}]
) )
@ -2588,6 +2600,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
'gateway_ip': '20.0.0.1'}], 'gateway_ip': '20.0.0.1'}],
'id': port_id, 'id': port_id,
'network_id': _uuid(), 'network_id': _uuid(),
'mtu': 1500,
'mac_address': 'ca:fe:de:ad:be:ef'} 'mac_address': 'ca:fe:de:ad:be:ef'}
interface_name = ri._get_snat_int_device_name(port_id) interface_name = ri._get_snat_int_device_name(port_id)