Browse Source

Merge "OVN to emit ICMP "fragmentation needed" packets"

tags/7.0.0.0b1
Zuul 1 week ago
parent
commit
ea14a7fde8

+ 13
- 0
networking_ovn/common/config.py View File

@@ -180,6 +180,15 @@ ovn_opts = [
180 180
                        "- ntp_server:,wpad:1.2.3.5 - Unset ntp_server and "
181 181
                        "set wpad\n"
182 182
                        "See the ovn-nb(5) man page for available options.")),
183
+    cfg.BoolOpt('ovn_emit_need_to_frag',
184
+                default=False,
185
+                help=_('Configure OVN to emit "need to frag" packets in '
186
+                       'case of MTU mismatch.\n'
187
+                       'Before enabling this configuration make sure that '
188
+                       'its supported by the host kernel (version >= 5.2) '
189
+                       'or by checking the output of the following command: \n'
190
+                       'ovs-appctl -t ovs-vswitchd dpif/show-dp-features '
191
+                       'br-int | grep "Check pkt length action".')),
183 192
 ]
184 193
 
185 194
 cfg.CONF.register_opts(ovn_opts, group='ovn')
@@ -288,3 +297,7 @@ def setup_logging():
288 297
              {'prog': sys.argv[0],
289 298
               'version': version.version_info.release_string()})
290 299
     LOG.debug("command line: %s", " ".join(sys.argv))
300
+
301
+
302
+def is_ovn_emit_need_to_frag_enabled():
303
+    return cfg.CONF.ovn.ovn_emit_need_to_frag

+ 1
- 0
networking_ovn/common/constants.py View File

@@ -53,6 +53,7 @@ OVN_GATEWAY_CHASSIS_KEY = 'redirect-chassis'
53 53
 OVN_CHASSIS_REDIRECT = 'chassisredirect'
54 54
 OVN_GATEWAY_NAT_ADDRESSES_KEY = 'nat-addresses'
55 55
 OVN_DROP_PORT_GROUP_NAME = 'neutron_pg_drop'
56
+OVN_ROUTER_PORT_GW_MTU_OPTION = 'gateway_mtu'
56 57
 
57 58
 OVN_PROVNET_PORT_NAME_PREFIX = 'provnet-'
58 59
 

+ 15
- 0
networking_ovn/common/maintenance.py View File

@@ -17,6 +17,7 @@ import inspect
17 17
 import threading
18 18
 
19 19
 from futurist import periodics
20
+from neutron_lib.api.definitions import external_net
20 21
 from neutron_lib import constants as n_const
21 22
 from neutron_lib import context as n_context
22 23
 from neutron_lib import exceptions as n_exc
@@ -435,6 +436,20 @@ class DBInconsistenciesPeriodics(object):
435 436
 
436 437
         raise periodics.NeverAgain()
437 438
 
439
+    # A static spacing value is used here, but this method will only run
440
+    # once per lock due to the use of periodics.NeverAgain().
441
+    @periodics.periodic(spacing=600, run_immediately=True)
442
+    def check_for_fragmentation_support(self):
443
+        if not self.has_lock:
444
+            return
445
+
446
+        context = n_context.get_admin_context()
447
+        for net in self._ovn_client._plugin.get_networks(
448
+                context, {external_net.EXTERNAL: [True]}):
449
+            self._ovn_client.set_gateway_mtu(context, net)
450
+
451
+        raise periodics.NeverAgain()
452
+
438 453
 
439 454
 class HashRingHealthCheckPeriodics(object):
440 455
 

+ 43
- 9
networking_ovn/common/ovn_client.py View File

@@ -1157,6 +1157,27 @@ class OVNClient(object):
1157 1157
 
1158 1158
         return ext_ids
1159 1159
 
