Add support for localnet_learn_fdb OVN option

In OVN 22.09, the option "localnet_learn_fdb" was added so that
localnet ports can learn MAC addresses and store them in the FDB
table. This avoids flooding issues for VMs on provider networks
when port security is disabled

Closes-Bug: #2012069
Change-Id: I93574b4fe9a79b649bfe755cf7e0697ccc7eb83a
This commit is contained in:
Luis Tomas Bolivar 2023-03-17 15:59:05 +01:00
parent b1ca854791
commit 7dfbdf65a7
9 changed files with 197 additions and 4 deletions

View File

@ -53,6 +53,7 @@ OVN_NB_DB_SCHEMA_GATEWAY_CHASSIS = '5.7'
OVN_NB_DB_SCHEMA_PORT_GROUP = '5.11'
OVN_NB_DB_SCHEMA_STATELESS_NAT = '5.17'
OVN_SB_DB_SCHEMA_VIRTUAL_PORT = '2.5'
OVN_LOCALNET_LEARN_FDB = '22.09'
class OVNCheckType(enum.Enum):
@ -657,3 +658,16 @@ def ovn_nb_db_schema_gateway_chassis_supported():
'Exception: %s', e)
return False
return True
def ovn_localnet_learn_fdb_support():
try:
ver = _get_ovn_version(OVNCheckType.nb_version)
minver = versionutils.convert_version_to_tuple(OVN_LOCALNET_LEARN_FDB)
if ver < minver:
return False
except (OSError, RuntimeError, ValueError) as e:
LOG.debug('Exception while checking OVN version. '
'Exception: %s', e)
return False
return True

View File

@ -347,6 +347,14 @@ def check_ovn_nb_db_schema_gateway_chassis():
return result
def check_ovn_localnet_learn_fdb_support():
result = checks.ovn_localnet_learn_fdb_support()
if not result:
LOG.warning('OVN does not support localnet_learn_fdb option. '
'This support was added in OVN 22.09.')
return result
# Define CLI opts to test specific features, with a callback for the test
OPTS = [
BoolOptCallback('ovs_vxlan', check_ovs_vxlan, default=False,
@ -431,6 +439,10 @@ OPTS = [
check_ovn_nb_db_schema_gateway_chassis,
help=_('Check OVN NB DB schema support Gateway_Chassis'),
default=False),
BoolOptCallback('ovn_localnet_learn_fdb_support',
check_ovn_localnet_learn_fdb_support,
help=_('Check OVN supports localnet_learn_fdb option'),
default=False),
]

View File

@ -391,6 +391,7 @@ LSP_OPTIONS_REQUESTED_CHASSIS_KEY = 'requested-chassis'
LSP_OPTIONS_MCAST_FLOOD_REPORTS = 'mcast_flood_reports'
LSP_OPTIONS_MCAST_FLOOD = 'mcast_flood'
LSP_OPTIONS_QOS_MIN_RATE = 'qos_min_rate'
LSP_OPTIONS_LOCALNET_LEARN_FDB = 'localnet_learn_fdb'
LRP_OPTIONS_RESIDE_REDIR_CH = 'reside-on-redirect-chassis'
LRP_OPTIONS_REDIRECT_TYPE = 'redirect-type'

View File

@ -217,6 +217,13 @@ ovn_opts = [
'order to disable the ``stateful-security-group`` API '
'extension as ``allow-stateless`` keyword is only '
'supported by OVN >= 21.06.')),
cfg.BoolOpt('localnet_learn_fdb',
default=False,
help=_('If enabled it will allow localnet ports to learn MAC '
'addresses and store them in FDB SB table. This avoids '
'flooding for traffic towards unknown IPs when port '
'security is disabled. It requires OVN 22.09 or '
'newer.')),
]
@ -330,3 +337,7 @@ def is_igmp_snooping_enabled():
def is_ovn_dhcp_disabled_for_baremetal():
return cfg.CONF.ovn.disable_ovn_dhcp_for_baremetal_ports
def is_learn_fdb_enabled():
return cfg.CONF.ovn.localnet_learn_fdb

View File

