[stable/zed] Add patch for "[ML2/OVN] Add gateway_port support for FIP" (#1378)

diff --git a/images/neutron/patches/neutron/0002-ML2-OVN-Add-gateway_port-support-for-FIP.patch b/images/neutron/patches/neutron/0002-ML2-OVN-Add-gateway_port-support-for-FIP.patch
new file mode 100644
index 0000000..0a24a2b
--- /dev/null
+++ b/images/neutron/patches/neutron/0002-ML2-OVN-Add-gateway_port-support-for-FIP.patch
@@ -0,0 +1,582 @@
+From 7d539bc0b093ceea9082b2526c0d40d519f13d19 Mon Sep 17 00:00:00 2001
+From: Roberto Bartzen Acosta <rbartzen@gmail.com>
+Date: Fri, 15 Sep 2023 08:44:52 -0300
+Subject: [PATCH] [ML2/OVN] Add gateway_port support for FIP
+
+The OVN changed support for NAT rules including a new column and auto discovery logic (which may not work in some cases) [1][2].
+If the OVN backend supports this column in the Northbound DB Schema, set gateway port uuid to any floating IP to prevent North/South traffic issues for floating IPs.
+
+This patch updates the method for creating FIP NAT rules in OVN backend and updates previously created FIP rules to include the gateway_port reference. This NAT rule update task runs only once during the maintenance task, and if all entries are already configured no action is performed.
+
+[1] https://github.com/ovn-org/ovn/commit/15348b7b806f7a9680606c3e9348708980129949
+[2] https://github.com/ovn-org/ovn/commit/2d942be7db1799f2778492331513ae2b5a556b92
+
+Conflicts:
+	neutron/common/ovn/utils.py
+	neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py
+	neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py
+
+(cherry picked from commit 78b5fe2ff417e176a3ccc356d9428287a0ee3c64)
+
+Closes-Bug: 2035281
+Change-Id: I802b6bd8c281cb6dacdee2e9c15285f069d4e04c
+---
+
+diff --git a/neutron/common/ovn/utils.py b/neutron/common/ovn/utils.py
+index 9b14b2d..1660c1b 100644
+--- a/neutron/common/ovn/utils.py
++++ b/neutron/common/ovn/utils.py
+@@ -851,3 +851,7 @@
+     if isinstance(requested_chassis, str):
+         return requested_chassis.split(',')
+     return []
++
++
++def is_nat_gateway_port_supported(idl):
++    return idl.is_col_present('NAT', 'gateway_port')
+diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py
+index e7093fe..4ca912a 100644
+--- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py
++++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py
+@@ -360,6 +360,10 @@
+                         columns['external_mac'] = nat.external_mac[0]
+                     if nat.logical_port:
+                         columns['logical_port'] = nat.logical_port[0]
++                    columns['external_ids'] = nat.external_ids
++                    columns['uuid'] = nat.uuid
++                    if utils.is_nat_gateway_port_supported(self):
++                        columns['gateway_port'] = nat.gateway_port
+                     dnat_and_snats.append(columns)
+                 elif nat.type == 'snat':
+                     snat.append(columns)
+diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py
+index 14a9e7d..28e8434 100644
+--- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py
++++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py
+@@ -1062,6 +1062,52 @@
+         context = n_context.get_admin_context()
+         hash_ring_db.cleanup_old_nodes(context, days=5)
+ 
++    @periodics.periodic(spacing=600, run_immediately=True)
++    def update_nat_floating_ip_with_gateway_port_reference(self):
++        """Set NAT rule gateway_port column to any floating IP without
++        router gateway port uuid reference - LP#2035281.
++        """
++
++        if not utils.is_nat_gateway_port_supported(self._nb_idl):
++            raise periodics.NeverAgain()
++
++        context = n_context.get_admin_context()
++        fip_update = []
++        lrouters = self._nb_idl.get_all_logical_routers_with_rports()
++        for router in lrouters:
++            ovn_fips = router['dnat_and_snats']
++            for ovn_fip in ovn_fips:
++                # Skip FIPs that are already configured with gateway_port
++                if ovn_fip['gateway_port']:
++                    continue
++                fip_id = ovn_fip['external_ids'].get(
++                            ovn_const.OVN_FIP_EXT_ID_KEY)
++                if fip_id:
++                    fip_update.append({'uuid': ovn_fip['uuid'],
++                                       'router_id': router['name']})
++
++        # Simple caching mechanism to avoid unnecessary DB calls
++        gw_port_id_cache = {}
++        lrp_cache = {}
++        cmds = []
++        for fip in fip_update:
++            lrouter = utils.ovn_name(fip['router_id'])
++            if lrouter not in gw_port_id_cache.keys():
++                router_db = self._ovn_client._l3_plugin.get_router(context,
++                    fip['router_id'], fields=['gw_port_id'])
++                gw_port_id_cache[lrouter] = router_db.get('gw_port_id')
++                lrp_cache[lrouter] = self._nb_idl.get_lrouter_port(
++                    gw_port_id_cache[lrouter])
++            columns = {'gateway_port': lrp_cache[lrouter].uuid}
++            cmds.append(self._nb_idl.set_nat_rule_in_lrouter(lrouter,
++                fip['uuid'], **columns))
++
++        if cmds:
++            with self._nb_idl.transaction(check_error=True) as txn:
++                for cmd in cmds:
++                    txn.add(cmd)
++        raise periodics.NeverAgain()
++
+ 
+ class HashRingHealthCheckPeriodics(object):
+ 
+diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py
+index 266e97f..5e8f73f 100644
+--- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py
++++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py
+@@ -967,6 +967,14 @@
+                    'logical_port': floatingip['port_id'],
+                    'external_ids': ext_ids}
+ 
++        # If OVN supports gateway_port column for NAT rules set gateway port
++        # uuid to any floating IP without gw port reference - LP#2035281.
++        if utils.is_nat_gateway_port_supported(self._nb_idl):
++            router_db = self._l3_plugin.get_router(admin_context, router_id)
++            gw_port_id = router_db.get('gw_port_id')
++            lrp = self._nb_idl.get_lrouter_port(gw_port_id)
++            columns['gateway_port'] = lrp.uuid
++
+         if ovn_conf.is_ovn_distributed_floating_ip():
+             if self._nb_idl.lsp_get_up(floatingip['port_id']).execute():
+                 columns['external_mac'] = port_db['mac_address']
+diff --git a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py
+index 5abbd43..a1ceb5e 100644
+--- a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py
++++ b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py
+@@ -1014,6 +1014,65 @@
+         # "Chassis_Private" register was missing.
+         self.assertEqual(2, len(chassis_result))
+ 
++    def test_floating_ip_with_gateway_port(self):
++        ext_net = self._create_network('ext_networktest', external=True)
++        ext_subnet = self._create_subnet(
++            'ext_subnettest',
++            ext_net['id'],
++            **{'cidr': '100.0.0.0/24',
++               'gateway_ip': '100.0.0.254',
++               'allocation_pools': [
++                   {'start': '100.0.0.2', 'end': '100.0.0.253'}],
++               'enable_dhcp': False})
++        net1 = self._create_network('network1test', external=False)
++        subnet1 = self._create_subnet('subnet1test', net1['id'])
++        external_gateway_info = {
++            'enable_snat': True,
++            'network_id': ext_net['id'],
++            'external_fixed_ips': [
++                {'ip_address': '100.0.0.2', 'subnet_id': ext_subnet['id']}]}
++        router = self._create_router(
++            'routertest', external_gateway_info=external_gateway_info)
++        self._add_router_interface(router['id'], subnet1['id'])
++
++        p1 = self._create_port('testp1', net1['id'])
++        logical_ip = p1['fixed_ips'][0]['ip_address']
++        fip_info = {'floatingip': {
++            'tenant_id': self._tenant_id,
++            'description': 'test_fip',
++            'floating_network_id': ext_net['id'],
++            'port_id': p1['id'],
++            'fixed_ip_address': logical_ip}}
++
++        # Create floating IP without gateway_port
++        with mock.patch.object(utils,
++                'is_nat_gateway_port_supported', return_value=False):
++            fip = self.l3_plugin.create_floatingip(self.context, fip_info)
++
++        self.assertEqual(router['id'], fip['router_id'])
++        self.assertEqual('testp1', fip['port_details']['name'])
++        self.assertIsNotNone(self.nb_api.get_lswitch_port(fip['port_id']))
++
++        rules = self.nb_api.get_all_logical_routers_with_rports()[0]
++        fip_rule = rules['dnat_and_snats'][0]
++        if utils.is_nat_gateway_port_supported(self.nb_api):
++            self.assertEqual([], fip_rule['gateway_port'])
++        else:
++            self.assertNotIn('gateway_port', fip_rule)
++
++        # Call the maintenance task and check that the value has been
++        # updated in the NAT rule
++        self.assertRaises(periodics.NeverAgain,
++            self.maint.update_nat_floating_ip_with_gateway_port_reference)
++
++        rules = self.nb_api.get_all_logical_routers_with_rports()[0]
++        fip_rule = rules['dnat_and_snats'][0]
++
++        if utils.is_nat_gateway_port_supported(self.nb_api):
++            self.assertNotEqual([], fip_rule['gateway_port'])
++        else:
++            self.assertNotIn('gateway_port', fip_rule)
++
+ 
+ class TestLogMaintenance(_TestMaintenanceHelper,
+                          test_log_driver.LogApiTestCaseBase):
+diff --git a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py
+index e1c63d7..d716cbc 100644
+--- a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py
++++ b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py
+@@ -1248,3 +1248,103 @@
+         self.plugin.delete_agent(self.context, metadata_id)
+         self.assertRaises(agent_exc.AgentNotFound, self.plugin.get_agent,
+                           self.context, metadata_id)
++
++
++class TestNATRuleGatewayPort(base.TestOVNFunctionalBase):
++
++    def setUp(self):
++        super().setUp()
++        self._ovn_client = self.mech_driver._ovn_client
++
++    def deserialize(self, content_type, response):
++        ctype = 'application/%s' % content_type
++        data = self._deserializers[ctype].deserialize(response.body)['body']
++        return data
++
++    def _create_router(self, name, external_gateway_info=None):
++        data = {'router': {'name': name, 'tenant_id': self._tenant_id,
++                           'external_gateway_info': external_gateway_info}}
++        req = self.new_create_request('routers', data, self.fmt)
++        res = req.get_response(self.api)
++        return self.deserialize(self.fmt, res)['router']
++
++    def _process_router_interface(self, action, router_id, subnet_id):
++        req = self.new_action_request(
++            'routers', {'subnet_id': subnet_id}, router_id,
++            '%s_router_interface' % action)
++        res = req.get_response(self.api)
++        return self.deserialize(self.fmt, res)
++
++    def _add_router_interface(self, router_id, subnet_id):
++        return self._process_router_interface('add', router_id, subnet_id)
++
++    def _create_port(self, name, net_id, security_groups=None,
++                     device_owner=None):
++        data = {'port': {'name': name,
++                         'tenant_id': self._tenant_id,
++                         'network_id': net_id}}
++
++        if security_groups is not None:
++            data['port']['security_groups'] = security_groups
++
++        if device_owner is not None:
++            data['port']['device_owner'] = device_owner
++
++        req = self.new_create_request('ports', data, self.fmt)
++        res = req.get_response(self.api)
++        return self.deserialize(self.fmt, res)['port']
++
++    def test_create_floatingip(self):
++        ext_net = self._make_network(
++            self.fmt, 'ext_networktest', True,
++            arg_list=('router:external',
++                      'provider:network_type',
++                      'provider:physical_network'),
++            **{'router:external': True,
++               'provider:network_type': 'flat',
++               'provider:physical_network': 'public'})['network']
++        res = self._create_subnet(self.fmt, ext_net['id'],
++            '100.0.0.0/24', gateway_ip='100.0.0.254',
++            allocation_pools=[{'start': '100.0.0.2',
++                               'end': '100.0.0.253'}],
++            enable_dhcp=False)
++        ext_subnet = self.deserialize(self.fmt, res)['subnet']
++        net1 = self._make_network(
++            self.fmt, 'network1test', True)['network']
++        res = self._create_subnet(self.fmt, net1['id'],
++            '192.168.0.0/24', gateway_ip='192.168.0.1',
++            allocation_pools=[{'start': '192.168.0.2',
++                               'end': '192.168.0.253'}],
++            enable_dhcp=False)
++        subnet1 = self.deserialize(self.fmt, res)['subnet']
++        external_gateway_info = {
++            'enable_snat': True,
++            'network_id': ext_net['id'],
++            'external_fixed_ips': [
++                {'ip_address': '100.0.0.2', 'subnet_id': ext_subnet['id']}]}
++        router = self._create_router(
++            'routertest', external_gateway_info=external_gateway_info)
++        self._add_router_interface(router['id'], subnet1['id'])
++
++        p1 = self._create_port('testp1', net1['id'])
++        logical_ip = p1['fixed_ips'][0]['ip_address']
++        fip_info = {'floatingip': {
++            'tenant_id': self._tenant_id,
++            'description': 'test_fip',
++            'floating_network_id': ext_net['id'],
++            'port_id': p1['id'],
++            'fixed_ip_address': logical_ip}}
++
++        fip = self.l3_plugin.create_floatingip(self.context, fip_info)
++
++        self.assertEqual(router['id'], fip['router_id'])
++        self.assertEqual('testp1', fip['port_details']['name'])
++        self.assertIsNotNone(self.nb_api.get_lswitch_port(fip['port_id']))
++
++        rules = self.nb_api.get_all_logical_routers_with_rports()[0]
++        fip_rule = rules['dnat_and_snats'][0]
++
++        if utils.is_nat_gateway_port_supported(self.nb_api):
++            self.assertNotEqual([], fip_rule['gateway_port'])
++        else:
++            self.assertNotIn('gateway_port', fip_rule)
+diff --git a/neutron/tests/unit/fake_resources.py b/neutron/tests/unit/fake_resources.py
+index bca73ee..23c5a92 100644
+--- a/neutron/tests/unit/fake_resources.py
++++ b/neutron/tests/unit/fake_resources.py
+@@ -164,6 +164,7 @@
+         self.ha_chassis_group_del_chassis = mock.Mock()
+         self.lrp_get = mock.Mock()
+         self.get_schema_version = mock.Mock(return_value='3.6.0')
++        self.get_lrouter_port = mock.Mock()
+ 
+ 
+ class FakeOvsdbSbOvnIdl(object):
+diff --git a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl_ovn.py b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl_ovn.py
+index 209a41b..584f13f 100644
+--- a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl_ovn.py
++++ b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl_ovn.py
+@@ -15,6 +15,7 @@
+ import copy
+ from unittest import mock
+ 
++from oslo_utils import uuidutils
+ from ovsdbapp.backend import ovs_idl
+ 
+ from neutron.common.ovn import constants as ovn_const
+@@ -193,11 +194,15 @@
+                   'type': 'snat'},
+                  {'external_ip': '20.0.2.4', 'logical_ip': '10.0.0.4',
+                   'type': 'dnat_and_snat', 'external_mac': [],
+-                  'logical_port': []},
++                  'logical_port': [],
++                  'external_ids': {ovn_const.OVN_FIP_EXT_ID_KEY: 'fip_id_a'},
++                  'gateway_port': uuidutils.generate_uuid()},
+                  {'external_ip': '20.0.2.5', 'logical_ip': '10.0.0.5',
+                   'type': 'dnat_and_snat',
+                   'external_mac': ['00:01:02:03:04:05'],
+-                  'logical_port': ['lsp-id-001']}],
++                  'logical_port': ['lsp-id-001'],
++                  'external_ids': {ovn_const.OVN_FIP_EXT_ID_KEY: 'fip_id_b'},
++                  'gateway_port': []}],
+         'acls': [
+             {'unit_test_id': 1,
+              'action': 'allow-related', 'direction': 'from-lport',
+@@ -446,13 +451,39 @@
+                      'provnet_ports': []}]
+         self.assertCountEqual(mapping, expected)
+ 
+-    def test_get_all_logical_routers_with_rports(self):
++    def _test_get_all_logical_routers_with_rports(self, is_gw_port):
+         # Test empty
+         mapping = self.nb_ovn_idl.get_all_logical_switches_with_ports()
+         self.assertCountEqual(mapping, {})
+         # Test loaded values
+         self._load_nb_db()
++
++        # Test with gateway_port_support enabled
++        utils.is_nat_gateway_port_supported = mock.Mock()
++        utils.is_nat_gateway_port_supported.return_value = is_gw_port
+         mapping = self.nb_ovn_idl.get_all_logical_routers_with_rports()
++        lra_nat = self._find_ovsdb_fake_row(self.nat_table,
++                                            'external_ip', '20.0.2.4')
++        lrb_nat = self._find_ovsdb_fake_row(self.nat_table,
++                                            'external_ip', '20.0.2.5')
++
++        lra_fip = {'external_ip': '20.0.2.4',
++                   'logical_ip': '10.0.0.4',
++                   'type': 'dnat_and_snat',
++                   'external_ids': {ovn_const.OVN_FIP_EXT_ID_KEY: 'fip_id_a'},
++                   'uuid': lra_nat.uuid}
++        lrb_fip = {'external_ip': '20.0.2.5',
++                   'logical_ip': '10.0.0.5',
++                   'type': 'dnat_and_snat',
++                   'external_mac': '00:01:02:03:04:05',
++                   'logical_port': 'lsp-id-001',
++                   'external_ids': {ovn_const.OVN_FIP_EXT_ID_KEY: 'fip_id_b'},
++                   'uuid': lrb_nat.uuid}
++
++        if is_gw_port:
++            lra_fip['gateway_port'] = lra_nat.gateway_port
++            lrb_fip['gateway_port'] = lrb_nat.gateway_port
++
+         expected = [{'name': 'lr-id-a',
+                      'ports': {'orp-id-a1': ['10.0.1.0/24'],
+                                'orp-id-a2': ['10.0.2.0/24'],
+@@ -471,14 +502,7 @@
+                      'snats': [{'external_ip': '20.0.2.1',
+                                 'logical_ip': '10.0.0.0/24',
+                                 'type': 'snat'}],
+-                     'dnat_and_snats': [{'external_ip': '20.0.2.4',
+-                                         'logical_ip': '10.0.0.4',
+-                                         'type': 'dnat_and_snat'},
+-                                        {'external_ip': '20.0.2.5',
+-                                         'logical_ip': '10.0.0.5',
+-                                         'type': 'dnat_and_snat',
+-                                         'external_mac': '00:01:02:03:04:05',
+-                                         'logical_port': 'lsp-id-001'}]},
++                     'dnat_and_snats': [lra_fip, lrb_fip]},
+                     {'name': 'lr-id-c', 'ports': {}, 'static_routes': [],
+                      'snats': [], 'dnat_and_snats': []},
+                     {'name': 'lr-id-d', 'ports': {}, 'static_routes': [],
+@@ -487,6 +511,12 @@
+                      'snats': [], 'dnat_and_snats': []}]
+         self.assertCountEqual(mapping, expected)
+ 
++    def test_get_all_logical_routers_with_rports(self):
++        self._test_get_all_logical_routers_with_rports(True)
++
++    def test_get_all_logical_routers_with_rports_without_nat_gw_port(self):
++        self._test_get_all_logical_routers_with_rports(False)
++
+     def test_get_acls_for_lswitches(self):
+         self._load_nb_db()
+         # Test neutron switches
+diff --git a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py
+index 24fb3f8..430d230 100644
+--- a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py
++++ b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py
+@@ -915,3 +915,66 @@
+         expected_calls = [mock.call('Logical_Switch_Port', lsp0.uuid,
+                                     ('type', constants.LSP_TYPE_VIRTUAL))]
+         nb_idl.db_set.assert_has_calls(expected_calls)
++
++    def test_update_nat_floating_ip_with_gateway_port(self):
++        _nb_idl = self.fake_ovn_client._nb_idl
++        utils.is_nat_gateway_port_supported.return_value = True
++
++        lrp = fakes.FakeOvsdbRow.create_one_ovsdb_row(attrs={'options': {}})
++        _nb_idl.get_lrouter_port.return_value = lrp
++        self.fake_external_fixed_ips = {
++            'network_id': 'ext-network-id',
++            'external_fixed_ips': [{'ip_address': '20.0.2.1',
++                                    'subnet_id': 'ext-subnet-id'}]}
++        lrouter = {
++            'id': 'lr-id-b',
++            'name': utils.ovn_name('lr-id-b'),
++            'admin_state_up': True,
++            'external_gateway_info': self.fake_external_fixed_ips,
++            'gw_port_id': 'gw-port-id'
++        }
++        _nb_idl._l3_plugin.get_router.return_value = lrouter
++
++        lra_nat = {'external_ip': '20.0.2.4', 'logical_ip': '10.0.0.4',
++                   'type': 'dnat_and_snat', 'external_mac': [],
++                   'logical_port': [],
++                   'external_ids': {constants.OVN_FIP_EXT_ID_KEY: 'fip_id_1'},
++                   'gateway_port': uuidutils.generate_uuid(),
++                   'uuid': uuidutils.generate_uuid()}
++
++        lrb_nat = {'external_ip': '20.0.2.5', 'logical_ip': '10.0.0.5',
++                   'type': 'dnat_and_snat',
++                   'external_mac': ['00:01:02:03:04:05'],
++                   'logical_port': ['lsp-id-001'],
++                   'external_ids': {constants.OVN_FIP_EXT_ID_KEY: 'fip_id_2'},
++                   'gateway_port': [],
++                   'uuid': uuidutils.generate_uuid()}
++
++        expected = [{'name': 'lr-id-a',
++                     'ports': {'orp-id-a1': ['10.0.1.0/24'],
++                               'orp-id-a2': ['10.0.2.0/24'],
++                               'orp-id-a3': ['10.0.3.0/24']},
++                     'static_routes': [{'destination': '20.0.0.0/16',
++                                        'nexthop': '10.0.3.253'}],
++                     'snats': [{'external_ip': '10.0.3.1',
++                                'logical_ip': '20.0.0.0/16',
++                                'type': 'snat'}],
++                     'dnat_and_snats': []},
++                    {'name': 'lr-id-b',
++                     'ports': {'xrp-id-b1': ['20.0.1.0/24'],
++                               'orp-id-b2': ['20.0.2.0/24']},
++                     'static_routes': [{'destination': '10.0.0.0/16',
++                                        'nexthop': '20.0.2.253'}],
++                     'snats': [{'external_ip': '20.0.2.1',
++                                'logical_ip': '10.0.0.0/24',
++                                'type': 'snat'}],
++                     'dnat_and_snats': [lra_nat, lrb_nat]}]
++        _nb_idl.get_all_logical_routers_with_rports.return_value = expected
++
++        self.assertRaises(periodics.NeverAgain,
++            self.periodic.update_nat_floating_ip_with_gateway_port_reference)
++
++        _nb_idl.set_nat_rule_in_lrouter.assert_called_once_with(
++            utils.ovn_name('lr-id-b'),
++            lrb_nat['uuid'],
++            gateway_port=lrp.uuid)
+diff --git a/neutron/tests/unit/services/ovn_l3/test_plugin.py b/neutron/tests/unit/services/ovn_l3/test_plugin.py
+index fb686b3..1388581 100644
+--- a/neutron/tests/unit/services/ovn_l3/test_plugin.py
++++ b/neutron/tests/unit/services/ovn_l3/test_plugin.py
+@@ -299,6 +299,9 @@
+             'neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.ovn_client.'
+             'OVNClient.delete_mac_binding_entries_by_mac',
+             return_value=1)
++        self._start_mock(
++            'neutron.common.ovn.utils.is_nat_gateway_port_supported',
++            return_value=False)
+ 
+     def test__plugin_driver(self):
+         # No valid mech drivers should raise an exception.
+@@ -1140,6 +1143,58 @@
+             mock.call('NAT', self.fake_ovn_nat_rule.uuid, 'external_mac'),
+             mock.call('NAT', self.fake_ovn_nat_rule.uuid, 'logical_port')])
+ 
++    def _test_create_floatingip_gateway_port_option(self, is_gw_port):
++        _nb_ovn = self.l3_inst._nb_ovn
++        _nb_ovn.is_col_present.return_value = True
++        self._get_floatingip.return_value = {'floating_port_id': 'fip-port-id'}
++        _nb_ovn.get_lrouter_nat_rules.return_value = [
++            {'external_ip': '192.168.0.10', 'logical_ip': '10.0.0.0/24',
++             'type': 'snat', 'uuid': 'uuid1'}]
++        utils.is_nat_gateway_port_supported.return_value = is_gw_port
++
++        lrp = fake_resources.FakeOvsdbRow.create_one_ovsdb_row(
++            attrs={'options': {}})
++        _nb_ovn.get_lrouter_port.return_value = lrp
++        self.l3_inst.get_router.return_value = self.fake_router_with_ext_gw
++
++        self.l3_inst.create_floatingip(self.context, 'floatingip')
++        _nb_ovn.set_nat_rule_in_lrouter.assert_not_called()
++
++        expected_ext_ids = {
++            ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip['id'],
++            ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
++            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']),
++            ovn_const.OVN_FIP_EXT_MAC_KEY: 'aa:aa:aa:aa:aa:aa',
++            ovn_const.OVN_FIP_NET_ID:
++                self.fake_floating_ip['floating_network_id']}
++
++        if is_gw_port:
++            _nb_ovn.add_nat_rule_in_lrouter.assert_called_once_with(
++                '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,
++                gateway_port=lrp.uuid)
++        else:
++            _nb_ovn.add_nat_rule_in_lrouter.assert_called_once_with(
++                '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)
++
++    def test_create_floatingip_with_gateway_port(self):
++        self._test_create_floatingip_gateway_port_option(True)
++
++    def test_create_floatingip_without_gateway_port(self):
++        self._test_create_floatingip_gateway_port_option(False)
++
+     @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.delete_floatingip')
+     def test_delete_floatingip(self, df):
+         nb_ovn = self.l3_inst._nb_ovn
+diff --git a/releasenotes/notes/add-gw-port-support-for-FIP-fb97b85f5928740b.yaml b/releasenotes/notes/add-gw-port-support-for-FIP-fb97b85f5928740b.yaml
+new file mode 100644
+index 0000000..07544b8
+--- /dev/null
++++ b/releasenotes/notes/add-gw-port-support-for-FIP-fb97b85f5928740b.yaml
+@@ -0,0 +1,15 @@
++---
++prelude: >
++    The OVN changed support for NAT rules including a new column and
++    auto-discovery logic to know about logical router gateway ports for NAT on
++    a Logical Router.
++features:
++  - |
++    A new OVN driver Northbound DB column has been added to allow configuring
++    gateway port for NAT rule. If the OVN backend supports the `gateway_port`
++    column in the Northbound DB NAT table, the gateway port uuid will be
++    configured to any floating IP to prevent North/South traffic issues.
++    Previously created FIP rules will be updated only once during the
++    maintenance task to include the gateway_port reference (if OVN backend
++    supports it). In case all FIP entries are already configured no maintenance
++    action will be performed.