Skip to content

Commit 74ca081

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "[ML2/OVN] Add gateway_port support for FIP" into unmaintained/zed
2 parents c32eb56 + 7d539bc commit 74ca081

File tree

11 files changed

+396
-11
lines changed

11 files changed

+396
-11
lines changed

neutron/common/ovn/utils.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -851,3 +851,7 @@ def get_requested_chassis(requested_chassis):
851851
if isinstance(requested_chassis, str):
852852
return requested_chassis.split(',')
853853
return []
854+
855+
856+
def is_nat_gateway_port_supported(idl):
857+
return idl.is_col_present('NAT', 'gateway_port')

neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,10 @@ def get_all_logical_routers_with_rports(self):
360360
columns['external_mac'] = nat.external_mac[0]
361361
if nat.logical_port:
362362
columns['logical_port'] = nat.logical_port[0]
363+
columns['external_ids'] = nat.external_ids
364+
columns['uuid'] = nat.uuid
365+
if utils.is_nat_gateway_port_supported(self):
366+
columns['gateway_port'] = nat.gateway_port
363367
dnat_and_snats.append(columns)
364368
elif nat.type == 'snat':
365369
snat.append(columns)

neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,6 +1062,52 @@ def cleanup_old_hash_ring_nodes(self):
10621062
context = n_context.get_admin_context()
10631063
hash_ring_db.cleanup_old_nodes(context, days=5)
10641064

1065+
@periodics.periodic(spacing=600, run_immediately=True)
1066+
def update_nat_floating_ip_with_gateway_port_reference(self):
1067+
"""Set NAT rule gateway_port column to any floating IP without
1068+
router gateway port uuid reference - LP#2035281.
1069+
"""
1070+
1071+
if not utils.is_nat_gateway_port_supported(self._nb_idl):
1072+
raise periodics.NeverAgain()
1073+
1074+
context = n_context.get_admin_context()
1075+
fip_update = []
1076+
lrouters = self._nb_idl.get_all_logical_routers_with_rports()
1077+
for router in lrouters:
1078+
ovn_fips = router['dnat_and_snats']
1079+
for ovn_fip in ovn_fips:
1080+
# Skip FIPs that are already configured with gateway_port
1081+
if ovn_fip['gateway_port']:
1082+
continue
1083+
fip_id = ovn_fip['external_ids'].get(
1084+
ovn_const.OVN_FIP_EXT_ID_KEY)
1085+
if fip_id:
1086+
fip_update.append({'uuid': ovn_fip['uuid'],
1087+
'router_id': router['name']})
1088+
1089+
# Simple caching mechanism to avoid unnecessary DB calls
1090+
gw_port_id_cache = {}
1091+
lrp_cache = {}
1092+
cmds = []
1093+
for fip in fip_update:
1094+
lrouter = utils.ovn_name(fip['router_id'])
1095+
if lrouter not in gw_port_id_cache.keys():
1096+
router_db = self._ovn_client._l3_plugin.get_router(context,
1097+
fip['router_id'], fields=['gw_port_id'])
1098+
gw_port_id_cache[lrouter] = router_db.get('gw_port_id')
1099+
lrp_cache[lrouter] = self._nb_idl.get_lrouter_port(
1100+
gw_port_id_cache[lrouter])
1101+
columns = {'gateway_port': lrp_cache[lrouter].uuid}
1102+
cmds.append(self._nb_idl.set_nat_rule_in_lrouter(lrouter,
1103+
fip['uuid'], **columns))
1104+
1105+
if cmds:
1106+
with self._nb_idl.transaction(check_error=True) as txn:
1107+
for cmd in cmds:
1108+
txn.add(cmd)
1109+
raise periodics.NeverAgain()
1110+
10651111

10661112
class HashRingHealthCheckPeriodics(object):
10671113

neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,14 @@ def _create_or_update_floatingip(self, floatingip, txn=None):
967967
'logical_port': floatingip['port_id'],
968968
'external_ids': ext_ids}
969969

970+
# If OVN supports gateway_port column for NAT rules set gateway port
971+
# uuid to any floating IP without gw port reference - LP#2035281.
972+
if utils.is_nat_gateway_port_supported(self._nb_idl):
973+
router_db = self._l3_plugin.get_router(admin_context, router_id)
974+
gw_port_id = router_db.get('gw_port_id')
975+
lrp = self._nb_idl.get_lrouter_port(gw_port_id)
976+
columns['gateway_port'] = lrp.uuid
977+
970978
if ovn_conf.is_ovn_distributed_floating_ip():
971979
if self._nb_idl.lsp_get_up(floatingip['port_id']).execute():
972980
columns['external_mac'] = port_db['mac_address']

neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,6 +1014,65 @@ def test_remove_duplicated_chassis_registers_no_ch_private_register(self):
10141014
# "Chassis_Private" register was missing.
10151015
self.assertEqual(2, len(chassis_result))
10161016

1017+
def test_floating_ip_with_gateway_port(self):
1018+
ext_net = self._create_network('ext_networktest', external=True)
1019+
ext_subnet = self._create_subnet(
1020+
'ext_subnettest',
1021+
ext_net['id'],
1022+
**{'cidr': '100.0.0.0/24',
1023+
'gateway_ip': '100.0.0.254',
1024+
'allocation_pools': [
1025+
{'start': '100.0.0.2', 'end': '100.0.0.253'}],
1026+
'enable_dhcp': False})
1027+
net1 = self._create_network('network1test', external=False)
1028+
subnet1 = self._create_subnet('subnet1test', net1['id'])
1029+
external_gateway_info = {
1030+
'enable_snat': True,
1031+
'network_id': ext_net['id'],
1032+
'external_fixed_ips': [
1033+
{'ip_address': '100.0.0.2', 'subnet_id': ext_subnet['id']}]}
1034+
router = self._create_router(
1035+
'routertest', external_gateway_info=external_gateway_info)
1036+
self._add_router_interface(router['id'], subnet1['id'])
1037+
1038+
p1 = self._create_port('testp1', net1['id'])
1039+
logical_ip = p1['fixed_ips'][0]['ip_address']
1040+
fip_info = {'floatingip': {
1041+
'tenant_id': self._tenant_id,
1042+
'description': 'test_fip',
1043+
'floating_network_id': ext_net['id'],
1044+
'port_id': p1['id'],
1045+
'fixed_ip_address': logical_ip}}
1046+
1047+
# Create floating IP without gateway_port
1048+
with mock.patch.object(utils,
1049+
'is_nat_gateway_port_supported', return_value=False):
1050+
fip = self.l3_plugin.create_floatingip(self.context, fip_info)
1051+
1052+
self.assertEqual(router['id'], fip['router_id'])
1053+
self.assertEqual('testp1', fip['port_details']['name'])
1054+
self.assertIsNotNone(self.nb_api.get_lswitch_port(fip['port_id']))
1055+
1056+
rules = self.nb_api.get_all_logical_routers_with_rports()[0]
1057+
fip_rule = rules['dnat_and_snats'][0]
1058+
if utils.is_nat_gateway_port_supported(self.nb_api):
1059+
self.assertEqual([], fip_rule['gateway_port'])
1060+
else:
1061+
self.assertNotIn('gateway_port', fip_rule)
1062+
1063+
# Call the maintenance task and check that the value has been
1064+
# updated in the NAT rule
1065+
self.assertRaises(periodics.NeverAgain,
1066+
self.maint.update_nat_floating_ip_with_gateway_port_reference)
1067+
1068+
rules = self.nb_api.get_all_logical_routers_with_rports()[0]
1069+
fip_rule = rules['dnat_and_snats'][0]
1070+
1071+
if utils.is_nat_gateway_port_supported(self.nb_api):
1072+
self.assertNotEqual([], fip_rule['gateway_port'])
1073+
else:
1074+
self.assertNotIn('gateway_port', fip_rule)
1075+
10171076

10181077
class TestLogMaintenance(_TestMaintenanceHelper,
10191078
test_log_driver.LogApiTestCaseBase):

neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,3 +1248,103 @@ def test_agent_delete(self):
12481248
self.plugin.delete_agent(self.context, metadata_id)
12491249
self.assertRaises(agent_exc.AgentNotFound, self.plugin.get_agent,
12501250
self.context, metadata_id)
1251+
1252+
1253+
class TestNATRuleGatewayPort(base.TestOVNFunctionalBase):
1254+
1255+
def setUp(self):
1256+
super().setUp()
1257+
self._ovn_client = self.mech_driver._ovn_client
1258+
1259+
def deserialize(self, content_type, response):
1260+
ctype = 'application/%s' % content_type
1261+
data = self._deserializers[ctype].deserialize(response.body)['body']
1262+
return data
1263+
1264+
def _create_router(self, name, external_gateway_info=None):
1265+
data = {'router': {'name': name, 'tenant_id': self._tenant_id,
1266+
'external_gateway_info': external_gateway_info}}
1267+
req = self.new_create_request('routers', data, self.fmt)
1268+
res = req.get_response(self.api)
1269+
return self.deserialize(self.fmt, res)['router']
1270+
1271+
def _process_router_interface(self, action, router_id, subnet_id):
1272+
req = self.new_action_request(
1273+
'routers', {'subnet_id': subnet_id}, router_id,
1274+
'%s_router_interface' % action)
1275+
res = req.get_response(self.api)
1276+
return self.deserialize(self.fmt, res)
1277+
1278+
def _add_router_interface(self, router_id, subnet_id):
1279+
return self._process_router_interface('add', router_id, subnet_id)
1280+
1281+
def _create_port(self, name, net_id, security_groups=None,
1282+
device_owner=None):
1283+
data = {'port': {'name': name,
1284+
'tenant_id': self._tenant_id,
1285+
'network_id': net_id}}
1286+
1287+
if security_groups is not None:
1288+
data['port']['security_groups'] = security_groups
1289+
1290+
if device_owner is not None:
1291+
data['port']['device_owner'] = device_owner
1292+
1293+
req = self.new_create_request('ports', data, self.fmt)
1294+
res = req.get_response(self.api)
1295+
return self.deserialize(self.fmt, res)['port']
1296+
1297+
def test_create_floatingip(self):
1298+
ext_net = self._make_network(
1299+
self.fmt, 'ext_networktest', True,
1300+
arg_list=('router:external',
1301+
'provider:network_type',
1302+
'provider:physical_network'),
1303+
**{'router:external': True,
1304+
'provider:network_type': 'flat',
1305+
'provider:physical_network': 'public'})['network']
1306+
res = self._create_subnet(self.fmt, ext_net['id'],
1307+
'100.0.0.0/24', gateway_ip='100.0.0.254',
1308+
allocation_pools=[{'start': '100.0.0.2',
1309+
'end': '100.0.0.253'}],
1310+
enable_dhcp=False)
1311+
ext_subnet = self.deserialize(self.fmt, res)['subnet']
1312+
net1 = self._make_network(
1313+
self.fmt, 'network1test', True)['network']
1314+
res = self._create_subnet(self.fmt, net1['id'],
1315+
'192.168.0.0/24', gateway_ip='192.168.0.1',
1316+
allocation_pools=[{'start': '192.168.0.2',
1317+
'end': '192.168.0.253'}],
1318+
enable_dhcp=False)
1319+
subnet1 = self.deserialize(self.fmt, res)['subnet']
1320+
external_gateway_info = {
1321+
'enable_snat': True,
1322+
'network_id': ext_net['id'],
1323+
'external_fixed_ips': [
1324+
{'ip_address': '100.0.0.2', 'subnet_id': ext_subnet['id']}]}
1325+
router = self._create_router(
1326+
'routertest', external_gateway_info=external_gateway_info)
1327+
self._add_router_interface(router['id'], subnet1['id'])
1328+
1329+
p1 = self._create_port('testp1', net1['id'])
1330+
logical_ip = p1['fixed_ips'][0]['ip_address']
1331+
fip_info = {'floatingip': {
1332+
'tenant_id': self._tenant_id,
1333+
'description': 'test_fip',
1334+
'floating_network_id': ext_net['id'],
1335+
'port_id': p1['id'],
1336+
'fixed_ip_address': logical_ip}}
1337+
1338+
fip = self.l3_plugin.create_floatingip(self.context, fip_info)
1339+
1340+
self.assertEqual(router['id'], fip['router_id'])
1341+
self.assertEqual('testp1', fip['port_details']['name'])
1342+
self.assertIsNotNone(self.nb_api.get_lswitch_port(fip['port_id']))
1343+
1344+
rules = self.nb_api.get_all_logical_routers_with_rports()[0]
1345+
fip_rule = rules['dnat_and_snats'][0]
1346+
1347+
if utils.is_nat_gateway_port_supported(self.nb_api):
1348+
self.assertNotEqual([], fip_rule['gateway_port'])
1349+
else:
1350+
self.assertNotIn('gateway_port', fip_rule)

neutron/tests/unit/fake_resources.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ def __init__(self, **kwargs):
164164
self.ha_chassis_group_del_chassis = mock.Mock()
165165
self.lrp_get = mock.Mock()
166166
self.get_schema_version = mock.Mock(return_value='3.6.0')
167+
self.get_lrouter_port = mock.Mock()
167168

168169

169170
class FakeOvsdbSbOvnIdl(object):

neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl_ovn.py

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import copy
1616
from unittest import mock
1717

18+
from oslo_utils import uuidutils
1819
from ovsdbapp.backend import ovs_idl
1920

