Merge "ovn: use stateless NAT rules for FIPs"

This commit is contained in:
Zuul
2021-09-24 09:44:13 +00:00
committed by Gerrit Code Review
8 changed files with 122 additions and 13 deletions

View File

@@ -293,6 +293,14 @@ class OvsdbNbOvnIdl(nb_impl_idl.OvnNbApiIdlImpl, Backend):
raise RuntimeError(_("Currently only supports "
"delete by lport-name"))
def get_all_stateful_fip_nats(self):
cmd = self.db_find('NAT',
('external_ids', '!=', {ovn_const.OVN_FIP_EXT_ID_KEY: ''}),
('options', '!=', {'stateless': ''}),
('type', '=', 'dnat_and_snat')
)
return cmd.execute(check_error=True)
def get_all_logical_switches_with_ports(self):
result = []
for lswitch in self._tables['Logical_Switch'].rows.values():

View File

@@ -274,6 +274,29 @@ class DBInconsistenciesPeriodics(SchemaAwarePeriodicsBase):
else:
self._ovn_client.update_subnet(context, sn_db_obj, n_db_obj)
# The migration will run just once per neutron-server instance. If the lock
# is held by some other neutron-server instance in the cloud, we'll attempt
# to perform the migration every 10 seconds until completed.
# TODO(ihrachys): Remove the migration to stateless fips at some point.
@periodics.periodic(spacing=10, run_immediately=True)
@rerun_on_schema_updates
def migrate_to_stateless_fips(self):
"""Perform the migration from stateful to stateless Floating IPs. """
if not self._ovn_client.is_stateless_nat_supported():
raise periodics.NeverAgain()
# Only the worker holding a valid lock within OVSDB will perform the
# migration.
if not self.has_lock:
return
admin_context = n_context.get_admin_context()
nb_sync = ovn_db_sync.OvnNbSynchronizer(
self._ovn_client._plugin, self._nb_idl, self._ovn_client._sb_idl,
None, None)
nb_sync.migrate_to_stateless_fips(admin_context)
raise periodics.NeverAgain()
# The migration will run just once per neutron-server instance. If the lock
# is held by some other neutron-server instance in the cloud, we'll attempt
# to perform the migration every 10 seconds until completed.

View File

@@ -113,6 +113,10 @@ class OVNClient(object):
return self._nb_idl.is_col_supports_value('ACL', 'action',
'allow-stateless')
# TODO(ihrachys) remove when min OVN version >= 20.03
def is_stateless_nat_supported(self):
return self._nb_idl.is_col_present('NAT', 'options')
def _get_allowed_addresses_from_port(self, port):
if not port.get(psec.PORTSECURITY):
return [], []
@@ -734,6 +738,8 @@ class OVNClient(object):
'external_ip': floatingip['floating_ip_address'],
'logical_port': floatingip['port_id'],
'external_ids': ext_ids}
if self.is_stateless_nat_supported():
columns['options'] = {'stateless': 'true'}
if ovn_conf.is_ovn_distributed_floating_ip():
if self._nb_idl.lsp_get_up(floatingip['port_id']).execute():

View File

@@ -103,6 +103,7 @@ class OvnNbSynchronizer(OvnDbSynchronizer):
self.sync_port_dns_records(ctx)
self.sync_acls(ctx)
self.sync_routers_and_rports(ctx)
self.migrate_to_stateless_fips(ctx)
def _create_port_in_ovn(self, ctx, port):
# Remove any old ACLs for the port to avoid creating duplicate ACLs.
@@ -1188,6 +1189,15 @@ class OvnNbSynchronizer(OvnDbSynchronizer):
txn.add(self.ovn_api.pg_add_ports(
utils.ovn_port_group_name(sg), port['id']))
def migrate_to_stateless_fips(self, ctx):
# This routine will set options:stateless=true for all dnat_and_snats
# that belong to neutron fips.
with self.ovn_api.transaction(check_error=True) as txn:
for nat in self.ovn_api.get_all_stateful_fip_nats():
txn.add(self.ovn_api.db_set(
'NAT', nat['_uuid'],
('options', {'stateless': 'true'})))
def migrate_to_port_groups(self, ctx):
# This routine is responsible for migrating the current Security
# Groups and SG Rules to the new Port Groups implementation.

