From 1e9f50c73638171403b71d742321464dcd5ef7ed Mon Sep 17 00:00:00 2001 From: Luis Tomas Bolivar Date: Fri, 8 Sep 2023 10:40:32 +0200 Subject: [PATCH] Add support for FDB aging In [1] we added support for FDB learning. In order to avoid issues due to that table increasing without limits, which will impact OVN performance, this patch is adding support for its aging mechanisms which was added in OVN 23.09 in [2]. By default is disabled, so if `localnet_learn_fdb` is enabled, the new configuration parameters should be appropriately configured too: `fdb_age_threshold` and `fdb_removal_limit` [1] https://review.opendev.org/c/openstack/neutron/+/877675 [2] https://github.com/ovn-org/ovn/commit/ae9a5488824c49e25215b02e7e81a62eb4d0bd53 Closes-Bug: 2035325 Change-Id: Ifdfaec35cc6b52040487a2b5ee08aba9282fc68b --- neutron/common/ovn/constants.py | 3 ++ .../conf/plugins/ml2/drivers/ovn/ovn_conf.py | 22 +++++++++++++ .../ovn/mech_driver/ovsdb/maintenance.py | 33 +++++++++++++++++++ .../ovn/mech_driver/ovsdb/ovn_client.py | 3 ++ .../ovn/mech_driver/ovsdb/test_maintenance.py | 29 ++++++++++++++-- .../ovn/mech_driver/ovsdb/test_maintenance.py | 31 +++++++++++++++++ .../support-fdb-aging-b9ab82d75db81bbc.yaml | 14 ++++++++ 7 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/support-fdb-aging-b9ab82d75db81bbc.yaml diff --git a/neutron/common/ovn/constants.py b/neutron/common/ovn/constants.py index 3de372b2f7b..9f04d75f94f 100644 --- a/neutron/common/ovn/constants.py +++ b/neutron/common/ovn/constants.py @@ -402,6 +402,9 @@ LRP_OPTIONS_RESIDE_REDIR_CH = 'reside-on-redirect-chassis' LRP_OPTIONS_REDIRECT_TYPE = 'redirect-type' BRIDGE_REDIRECT_TYPE = "bridged" +# FDB AGE Settings +LS_OPTIONS_FDB_AGE_THRESHOLD = 'fdb_age_threshold' + # Port Binding types PB_TYPE_VIRTUAL = 'virtual' diff --git a/neutron/conf/plugins/ml2/drivers/ovn/ovn_conf.py b/neutron/conf/plugins/ml2/drivers/ovn/ovn_conf.py index 290dd4dce05..6265920af1c 100644 --- a/neutron/conf/plugins/ml2/drivers/ovn/ovn_conf.py +++ b/neutron/conf/plugins/ml2/drivers/ovn/ovn_conf.py @@ -226,6 +226,12 @@ ovn_opts = [ 'flooding for traffic towards unknown IPs when port ' 'security is disabled. It requires OVN 22.09 or ' 'newer.')), + cfg.IntOpt('fdb_age_threshold', + min=0, + default=0, + help=_('The number of seconds to keep FDB entries in the OVN ' + 'DB. The value defaults to 0, which means disabled. ' + 'This is supported by OVN >= 23.09.')), ] nb_global_opts = [ @@ -241,6 +247,14 @@ nb_global_opts = [ 'is not an issue, setting it to True can reduce ' 'the load and latency of the control plane. ' 'The default value is False.')), + cfg.IntOpt('fdb_removal_limit', + min=0, + default=0, + help=_('FDB aging bulk removal limit. This limits how many ' + 'rows can expire in a single transaction. Default ' + 'is 0, which is unlimited. When the limit is reached, ' + 'the next batch removal is delayed by 5 seconds. ' + 'This is supported by OVN >= 23.09.')), ] @@ -360,3 +374,11 @@ def is_ovn_dhcp_disabled_for_baremetal(): def is_learn_fdb_enabled(): return cfg.CONF.ovn.localnet_learn_fdb + + +def get_fdb_age_threshold(): + return str(cfg.CONF.ovn.fdb_age_threshold) + + +def get_fdb_removal_limit(): + return str(cfg.CONF.ovn_nb_global.fdb_removal_limit) diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py index b639af08713..69ed63ef96f 100644 --- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py +++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py @@ -798,6 +798,39 @@ class DBInconsistenciesPeriodics(SchemaAwarePeriodicsBase): txn.add(cmd) raise periodics.NeverAgain() + # A static spacing value is used here, but this method will only run + # once per lock due to the use of periodics.NeverAgain(). + @has_lock_periodic(spacing=600, run_immediately=True) + def check_fdb_aging_settings(self): + """Check FDB aging settings + Ensure FDB aging settings are enforced. + """ + context = n_context.get_admin_context() + cmds = [] + + config_fdb_age_threshold = ovn_conf.get_fdb_age_threshold() + # Get provider networks + nets = self._ovn_client._plugin.get_networks(context) + for net in nets: + if not utils.is_provider_network(net): + continue + ls_name = utils.ovn_name(net['id']) + ls = self._nb_idl.get_lswitch(ls_name) + ls_fdb_age_threshold = ls.other_config.get( + ovn_const.LS_OPTIONS_FDB_AGE_THRESHOLD) + + if config_fdb_age_threshold != ls_fdb_age_threshold: + other_config = {ovn_const.LS_OPTIONS_FDB_AGE_THRESHOLD: + config_fdb_age_threshold} + cmds.append(self._nb_idl.db_set( + 'Logical_Switch', ls_name, + ('other_config', other_config))) + if cmds: + with self._nb_idl.transaction(check_error=True) as txn: + for cmd in cmds: + txn.add(cmd) + raise periodics.NeverAgain() + # TODO(fnordahl): Remove this in the B+3 cycle. This method removes the # now redundant "external_ids:OVN_GW_NETWORK_EXT_ID_KEY" and # "external_ids:OVN_GW_PORT_EXT_ID_KEY" from to each router. diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py index 9e2bde5497c..34045673b66 100644 --- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py +++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py @@ -1914,6 +1914,9 @@ class OVNClient(object): params['other_config'] = {ovn_const.MCAST_SNOOP: value, ovn_const.MCAST_FLOOD_UNREGISTERED: 'false', ovn_const.VLAN_PASSTHRU: vlan_transparent} + if utils.is_provider_network(network): + params['other_config'][ovn_const.LS_OPTIONS_FDB_AGE_THRESHOLD] = ( + ovn_conf.get_fdb_age_threshold()) return params def create_network(self, context, network): diff --git a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py index 20129fafb99..eadc9383b82 100644 --- a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py +++ b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py @@ -20,6 +20,7 @@ from oslo_config import cfg from futurist import periodics from neutron_lib.api.definitions import external_net as extnet_apidef from neutron_lib.api.definitions import floating_ip_port_forwarding as pf_def +from neutron_lib.api.definitions import provider_net as provnet_apidef from neutron_lib import constants as n_const from neutron_lib import context as n_context from neutron_lib.exceptions import l3 as lib_l3_exc @@ -60,8 +61,12 @@ class _TestMaintenanceHelper(base.TestOVNFunctionalBase): ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY) == name): return row - def _create_network(self, name, external=False): - data = {'network': {'name': name, extnet_apidef.EXTERNAL: external}} + def _create_network(self, name, external=False, provider=None): + data = {'network': {'name': name, + extnet_apidef.EXTERNAL: external}} + if provider: + data['network'][provnet_apidef.NETWORK_TYPE] = 'flat' + data['network'][provnet_apidef.PHYSICAL_NETWORK] = provider req = self.new_create_request('networks', data, self.fmt, as_admin=True) res = req.get_response(self.api) @@ -756,6 +761,26 @@ class TestMaintenance(_TestMaintenanceHelper): self.assertEqual( 'false', ls['other_config'][ovn_const.MCAST_FLOOD_UNREGISTERED]) + def test_check_for_aging_settings(self): + net = self._create_network('net', provider='datacentre') + ls = self.nb_api.get_lswitch(utils.ovn_name(net['id'])) + + self.assertEqual( + '0', ls.other_config.get(ovn_const.LS_OPTIONS_FDB_AGE_THRESHOLD)) + + # Change the value of the configuration + cfg.CONF.set_override('fdb_age_threshold', 5, group='ovn') + + # Call the maintenance task and check that the value has been + # updated in the Logical Switch + self.assertRaises(periodics.NeverAgain, + self.maint.check_fdb_aging_settings) + + ls = self.nb_api.get_lswitch(utils.ovn_name(net['id'])) + + self.assertEqual( + '5', ls.other_config.get(ovn_const.LS_OPTIONS_FDB_AGE_THRESHOLD)) + def test_floating_ip(self): ext_net = self._create_network('ext_networktest', external=True) ext_subnet = self._create_subnet( diff --git a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py index 0788576f105..f73a66c325a 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py +++ b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py @@ -891,6 +891,37 @@ class TestDBInconsistenciesPeriodics(testlib_api.SqlTestCaseLight, self.fake_ovn_client._nb_idl.db_set.assert_has_calls( expected_calls) + def test_check_fdb_aging_settings(self): + cfg.CONF.set_override('fdb_age_threshold', 5, group='ovn') + networks = [{'id': 'foo', + 'provider:physical_network': 'datacentre'}] + self.fake_ovn_client._plugin.get_networks.return_value = networks + fake_ls = mock.Mock(other_config={}) + self.fake_ovn_client._nb_idl.get_lswitch.return_value = fake_ls + + self.assertRaises( + periodics.NeverAgain, + self.periodic.check_fdb_aging_settings) + + self.fake_ovn_client._nb_idl.db_set.assert_called_once_with( + 'Logical_Switch', 'neutron-foo', + ('other_config', {constants.LS_OPTIONS_FDB_AGE_THRESHOLD: '5'})) + + def test_check_fdb_aging_settings_with_threshold_set(self): + cfg.CONF.set_override('fdb_age_threshold', 5, group='ovn') + networks = [{'id': 'foo', + 'provider:network_type': n_const.TYPE_VLAN}] + self.fake_ovn_client._plugin.get_networks.return_value = networks + fake_ls = mock.Mock(other_config={ + constants.LS_OPTIONS_FDB_AGE_THRESHOLD: '5'}) + self.fake_ovn_client._nb_idl.get_lswitch.return_value = fake_ls + + self.assertRaises( + periodics.NeverAgain, + self.periodic.check_fdb_aging_settings) + + self.fake_ovn_client._nb_idl.db_set.assert_not_called() + def test_remove_gw_ext_ids_from_logical_router(self): nb_idl = self.fake_ovn_client._nb_idl # lr0: GW port ID, not GW network ID --> we need to remove port ID. diff --git a/releasenotes/notes/support-fdb-aging-b9ab82d75db81bbc.yaml b/releasenotes/notes/support-fdb-aging-b9ab82d75db81bbc.yaml new file mode 100644 index 00000000000..2855046e534 --- /dev/null +++ b/releasenotes/notes/support-fdb-aging-b9ab82d75db81bbc.yaml @@ -0,0 +1,14 @@ +--- +features: + - | + In OVN 22.09 the option ``localnet_learn_fdb`` was added, enabling localnet + ports to learn MAC addresses and store them at the FDB table. There was no + aging mechanism for those MACs until OVN 23.06, where the configuration + option ``fdb_age_threshold`` was added. This enables to set the maximum + time the learned MACs will stay in the FDB table (in seconds). When the + ``localnet_learn_fdb`` configuration option is enabled, the proper value + for ``fdb_age_threshold`` should also be set, to avoid + performance/scalability issues due to the table growing too much -- + especially when provider networks are large. In addition the configuration + option ``fdb_removal_limit`` was also added to avoid removing a large + number of entries at once.