From 6f06d8fa6684b6cbd41502163acaffc17f98cc8c Mon Sep 17 00:00:00 2001 From: LIU Yulong Date: Tue, 26 Nov 2024 17:37:56 +0800 Subject: [PATCH] Remove port resource change after unplug Ports are removed (unplug) from the br-int, but the ovs-agent resource_cache still have them. If during this removal period, the port attributes changed/updated. Even worse if the update events were not received by the agent, as well as the port plug to the br-int again. The ovs-agent will use the obsolete resource informaction of the port. Something may not be processed well then. So, resource cache should be delete after port removed from br-int. This patch addresses this issue. Closes-Bug: #2089661 Change-Id: Ia628ee196382b0672ce3424d7cb86f29a15a6d4d --- neutron/agent/resource_cache.py | 11 +++++++ .../openvswitch/agent/ovs_neutron_agent.py | 6 ++++ .../agent/test_ovs_neutron_agent.py | 30 +++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/neutron/agent/resource_cache.py b/neutron/agent/resource_cache.py index 265d164d619..faf0f054c4a 100644 --- a/neutron/agent/resource_cache.py +++ b/neutron/agent/resource_cache.py @@ -202,6 +202,17 @@ class RemoteResourceCache: resource_id=resource.id, states=(existing, resource))) + def record_resource_remove(self, rtype, resource_id): + filters = {'id': (resource_id, )} + for i in self._get_query_ids(rtype, filters): + try: + self._satisfied_server_queries.remove(i) + except KeyError: + continue + LOG.debug("Remove resource cache for resource %s: %s", + rtype, resource_id) + self._type_cache(rtype).pop(resource_id, None) + def record_resource_delete(self, context, rtype, resource_id): # deletions are final, record them so we never # accept new data for the same ID. diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py b/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py index a6b6dc3bdba..9b240bbf8e8 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py @@ -773,6 +773,7 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, self._deferred_delete_direct_flows(self.deleted_ports) + rcache_rpc = self.plugin_rpc.remote_resource_cache while self.deleted_ports: port_id = self.deleted_ports.pop() port = self.int_br.get_vif_port_by_id(port_id) @@ -788,6 +789,7 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, # removing the port from the bridge at the same time self.port_dead(port, log_errors=False) self.port_unbound(port_id) + rcache_rpc.record_resource_remove(resources.PORT, port_id) # Flush firewall rules after ports are put on dead VLAN to be # more secure @@ -2142,9 +2144,13 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, failed_devices = set(devices_down.get('failed_devices_down')) LOG.debug("Port removal failed for %s", failed_devices) self._deferred_delete_direct_flows(devices) + rcache_rpc = self.plugin_rpc.remote_resource_cache for device in devices: self.ext_manager.delete_port(self.context, {'port_id': device}) self.port_unbound(device) + if device: + rcache_rpc.record_resource_remove(resources.PORT, device) + return failed_devices def treat_ancillary_devices_removed(self, devices): diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py index ffa598e8ed1..fa6c3a81a31 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py @@ -1095,6 +1095,23 @@ class TestOvsNeutronAgent: self.assertEqual({dev_mock}, failed_devices.get('removed')) + def test_treat_devices_removed_rcache_removed(self): + device_id = 'dev_id' + rcache = self.agent.plugin_rpc.remote_resource_cache + rcache._cache_by_type_and_id['Port'][device_id] = 1 + with mock.patch.object(self.agent.plugin_rpc, + 'update_device_list', + return_value={'devices_up': [], + 'devices_down': [], + 'failed_devices_up': [], + 'failed_devices_down': []}): + with mock.patch.object(self.agent.int_br, + 'get_vif_port_by_id', + return_value=None): + self.agent.treat_devices_removed([device_id]) + self.assertNotIn(device_id, + rcache._cache_by_type_and_id['Port']) + def test_treat_devices_removed_ext_delete_port(self): port_id = 'fake-id' @@ -1561,6 +1578,19 @@ class TestOvsNeutronAgent: log_errors=False) int_br.drop_port.assert_called_once_with(in_port=vif.ofport) + def test_port_delete_rcache_removed(self): + vif = FakeVif() + port_id = 'id' + rcache = self.agent.plugin_rpc.remote_resource_cache + rcache._cache_by_type_and_id['Port'][port_id] = 1 + with mock.patch.object(self.agent, 'int_br') as int_br: + int_br.get_vif_by_port_id.return_value = vif.port_name + int_br.get_vif_port_by_id.return_value = vif + self.agent.port_delete("unused_context", + port_id=port_id) + self.agent.process_deleted_ports(port_info={}) + self.assertNotIn(port_id, rcache._cache_by_type_and_id['Port']) + def test_port_delete_removed_port(self): with mock.patch.object(self.agent, 'int_br') as int_br: self.agent.port_delete("unused_context",