View File

@@ -139,6 +139,45 @@ class TestDBInconsistenciesPeriodics(testlib_api.SqlTestCaseLight,
migration_expected=False,
never_again=False)
def _test_migrate_to_stateless_fips_helper(
self, stateless_supported, migration_expected, never_again):
self.fake_ovn_client.is_stateless_nat_supported.return_value = (
stateless_supported)
with mock.patch.object(ovn_db_sync.OvnNbSynchronizer,
'migrate_to_stateless_fips') as mtsf:
if never_again:
self.assertRaises(periodics.NeverAgain,
self.periodic.migrate_to_stateless_fips)
else:
self.periodic.migrate_to_stateless_fips()
if migration_expected:
mtsf.assert_called_once_with(mock.ANY)
else:
mtsf.assert_not_called()
def test_migrate_to_stateless_fips_not_needed(self):
self._test_migrate_to_stateless_fips_helper(
stateless_supported=False, migration_expected=False,
never_again=True)
def test_migrate_to_stateless_fips(self):
# Check normal migration path: if the migration has to be done, it will
# take place and won't be attempted in the future.
self._test_migrate_to_stateless_fips_helper(stateless_supported=True,
migration_expected=True,
never_again=True)
def test_migrate_to_stateless_fips_no_lock(self):
with mock.patch.object(maintenance.DBInconsistenciesPeriodics,
'has_lock', mock.PropertyMock(
return_value=False)):
# Check that if this worker doesn't have the lock, it won't
# perform the migration and it will try again later.
self._test_migrate_to_stateless_fips_helper(
stateless_supported=True, migration_expected=False,
never_again=False)
def _test_fix_create_update_network(self, ovn_rev, neutron_rev):
with db_api.CONTEXT_WRITER.using(self.ctx):
self.net['revision_number'] = neutron_rev

View File

@@ -453,6 +453,8 @@ class TestOvnNbSyncML2(test_mech_driver.OVNMechanismDriverTestCase):
ovn_api.get_all_logical_switches_with_ports.return_value = (
self.lswitches_with_ports)
ovn_api.get_all_stateful_fip_nats = mock.Mock()
ovn_api.get_all_stateful_fip_nats.return_value = []
ovn_api.get_all_logical_routers_with_rports = mock.Mock()
ovn_api.get_all_logical_routers_with_rports.return_value = (
self.lrouters_with_rports)

View File

