From e1df8b54d27680eb3351b20e3d3259b8179c9dac Mon Sep 17 00:00:00 2001 From: Hong Hui Xiao Date: Sun, 29 Nov 2015 09:00:23 -0500 Subject: [PATCH] Update related router when subnetpool change scope When the address scope of a subnetpool changes, the scope of related subnet will change too. If this subnet has been connected to a router, the related routing information needs to be updated. This patch will fire the router update to correlative routers. The routers will get all things updated in the update action. Change-Id: Ia2eb484eb949a8d42fc909291de7be07b002adda Partially-Implements: blueprint address-scopes --- neutron/callbacks/resources.py | 1 + neutron/common/constants.py | 2 + neutron/db/db_base_plugin_v2.py | 12 ++++ neutron/db/l3_db.py | 25 ++++++++ neutron/tests/unit/db/test_l3_db.py | 14 +++++ .../unit/extensions/test_address_scope.py | 60 +++++++++++++++++++ neutron/tests/unit/extensions/test_l3.py | 31 ++++++++++ 7 files changed, 145 insertions(+) diff --git a/neutron/callbacks/resources.py b/neutron/callbacks/resources.py index d6d8efa1ab8..cb0067eea10 100644 --- a/neutron/callbacks/resources.py +++ b/neutron/callbacks/resources.py @@ -21,3 +21,4 @@ SECURITY_GROUP = 'security_group' SECURITY_GROUP_RULE = 'security_group_rule' SUBNET = 'subnet' SUBNET_GATEWAY = 'subnet_gateway' +SUBNETPOOL_ADDRESS_SCOPE = 'subnetpool_address_scope' diff --git a/neutron/common/constants.py b/neutron/common/constants.py index b80d5c00254..30c50041d71 100644 --- a/neutron/common/constants.py +++ b/neutron/common/constants.py @@ -59,6 +59,8 @@ ROUTER_INTERFACE_OWNERS = (DEVICE_OWNER_ROUTER_INTF, ROUTER_INTERFACE_OWNERS_SNAT = (DEVICE_OWNER_ROUTER_INTF, DEVICE_OWNER_DVR_INTERFACE, DEVICE_OWNER_ROUTER_SNAT) +ROUTER_PORT_OWNERS = ROUTER_INTERFACE_OWNERS_SNAT + (DEVICE_OWNER_ROUTER_GW,) + L3_AGENT_MODE_DVR = 'dvr' L3_AGENT_MODE_DVR_SNAT = 'dvr_snat' L3_AGENT_MODE_LEGACY = 'legacy' diff --git a/neutron/db/db_base_plugin_v2.py b/neutron/db/db_base_plugin_v2.py index a93fa4fb64e..e2a047558ff 100644 --- a/neutron/db/db_base_plugin_v2.py +++ b/neutron/db/db_base_plugin_v2.py @@ -1030,12 +1030,24 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon, self._validate_address_scope_id(context, reader.address_scope_id, id, reader.prefixes, reader.ip_version) + address_scope_changed = (orig_sp.address_scope_id != + reader.address_scope_id) + orig_sp.update(self._filter_non_model_columns( reader.subnetpool, models_v2.SubnetPool)) self._update_subnetpool_prefixes(context, reader.prefixes, id) + + if address_scope_changed: + # Notify about the update of subnetpool's address scope + kwargs = {'context': context, 'subnetpool_id': id} + registry.notify(resources.SUBNETPOOL_ADDRESS_SCOPE, + events.AFTER_UPDATE, + self.update_subnetpool, + **kwargs) + for key in ['min_prefixlen', 'max_prefixlen', 'default_prefixlen']: updated['key'] = str(updated[key]) diff --git a/neutron/db/l3_db.py b/neutron/db/l3_db.py index a8df937590e..85bafda7f4f 100644 --- a/neutron/db/l3_db.py +++ b/neutron/db/l3_db.py @@ -1654,6 +1654,27 @@ def _notify_subnet_gateway_ip_update(resource, event, trigger, **kwargs): l3plugin.notify_router_updated(context, router_id) +def _notify_subnetpool_address_scope_update(resource, event, + trigger, **kwargs): + context = kwargs['context'] + subnetpool_id = kwargs['subnetpool_id'] + + query = context.session.query(RouterPort.router_id) + query = query.join(models_v2.Port) + query = query.join( + models_v2.Subnet, + models_v2.Subnet.network_id == models_v2.Port.network_id) + query = query.filter( + models_v2.Subnet.subnetpool_id == subnetpool_id, + RouterPort.port_type.in_(l3_constants.ROUTER_PORT_OWNERS)) + query = query.distinct() + + router_ids = [r[0] for r in query] + l3plugin = manager.NeutronManager.get_service_plugins().get( + constants.L3_ROUTER_NAT) + l3plugin.notify_routers_updated(context, router_ids) + + def subscribe(): registry.subscribe( _prevent_l3_port_delete_callback, resources.PORT, events.BEFORE_DELETE) @@ -1662,6 +1683,10 @@ def subscribe(): registry.subscribe( _notify_subnet_gateway_ip_update, resources.SUBNET_GATEWAY, events.AFTER_UPDATE) + registry.subscribe( + _notify_subnetpool_address_scope_update, + resources.SUBNETPOOL_ADDRESS_SCOPE, + 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/db/test_l3_db.py b/neutron/tests/unit/db/test_l3_db.py index 8d7972c4c39..5ed1aeea429 100644 --- a/neutron/tests/unit/db/test_l3_db.py +++ b/neutron/tests/unit/db/test_l3_db.py @@ -16,6 +16,9 @@ import mock import testtools +from neutron.callbacks import events +from neutron.callbacks import registry +from neutron.callbacks import resources from neutron.common import exceptions as n_exc from neutron.db import l3_db from neutron.extensions import l3 @@ -185,3 +188,14 @@ class TestL3_NAT_dbonly_mixin(base.BaseTestCase): self.db.get_floatingip = mock.Mock() with testtools.ExpectedException(n_exc.ServicePortInUse): self.db.prevent_l3_port_deletion(mock.Mock(), None) + + @mock.patch.object(l3_db, '_notify_subnetpool_address_scope_update') + def test_subscribe_address_scope_of_subnetpool(self, notify): + l3_db.subscribe() + registry.notify(resources.SUBNETPOOL_ADDRESS_SCOPE, + events.AFTER_UPDATE, mock.ANY, context=mock.ANY, + subnetpool_id='fake_id') + notify.assert_called_once_with(resources.SUBNETPOOL_ADDRESS_SCOPE, + events.AFTER_UPDATE, mock.ANY, + context=mock.ANY, + subnetpool_id='fake_id') diff --git a/neutron/tests/unit/extensions/test_address_scope.py b/neutron/tests/unit/extensions/test_address_scope.py index 87ad62aed4e..c28cdf55a5c 100644 --- a/neutron/tests/unit/extensions/test_address_scope.py +++ b/neutron/tests/unit/extensions/test_address_scope.py @@ -14,10 +14,14 @@ import contextlib +import mock import netaddr import webob.exc from neutron.api.v2 import attributes as attr +from neutron.callbacks import events +from neutron.callbacks import registry +from neutron.callbacks import resources from neutron.common import constants from neutron import context from neutron.db import address_scope_db @@ -370,6 +374,62 @@ class TestSubnetPoolsWithAddressScopes(AddressScopeTestCase): self._compare_resource(res, update_data['subnetpool'], 'subnetpool') + def _test_update_subnetpool_address_scope_notify(self, as_change=True): + with self.address_scope(name='foo-address-scope') as addr_scope: + foo_as_id = addr_scope['address_scope']['id'] + subnet = netaddr.IPNetwork('10.10.10.0/24') + initial_subnetpool = self._test_create_subnetpool( + [subnet.cidr], name='foo-sp', + min_prefixlen='21', address_scope_id=foo_as_id) + subnetpool_id = initial_subnetpool['subnetpool']['id'] + with self.address_scope(name='bar-address-scope') as other_as, \ + self.network() as network: + data = {'subnet': { + 'network_id': network['network']['id'], + 'subnetpool_id': subnetpool_id, + 'prefixlen': 24, + 'ip_version': 4, + 'tenant_id': network['network']['tenant_id']}} + req = self.new_create_request('subnets', data) + subnet = self.deserialize(self.fmt, + req.get_response(self.api)) + + with mock.patch.object(registry, 'notify') as notify: + plugin = db_base_plugin_v2.NeutronDbPluginV2() + plugin.is_address_scope_owned_by_tenant = mock.Mock( + return_value=True) + plugin._validate_address_scope_id = mock.Mock() + ctx = context.get_admin_context() + + bar_as_id = other_as['address_scope']['id'] + data = {'subnetpool': { + 'name': 'bar-sp'}} + if as_change: + data['subnetpool']['address_scope_id'] = bar_as_id + + updated_sp = plugin.update_subnetpool( + ctx, subnetpool_id, data) + + self.assertEqual('bar-sp', updated_sp['name']) + if as_change: + self.assertEqual(bar_as_id, + updated_sp['address_scope_id']) + notify.assert_called_once_with( + resources.SUBNETPOOL_ADDRESS_SCOPE, + events.AFTER_UPDATE, + plugin.update_subnetpool, context=ctx, + subnetpool_id=subnetpool_id) + else: + self.assertEqual(foo_as_id, + updated_sp['address_scope_id']) + self.assertFalse(notify.called) + + def test_update_subnetpool_address_scope_notify(self): + self._test_update_subnetpool_address_scope_notify() + + def test_not_update_subnetpool_address_scope_not_notify(self): + self._test_update_subnetpool_address_scope_notify(False) + def test_delete_address_scope_in_use(self): with self.address_scope(name='foo-address-scope') as addr_scope: address_scope_id = addr_scope['address_scope']['id'] diff --git a/neutron/tests/unit/extensions/test_l3.py b/neutron/tests/unit/extensions/test_l3.py index 0b4f328b131..59f51a5b30e 100644 --- a/neutron/tests/unit/extensions/test_l3.py +++ b/neutron/tests/unit/extensions/test_l3.py @@ -2683,6 +2683,37 @@ class L3NatTestCaseBase(L3NatTestCaseMixin): chk_method.assert_called_with(mock.ANY, ['fake_device'], None) + def test__notify_subnetpool_address_scope_update(self): + plugin = manager.NeutronManager.get_service_plugins()[ + service_constants.L3_ROUTER_NAT] + + tenant_id = _uuid() + with mock.patch.object( + plugin, 'notify_routers_updated') as chk_method, \ + self.subnetpool(prefixes=['10.0.0.0/24'], + admin=True, name='sp', + tenant_id=tenant_id) as subnetpool, \ + self.router(tenant_id=tenant_id) as router, \ + self.network(tenant_id=tenant_id) as network: + subnetpool_id = subnetpool['subnetpool']['id'] + data = {'subnet': { + 'network_id': network['network']['id'], + 'subnetpool_id': subnetpool_id, + 'prefixlen': 24, + 'ip_version': 4, + 'tenant_id': tenant_id}} + req = self.new_create_request('subnets', data) + subnet = self.deserialize(self.fmt, req.get_response(self.api)) + + admin_ctx = context.get_admin_context() + plugin.add_router_interface( + admin_ctx, + router['router']['id'], {'subnet_id': subnet['subnet']['id']}) + l3_db._notify_subnetpool_address_scope_update( + mock.ANY, mock.ANY, mock.ANY, + context=admin_ctx, subnetpool_id=subnetpool_id) + chk_method.assert_called_with(admin_ctx, [router['router']['id']]) + class L3AgentDbTestCaseBase(L3NatTestCaseMixin):