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] ae9a548882

Closes-Bug: 2035325

Change-Id: Ifdfaec35cc6b52040487a2b5ee08aba9282fc68b
This commit is contained in:
Luis Tomas Bolivar 2023-09-08 10:40:32 +02:00
parent 55c20cdf1a
commit 1e9f50c736
7 changed files with 133 additions and 2 deletions

View File

@ -402,6 +402,9 @@ LRP_OPTIONS_RESIDE_REDIR_CH = 'reside-on-redirect-chassis'
LRP_OPTIONS_REDIRECT_TYPE = 'redirect-type' LRP_OPTIONS_REDIRECT_TYPE = 'redirect-type'
BRIDGE_REDIRECT_TYPE = "bridged" BRIDGE_REDIRECT_TYPE = "bridged"
# FDB AGE Settings
LS_OPTIONS_FDB_AGE_THRESHOLD = 'fdb_age_threshold'
# Port Binding types # Port Binding types
PB_TYPE_VIRTUAL = 'virtual' PB_TYPE_VIRTUAL = 'virtual'

View File

@ -226,6 +226,12 @@ ovn_opts = [
'flooding for traffic towards unknown IPs when port ' 'flooding for traffic towards unknown IPs when port '
'security is disabled. It requires OVN 22.09 or ' 'security is disabled. It requires OVN 22.09 or '
'newer.')), '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 = [ nb_global_opts = [
@ -241,6 +247,14 @@ nb_global_opts = [
'is not an issue, setting it to True can reduce ' 'is not an issue, setting it to True can reduce '
'the load and latency of the control plane. ' 'the load and latency of the control plane. '
'The default value is False.')), '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(): def is_learn_fdb_enabled():
return cfg.CONF.ovn.localnet_learn_fdb 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)

View File

@ -798,6 +798,39 @@ class DBInconsistenciesPeriodics(SchemaAwarePeriodicsBase):
txn.add(cmd) txn.add(cmd)
raise periodics.NeverAgain() 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 # TODO(fnordahl): Remove this in the B+3 cycle. This method removes the
# now redundant "external_ids:OVN_GW_NETWORK_EXT_ID_KEY" and # now redundant "external_ids:OVN_GW_NETWORK_EXT_ID_KEY" and
# "external_ids:OVN_GW_PORT_EXT_ID_KEY" from to each router. # "external_ids:OVN_GW_PORT_EXT_ID_KEY" from to each router.

View File

@ -1914,6 +1914,9 @@ class OVNClient(object):
params['other_config'] = {ovn_const.MCAST_SNOOP: value, params['other_config'] = {ovn_const.MCAST_SNOOP: value,
ovn_const.MCAST_FLOOD_UNREGISTERED: 'false', ovn_const.MCAST_FLOOD_UNREGISTERED: 'false',
ovn_const.VLAN_PASSTHRU: vlan_transparent} 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 return params
def create_network(self, context, network): def create_network(self, context, network):

View File

@ -20,6 +20,7 @@ from oslo_config import cfg
from futurist import periodics from futurist import periodics
from neutron_lib.api.definitions import external_net as extnet_apidef 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 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 constants as n_const
from neutron_lib import context as n_context from neutron_lib import context as n_context
from neutron_lib.exceptions import l3 as lib_l3_exc 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): ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY) == name):
return row return row
def _create_network(self, name, external=False): def _create_network(self, name, external=False, provider=None):
data = {'network': {'name': name, extnet_apidef.EXTERNAL: external}} 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, req = self.new_create_request('networks', data, self.fmt,
as_admin=True) as_admin=True)
res = req.get_response(self.api) res = req.get_response(self.api)
@ -756,6 +761,26 @@ class TestMaintenance(_TestMaintenanceHelper):
self.assertEqual( self.assertEqual(
'false', ls['other_config'][ovn_const.MCAST_FLOOD_UNREGISTERED]) '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): def test_floating_ip(self):
ext_net = self._create_network('ext_networktest', external=True) ext_net = self._create_network('ext_networktest', external=True)
ext_subnet = self._create_subnet( ext_subnet = self._create_subnet(

View File

@ -891,6 +891,37 @@ class TestDBInconsistenciesPeriodics(testlib_api.SqlTestCaseLight,
self.fake_ovn_client._nb_idl.db_set.assert_has_calls( self.fake_ovn_client._nb_idl.db_set.assert_has_calls(
expected_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): def test_remove_gw_ext_ids_from_logical_router(self):
nb_idl = self.fake_ovn_client._nb_idl nb_idl = self.fake_ovn_client._nb_idl
# lr0: GW port ID, not GW network ID --> we need to remove port ID. # lr0: GW port ID, not GW network ID --> we need to remove port ID.

View File

@ -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.