2021
from neutron.common.ovn import constants as ovn_const
@@ -193,11 +194,15 @@ class TestNBImplIdlOvn(TestDBImplIdlOvn):
193194
'type': 'snat'},
194195
{'external_ip': '20.0.2.4', 'logical_ip': '10.0.0.4',
195196
'type': 'dnat_and_snat', 'external_mac': [],
196-
'logical_port': []},
197+
'logical_port': [],
198+
'external_ids': {ovn_const.OVN_FIP_EXT_ID_KEY: 'fip_id_a'},
199+
'gateway_port': uuidutils.generate_uuid()},
197200
{'external_ip': '20.0.2.5', 'logical_ip': '10.0.0.5',
198201
'type': 'dnat_and_snat',
199202
'external_mac': ['00:01:02:03:04:05'],
200-
'logical_port': ['lsp-id-001']}],
203+
'logical_port': ['lsp-id-001'],
204+
'external_ids': {ovn_const.OVN_FIP_EXT_ID_KEY: 'fip_id_b'},
205+
'gateway_port': []}],
201206
'acls': [
202207
{'unit_test_id': 1,
203208
'action': 'allow-related', 'direction': 'from-lport',
@@ -446,13 +451,39 @@ def test_get_all_logical_switches_with_ports(self):
446451
'provnet_ports': []}]
447452
self.assertCountEqual(mapping, expected)
448453

449-
def test_get_all_logical_routers_with_rports(self):
454+
def _test_get_all_logical_routers_with_rports(self, is_gw_port):
450455
# Test empty
451456
mapping = self.nb_ovn_idl.get_all_logical_switches_with_ports()
452457
self.assertCountEqual(mapping, {})
453458
# Test loaded values
454459
self._load_nb_db()
460+
461+
# Test with gateway_port_support enabled
462+
utils.is_nat_gateway_port_supported = mock.Mock()
463+
utils.is_nat_gateway_port_supported.return_value = is_gw_port
455464
mapping = self.nb_ovn_idl.get_all_logical_routers_with_rports()
465+
lra_nat = self._find_ovsdb_fake_row(self.nat_table,
466+
'external_ip', '20.0.2.4')
467+
lrb_nat = self._find_ovsdb_fake_row(self.nat_table,
468+
'external_ip', '20.0.2.5')
469+
470+
lra_fip = {'external_ip': '20.0.2.4',
471+
'logical_ip': '10.0.0.4',
472+
'type': 'dnat_and_snat',
473+
'external_ids': {ovn_const.OVN_FIP_EXT_ID_KEY: 'fip_id_a'},
474+
'uuid': lra_nat.uuid}
475+
lrb_fip = {'external_ip': '20.0.2.5',
476+
'logical_ip': '10.0.0.5',
477+
'type': 'dnat_and_snat',
478+
'external_mac': '00:01:02:03:04:05',
479+
'logical_port': 'lsp-id-001',
480+
'external_ids': {ovn_const.OVN_FIP_EXT_ID_KEY: 'fip_id_b'},
481+
'uuid': lrb_nat.uuid}
482+
483+
if is_gw_port:
484+
lra_fip['gateway_port'] = lra_nat.gateway_port
485+
lrb_fip['gateway_port'] = lrb_nat.gateway_port
486+
456487
expected = [{'name': 'lr-id-a',
457488
'ports': {'orp-id-a1': ['10.0.1.0/24'],
458489
'orp-id-a2': ['10.0.2.0/24'],
@@ -471,14 +502,7 @@ def test_get_all_logical_routers_with_rports(self):
471502
'snats': [{'external_ip': '20.0.2.1',
472503
'logical_ip': '10.0.0.0/24',
473504
'type': 'snat'}],
474-
'dnat_and_snats': [{'external_ip': '20.0.2.4',
475-
'logical_ip': '10.0.0.4',
476-
'type': 'dnat_and_snat'},
477-
{'external_ip': '20.0.2.5',
478-
'logical_ip': '10.0.0.5',
479-
'type': 'dnat_and_snat',
480-
'external_mac': '00:01:02:03:04:05',
481-
'logical_port': 'lsp-id-001'}]},
505+
'dnat_and_snats': [lra_fip, lrb_fip]},
482506
{'name': 'lr-id-c', 'ports': {}, 'static_routes': [],
483507
'snats': [], 'dnat_and_snats': []},
484508
{'name': 'lr-id-d', 'ports': {}, 'static_routes': [],
@@ -487,6 +511,12 @@ def test_get_all_logical_routers_with_rports(self):
487511
'snats': [], 'dnat_and_snats': []}]
488512
self.assertCountEqual(mapping, expected)
489513

514+
def test_get_all_logical_routers_with_rports(self):
515+
self._test_get_all_logical_routers_with_rports(True)
516+
517+
def test_get_all_logical_routers_with_rports_without_nat_gw_port(self):
518+
self._test_get_all_logical_routers_with_rports(False)
519+
490520
def test_get_acls_for_lswitches(self):
491521
self._load_nb_db()
492522
# Test neutron switches

0 commit comments

Comments
 (0)