diff --git a/neutron/plugins/ml2/drivers/ovn/agent/neutron_agent.py b/neutron/plugins/ml2/drivers/ovn/agent/neutron_agent.py index 6e3b6bfc07f..6f0d69eec76 100644 --- a/neutron/plugins/ml2/drivers/ovn/agent/neutron_agent.py +++ b/neutron/plugins/ml2/drivers/ovn/agent/neutron_agent.py @@ -13,6 +13,7 @@ # import abc +import copy import datetime from oslo_config import cfg @@ -229,7 +230,10 @@ class AgentCache: self.driver = driver def __iter__(self): - return iter(self.agents.values()) + # Copying self.agents will avoid any issue during the iteration if an + # agent is added or deleted. + _agents = copy.copy(self.agents) + return iter(_agents.values()) def __getitem__(self, key): return self.agents[key] @@ -253,12 +257,12 @@ class AgentCache: agent_ids = {cls.id_from_chassis_private(chassis_private) for cls in NeutronAgent.types.values()} # Return the cached agents of agent_ids whose keys are in the cache - return (self.agents[id_] for id_ in agent_ids & self.agents.keys()) + return (agent for agent in self if agent.agent_id in agent_ids) def get_agents(self, filters=None): filters = filters or {} agent_list = [] - for agent in self.agents.values(): + for agent in self: agent_dict = agent.as_dict() if all(agent_dict[k] in v for k, v in filters.items()): agent_list.append(agent) diff --git a/neutron/tests/unit/plugins/ml2/drivers/ovn/agent/__init__.py b/neutron/tests/unit/plugins/ml2/drivers/ovn/agent/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/neutron/tests/unit/plugins/ml2/drivers/ovn/agent/test_neutron_agent.py b/neutron/tests/unit/plugins/ml2/drivers/ovn/agent/test_neutron_agent.py new file mode 100644 index 00000000000..ed434cab186 --- /dev/null +++ b/neutron/tests/unit/plugins/ml2/drivers/ovn/agent/test_neutron_agent.py @@ -0,0 +1,69 @@ +# Copyright 2022 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from unittest import mock + +import eventlet + +from neutron.common.ovn import constants as ovn_const +from neutron.plugins.ml2.drivers.ovn.agent import neutron_agent +from neutron.tests import base +from neutron.tests.unit import fake_resources as fakes + + +class AgentCacheTestCase(base.BaseTestCase): + + def setUp(self): + super().setUp() + self.agent_cache = neutron_agent.AgentCache(driver=mock.ANY) + self.addCleanup(self._clean_agent_cache) + self.names_ref = [] + for i in range(10): # Add 10 agents. + chassis_private = fakes.FakeOvsdbRow.create_one_ovsdb_row( + attrs={'name': 'chassis' + str(i)}) + self.agent_cache.update(ovn_const.OVN_CONTROLLER_AGENT, + chassis_private) + self.names_ref.append('chassis' + str(i)) + + def _clean_agent_cache(self): + self.agent_cache.agents = {} + + def _list_agents(self): + self.names_read = [] + for idx, agent in enumerate(self.agent_cache): + self.names_read.append(agent.agent_id) + if idx == 5: # Swap to "_add_and_delete_agents" thread. + eventlet.sleep(0) + + def _add_and_delete_agents(self): + del self.agent_cache['chassis8'] + chassis_private = fakes.FakeOvsdbRow.create_one_ovsdb_row( + attrs={'name': 'chassis10'}) + self.agent_cache.update(ovn_const.OVN_CONTROLLER_AGENT, + chassis_private) + + def test_update_while_iterating_agents(self): + pool = eventlet.GreenPool(2) + pool.spawn(self._list_agents) + pool.spawn(self._add_and_delete_agents) + pool.waitall() + self.assertEqual(self.names_ref, self.names_read) + + def test_agents_by_chassis_private(self): + chassis_private = fakes.FakeOvsdbRow.create_one_ovsdb_row( + attrs={'name': 'chassis5'}) + agents = self.agent_cache.agents_by_chassis_private(chassis_private) + agents = list(agents) + self.assertEqual(1, len(agents)) + self.assertEqual('chassis5', agents[0].agent_id)