@ -650,6 +650,36 @@ class DBInconsistenciesPeriodics(SchemaAwarePeriodicsBase):
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().
@periodics.periodic(spacing=600, run_immediately=True)
def check_localnet_port_has_learn_fdb(self):
if not self.has_lock:
return
ports = self._nb_idl.db_find_rows(
"Logical_Switch_Port", ("type", "=", ovn_const.LSP_TYPE_LOCALNET)
).execute(check_error=True)
with self._nb_idl.transaction(check_error=True) as txn:
for port in ports:
if ovn_conf.is_learn_fdb_enabled():
fdb_opt = port.options.get(
ovn_const.LSP_OPTIONS_LOCALNET_LEARN_FDB)
if not fdb_opt or fdb_opt == 'false':
txn.add(self._nb_idl.db_set(
'Logical_Switch_Port', port.name,
('options',
{ovn_const.LSP_OPTIONS_LOCALNET_LEARN_FDB: 'true'}
)))
elif port.options.get(
ovn_const.LSP_OPTIONS_LOCALNET_LEARN_FDB) == 'true':
txn.add(self._nb_idl.db_set(
'Logical_Switch_Port', port.name,
('options',
{ovn_const.LSP_OPTIONS_LOCALNET_LEARN_FDB: 'false'})))
raise periodics.NeverAgain()
# TODO(lucasagomes): Remove this in the Z cycle
# A static spacing value is used here, but this method will only run
# once per lock due to the use of periodics.NeverAgain().

View File

