From d02bcb9c3917028948b08c319d1443d487c36846 Mon Sep 17 00:00:00 2001 From: Hirofumi Ichihara Date: Tue, 25 Aug 2015 09:10:00 +0900 Subject: [PATCH] Enable to update external network subnet's gateway-ip This patch enables users to update gateway_ip of a subnet even if the subnet is in use for an external network of a router. Change-Id: I78d2b024c99b1af0001bd454465d2fc02692cbf2 Closes-Bug: #1317363 --- neutron/callbacks/resources.py | 1 + neutron/db/db_base_plugin_v2.py | 23 ++++++++++++--- neutron/db/l3_db.py | 21 ++++++++++++++ neutron/tests/unit/extensions/test_l3.py | 37 ++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 4 deletions(-) diff --git a/neutron/callbacks/resources.py b/neutron/callbacks/resources.py index 1544fe5a4..029df4305 100644 --- a/neutron/callbacks/resources.py +++ b/neutron/callbacks/resources.py @@ -18,3 +18,4 @@ ROUTER_INTERFACE = 'router_interface' SECURITY_GROUP = 'security_group' SECURITY_GROUP_RULE = 'security_group_rule' SUBNET = 'subnet' +SUBNET_GATEWAY = 'subnet_gateway' diff --git a/neutron/db/db_base_plugin_v2.py b/neutron/db/db_base_plugin_v2.py index ca0c73015..8fef9b364 100644 --- a/neutron/db/db_base_plugin_v2.py +++ b/neutron/db/db_base_plugin_v2.py @@ -715,13 +715,22 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon, s['allocation_pools'] = range_pools # If either gateway_ip or allocation_pools were specified - gateway_ip = s.get('gateway_ip') - if gateway_ip is not None or s.get('allocation_pools') is not None: - if gateway_ip is None: - gateway_ip = db_subnet.gateway_ip + new_gateway_ip = s.get('gateway_ip') + gateway_ip_changed = (new_gateway_ip and + new_gateway_ip != db_subnet.gateway_ip) + if gateway_ip_changed or s.get('allocation_pools') is not None: + gateway_ip = new_gateway_ip or db_subnet.gateway_ip pools = range_pools if range_pools is not None else db_pools self.ipam.validate_gw_out_of_pools(gateway_ip, pools) + if gateway_ip_changed: + # Provide pre-update notification not to break plugins that don't + # support gateway ip change + kwargs = {'context': context, 'subnet_id': id, + 'network_id': db_subnet.network_id} + registry.notify(resources.SUBNET_GATEWAY, events.BEFORE_UPDATE, + self, **kwargs) + with context.session.begin(subtransactions=True): subnet, changes = self.ipam.update_db_subnet(context, id, s, db_pools) @@ -753,6 +762,12 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon, l3_rpc_notifier = l3_rpc_agent_api.L3AgentNotifyAPI() l3_rpc_notifier.routers_updated(context, routers) + if gateway_ip_changed: + kwargs = {'context': context, 'subnet_id': id, + 'network_id': db_subnet.network_id} + registry.notify(resources.SUBNET_GATEWAY, events.AFTER_UPDATE, + self, **kwargs) + return result def _subnet_check_ip_allocations(self, context, subnet_id): diff --git a/neutron/db/l3_db.py b/neutron/db/l3_db.py index a803dcb61..23d128cf1 100644 --- a/neutron/db/l3_db.py +++ b/neutron/db/l3_db.py @@ -1417,11 +1417,32 @@ def _notify_routers_callback(resource, event, trigger, **kwargs): l3plugin.notify_routers_updated(context, router_ids) +def _notify_subnet_gateway_ip_update(resource, event, trigger, **kwargs): + l3plugin = manager.NeutronManager.get_service_plugins().get( + constants.L3_ROUTER_NAT) + if not l3plugin: + return + context = kwargs['context'] + network_id = kwargs['network_id'] + subnet_id = kwargs['subnet_id'] + query = context.session.query(models_v2.Port).filter_by( + network_id=network_id, + device_owner=l3_constants.DEVICE_OWNER_ROUTER_GW) + query = query.join(models_v2.Port.fixed_ips).filter( + models_v2.IPAllocation.subnet_id == subnet_id) + router_ids = set(port['device_id'] for port in query) + for router_id in router_ids: + l3plugin.notify_router_updated(context, router_id) + + def subscribe(): registry.subscribe( _prevent_l3_port_delete_callback, resources.PORT, events.BEFORE_DELETE) registry.subscribe( _notify_routers_callback, resources.PORT, events.AFTER_DELETE) + registry.subscribe( + _notify_subnet_gateway_ip_update, resources.SUBNET_GATEWAY, + events.AFTER_UPDATE) # NOTE(armax): multiple l3 service plugins (potentially out of tree) inherit # from l3_db and may need the callbacks to be processed. Having an implicit diff --git a/neutron/tests/unit/extensions/test_l3.py b/neutron/tests/unit/extensions/test_l3.py index 7c6f47cad..7cf89f7b9 100644 --- a/neutron/tests/unit/extensions/test_l3.py +++ b/neutron/tests/unit/extensions/test_l3.py @@ -28,8 +28,10 @@ from webob import exc from neutron.api.rpc.agentnotifiers import l3_rpc_agent_api from neutron.api.rpc.handlers import l3_rpc from neutron.api.v2 import attributes +from neutron.callbacks import events from neutron.callbacks import exceptions from neutron.callbacks import registry +from neutron.callbacks import resources from neutron.common import constants as l3_constants from neutron.common import exceptions as n_exc from neutron import context @@ -2494,6 +2496,41 @@ class L3NatTestCaseBase(L3NatTestCaseMixin): routers = plugin.get_routers(ctx) self.assertEqual(0, len(routers)) + 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. + """ + 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") + # make sure the callback is registered. + registry.subscribe( + l3_db._notify_subnet_gateway_ip_update, resources.SUBNET_GATEWAY, + events.AFTER_UPDATE) + with mock.patch.object(plugin.l3_rpc_notifier, + 'routers_updated') as chk_method: + with self.network() as network: + allocation_pools = [{'start': '120.0.0.3', + 'end': '120.0.0.254'}] + with self.subnet(network=network, + gateway_ip='120.0.0.1', + allocation_pools=allocation_pools, + cidr='120.0.0.0/24') as subnet: + kwargs = { + 'device_owner': l3_constants.DEVICE_OWNER_ROUTER_GW, + 'device_id': 'fake_device'} + with self.port(subnet=subnet, **kwargs): + data = {'subnet': {'gateway_ip': '120.0.0.2'}} + req = self.new_update_request('subnets', data, + subnet['subnet']['id']) + res = self.deserialize(self.fmt, + req.get_response(self.api)) + self.assertEqual(res['subnet']['gateway_ip'], + data['subnet']['gateway_ip']) + chk_method.assert_called_with(mock.ANY, + ['fake_device'], None) + class L3AgentDbTestCaseBase(L3NatTestCaseMixin): -- 2.45.2