Update network object in DHCP agent with router interface changes

In Dnsmasq, the function get_isolated_subnets() returns a list of
subnets in a network if the subnet is not connected to a router.

The implementation of this function checks all the router interface
ports in a cached network object passed from DHCP agent. But the
cached network object is not updated when a subnet is attached to
or detached from a router.

This patch fixes that by adding callback functions in DHCP RPC client
to notify DHCP agent when changes happen on router interfaces.

Conflicts:
	neutron/api/rpc/agentnotifiers/dhcp_rpc_agent_api.py
	neutron/db/l3_db.py
	neutron/db/l3_dvr_db.py

Local changes:
- in a unit test, moved callback failure setup to after interface is
  added to a router, because now we call to register.notify in
  add_router_interface too, and the test intends to trigger failure mode
  for interface removal only.

Closes-Bug: #1554825
Change-Id: Ifaab163f49e0d1c5cb3eba6efa96214104647e4e
(cherry picked from commit aa00310eef)
This commit is contained in:
Shih-Hao Li 2016-03-11 15:13:34 -08:00 committed by Armando Migliaccio
parent 97bb4b0795
commit 59236a0148
5 changed files with 73 additions and 9 deletions

View File

@ -17,6 +17,9 @@ from oslo_config import cfg
from oslo_log import log as logging
import oslo_messaging
from neutron.callbacks import events
from neutron.callbacks import registry
from neutron.callbacks import resources
from neutron.common import constants
from neutron.common import rpc as n_rpc
from neutron.common import topics
@ -51,6 +54,11 @@ class DhcpAgentNotifyAPI(object):
self._plugin = plugin
target = oslo_messaging.Target(topic=topic, version='1.0')
self.client = n_rpc.get_client(target)
# register callbacks for router interface changes
registry.subscribe(self._after_router_interface_created,
resources.ROUTER_INTERFACE, events.AFTER_CREATE)
registry.subscribe(self._after_router_interface_deleted,
resources.ROUTER_INTERFACE, events.AFTER_DELETE)
@property
def plugin(self):
@ -169,6 +177,18 @@ class DhcpAgentNotifyAPI(object):
self._cast_message(context, 'agent_updated',
{'admin_state_up': admin_state_up}, host)
def _after_router_interface_created(self, resource, event, trigger,
**kwargs):
self._notify_agents(kwargs['context'], 'port_create_end',
{'port': kwargs['port']},
kwargs['port']['network_id'])
def _after_router_interface_deleted(self, resource, event, trigger,
**kwargs):
self._notify_agents(kwargs['context'], 'port_delete_end',
{'port_id': kwargs['port']['id']},
kwargs['port']['network_id'])
def notify(self, context, data, method_name):
# data is {'key' : 'value'} with only one key
if method_name not in self.VALID_METHOD_NAMES:

View File

@ -664,6 +664,12 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
)
context.session.add(router_port)
registry.notify(resources.ROUTER_INTERFACE,
events.AFTER_CREATE,
self,
context=context,
port=port)
return self._make_router_interface_info(
router.id, port['tenant_id'], port['id'], subnets[-1]['id'],
[subnet['id'] for subnet in subnets])
@ -768,6 +774,11 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
port, subnets = self._remove_interface_by_subnet(
context, router_id, subnet_id, device_owner)
registry.notify(resources.ROUTER_INTERFACE,
events.AFTER_DELETE,
self,
context=context,
port=port)
return self._make_router_interface_info(router_id, port['tenant_id'],
port['id'], subnets[0]['id'],
[subnet['id'] for subnet in

View File

@ -292,6 +292,20 @@ class L3_NAT_with_dvr_db_mixin(l3_db.L3_NAT_db_mixin,
[subnet['id'] for subnet in subnets])
self.notify_router_interface_action(
context, router_interface_info, 'add')
if router.gw_port:
gw_network_id = router.gw_port.network_id
gw_ips = [x['ip_address'] for x in router.gw_port.fixed_ips]
registry.notify(resources.ROUTER_INTERFACE,
events.AFTER_CREATE,
self,
context=context,
network_id=gw_network_id,
gateway_ips=gw_ips,
cidrs=[x['cidr'] for x in subnets],
port_id=port['id'],
router_id=router_id,
port=port,
interface_info=interface_info)
return router_interface_info
def _port_has_ipv6_address(self, port):

View File

@ -138,20 +138,25 @@ class TestDhcpAgentNotifyAPI(base.BaseTestCase):
mock.ANY, 'foo_network_id')
self.assertEqual(1, self.mock_fanout.call_count)
def _test__notify_agents(self, method,
expected_scheduling=0, expected_casts=0):
def _test__notify_agents_with_function(
self, function, expected_scheduling=0, expected_casts=0):
with mock.patch.object(self.notifier, '_schedule_network') as f:
with mock.patch.object(self.notifier, '_get_enabled_agents') as g:
agent = agents_db.Agent()
agent.admin_state_up = True
agent.heartbeat_timestamp = timeutils.utcnow()
g.return_value = [agent]
dummy_payload = {'port': {}}
self.notifier._notify_agents(mock.Mock(), method,
dummy_payload, 'foo_network_id')
function()
self.assertEqual(expected_scheduling, f.call_count)
self.assertEqual(expected_casts, self.mock_cast.call_count)
def _test__notify_agents(self, method,
expected_scheduling=0, expected_casts=0):
self._test__notify_agents_with_function(
lambda: self.notifier._notify_agents(
mock.Mock(), method, {'port': {}}, 'foo_network_id'),
expected_scheduling, expected_casts)
def test__notify_agents_cast_required_with_scheduling(self):
self._test__notify_agents('port_create_end',
expected_scheduling=1, expected_casts=1)
@ -168,6 +173,20 @@ class TestDhcpAgentNotifyAPI(base.BaseTestCase):
self._test__notify_agents('network_create_end',
expected_scheduling=0, expected_casts=0)
def test__notify_agents_with_router_interface_add(self):
self._test__notify_agents_with_function(
lambda: self.notifier._after_router_interface_created(
mock.ANY, mock.ANY, mock.ANY, context=mock.Mock(),
port={'id': 'foo_port_id', 'network_id': 'foo_network_id'}),
expected_scheduling=1, expected_casts=1)
def test__notify_agents_with_router_interface_delete(self):
self._test__notify_agents_with_function(
lambda: self.notifier._after_router_interface_deleted(
mock.ANY, mock.ANY, mock.ANY, context=mock.Mock(),
port={'id': 'foo_port_id', 'network_id': 'foo_network_id'}),
expected_scheduling=0, expected_casts=1)
def test__fanout_message(self):
self.notifier._fanout_message(mock.ANY, mock.ANY, mock.ANY)
self.assertEqual(1, self.mock_fanout.call_count)

View File

@ -1619,6 +1619,10 @@ class L3NatTestCaseBase(L3NatTestCaseMixin):
with self.router() as r,\
self.subnet() as s,\
mock.patch.object(registry, 'notify') as notify:
self._router_interface_action('add',
r['router']['id'],
s['subnet']['id'],
None)
errors = [
exceptions.NotificationError(
'foo_callback_id', n_exc.InUse()),
@ -1628,10 +1632,6 @@ class L3NatTestCaseBase(L3NatTestCaseMixin):
notify.side_effect = [
exceptions.CallbackFailure(errors=errors), None
]
self._router_interface_action('add',
r['router']['id'],
s['subnet']['id'],
None)
self._router_interface_action(
'remove',
r['router']['id'],