1 From 7f1af65475d5c3c1ea5440116ed1f03a186663ff Mon Sep 17 00:00:00 2001
2 From: Maru Newby <marun@redhat.com>
3 Date: Tue, 10 Dec 2013 16:10:42 +0000
4 Subject: [PATCH 1/1] Send DHCP notifications regardless of agent status
6 The Neutron service, when under load, may not be able to process
7 agent heartbeats in a timely fashion. This can result in
8 agents being erroneously considered inactive. Previously, DHCP
9 notifications for which active agents could not be found were
10 silently dropped. This change ensures that notifications for
11 a given network are sent to agents even if those agents do not
14 Additionally, if no enabled dhcp agents can be found for a given
15 network, an error will be logged. Raising an exception might be
16 preferable, but has such a large testing impact that it will be
17 submitted as a separate patch if deemed necessary.
20 (cherry picked from commit 522f9f94681de5903422cfde11b93f5c0e71e532)
22 Change-Id: Id3e639d9cf3d16708fd66a4baebd3fbeeed3dde8
24 .../api/rpc/agentnotifiers/dhcp_rpc_agent_api.py | 35 ++++++++--
25 neutron/db/agents_db.py | 4 ++
26 neutron/tests/unit/api/__init__.py | 0
27 neutron/tests/unit/api/rpc/__init__.py | 0
28 .../tests/unit/api/rpc/agentnotifiers/__init__.py | 0
29 .../rpc/agentnotifiers/test_dhcp_rpc_agent_api.py | 76 ++++++++++++++++++++++
30 6 files changed, 108 insertions(+), 7 deletions(-)
31 create mode 100644 neutron/tests/unit/api/__init__.py
32 create mode 100644 neutron/tests/unit/api/rpc/__init__.py
33 create mode 100644 neutron/tests/unit/api/rpc/agentnotifiers/__init__.py
34 create mode 100644 neutron/tests/unit/api/rpc/agentnotifiers/test_dhcp_rpc_agent_api.py
36 diff --git a/neutron/api/rpc/agentnotifiers/dhcp_rpc_agent_api.py b/neutron/api/rpc/agentnotifiers/dhcp_rpc_agent_api.py
37 index 1086a9e..4ed724d 100644
38 --- a/neutron/api/rpc/agentnotifiers/dhcp_rpc_agent_api.py
39 +++ b/neutron/api/rpc/agentnotifiers/dhcp_rpc_agent_api.py
40 @@ -43,12 +43,11 @@ class DhcpAgentNotifyAPI(proxy.RpcProxy):
41 super(DhcpAgentNotifyAPI, self).__init__(
42 topic=topic, default_version=self.BASE_RPC_API_VERSION)
44 - def _get_dhcp_agents(self, context, network_id):
45 + def _get_enabled_dhcp_agents(self, context, network_id):
46 + """Return enabled dhcp agents associated with the given network."""
47 plugin = manager.NeutronManager.get_plugin()
48 - dhcp_agents = plugin.get_dhcp_agents_hosting_networks(
49 - context, [network_id], active=True)
50 - return [(dhcp_agent.host, dhcp_agent.topic) for
51 - dhcp_agent in dhcp_agents]
52 + agents = plugin.get_dhcp_agents_hosting_networks(context, [network_id])
53 + return [x for x in agents if x.admin_state_up]
55 def _notification_host(self, context, method, payload, host):
56 """Notify the agent on host."""
57 @@ -76,11 +75,33 @@ class DhcpAgentNotifyAPI(proxy.RpcProxy):
58 context, 'network_create_end',
59 {'network': {'id': network_id}},
61 - for (host, topic) in self._get_dhcp_agents(context, network_id):
62 + agents = self._get_enabled_dhcp_agents(context, network_id)
64 + LOG.error(_("No DHCP agents are associated with network "
65 + "'%(net_id)s'. Unable to send notification "
66 + "for '%(method)s' with payload: %(payload)s"),
68 + 'net_id': network_id,
73 + active_agents = [x for x in agents if x.is_active]
74 + if active_agents != agents:
75 + LOG.warning(_("Only %(active)d of %(total)d DHCP agents "
76 + "associated with network '%(net_id)s' are "
77 + "marked as active, so notifications may "
78 + "be sent to inactive agents."),
80 + 'active': len(active_agents),
81 + 'total': len(agents),
82 + 'net_id': network_id,
84 + for agent in agents:
86 context, self.make_msg(method,
88 - topic='%s.%s' % (topic, host))
89 + topic='%s.%s' % (agent.topic, agent.host))
91 # besides the non-agentscheduler plugin,
92 # There is no way to query who is hosting the network
93 diff --git a/neutron/db/agents_db.py b/neutron/db/agents_db.py
94 index e095a4c..fdcc6d3 100644
95 --- a/neutron/db/agents_db.py
96 +++ b/neutron/db/agents_db.py
97 @@ -60,6 +60,10 @@ class Agent(model_base.BASEV2, models_v2.HasId):
98 # configurations: a json dict string, I think 4095 is enough
99 configurations = sa.Column(sa.String(4095), nullable=False)
102 + def is_active(self):
103 + return not AgentDbMixin.is_agent_down(self.heartbeat_timestamp)
106 class AgentDbMixin(ext_agent.AgentPluginBase):
107 """Mixin class to add agent extension to db_plugin_base_v2."""
108 diff --git a/neutron/tests/unit/api/__init__.py b/neutron/tests/unit/api/__init__.py
110 index 0000000..e69de29
111 diff --git a/neutron/tests/unit/api/rpc/__init__.py b/neutron/tests/unit/api/rpc/__init__.py
113 index 0000000..e69de29
114 diff --git a/neutron/tests/unit/api/rpc/agentnotifiers/__init__.py b/neutron/tests/unit/api/rpc/agentnotifiers/__init__.py
116 index 0000000..e69de29
117 diff --git a/neutron/tests/unit/api/rpc/agentnotifiers/test_dhcp_rpc_agent_api.py b/neutron/tests/unit/api/rpc/agentnotifiers/test_dhcp_rpc_agent_api.py
119 index 0000000..b175d34
121 +++ b/neutron/tests/unit/api/rpc/agentnotifiers/test_dhcp_rpc_agent_api.py
123 +# Copyright (c) 2013 Red Hat, Inc.
125 +# Licensed under the Apache License, Version 2.0 (the "License");
126 +# you may not use this file except in compliance with the License.
127 +# You may obtain a copy of the License at
129 +# http://www.apache.org/licenses/LICENSE-2.0
131 +# Unless required by applicable law or agreed to in writing, software
132 +# distributed under the License is distributed on an "AS IS" BASIS,
133 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
135 +# See the License for the specific language governing permissions and
136 +# limitations under the License.
142 +from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api
143 +from neutron.common import utils
144 +from neutron import manager
145 +from neutron.tests import base
148 +class TestDhcpAgentNotifyAPI(base.BaseTestCase):
151 + super(TestDhcpAgentNotifyAPI, self).setUp()
152 + self.notify = dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
154 + def test_get_enabled_dhcp_agents_filters_disabled_agents(self):
155 + disabled_agent = mock.Mock()
156 + disabled_agent.admin_state_up = False
157 + enabled_agent = mock.Mock()
158 + with mock.patch.object(manager.NeutronManager,
159 + 'get_plugin') as mock_get_plugin:
160 + mock_get_plugin.return_value = mock_plugin = mock.Mock()
161 + with mock.patch.object(
162 + mock_plugin, 'get_dhcp_agents_hosting_networks'
163 + ) as mock_get_agents:
164 + mock_get_agents.return_value = [disabled_agent, enabled_agent]
165 + result = self.notify._get_enabled_dhcp_agents('ctx', 'net_id')
166 + self.assertEqual(result, [enabled_agent])
168 + def _test_notification(self, agents):
169 + with contextlib.nested(
170 + mock.patch.object(manager.NeutronManager, 'get_plugin'),
171 + mock.patch.object(utils, 'is_extension_supported'),
172 + mock.patch.object(self.notify, '_get_enabled_dhcp_agents')
173 + ) as (m1, m2, mock_get_agents):
174 + mock_get_agents.return_value = agents
175 + self.notify._notification(mock.Mock(), 'foo', {}, 'net_id')
177 + def test_notification_sends_cast_for_enabled_agent(self):
178 + with mock.patch.object(self.notify, 'cast') as mock_cast:
179 + self._test_notification([mock.Mock()])
180 + self.assertEqual(mock_cast.call_count, 1)
182 + def test_notification_logs_error_for_no_enabled_agents(self):
183 + with mock.patch.object(self.notify, 'cast') as mock_cast:
184 + with mock.patch.object(dhcp_rpc_agent_api.LOG,
185 + 'error') as mock_log:
186 + self._test_notification([])
187 + self.assertEqual(mock_cast.call_count, 0)
188 + self.assertEqual(mock_log.call_count, 1)
190 + def test_notification_logs_warning_for_inactive_agents(self):
191 + agent = mock.Mock()
192 + agent.is_active = False
193 + with mock.patch.object(self.notify, 'cast') as mock_cast:
194 + with mock.patch.object(dhcp_rpc_agent_api.LOG,
195 + 'warning') as mock_log:
196 + self._test_notification([agent])
197 + self.assertEqual(mock_cast.call_count, 1)
198 + self.assertEqual(mock_log.call_count, 1)