1160
+    def _gen_router_port_options(self, port, network=None):
1161
+        options = {}
1162
+        if network is None:
1163
+            network = self._plugin.get_network(n_context.get_admin_context(),
1164
+                                               port['network_id'])
1165
+        # For VLAN type networks we need to set the
1166
+        # "reside-on-redirect-chassis" option so the routing for this
1167
+        # logical router port is centralized in the chassis hosting the
1168
+        # distributed gateway port.
1169
+        # https://github.com/openvswitch/ovs/commit/85706c34d53d4810f54bec1de662392a3c06a996
1170
+        if network.get(pnet.NETWORK_TYPE) == const.TYPE_VLAN:
1171
+            options['reside-on-redirect-chassis'] = 'true'
1172
+
1173
+        is_gw_port = const.DEVICE_OWNER_ROUTER_GW == port.get(
1174
+            'device_owner')
1175
+        if is_gw_port and config.is_ovn_emit_need_to_frag_enabled():
1176
+            options[ovn_const.OVN_ROUTER_PORT_GW_MTU_OPTION] = str(
1177
+                network['mtu'])
1178
+
1179
+        return options
1180
+
1160 1181
     def _create_lrouter_port(self, router_id, port, txn=None):
1161 1182
         """Create a logical router port."""
1162 1183
         lrouter = utils.ovn_name(router_id)
@@ -1167,17 +1188,11 @@ class OVNClient(object):
1167 1188
         is_gw_port = const.DEVICE_OWNER_ROUTER_GW == port.get(
1168 1189
             'device_owner')
1169 1190
         columns = {}
1170
-        port_net = self._plugin.get_network(n_context.get_admin_context(),
1171
-                                            port['network_id'])
1172
-        # For VLAN type networks we need to set the
1173
-        # "reside-on-redirect-chassis" option so the routing for this
1174
-        # logical router port is centralized in the chassis hosting the
1175
-        # distributed gateway port.
1176
-        # https://github.com/openvswitch/ovs/commit/85706c34d53d4810f54bec1de662392a3c06a996
1177
-        if port_net.get(pnet.NETWORK_TYPE) == const.TYPE_VLAN:
1178
-            columns['options'] = {'reside-on-redirect-chassis': 'true'}
1191
+        columns['options'] = self._gen_router_port_options(port)
1179 1192
 
1180 1193
         if is_gw_port:
1194
+            port_net = self._plugin.get_network(n_context.get_admin_context(),
1195
+                                                port['network_id'])
1181 1196
             physnet = self._get_physnet(port_net)
1182 1197
             candidates = self.get_candidates_for_scheduling(physnet)
