Notify router_update after directly gateway IP change

If directly change router gateway port IP address, the gateway IP
does not changed in router related namespace in l3 agent side. This
patch adds a method to catch a 'PORT' IP change event, and notify
the L3 agent.

Closes-Bug: #1795222
Change-Id: If276a7613c156f8c826967c9c8cbd6f2a8d32674
This commit is contained in:
LIU Yulong 2018-10-01 09:21:26 +08:00 committed by Brian Haley
parent 6efb09fe22
commit 8c17df7138
4 changed files with 153 additions and 0 deletions

View File

@ -1907,6 +1907,22 @@ class L3RpcNotifierMixin(object):
for router_id in router_ids:
l3plugin.notify_router_updated(context, router_id)
@staticmethod
@registry.receives(resources.PORT, [events.AFTER_UPDATE])
def _notify_gateway_port_ip_changed(resource, event, trigger, **kwargs):
l3plugin = directory.get_plugin(plugin_constants.L3)
if not l3plugin:
return
new_port = kwargs.get('port')
original_port = kwargs.get('original_port')
if original_port['device_owner'] != constants.DEVICE_OWNER_ROUTER_GW:
return
if utils.port_ip_changed(new_port, original_port):
l3plugin.notify_router_updated(kwargs['context'],
new_port['device_id'])
@staticmethod
@registry.receives(resources.SUBNETPOOL_ADDRESS_SCOPE,
[events.AFTER_UPDATE])

View File

