DVR: notify specific agent when creating floating ip
Currently when floating ip is created, a lot of useless action is happening: floating ip router is scheduled, all l3 agents where router is scheduled are notified about router update, all agents request full router info from server. All this becomes a big performance problem at scale with lots of compute nodes. In fact on (associated) Floating IP creation we really need to notify specific l3 agent on compute node where associated VM port is located and do not need to schedule router and bother other agents where rourter is scheduled. This should significally decrease unneeded load on neutron server at scale. Partial-Bug: #1486828 Change-Id: I0cbe8c51c3714e6cbdc48ca37135b783f8014905 (cherry picked from commit865dc46bc5
) ========= DVR: Notify specific agent when update floatingip The L3 agent was determined when update floatingip. So notify the specific agent rather than notify all agents. This will save some RPC resources. This is only for DVR routers. Legacy and HA routers notify only the relevant agents. This reproposes commit52e91f48f2
which was reverted by commita2f7e0343a
because of Ironic gate failures. Now the patch preserves original behavior for legacy routers and should not break Ironic tests. Partial-Bug: #1486828 Related-Bug: #1507602 Change-Id: I4ef7a69ad033b979ea0e29620a4febfe5e0c30dd (cherry picked from commit8a51ddf3be
) ========= Use admin context when requesting floating ip's router info Currently it is possible for admin to create router and add any tenant subnet to it, thus connecting the subnet to the external network. In this case tenant user can assign floating ips to its VMs though it does not own the router. For proper notification we need to get router info using admin context. Closes-Bug: #1507602 Change-Id: I17330ddca577d15e42c13ef0af96d56e6f20abd7 ========= Three patches are squashed because the first one introduced a regression when some notifications were not sent in particular scenarios. Two consequent patches target the regression. Change-Id: I0cbe8c51c3714e6cbdc48ca37135b783f8014905
This commit is contained in:
parent
e1010d0fb3
commit
6902c87048
|
@ -38,15 +38,15 @@ class L3AgentNotifyAPI(object):
|
|||
target = oslo_messaging.Target(topic=topic, version='1.0')
|
||||
self.client = n_rpc.get_client(target)
|
||||
|
||||
def _notification_host(self, context, method, payload, host,
|
||||
use_call=False,):
|
||||
def _notification_host(self, context, method, host, use_call=False,
|
||||
**kwargs):
|
||||
"""Notify the agent that is hosting the router."""
|
||||
LOG.debug('Notify agent at %(host)s the message '
|
||||
'%(method)s', {'host': host,
|
||||
'method': method})
|
||||
cctxt = self.client.prepare(server=host)
|
||||
rpc_method = cctxt.call if use_call else cctxt.cast
|
||||
rpc_method(context, method, payload=payload)
|
||||
rpc_method(context, method, **kwargs)
|
||||
|
||||
def _agent_notification(self, context, method, router_ids, operation,
|
||||
shuffle_agents):
|
||||
|
@ -133,9 +133,8 @@ class L3AgentNotifyAPI(object):
|
|||
cctxt.cast(context, method, router_id=router_id)
|
||||
|
||||
def agent_updated(self, context, admin_state_up, host):
|
||||
self._notification_host(context, 'agent_updated',
|
||||
{'admin_state_up': admin_state_up},
|
||||
host)
|
||||
self._notification_host(context, 'agent_updated', host,
|
||||
payload={'admin_state_up': admin_state_up})
|
||||
|
||||
def router_deleted(self, context, router_id):
|
||||
self._notification_fanout(context, 'router_deleted', router_id)
|
||||
|
@ -155,13 +154,17 @@ class L3AgentNotifyAPI(object):
|
|||
operation, arp_table)
|
||||
|
||||
def router_removed_from_agent(self, context, router_id, host):
|
||||
self._notification_host(context, 'router_removed_from_agent',
|
||||
{'router_id': router_id}, host)
|
||||
self._notification_host(context, 'router_removed_from_agent', host,
|
||||
payload={'router_id': router_id})
|
||||
|
||||
def router_added_to_agent(self, context, router_ids, host):
|
||||
# need to use call here as we want to be sure agent received
|
||||
# notification and router will not be "lost". However using call()
|
||||
# itself is not a guarantee, calling code should handle exceptions and
|
||||
# retry
|
||||
self._notification_host(context, 'router_added_to_agent',
|
||||
router_ids, host, use_call=True)
|
||||
self._notification_host(context, 'router_added_to_agent', host,
|
||||
use_call=True, payload=router_ids)
|
||||
|
||||
def routers_updated_on_host(self, context, router_ids, host):
|
||||
self._notification_host(context, 'routers_updated', host,
|
||||
routers=router_ids)
|
||||
|
|
|
@ -938,7 +938,7 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
|
|||
net = self._core_plugin._get_network(context, net_id)
|
||||
return any(s.ip_version == 4 for s in net.subnets)
|
||||
|
||||
def create_floatingip(self, context, floatingip,
|
||||
def _create_floatingip(self, context, floatingip,
|
||||
initial_status=l3_constants.FLOATINGIP_STATUS_ACTIVE):
|
||||
fip = floatingip['floatingip']
|
||||
tenant_id = self._get_tenant_id_for_create(context, fip)
|
||||
|
@ -1002,6 +1002,10 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
|
|||
|
||||
return self._make_floatingip_dict(floatingip_db)
|
||||
|
||||
def create_floatingip(self, context, floatingip,
|
||||
initial_status=l3_constants.FLOATINGIP_STATUS_ACTIVE):
|
||||
return self._create_floatingip(context, floatingip, initial_status)
|
||||
|
||||
def _update_floatingip(self, context, id, floatingip):
|
||||
fip = floatingip['floatingip']
|
||||
with context.session.begin(subtransactions=True):
|
||||
|
|
|
@ -32,7 +32,7 @@ from neutron.db import l3_dvrscheduler_db as l3_dvrsched_db
|
|||
from neutron.db import models_v2
|
||||
from neutron.extensions import l3
|
||||
from neutron.extensions import portbindings
|
||||
from neutron.i18n import _LI
|
||||
from neutron.i18n import _LI, _LW
|
||||
from neutron import manager
|
||||
from neutron.plugins.common import constants
|
||||
from neutron.plugins.common import utils as p_utils
|
||||
|
@ -683,6 +683,45 @@ class L3_NAT_with_dvr_db_mixin(l3_db.L3_NAT_db_mixin,
|
|||
p['id'],
|
||||
l3_port_check=False)
|
||||
|
||||
def create_floatingip(self, context, floatingip,
|
||||
initial_status=l3_const.FLOATINGIP_STATUS_ACTIVE):
|
||||
floating_ip = self._create_floatingip(
|
||||
context, floatingip, initial_status)
|
||||
self._notify_floating_ip_change(context, floating_ip)
|
||||
return floating_ip
|
||||
|
||||
def _notify_floating_ip_change(self, context, floating_ip):
|
||||
router_id = floating_ip['router_id']
|
||||
fixed_port_id = floating_ip['port_id']
|
||||
# we need to notify agents only in case Floating IP is associated
|
||||
if not router_id or not fixed_port_id:
|
||||
return
|
||||
|
||||
try:
|
||||
# using admin context as router may belong to admin tenant
|
||||
router = self._get_router(context.elevated(), router_id)
|
||||
except l3.RouterNotFound:
|
||||
LOG.warning(_LW("Router %s was not found. "
|
||||
"Skipping agent notification."),
|
||||
router_id)
|
||||
return
|
||||
|
||||
if is_distributed_router(router):
|
||||
host = self._get_vm_port_hostid(context, fixed_port_id)
|
||||
self.l3_rpc_notifier.routers_updated_on_host(
|
||||
context, [router_id], host)
|
||||
else:
|
||||
self.notify_router_updated(context, router_id)
|
||||
|
||||
def update_floatingip(self, context, id, floatingip):
|
||||
old_floatingip, floatingip = self._update_floatingip(
|
||||
context, id, floatingip)
|
||||
self._notify_floating_ip_change(context, old_floatingip)
|
||||
if (floatingip['router_id'] != old_floatingip['router_id'] or
|
||||
floatingip['port_id'] != old_floatingip['port_id']):
|
||||
self._notify_floating_ip_change(context, floatingip)
|
||||
return floatingip
|
||||
|
||||
|
||||
def is_distributed_router(router):
|
||||
"""Return True if router to be handled is distributed."""
|
||||
|
|
|
@ -258,3 +258,126 @@ class L3DvrTestCase(ml2_test_base.ML2TestFramework):
|
|||
l3_notifier.routers_updated_on_host.assert_called_once_with(
|
||||
self.context, {router['id']}, HOST)
|
||||
self.assertFalse(l3_notifier.routers_updated.called)
|
||||
|
||||
def _test_create_floating_ip_agent_notification(self, dvr=True):
|
||||
with self.subnet() as ext_subnet,\
|
||||
self.subnet(cidr='20.0.0.0/24') as int_subnet,\
|
||||
self.port(subnet=int_subnet,
|
||||
device_owner='compute:None') as int_port:
|
||||
# make net external
|
||||
ext_net_id = ext_subnet['subnet']['network_id']
|
||||
self._update('networks', ext_net_id,
|
||||
{'network': {external_net.EXTERNAL: True}})
|
||||
|
||||
router = self._create_router(distributed=dvr)
|
||||
self.l3_plugin.update_router(
|
||||
self.context, router['id'],
|
||||
{'router': {
|
||||
'external_gateway_info': {'network_id': ext_net_id}}})
|
||||
self.l3_plugin.add_router_interface(
|
||||
self.context, router['id'],
|
||||
{'subnet_id': int_subnet['subnet']['id']})
|
||||
|
||||
floating_ip = {'floating_network_id': ext_net_id,
|
||||
'router_id': router['id'],
|
||||
'port_id': int_port['port']['id'],
|
||||
'tenant_id': int_port['port']['tenant_id']}
|
||||
with mock.patch.object(
|
||||
self.l3_plugin, '_l3_rpc_notifier') as l3_notif:
|
||||
self.l3_plugin.create_floatingip(
|
||||
self.context, {'floatingip': floating_ip})
|
||||
if dvr:
|
||||
l3_notif.routers_updated_on_host.assert_called_once_with(
|
||||
self.context, [router['id']],
|
||||
int_port['port']['binding:host_id'])
|
||||
self.assertFalse(l3_notif.routers_updated.called)
|
||||
else:
|
||||
l3_notif.routers_updated.assert_called_once_with(
|
||||
self.context, [router['id']], None)
|
||||
self.assertFalse(
|
||||
l3_notif.routers_updated_on_host.called)
|
||||
|
||||
def test_create_floating_ip_agent_notification(self):
|
||||
self._test_create_floating_ip_agent_notification()
|
||||
|
||||
def test_create_floating_ip_agent_notification_non_dvr(self):
|
||||
self._test_create_floating_ip_agent_notification(dvr=False)
|
||||
|
||||
def _test_update_floating_ip_agent_notification(self, dvr=True):
|
||||
with self.subnet() as ext_subnet,\
|
||||
self.subnet(cidr='20.0.0.0/24') as int_subnet1,\
|
||||
self.subnet(cidr='30.0.0.0/24') as int_subnet2,\
|
||||
self.port(subnet=int_subnet1,
|
||||
device_owner='compute:None') as int_port1,\
|
||||
self.port(subnet=int_subnet2,
|
||||
device_owner='compute:None') as int_port2:
|
||||
# locate internal ports on different hosts
|
||||
self.core_plugin.update_port(
|
||||
self.context, int_port1['port']['id'],
|
||||
{'port': {'binding:host_id': 'host1'}})
|
||||
self.core_plugin.update_port(
|
||||
self.context, int_port2['port']['id'],
|
||||
{'port': {'binding:host_id': 'host2'}})
|
||||
# and create l3 agents on corresponding hosts
|
||||
helpers.register_l3_agent(host='host1',
|
||||
agent_mode=l3_const.L3_AGENT_MODE_DVR)
|
||||
helpers.register_l3_agent(host='host2',
|
||||
agent_mode=l3_const.L3_AGENT_MODE_DVR)
|
||||
|
||||
# make net external
|
||||
ext_net_id = ext_subnet['subnet']['network_id']
|
||||
self._update('networks', ext_net_id,
|
||||
{'network': {external_net.EXTERNAL: True}})
|
||||
|
||||
router1 = self._create_router(distributed=dvr)
|
||||
router2 = self._create_router(distributed=dvr)
|
||||
for router in (router1, router2):
|
||||
self.l3_plugin.update_router(
|
||||
self.context, router['id'],
|
||||
{'router': {
|
||||
'external_gateway_info': {'network_id': ext_net_id}}})
|
||||
self.l3_plugin.add_router_interface(
|
||||
self.context, router1['id'],
|
||||
{'subnet_id': int_subnet1['subnet']['id']})
|
||||
self.l3_plugin.add_router_interface(
|
||||
self.context, router2['id'],
|
||||
{'subnet_id': int_subnet2['subnet']['id']})
|
||||
|
||||
floating_ip = {'floating_network_id': ext_net_id,
|
||||
'router_id': router1['id'],
|
||||
'port_id': int_port1['port']['id'],
|
||||
'tenant_id': int_port1['port']['tenant_id']}
|
||||
floating_ip = self.l3_plugin.create_floatingip(
|
||||
self.context, {'floatingip': floating_ip})
|
||||
|
||||
with mock.patch.object(
|
||||
self.l3_plugin, '_l3_rpc_notifier') as l3_notif:
|
||||
updated_floating_ip = {'router_id': router2['id'],
|
||||
'port_id': int_port2['port']['id']}
|
||||
self.l3_plugin.update_floatingip(
|
||||
self.context, floating_ip['id'],
|
||||
{'floatingip': updated_floating_ip})
|
||||
if dvr:
|
||||
self.assertEqual(
|
||||
2, l3_notif.routers_updated_on_host.call_count)
|
||||
expected_calls = [
|
||||
mock.call(self.context, [router1['id']], 'host1'),
|
||||
mock.call(self.context, [router2['id']], 'host2')]
|
||||
l3_notif.routers_updated_on_host.assert_has_calls(
|
||||
expected_calls)
|
||||
self.assertFalse(l3_notif.routers_updated.called)
|
||||
else:
|
||||
self.assertEqual(
|
||||
2, l3_notif.routers_updated.call_count)
|
||||
expected_calls = [
|
||||
mock.call(self.context, [router1['id']], None),
|
||||
mock.call(self.context, [router2['id']], None)]
|
||||
l3_notif.routers_updated.assert_has_calls(
|
||||
expected_calls)
|
||||
self.assertFalse(l3_notif.routers_updated_on_host.called)
|
||||
|
||||
def test_update_floating_ip_agent_notification(self):
|
||||
self._test_update_floating_ip_agent_notification()
|
||||
|
||||
def test_update_floating_ip_agent_notification_non_dvr(self):
|
||||
self._test_update_floating_ip_agent_notification(dvr=False)
|
||||
|
|
|
@ -1874,6 +1874,35 @@ class L3NatTestCaseBase(L3NatTestCaseMixin):
|
|||
self.assertIsNotNone(body['floatingip']['fixed_ip_address'])
|
||||
self.assertIsNotNone(body['floatingip']['router_id'])
|
||||
|
||||
def test_create_floatingip_non_admin_context_agent_notification(self):
|
||||
plugin = manager.NeutronManager.get_service_plugins()[
|
||||
service_constants.L3_ROUTER_NAT]
|
||||
if not hasattr(plugin, 'l3_rpc_notifier'):
|
||||
self.skipTest("Plugin does not support l3_rpc_notifier")
|
||||
|
||||
with self.subnet(cidr='11.0.0.0/24') as public_sub,\
|
||||
self.port() as private_port,\
|
||||
self.router() as r:
|
||||
self._set_net_external(public_sub['subnet']['network_id'])
|
||||
subnet_id = private_port['port']['fixed_ips'][0]['subnet_id']
|
||||
private_sub = {'subnet': {'id': subnet_id}}
|
||||
|
||||
self._add_external_gateway_to_router(
|
||||
r['router']['id'],
|
||||
public_sub['subnet']['network_id'])
|
||||
self._router_interface_action(
|
||||
'add', r['router']['id'],
|
||||
private_sub['subnet']['id'], None)
|
||||
|
||||
with mock.patch.object(plugin.l3_rpc_notifier,
|
||||
'routers_updated') as agent_notification:
|
||||
self._make_floatingip(
|
||||
self.fmt,
|
||||
public_sub['subnet']['network_id'],
|
||||
port_id=private_port['port']['id'],
|
||||
set_context=True)
|
||||
self.assertTrue(agent_notification.called)
|
||||
|
||||
def test_floating_port_status_not_applicable(self):
|
||||
with self.floatingip_with_assoc():
|
||||
port_body = self._list('ports',
|
||||
|
|
Loading…
Reference in New Issue