From c8c07d6f47099bbe6070369db2d70a6294a9be33 Mon Sep 17 00:00:00 2001 From: Eugene Nikanorov Date: Tue, 24 Feb 2015 15:35:22 +0300 Subject: [PATCH] Avoid ObjectDeletedError while accessing deleted binding In some cases access to db object's attribute triggers its refresh. When the object has been already removed from the database, it could lead to ObjectDeletedError being thrown. Unit test was added to cover code path where the issue occurs however issue doesn't seem to be reproducible with sqlite. Change-Id: I0d6f9a91572c89cf7da1b66b880aeaa6b4b1987e Closes-Bug: #1424593 --- neutron/db/agentschedulers_db.py | 12 +++++++----- neutron/tests/unit/test_dhcp_scheduler.py | 15 +++++++++++++-- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/neutron/db/agentschedulers_db.py b/neutron/db/agentschedulers_db.py index 11728e54e..4cc645d85 100644 --- a/neutron/db/agentschedulers_db.py +++ b/neutron/db/agentschedulers_db.py @@ -259,6 +259,10 @@ class DhcpAgentSchedulerDbMixin(dhcpagentscheduler {'network': binding.network_id, 'agent': binding.dhcp_agent_id, 'dead_time': agent_dead_limit}) + # save binding object to avoid ObjectDeletedError + # in case binding is concurrently deleted from the DB + saved_binding = {'net': binding.network_id, + 'agent': binding.dhcp_agent_id} try: self.remove_network_from_dhcp_agent(context, binding.dhcp_agent_id, @@ -267,19 +271,17 @@ class DhcpAgentSchedulerDbMixin(dhcpagentscheduler # measures against concurrent operation LOG.debug("Network %(net)s already removed from DHCP agent " "%(agent)s", - {'net': binding.network_id, - 'agent': binding.dhcp_agent_id}) + saved_binding) # still continue and allow concurrent scheduling attempt except Exception: LOG.exception(_LE("Unexpected exception occured while " "removing network %(net)s from agent " "%(agent)s"), - {'net': binding.network_id, - 'agent': binding.dhcp_agent_id}) + saved_binding) if cfg.CONF.network_auto_schedule: self._schedule_network( - context, binding.network_id, dhcp_notifier) + context, saved_binding['net'], dhcp_notifier) def get_dhcp_agents_hosting_networks( self, context, network_ids, active=None): diff --git a/neutron/tests/unit/test_dhcp_scheduler.py b/neutron/tests/unit/test_dhcp_scheduler.py index 140710237..0daf26b66 100644 --- a/neutron/tests/unit/test_dhcp_scheduler.py +++ b/neutron/tests/unit/test_dhcp_scheduler.py @@ -26,6 +26,7 @@ from neutron import context from neutron.db import agents_db from neutron.db import agentschedulers_db as sched_db from neutron.db import models_v2 +from neutron.extensions import dhcpagentscheduler from neutron.scheduler import dhcp_agent_scheduler from neutron.tests.unit import testlib_api @@ -216,11 +217,13 @@ class TestNetworksFailover(TestDhcpSchedulerBaseTestCase, notifier.network_added_to_agent.assert_called_with( mock.ANY, self.network_id, agents[1].host) - def test_reschedule_network_from_down_agent_failed(self): + def _test_failed_rescheduling(self, rn_side_effect=None): agents = self._create_and_set_agents_down(['host-a'], 1) self._test_schedule_bind_network([agents[0]], self.network_id) with contextlib.nested( - mock.patch.object(self, 'remove_network_from_dhcp_agent'), + mock.patch.object( + self, 'remove_network_from_dhcp_agent', + side_effect=rn_side_effect), mock.patch.object(self, 'schedule_network', return_value=None), mock.patch.object(self, 'get_network', create=True, @@ -233,6 +236,14 @@ class TestNetworksFailover(TestDhcpSchedulerBaseTestCase, sch.assert_called_with(mock.ANY, {'id': self.network_id}) self.assertFalse(notifier.network_added_to_agent.called) + def test_reschedule_network_from_down_agent_failed(self): + self._test_failed_rescheduling() + + def test_reschedule_network_from_down_agent_concurrent_removal(self): + self._test_failed_rescheduling( + rn_side_effect=dhcpagentscheduler.NetworkNotHostedByDhcpAgent( + network_id='foo', agent_id='bar')) + def test_filter_bindings(self): bindings = [ sched_db.NetworkDhcpAgentBinding(network_id='foo1', -- 2.45.2