@ -118,6 +118,10 @@ class ClientFixture(fixtures.Fixture):
return self._create_resource(resource_type, spec)
def list_ports(self, retrieve_all=True, **kwargs):
resp = self.client.list_ports(retrieve_all=retrieve_all, **kwargs)
return resp['ports']
def create_port(self, tenant_id, network_id, hostname=None,
qos_policy_id=None, security_groups=None, **kwargs):
spec = {

View File

@ -17,6 +17,7 @@ import os
import time
import netaddr
from neutron_lib import constants
from oslo_utils import uuidutils
from neutron.agent.l3 import ha_router
@ -76,6 +77,50 @@ class TestL3Agent(base.BaseFullStackTestCase):
return self._boot_fake_vm_in_network(host, tenant_id, network['id'])
def _test_gateway_ip_changed(self):
tenant_id = uuidutils.generate_uuid()
ext_net, ext_sub = self._create_external_network_and_subnet(tenant_id)
external_vm = self.useFixture(
machine_fixtures.FakeMachine(
self.environment.central_bridge,
common_utils.ip_to_cidr(ext_sub['gateway_ip'], 24)))
router = self.safe_client.create_router(tenant_id,
external_network=ext_net['id'])
vm = self._create_net_subnet_and_vm(
tenant_id, ['20.0.0.0/24', '2001:db8:aaaa::/64'],
self.environment.hosts[1], router)
# ping external vm to test snat
vm.block_until_ping(external_vm.ip)
fip = self.safe_client.create_floatingip(
tenant_id, ext_net['id'], vm.ip, vm.neutron_port['id'])
# ping floating ip from external vm
external_vm.block_until_ping(fip['floating_ip_address'])
# ping router gateway IP
old_gw_ip = router['external_gateway_info'][
'external_fixed_ips'][0]['ip_address']
external_vm.block_until_ping(old_gw_ip)
gateway_port = self.safe_client.list_ports(
device_id=router['id'],
device_owner=constants.DEVICE_OWNER_ROUTER_GW)[0]
ip_1 = str(netaddr.IPNetwork(
ext_sub['gateway_ip']).next(100)).split('/')[0]
ip_2 = str(netaddr.IPNetwork(
ext_sub['gateway_ip']).next(101)).split('/')[0]
self.safe_client.update_port(gateway_port['id'], fixed_ips=[
{'ip_address': ip_1},
{'ip_address': ip_2}])
# ping router gateway new IPs
external_vm.block_until_ping(ip_1)
external_vm.block_until_ping(ip_2)
# ping router old gateway IP, should fail now
external_vm.block_until_no_ping(old_gw_ip)
class TestLegacyL3Agent(TestL3Agent):
@ -224,6 +269,9 @@ class TestLegacyL3Agent(TestL3Agent):
# Verify north-south connectivity using ping6 to external_vm.
vm.block_until_ping(external_vm.ipv6)
def test_gateway_ip_changed(self):
self._test_gateway_ip_changed()
class TestHAL3Agent(TestL3Agent):
@ -375,3 +423,6 @@ class TestHAL3Agent(TestL3Agent):
self._assert_ping_during_agents_restart(
l3_active_agents, external_vm.namespace, [router_ip], count=60)
def test_gateway_ip_changed(self):
self._test_gateway_ip_changed()

View File

@ -269,6 +269,21 @@ class TestL3NatBasePlugin(db_base_plugin_v2.NeutronDbPluginV2,
plugin.disassociate_floatingips(context, id)
return super(TestL3NatBasePlugin, self).delete_port(context, id)
def update_port(self, context, id, port):
original_port = self.get_port(context, id)
session = context.session
with session.begin(subtransactions=True):
new_port = super(TestL3NatBasePlugin, self).update_port(
context, id, port)
# Notifications must be sent after the above transaction is complete
kwargs = {
'context': context,
'port': new_port,
'original_port': original_port,
}
registry.notify(resources.PORT, events.AFTER_UPDATE, self, **kwargs)
return new_port
# This plugin class is for tests with plugin that integrates L3.
class TestL3NatIntPlugin(TestL3NatBasePlugin,
@ -3434,6 +3449,73 @@ class L3NatTestCaseBase(L3NatTestCaseMixin):
self.assertEqual('', body['port']['device_owner'])
self.assertEqual('', body['port']['device_id'])
def _test__notify_gateway_port_ip_changed_helper(self, gw_ip_change=True):
plugin = directory.get_plugin(plugin_constants.L3)
if not hasattr(plugin, 'l3_rpc_notifier'):
self.skipTest("Plugin does not support l3_rpc_notifier")
# make sure the callback is registered.
registry.subscribe(
l3_db.L3RpcNotifierMixin._notify_gateway_port_ip_changed,
resources.PORT,
events.AFTER_UPDATE)
with mock.patch.object(plugin.l3_rpc_notifier,
'routers_updated') as chk_method:
with self.router() as router:
with self.subnet(cidr='1.1.1.0/24') as subnet:
self._set_net_external(subnet['subnet']['network_id'])
router_id = router['router']['id']
self._add_external_gateway_to_router(
router_id,
subnet['subnet']['network_id'])
body = self._show('routers', router_id)
gateway_ips = body['router']['external_gateway_info'][
'external_fixed_ips']
gateway_ip_len = len(gateway_ips)
self.assertEqual(1, gateway_ip_len)
gw_port_id = None
for p in self._list('ports')['ports']:
if (p['device_owner'] ==
lib_constants.DEVICE_OWNER_ROUTER_GW and
p['device_id'] == router_id):
gw_port_id = p['id']
self.assertIsNotNone(gw_port_id)
gw_ip_len = 1
if gw_ip_change:
gw_ip_len += 1
data = {'port': {'fixed_ips': [
{'ip_address': '1.1.1.101'},
{'ip_address': '1.1.1.100'}]}}
else:
gw_ip = gateway_ips[0]['ip_address']
data = {'port': {'fixed_ips': [
{'ip_address': gw_ip}]}}
req = self.new_update_request('ports', data,
gw_port_id)
res = self.deserialize(self.fmt,
req.get_response(self.api))
self.assertEqual(gw_ip_len, len(res['port']['fixed_ips']))
body = self._show('routers', router_id)
gateway_ip_len = len(
body['router']['external_gateway_info'][
'external_fixed_ips'])
self.assertEqual(gw_ip_len, gateway_ip_len)
chk_method.assert_called_with(mock.ANY,
[router_id], None)
self.assertEqual(gw_ip_len, chk_method.call_count)
def test__notify_gateway_port_ip_changed(self):
"""Test to make sure notification to routers occurs when the gateway
ip address changed.
"""
self._test__notify_gateway_port_ip_changed_helper()
def test__notify_gateway_port_ip_not_changed(self):
"""Test to make sure no notification to routers occurs when the gateway
ip address is not changed.
"""
self._test__notify_gateway_port_ip_changed_helper(gw_ip_change=False)
def test_update_subnet_gateway_for_external_net(self):
"""Test to make sure notification to routers occurs when the gateway
ip address of a subnet of the external network is changed.