1183 1198
             selected_chassis = self._ovn_scheduler.select(
@@ -1254,6 +1269,7 @@ class OVNClient(object):
1254 1269
             self._nb_idl.update_lrouter_port(
1255 1270
                 name=lrp_name,
1256 1271
                 external_ids=self._gen_router_port_ext_ids(port),
1272
+                options=self._gen_router_port_options(port),
1257 1273
                 if_exists=if_exists,
1258 1274
                 **update),
1259 1275
             self._nb_idl.set_lrouter_port_in_lswitch_port(
@@ -1429,6 +1445,21 @@ class OVNClient(object):
1429 1445
         return new_qos_id != ovn_net.external_ids[
1430 1446
             ovn_const.OVN_QOS_POLICY_EXT_ID_KEY]
1431 1447
 
1448
+    def set_gateway_mtu(self, context, prov_net, txn=None):
1449
+        ports = self._plugin.get_ports(
1450
+            context, filters=dict(network_id=[prov_net['id']],
1451
+                                  device_owner=[const.DEVICE_OWNER_ROUTER_GW]))
1452
+        commands = []
1453
+        for port in ports:
1454
+            lrp_name = utils.ovn_lrouter_port_name(port['id'])
1455
+            # TODO(lucasagomes): Use lrp_set_options() once
1456
+            # https://review.opendev.org/671765 is merged and a new version
1457
+            # of ovsdbapp is released
1458
+            options = self._gen_router_port_options(port, prov_net)
1459
+            commands.append(self._nb_idl.update_lrouter_port(
1460
+                            name=lrp_name, if_exists=True, options=options))
1461
+        self._transaction(commands, txn=txn)
1462
+
1432 1463
     def update_network(self, network):
1433 1464
         lswitch_name = utils.ovn_name(network['id'])
1434 1465
         # Check if QoS needs to be update, before updating OVNDB
@@ -1480,6 +1511,9 @@ class OVNClient(object):
1480 1511
                 for subnet in subnets:
1481 1512
                     self.update_subnet(subnet, network, txn)
1482 1513
 
1514
+                if utils.is_provider_network(network):
1515
+                    self.set_gateway_mtu(context, network, txn)
1516
+
1483 1517
         if check_rev_cmd.result == ovn_const.TXN_COMMITTED:
1484 1518
             if qos_update_required:
1485 1519
                 self._qos_driver.update_network(network)

+ 5
- 0
networking_ovn/common/utils.py View File

@@ -15,6 +15,7 @@ import os
15 15
 import re
16 16
 
17 17
 import netaddr
18
+from neutron_lib.api.definitions import external_net
18 19
 from neutron_lib.api.definitions import extra_dhcp_opt as edo_ext
19 20
 from neutron_lib.api.definitions import l3
20 21
 from neutron_lib.api.definitions import port_security as psec
@@ -414,3 +415,7 @@ def is_gateway_chassis_invalid(chassis_name, gw_chassis,
414 415
     elif gw_chassis and chassis_name not in gw_chassis:
415 416
         return True
416 417
     return False
418
+
419
+
420
+def is_provider_network(network):
421
+    return external_net.EXTERNAL in network

+ 50
- 2
networking_ovn/tests/unit/l3/test_l3_ovn.py View File

@@ -20,6 +20,7 @@ from neutron.tests.unit.api import test_extensions
20 20
 from neutron.tests.unit.extensions import test_extraroute
21 21
 from neutron.tests.unit.extensions import test_l3
22 22
 from neutron.tests.unit.extensions import test_l3_ext_gw_mode as test_l3_gw
23
+from neutron_lib.api.definitions import external_net
23 24
 from neutron_lib.api.definitions import portbindings
24 25
 from neutron_lib.api.definitions import provider_net as pnet
25 26
 from neutron_lib.callbacks import events
@@ -51,7 +52,7 @@ class OVNL3RouterPlugin(test_mech_driver.OVNMechanismDriverTestCase):
51 52
     def setUp(self):
52 53
         super(OVNL3RouterPlugin, self).setUp()
53 54
         revision_plugin.RevisionPlugin()
54
-        network_attrs = {'router:external': True}
55
+        network_attrs = {external_net.EXTERNAL: True, 'mtu': 1500}
55 56
         self.fake_network = \
56 57
             fakes.FakeNetwork.create_one_network(attrs=network_attrs).info()
57 58
         self.fake_router_port = {'device_id': '',
@@ -67,6 +68,7 @@ class OVNL3RouterPlugin(test_mech_driver.OVNMechanismDriverTestCase):
67 68
             'name': 'lrp-router-port-id',
68 69
             'may_exist': True,
69 70
             'networks': ['10.0.0.100/24'],
71
+            'options': {},
70 72
             'external_ids': {
71 73
                 ovn_const.OVN_SUBNET_EXT_IDS_KEY: 'subnet-id',
72 74
                 ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
@@ -128,7 +130,8 @@ class OVNL3RouterPlugin(test_mech_driver.OVNMechanismDriverTestCase):
128 130
                 ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
129 131
                 ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY:
130 132
                 utils.ovn_name(self.fake_network['id'])},
131
-            'gateway_chassis': ['hv1']}
133
+            'gateway_chassis': ['hv1'],
134
+            'options': {}}
132 135
         self.fake_floating_ip_attrs = {'floating_ip_address': '192.168.0.10',
133 136
                                        'fixed_ip_address': '10.0.0.10'}
134 137
         self.fake_floating_ip = fakes.FakeFloatingIp.create_one_fip(
@@ -291,6 +294,7 @@ class OVNL3RouterPlugin(test_mech_driver.OVNMechanismDriverTestCase):
291 294
             if_exists=False, name='lrp-router-port-id',
292 295
             ipv6_ra_configs={},
293 296
             networks=['10.0.0.100/24'],
297
+            options={},
294 298
             external_ids={
295 299
                 ovn_const.OVN_SUBNET_EXT_IDS_KEY: 'subnet-id',
296 300
                 ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
@@ -1274,6 +1278,50 @@ class OVNL3RouterPlugin(test_mech_driver.OVNMechanismDriverTestCase):
1274 1278
             mock.call('lrp-foo-3',
1275 1279
                       gateway_chassis=['chassis3', 'chassis2', 'chassis1'])])
1276 1280
 
1281
+    @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_network')
1282
+    @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_port')
1283
+    @mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_subnet')
1284
+    @mock.patch('networking_ovn.common.ovn_client.OVNClient._get_router_ports')
1285
+    @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.get_router')
1286
+    @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.add_router_interface')
1287
+    def test_add_router_interface_need_to_frag_enabled(self, ari, gr, grps,
1288
+                                                       gs, gp, gn):
1289
+
1290
+        config.cfg.CONF.set_override(
1291
+            'ovn_emit_need_to_frag', True, group='ovn')
1292
+        router_id = 'router-id'
1293
+        interface_info = {'port_id': 'router-port-id'}
1294
+        ari.return_value = self.fake_router_interface_info
1295
+        gr.return_value = self.fake_router_with_ext_gw
1296
+        gs.return_value = self.fake_subnet
1297
+        gn.return_value = self.fake_network
1298
+        self.fake_router_port['device_owner'] = (
1299
+            constants.DEVICE_OWNER_ROUTER_GW)
1300
+        gp.return_value = self.fake_router_port
1301
+
1302
+        self.l3_inst.add_router_interface(self.context, router_id,
1303
+                                          interface_info)
1304
+
1305
+        # Make sure that the "gateway_mtu" option was set to the router port
1306
+        fake_router_port_assert = self.fake_router_port_assert
1307
+        fake_router_port_assert['gateway_chassis'] = mock.ANY
1308
+        fake_router_port_assert['options'] = {
1309
+            ovn_const.OVN_ROUTER_PORT_GW_MTU_OPTION:
1310
+            str(self.fake_network['mtu'])}
1311
+
1312
+        self.l3_inst._ovn.add_lrouter_port.assert_called_once_with(
1313
+            **fake_router_port_assert)
1314
+        self.l3_inst._ovn.set_lrouter_port_in_lswitch_port.\
1315
+            assert_called_once_with(
1316
+                'router-port-id', 'lrp-router-port-id', is_gw_port=True,
1317
+                lsp_address=ovn_const.DEFAULT_ADDR_FOR_LSP_WITH_PEER)
1318
+        self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with(
1319
+            'neutron-router-id', logical_ip='10.0.0.0/24',
1320
+            external_ip='192.168.1.1', type='snat')
1321
+
1322
+        self.bump_rev_p.assert_called_with(self.fake_router_port,
1323
+                                           ovn_const.TYPE_ROUTER_PORTS)
1324
+
1277 1325
 
1278 1326
 class OVNL3ExtrarouteTests(test_l3_gw.ExtGwModeIntTestCase,
1279 1327
                            test_l3.L3NatDBIntTestCase,

+ 38
- 0
networking_ovn/tests/unit/ml2/test_mech_driver.py View File

@@ -25,6 +25,7 @@ from neutron.tests.unit.extensions import test_segment
25 25
 from neutron.tests.unit.plugins.ml2 import test_ext_portsecurity
26 26
 from neutron.tests.unit.plugins.ml2 import test_plugin
27 27
 from neutron.tests.unit.plugins.ml2 import test_security_group
28
+from neutron_lib.api.definitions import external_net
28 29
 from neutron_lib.api.definitions import portbindings
29 30
 from neutron_lib.api.definitions import provider_net as pnet
30 31
 from neutron_lib.callbacks import events
@@ -1624,6 +1625,43 @@ class TestOVNMechanismDriver(test_plugin.Ml2PluginV2TestCase):
1624 1625
     def test__update_dnat_entry_if_needed_down(self):
1625 1626
         self._test__update_dnat_entry_if_needed(up=False)
1626 1627
 
1628
+    def _test_update_network_fragmentation(self, new_mtu, expected_opts):
1629
+        network_attrs = {external_net.EXTERNAL: True}
1630
+        network = self._make_network(
1631
+            self.fmt, 'net1', True, arg_list=(external_net.EXTERNAL,),
1632
+            **network_attrs)
1633
+
1634
+        with self.subnet(network=network) as subnet:
1635
+            with self.port(subnet=subnet,
1636
+                           device_owner=const.DEVICE_OWNER_ROUTER_GW) as port:
1637
+                # Let's update the MTU to something different
1638
+                network['network']['mtu'] = new_mtu
1639
+                fake_ctx = mock.Mock(current=network['network'])
1640
+                fake_ctx._plugin_context.session.is_active = False
1641
+
1642
+                self.mech_driver.update_network_postcommit(fake_ctx)
1643
+
1644
+                lrp_name = ovn_utils.ovn_lrouter_port_name(port['port']['id'])
1645
+                self.nb_ovn.update_lrouter_port.assert_called_once_with(
1646
+                    if_exists=True, name=lrp_name, options=expected_opts)
1647
+
1648
+    def test_update_network_need_to_frag_enabled(self):
1649
+        ovn_config.cfg.CONF.set_override(
1650
+            'ovn_emit_need_to_frag', True, group='ovn')
1651
+        new_mtu = 1234
1652
+        expected_opts = {ovn_const.OVN_ROUTER_PORT_GW_MTU_OPTION:
1653
+                         str(new_mtu)}
1654
+        self._test_update_network_fragmentation(new_mtu, expected_opts)
1655
+
1656
+    def test_update_network_need_to_frag_disabled(self):
1657
+        ovn_config.cfg.CONF.set_override(
1658
+            'ovn_emit_need_to_frag', False, group='ovn')
1659
+        new_mtu = 1234
1660
+        # Assert that the options column is empty (cleaning up an '
1661
+        # existing value if set before)
1662
+        expected_opts = {}
1663
+        self._test_update_network_fragmentation(new_mtu, expected_opts)
1664
+
1627 1665
 
1628 1666
 class OVNMechanismDriverTestCase(test_plugin.Ml2PluginV2TestCase):
1629 1667
     _mechanism_drivers = ['logger', 'ovn']

+ 11
- 0
releasenotes/notes/fragmentation-support-2860870dc7b8bb6b.yaml View File

@@ -0,0 +1,11 @@
1
+---
2
+features:
3
+  - |
4
+    Adds a new configuration option called ``ovn_emit_need_to_frag``
5
+    which defaults to False. When enabled, networking-ovn will configure
6
+    OVN into emitting ICMP "need to frag" packets whenever needed. By
7
+    default this option is disabled because it requires a newer kernel
8
+    version for it to work (upstream >= 5.2). It's also possible to find
9
+    out if it's supported by the host by running the following command
10
+    as root: ``ovs-appctl -t ovs-vswitchd dpif/show-dp-features br-int``
11
+    (check for "Check pkt length action: Yes").

Loading…
Cancel
Save