Merge "ovn: use stateless NAT rules for FIPs"
This commit is contained in:
@@ -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():
|
||||
|
@@ -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.
|
||||
|
@@ -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():
|
||||
|
@@ -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.
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
@@ -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):
|
||||
|
@@ -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.
|
Reference in New Issue
Block a user