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:
parent
55c20cdf1a
commit
1e9f50c736
@ -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'
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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.
|
||||||
|
@ -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):
|
||||||
|
@ -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(
|
||||||
|
@ -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.
|
||||||
|
14
releasenotes/notes/support-fdb-aging-b9ab82d75db81bbc.yaml
Normal file
14
releasenotes/notes/support-fdb-aging-b9ab82d75db81bbc.yaml
Normal 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.
|
Loading…
Reference in New Issue
Block a user