@@ -167,7 +167,9 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
ovn_const.OVN_FIP_PORT_EXT_ID_KEY:
self.fake_floating_ip['port_id'],
ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: utils.ovn_name(
self.fake_floating_ip['router_id'])}}))
self.fake_floating_ip['router_id'])},
'options': {'stateless': 'true'}
}))
self.l3_inst = directory.get_plugin(plugin_constants.L3)
self.lb_id = uuidutils.generate_uuid()
self.member_subnet = {'id': 'subnet-id',
@@ -941,7 +943,8 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
logical_ip='10.0.0.10',
external_ip='192.168.0.10',
logical_port='port_id',
external_ids=expected_ext_ids)
external_ids=expected_ext_ids,
options={'stateless': 'true'})
def test_create_floatingip_distributed(self):
self.l3_inst._ovn.is_col_present.return_value = True
@@ -965,7 +968,8 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
'neutron-router-id', type='dnat_and_snat', logical_ip='10.0.0.10',
external_ip='192.168.0.10', external_mac='00:01:02:03:04:05',
logical_port='port_id',
external_ids=expected_ext_ids)
external_ids=expected_ext_ids,
options={'stateless': 'true'})
def test_create_floatingip_distributed_logical_port_down(self):
# Check that when the port is down, the external_mac field is not
@@ -993,7 +997,8 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
'neutron-router-id', type='dnat_and_snat', logical_ip='10.0.0.10',
external_ip='192.168.0.10',
logical_port='port_id',
external_ids=expected_ext_ids)
external_ids=expected_ext_ids,
options={'stateless': 'true'})
def test_create_floatingip_external_ip_present_in_nat_rule(self):
self.l3_inst._ovn.is_col_present.return_value = True
@@ -1018,7 +1023,8 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
logical_ip='10.0.0.10',
external_ip='192.168.0.10',
logical_port='port_id',
external_ids=expected_ext_ids)
external_ids=expected_ext_ids,
options={'stateless': 'true'})
def test_create_floatingip_external_ip_present_type_snat(self):
self.l3_inst._ovn.is_col_present.return_value = True
@@ -1044,7 +1050,8 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
logical_ip='10.0.0.10',
external_ip='192.168.0.10',
logical_port='port_id',
external_ids=expected_ext_ids)
external_ids=expected_ext_ids,
options={'stateless': 'true'})
def test_create_floatingip_lsp_external_id(self):
foo_lport = fake_resources.FakeOvsdbRow.create_one_ovsdb_row()
@@ -1064,6 +1071,7 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
# Stop this mock.
self.mock_is_lb_member_fip.stop()
self.get_port.return_value = self.member_port
self.l3_inst._ovn.is_col_present.return_value = True
self.l3_inst._ovn.lookup.return_value = self.lb_network
self.l3_inst._ovn.get_lswitch_port.return_value = self.member_lsp
fip = self.l3_inst.create_floatingip(self.context, 'floatingip')
@@ -1081,12 +1089,14 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
external_ip='192.168.0.10',
logical_ip='10.0.0.10',
type='dnat_and_snat',
external_ids=expected_ext_ids)
external_ids=expected_ext_ids,
options={'stateless': 'true'})
def test_create_floatingip_lb_vip_fip(self):
config.cfg.CONF.set_override(
'enable_distributed_floating_ip', True, group='ovn')
self.get_subnet.return_value = self.member_subnet
self.l3_inst._ovn.is_col_present.return_value = True
self.l3_inst._ovn.get_lswitch_port.return_value = self.lb_vip_lsp
self.l3_inst._ovn.db_find_rows.return_value.execute.side_effect = [
[self.ovn_lb],
@@ -1110,7 +1120,8 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
logical_ip='10.0.0.10',
logical_port='port_id',
type='dnat_and_snat',
external_ids=expected_ext_ids)
external_ids=expected_ext_ids,
options={'stateless': 'true'})
self.l3_inst._ovn.db_find_rows.assert_called_with(
'NAT', ('external_ids', '=', {ovn_const.OVN_FIP_PORT_EXT_ID_KEY:
self.member_lsp.name}))
@@ -1217,7 +1228,8 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
logical_ip='10.10.10.10',
external_ip='192.168.0.10',
logical_port='new-port_id',
external_ids=expected_ext_ids)
external_ids=expected_ext_ids,
options={'stateless': 'true'})
@mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.'
'update_floatingip')
@@ -1243,7 +1255,8 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
logical_ip='10.10.10.10',
external_ip='192.168.0.10',
logical_port='new-port_id',
external_ids=expected_ext_ids)
external_ids=expected_ext_ids,
options={'stateless': 'true'})
@mock.patch('neutron.db.db_base_plugin_v2.NeutronDbPluginV2.get_network')
@mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.'
@@ -1277,7 +1290,7 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
'neutron-new-router-id', type='dnat_and_snat',
logical_ip='10.10.10.10', external_ip='192.168.0.10',
external_mac='00:01:02:03:04:05', logical_port='new-port_id',
external_ids=expected_ext_ids)
external_ids=expected_ext_ids, options={'stateless': 'true'})
@mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.'
'update_floatingip')
@@ -1310,7 +1323,8 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
logical_ip='10.10.10.10',
external_ip='192.168.0.10',
logical_port='foo',
external_ids=expected_ext_ids)
external_ids=expected_ext_ids,
options={'stateless': 'true'})
@mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.'
'update_floatingip')
@@ -1345,7 +1359,8 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
logical_ip='10.10.10.10',
external_ip='192.168.0.10',
logical_port='port_id',
external_ids=expected_ext_ids)
external_ids=expected_ext_ids,
options={'stateless': 'true'})
@mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.get_floatingips')
def test_disassociate_floatingips(self, gfs):

View File

@@ -0,0 +1,6 @@
---
other:
- |
OVN driver now uses stateless NAT for floating IP implementation. This allows
to avoid hitting conntrack, potentially improving performance and also allowing
to offload NAT rules to hardware.