@ -1914,9 +1914,12 @@ class OVNClient(object):
def create_provnet_port(self, network_id, segment, txn=None):
tag = segment.get(segment_def.SEGMENTATION_ID, [])
physnet = segment.get(segment_def.PHYSICAL_NETWORK)
fdb_enabled = ('true' if ovn_conf.is_learn_fdb_enabled()
else 'false')
options = {'network_name': physnet,
ovn_const.LSP_OPTIONS_MCAST_FLOOD_REPORTS: 'true',
ovn_const.LSP_OPTIONS_MCAST_FLOOD: 'false'}
ovn_const.LSP_OPTIONS_MCAST_FLOOD: 'false',
ovn_const.LSP_OPTIONS_LOCALNET_LEARN_FDB: fdb_enabled}
cmd = self._nb_idl.create_lswitch_port(
lport_name=utils.ovn_provnet_port_name(segment['id']),
lswitch_name=utils.ovn_name(network_id),

View File

@ -586,6 +586,102 @@ class TestDBInconsistenciesPeriodics(testlib_api.SqlTestCaseLight,
nb_idl.lsp_set_options.assert_has_calls(expected_calls)
def test_check_localnet_port_has_learn_fdb(self):
cfg.CONF.set_override('localnet_learn_fdb', 'True',
group='ovn')
nb_idl = self.fake_ovn_client._nb_idl
# Already has the learn fdb option enabled
lsp0 = fakes.FakeOvsdbRow.create_one_ovsdb_row(
attrs={
"name": "lsp0",
"options": {
constants.LSP_OPTIONS_LOCALNET_LEARN_FDB: "true",
},
}
)
# learn fdb option missing, needs update
lsp1 = fakes.FakeOvsdbRow.create_one_ovsdb_row(
attrs={
"name": "lsp1",
"options": {},
}
)
# learn fdb option set to false, needs update
lsp2 = fakes.FakeOvsdbRow.create_one_ovsdb_row(
attrs={
"name": "lsp2",
"options": {
constants.LSP_OPTIONS_LOCALNET_LEARN_FDB: "false",
},
}
)
nb_idl.db_find_rows.return_value.execute.return_value = [
lsp0,
lsp1,
lsp2,
]
self.assertRaises(
periodics.NeverAgain,
self.periodic.check_localnet_port_has_learn_fdb)
options = {constants.LSP_OPTIONS_LOCALNET_LEARN_FDB: 'true'}
expected_calls = [mock.call('Logical_Switch_Port', 'lsp1',
('options', options)),
mock.call('Logical_Switch_Port', 'lsp2',
('options', options))]
nb_idl.db_set.assert_has_calls(expected_calls)
def test_check_localnet_port_has_learn_fdb_disabled(self):
nb_idl = self.fake_ovn_client._nb_idl
# learn fdb option enabled, needs update
lsp0 = fakes.FakeOvsdbRow.create_one_ovsdb_row(
attrs={
"name": "lsp0",
"options": {
constants.LSP_OPTIONS_LOCALNET_LEARN_FDB: "true",
},
}
)
# learn fdb option missing, no update needed
lsp1 = fakes.FakeOvsdbRow.create_one_ovsdb_row(
attrs={
"name": "lsp1",
"options": {},
}
)
# learn fdb option set to false, no update needed
lsp2 = fakes.FakeOvsdbRow.create_one_ovsdb_row(
attrs={
"name": "lsp2",
"options": {
constants.LSP_OPTIONS_LOCALNET_LEARN_FDB: "false",
},
}
)
nb_idl.db_find_rows.return_value.execute.return_value = [
lsp0,
lsp1,
lsp2,
]
self.assertRaises(
periodics.NeverAgain,
self.periodic.check_localnet_port_has_learn_fdb)
options = {constants.LSP_OPTIONS_LOCALNET_LEARN_FDB: 'false'}
expected_calls = [mock.call('Logical_Switch_Port', 'lsp0',
('options', options))]
nb_idl.db_set.assert_has_calls(expected_calls)
def test_check_router_mac_binding_options(self):
nb_idl = self.fake_ovn_client._nb_idl
lr0 = fakes.FakeOvsdbRow.create_one_ovsdb_row(

View File

@ -877,7 +877,8 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase):
lswitch_name=ovn_utils.ovn_name(net['id']),
options={'network_name': 'physnet1',
ovn_const.LSP_OPTIONS_MCAST_FLOOD_REPORTS: 'true',
ovn_const.LSP_OPTIONS_MCAST_FLOOD: 'false'},
ovn_const.LSP_OPTIONS_MCAST_FLOOD: 'false',
ovn_const.LSP_OPTIONS_LOCALNET_LEARN_FDB: 'false'},
tag=2,
type='localnet')
@ -2949,7 +2950,8 @@ class TestOVNMechanismDriverSegment(MechDriverSetupBase,
lswitch_name=ovn_utils.ovn_name(net['id']),
options={'network_name': 'physnet1',
ovn_const.LSP_OPTIONS_MCAST_FLOOD_REPORTS: 'true',
ovn_const.LSP_OPTIONS_MCAST_FLOOD: 'false'},
ovn_const.LSP_OPTIONS_MCAST_FLOOD: 'false',
ovn_const.LSP_OPTIONS_LOCALNET_LEARN_FDB: 'false'},
tag=200,
type='localnet')
ovn_nb_api.create_lswitch_port.reset_mock()
@ -2963,7 +2965,8 @@ class TestOVNMechanismDriverSegment(MechDriverSetupBase,
lswitch_name=ovn_utils.ovn_name(net['id']),
options={'network_name': 'physnet2',
ovn_const.LSP_OPTIONS_MCAST_FLOOD_REPORTS: 'true',
ovn_const.LSP_OPTIONS_MCAST_FLOOD: 'false'},
ovn_const.LSP_OPTIONS_MCAST_FLOOD: 'false',
ovn_const.LSP_OPTIONS_LOCALNET_LEARN_FDB: 'false'},
tag=300,
type='localnet')
segments = segments_db.get_network_segments(

View File

@ -0,0 +1,23 @@
---
issues:
- |
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 is no aging mechanism for those MACs (that is the reason for not
having this option enabled by default) and therefore it needs to be used
with care, specially when provider networks are big. It is recommended to
perform periodic manual cleanups of FDB table, to avoid scalability
issues -- until OVN implements an aging mechanism for this, tracked at
https://bugzilla.redhat.com/show_bug.cgi?id=2179942.
fixes:
- |
By default localnet ports don't learn MAC addresses and therefore they are
not stored in the FDB table at OVN SB DB. This leads to flooding issues
when the destination traffic is an unknown IP by OpenStack. In OVN 22.09
the option "localnet_learn_fdb" was added, enabling those ports to learn
MAC addresses and store them at the FDB table. Note there is no aging
mechanism for those MACs, thus this is not enabled by default and needs
to be used carefully, specially when provider networks are big, and/or
performing manual cleanup of FDB table over time to avoid scalability
issues, until OVN implements it at
https://bugzilla.redhat.com/show_bug.cgi?id=2179942.