From b9067eb9c1c1c7bd8a65e818e894c72b69caafc0 Mon Sep 17 00:00:00 2001 From: Matt Crees Date: Tue, 14 Nov 2023 11:12:43 +0000 Subject: [PATCH 1/2] Revert: Revert: Ensure traffic is not centralized if DVR is enabled --- .../drivers/ovn/mech_driver/mech_driver.py | 10 +++---- .../ovn/mech_driver/test_mech_driver.py | 28 ++++++++++++------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py index c03a89f38db..081ecd80733 100644 --- a/neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py +++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py @@ -1048,7 +1048,7 @@ def get_workers(self): # See doc/source/design/ovn_worker.rst for more details. return [worker.MaintenanceWorker()] - def _update_dnat_entry_if_needed(self, port_id, up=True): + def _update_dnat_entry_if_needed(self, port_id): """Update DNAT entry if using distributed floating ips.""" if not self.nb_ovn: self.nb_ovn = self._ovn_client._nb_idl @@ -1069,9 +1069,9 @@ def _update_dnat_entry_if_needed(self, port_id, up=True): {ovn_const.OVN_FIP_EXT_MAC_KEY: nat['external_mac']})).execute() - if up and ovn_conf.is_ovn_distributed_floating_ip(): - mac = nat['external_ids'][ovn_const.OVN_FIP_EXT_MAC_KEY] - if nat['external_mac'] != mac: + if ovn_conf.is_ovn_distributed_floating_ip(): + mac = nat['external_ids'].get(ovn_const.OVN_FIP_EXT_MAC_KEY) + if mac and nat['external_mac'] != mac: LOG.debug("Setting external_mac of port %s to %s", port_id, mac) self.nb_ovn.db_set( @@ -1139,7 +1139,7 @@ def set_port_status_down(self, port_id): # to prevent another entity from bypassing the block with its own # port status update. LOG.info("OVN reports status down for port: %s", port_id) - self._update_dnat_entry_if_needed(port_id, False) + self._update_dnat_entry_if_needed(port_id) admin_context = n_context.get_admin_context() try: db_port = ml2_db.get_port(admin_context, port_id) diff --git a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py index f8920f15cb9..8f3d5d26757 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py +++ b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py @@ -1112,7 +1112,7 @@ def _test_set_port_status_down(self, is_compute_port=False): resources.PORT, provisioning_blocks.L2_AGENT_ENTITY ) - ude.assert_called_once_with(port1['port']['id'], False) + ude.assert_called_once_with(port1['port']['id']) # If the port does NOT bellong to compute, do not notify Nova # about it's status changes @@ -1157,7 +1157,7 @@ def test_set_port_status_concurrent_delete(self): side_effect=exc) as apc, \ mock.patch.object(self.mech_driver, '_update_dnat_entry_if_needed') as ude: - self.mech_driver.set_port_status_down(port1['port']['id'], False) + self.mech_driver.set_port_status_down(port1['port']['id']) apc.assert_called_once_with( mock.ANY, port1['port']['id'], @@ -2358,9 +2358,10 @@ def test_agent_with_nb_cfg_timestamp_not_timeout(self): self.assertTrue(agent.alive, "Agent of type %s alive=%s" % ( agent.agent_type, agent.alive)) - def _test__update_dnat_entry_if_needed(self, up=True): - ovn_conf.cfg.CONF.set_override( - 'enable_distributed_floating_ip', True, group='ovn') + def _test__update_dnat_entry_if_needed(self, dvr=True): + if dvr: + ovn_conf.cfg.CONF.set_override( + 'enable_distributed_floating_ip', True, group='ovn') port_id = 'fake-port-id' fake_ext_mac_key = 'fake-ext-mac-key' fake_nat_uuid = uuidutils.generate_uuid() @@ -2368,22 +2369,29 @@ def _test__update_dnat_entry_if_needed(self, up=True): attrs={'_uuid': fake_nat_uuid, 'external_ids': { ovn_const.OVN_FIP_EXT_MAC_KEY: fake_ext_mac_key}, 'external_mac': 'aa:aa:aa:aa:aa:aa'}) + fake_db_find = mock.Mock() fake_db_find.execute.return_value = [nat_row] self.nb_ovn.db_find.return_value = fake_db_find - self.mech_driver._update_dnat_entry_if_needed(port_id, up=up) - if up: + + self.mech_driver._update_dnat_entry_if_needed(port_id) + + if dvr: # Assert that we are setting the external_mac in the NAT table self.nb_ovn.db_set.assert_called_once_with( 'NAT', fake_nat_uuid, ('external_mac', fake_ext_mac_key)) + self.nb_ovn.db_clear.assert_not_called() else: + self.nb_ovn.db_set.assert_not_called() # Assert that we are cleaning the external_mac from the NAT table self.nb_ovn.db_clear.assert_called_once_with( 'NAT', fake_nat_uuid, 'external_mac') - def test__update_dnat_entry_if_needed_up(self): + + def test__update_dnat_entry_if_needed_dvr(self): self._test__update_dnat_entry_if_needed() - def test__update_dnat_entry_if_needed_down(self): - self._test__update_dnat_entry_if_needed(up=False) + + def test__update_dnat_entry_if_needed_no_dvr(self): + self._test__update_dnat_entry_if_needed(dvr=False) @mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.' 'ovn_client.OVNClient._get_router_ports') From c5ff5baec637a0d149508d4205f05662ba0e61c0 Mon Sep 17 00:00:00 2001 From: Luis Tomas Bolivar Date: Mon, 13 Nov 2023 16:42:51 +0100 Subject: [PATCH 2/2] Ensure ovn loadbalancer FIPs are centralized upon neutron restarts When neutron server restarts the mac address for NAT entries related to ovn-lb FIPs gets re-added, distributing the traffic that should be centralized and therefore breaking the connectivity. This happens due to the port being down. This patch is ensuring the MAC entry is only being readded in case the port is UP Closes-Bug: #2042938 Change-Id: I6203009750a4e589eeb808f842cb522d61476179 --- .../drivers/ovn/mech_driver/mech_driver.py | 19 +++++------ .../ovn/mech_driver/test_mech_driver.py | 32 ++++++++++++------- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py index 081ecd80733..d743ba80f91 100644 --- a/neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py +++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py @@ -1048,7 +1048,7 @@ def get_workers(self): # See doc/source/design/ovn_worker.rst for more details. return [worker.MaintenanceWorker()] - def _update_dnat_entry_if_needed(self, port_id): + def _update_dnat_entry_if_needed(self, port_id, up=True): """Update DNAT entry if using distributed floating ips.""" if not self.nb_ovn: self.nb_ovn = self._ovn_client._nb_idl @@ -1070,13 +1070,14 @@ def _update_dnat_entry_if_needed(self, port_id): nat['external_mac']})).execute() if ovn_conf.is_ovn_distributed_floating_ip(): - mac = nat['external_ids'].get(ovn_const.OVN_FIP_EXT_MAC_KEY) - if mac and nat['external_mac'] != mac: - LOG.debug("Setting external_mac of port %s to %s", - port_id, mac) - self.nb_ovn.db_set( - 'NAT', nat['_uuid'], ('external_mac', mac)).execute( - check_error=True) + if up: + mac = nat['external_ids'].get(ovn_const.OVN_FIP_EXT_MAC_KEY) + if mac and nat['external_mac'] != mac: + LOG.debug("Setting external_mac of port %s to %s", + port_id, mac) + self.nb_ovn.db_set( + 'NAT', nat['_uuid'], ('external_mac', mac)).execute( + check_error=True) else: if nat['external_mac']: LOG.debug("Clearing up external_mac of port %s", port_id) @@ -1139,7 +1140,7 @@ def set_port_status_down(self, port_id): # to prevent another entity from bypassing the block with its own # port status update. LOG.info("OVN reports status down for port: %s", port_id) - self._update_dnat_entry_if_needed(port_id) + self._update_dnat_entry_if_needed(port_id, False) admin_context = n_context.get_admin_context() try: db_port = ml2_db.get_port(admin_context, port_id) diff --git a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py index 8f3d5d26757..b5d4775ee4d 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py +++ b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py @@ -1112,7 +1112,7 @@ def _test_set_port_status_down(self, is_compute_port=False): resources.PORT, provisioning_blocks.L2_AGENT_ENTITY ) - ude.assert_called_once_with(port1['port']['id']) + ude.assert_called_once_with(port1['port']['id'], False) # If the port does NOT bellong to compute, do not notify Nova # about it's status changes @@ -1164,7 +1164,7 @@ def test_set_port_status_concurrent_delete(self): resources.PORT, provisioning_blocks.L2_AGENT_ENTITY ) - ude.assert_called_once_with(port1['port']['id']) + ude.assert_called_once_with(port1['port']['id'], False) def test_bind_port_unsupported_vnic_type(self): fake_port = fakes.FakePort.create_one_port( @@ -2358,7 +2358,7 @@ def test_agent_with_nb_cfg_timestamp_not_timeout(self): self.assertTrue(agent.alive, "Agent of type %s alive=%s" % ( agent.agent_type, agent.alive)) - def _test__update_dnat_entry_if_needed(self, dvr=True): + def _test__update_dnat_entry_if_needed(self, up=True, dvr=True): if dvr: ovn_conf.cfg.CONF.set_override( 'enable_distributed_floating_ip', True, group='ovn') @@ -2374,25 +2374,33 @@ def _test__update_dnat_entry_if_needed(self, dvr=True): fake_db_find.execute.return_value = [nat_row] self.nb_ovn.db_find.return_value = fake_db_find - self.mech_driver._update_dnat_entry_if_needed(port_id) + self.mech_driver._update_dnat_entry_if_needed(port_id, up=up) - if dvr: + if up and dvr: # Assert that we are setting the external_mac in the NAT table self.nb_ovn.db_set.assert_called_once_with( 'NAT', fake_nat_uuid, ('external_mac', fake_ext_mac_key)) - self.nb_ovn.db_clear.assert_not_called() else: - self.nb_ovn.db_set.assert_not_called() - # Assert that we are cleaning the external_mac from the NAT table - self.nb_ovn.db_clear.assert_called_once_with( - 'NAT', fake_nat_uuid, 'external_mac') + if dvr: + self.nb_ovn.db_set.assert_not_called() + else: + # Assert that we are cleaning the external_mac from the NAT + # table + self.nb_ovn.db_clear.assert_called_once_with( + 'NAT', fake_nat_uuid, 'external_mac') - def test__update_dnat_entry_if_needed_dvr(self): + def test__update_dnat_entry_if_needed_up_dvr(self): self._test__update_dnat_entry_if_needed() - def test__update_dnat_entry_if_needed_no_dvr(self): + def test__update_dnat_entry_if_needed_up_no_dvr(self): self._test__update_dnat_entry_if_needed(dvr=False) + def test__update_dnat_entry_if_needed_down_dvr(self): + self._test__update_dnat_entry_if_needed(up=False) + + def test__update_dnat_entry_if_needed_down_no_dvr(self): + self._test__update_dnat_entry_if_needed(up=False, dvr=False) + @mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.' 'ovn_client.OVNClient._get_router_ports') def _test_update_network_fragmentation(self, new_mtu, expected_opts, grps):