L3 DVR: use fanout when sending dvr arp table update

Sending arp update to each l3 dvr agent one by one on every port
creation is not scalable and causes serious performance degradation
if router is hosted on lots of l3 dvr agents on compute nodes (see
bug report). This increases port creation time and eventually leads
to timeouts in Nova and VMs going to ERROR state.

This patch changes notification to be fanout.
The downside is that with fanout the arp notification will be sent to
each l3 agent, even those not hosting the router. However such agents
will just skip the notification if not hosting the router - this should
be quite cheap.

Closes-Bug: #1614452
Change-Id: I1fb533d7804b131f709b790fc730ed7b97cb5499
This commit is contained in:
Oleg Bondarev 2016-08-18 11:55:33 +03:00
parent 0f3008dd32
commit 4bdab5cf1d
2 changed files with 51 additions and 17 deletions

View File

@ -79,23 +79,10 @@ class L3AgentNotifyAPI(object):
"""Notify arp details to l3 agents hosting router."""
if not router_id:
return
adminContext = (context.is_admin and
context or context.elevated())
plugin = manager.NeutronManager.get_service_plugins().get(
service_constants.L3_ROUTER_NAT)
hosts = plugin.get_hosts_to_notify(adminContext, router_id)
# TODO(murali): replace cast with fanout to avoid performance
# issues at greater scale.
for host in hosts:
log_topic = '%s.%s' % (topics.L3_AGENT, host)
LOG.debug('Casting message %(method)s with topic %(topic)s',
{'topic': log_topic, 'method': method})
dvr_arptable = {'router_id': router_id,
'arp_table': data}
cctxt = self.client.prepare(topic=topics.L3_AGENT,
server=host,
version='1.2')
cctxt.cast(context, method, payload=dvr_arptable)
dvr_arptable = {'router_id': router_id, 'arp_table': data}
LOG.debug('Fanout dvr_arptable update: %s', dvr_arptable)
cctxt = self.client.prepare(fanout=True, version='1.2')
cctxt.cast(context, method, payload=dvr_arptable)
def _notification(self, context, method, router_ids, operation,
shuffle_agents, schedule_routers=True):

View File

@ -0,0 +1,47 @@
# Copyright (c) 2016 OpenStack Foundation.
#
# 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.
import mock
from neutron.api.rpc.agentnotifiers import l3_rpc_agent_api
from neutron.tests import base
class TestL3AgentNotifyAPI(base.BaseTestCase):
def setUp(self):
super(TestL3AgentNotifyAPI, self).setUp()
self.rpc_client_mock = mock.patch(
'neutron.common.rpc.get_client').start().return_value
self.l3_notifier = l3_rpc_agent_api.L3AgentNotifyAPI()
def _test_arp_update(self, method):
arp_table = {'ip_address': '1.1.1.1',
'mac_address': '22:f1:6c:9c:79:4a',
'subnet_id': 'subnet_id'}
router_id = 'router_id'
getattr(self.l3_notifier, method)(mock.Mock(), router_id, arp_table)
self.rpc_client_mock.prepare.assert_called_once_with(
fanout=True, version='1.2')
cctxt = self.rpc_client_mock.prepare.return_value
cctxt.cast.assert_called_once_with(
mock.ANY, method,
payload={'router_id': router_id, 'arp_table': arp_table})
def test_add_arp_entry(self):
self._test_arp_update('add_arp_entry')
def test_del_arp_entry(self):
self._test_arp_update('del_arp_entry')