diff --git a/neutron/db/extraroute_db.py b/neutron/db/extraroute_db.py index 336f2045d90..a6c1d06fea8 100644 --- a/neutron/db/extraroute_db.py +++ b/neutron/db/extraroute_db.py @@ -13,8 +13,13 @@ # License for the specific language governing permissions and limitations # under the License. +import copy + import netaddr from neutron_lib.api.definitions import l3 as l3_apidef +from neutron_lib.callbacks import events +from neutron_lib.callbacks import registry +from neutron_lib.callbacks import resources from neutron_lib.exceptions import extraroute as xroute_exc from neutron_lib.utils import helpers from oslo_config import cfg @@ -51,7 +56,17 @@ class ExtraRoute_dbonly_mixin(l3_db.L3_NAT_dbonly_mixin): with context.session.begin(subtransactions=True): # check if route exists and have permission to access router_db = self._get_router(context, id) - self._update_extra_routes(context, router_db, r['routes']) + old_router = self._make_router_dict(router_db) + routes_added, routes_removed = self._update_extra_routes( + context, router_db, r['routes']) + router_data = copy.deepcopy(r) + router_data['routes_added'] = routes_added + router_data['routes_removed'] = routes_removed + registry.publish(resources.ROUTER, events.PRECOMMIT_UPDATE, + self, payload=events.DBEventPayload( + context, request_body=router_data, + states=(old_router,), resource_id=id, + desired_state=router_db)) # NOTE(yamamoto): expire to ensure the following update_router # see the effects of the above _update_extra_routes. context.session.expire(router_db, attribute_names=['route_list']) @@ -112,6 +127,7 @@ class ExtraRoute_dbonly_mixin(l3_db.L3_NAT_dbonly_mixin): router_id=router['id'], destination=route['destination'], nexthop=route['nexthop']).delete() + return added, removed @staticmethod def _make_extra_route_list(extra_routes): diff --git a/neutron/db/l3_dvr_db.py b/neutron/db/l3_dvr_db.py index 79f077893b3..902d8d705b6 100644 --- a/neutron/db/l3_dvr_db.py +++ b/neutron/db/l3_dvr_db.py @@ -19,6 +19,7 @@ from neutron_lib.api.definitions import portbindings from neutron_lib.api import validators from neutron_lib.callbacks import events from neutron_lib.callbacks import exceptions +from neutron_lib.callbacks import priority_group from neutron_lib.callbacks import registry from neutron_lib.callbacks import resources from neutron_lib import constants as const @@ -69,7 +70,8 @@ class DVRResourceOperationHandler(object): def l3plugin(self): return directory.get_plugin(plugin_constants.L3) - @registry.receives(resources.ROUTER, [events.PRECOMMIT_CREATE]) + @registry.receives(resources.ROUTER, [events.PRECOMMIT_CREATE], + priority_group.PRIORITY_ROUTER_EXTENDED_ATTRIBUTE) def _set_distributed_flag(self, resource, event, trigger, context, router, router_db, **kwargs): """Event handler to set distributed flag on creation.""" @@ -107,7 +109,8 @@ class DVRResourceOperationHandler(object): raise l3_exc.RouterInUse(router_id=router_db['id'], reason=e) return True - @registry.receives(resources.ROUTER, [events.PRECOMMIT_UPDATE]) + @registry.receives(resources.ROUTER, [events.PRECOMMIT_UPDATE], + priority_group.PRIORITY_ROUTER_EXTENDED_ATTRIBUTE) def _handle_distributed_migration(self, resource, event, trigger, payload=None): """Event handler for router update migration to distributed.""" @@ -149,7 +152,8 @@ class DVRResourceOperationHandler(object): payload.context, payload.desired_state, 'distributed', migrating_to_distributed) - @registry.receives(resources.ROUTER, [events.AFTER_UPDATE]) + @registry.receives(resources.ROUTER, [events.AFTER_UPDATE], + priority_group.PRIORITY_ROUTER_EXTENDED_ATTRIBUTE) def _delete_snat_interfaces_after_change(self, resource, event, trigger, context, router_id, router, request_attrs, router_db, @@ -162,7 +166,8 @@ class DVRResourceOperationHandler(object): context.elevated(), router_db) @registry.receives(resources.ROUTER, - [events.AFTER_CREATE, events.AFTER_UPDATE]) + [events.AFTER_CREATE, events.AFTER_UPDATE], + priority_group.PRIORITY_ROUTER_EXTENDED_ATTRIBUTE) def _create_snat_interfaces_after_change(self, resource, event, trigger, context, router_id, router, request_attrs, router_db, diff --git a/neutron/db/l3_gwmode_db.py b/neutron/db/l3_gwmode_db.py index 3951e754a51..7bdc8610802 100644 --- a/neutron/db/l3_gwmode_db.py +++ b/neutron/db/l3_gwmode_db.py @@ -14,6 +14,9 @@ # from neutron_lib.api.definitions import l3 as l3_apidef +from neutron_lib.callbacks import events +from neutron_lib.callbacks import registry +from neutron_lib.callbacks import resources from oslo_config import cfg import sqlalchemy as sa from sqlalchemy import sql @@ -57,7 +60,15 @@ class L3_NAT_dbonly_mixin(l3_db.L3_NAT_dbonly_mixin): if not router: router = self._get_router(context, router_id) with context.session.begin(subtransactions=True): + old_router = self._make_router_dict(router) router.enable_snat = self._get_enable_snat(info) + router_body = {l3_apidef.ROUTER: + {l3_apidef.EXTERNAL_GW_INFO: info}} + registry.publish(resources.ROUTER, events.PRECOMMIT_UPDATE, self, + payload=events.DBEventPayload( + context, request_body=router_body, + states=(old_router,), resource_id=router_id, + desired_state=router)) # Calls superclass, pass router db object for avoiding re-loading super(L3_NAT_dbonly_mixin, self)._update_router_gw_info( diff --git a/neutron/db/l3_hamode_db.py b/neutron/db/l3_hamode_db.py index 0badefbea8e..7e21793e2c8 100644 --- a/neutron/db/l3_hamode_db.py +++ b/neutron/db/l3_hamode_db.py @@ -16,12 +16,15 @@ import functools import netaddr +from neutron_lib.api.definitions import l3 as l3_apidef +from neutron_lib.api.definitions import l3_ext_ha_mode as l3_ext_ha_apidef from neutron_lib.api.definitions import port as port_def from neutron_lib.api.definitions import portbindings from neutron_lib.api.definitions import provider_net as providernet from neutron_lib.api import extensions from neutron_lib.api import validators from neutron_lib.callbacks import events +from neutron_lib.callbacks import priority_group from neutron_lib.callbacks import registry from neutron_lib.callbacks import resources from neutron_lib import constants @@ -124,6 +127,7 @@ class L3_HA_NAT_db_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin, 'ha_vr_id': router_db.extra_attributes.ha_vr_id}) return + old_router = self._make_router_dict(router_db) allocated_vr_ids = self._get_allocated_vr_id(context, network_id) available_vr_ids = VR_ID_RANGE - allocated_vr_ids @@ -141,6 +145,15 @@ class L3_HA_NAT_db_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin, "Router %(router_id)s has been allocated a ha_vr_id " "%(ha_vr_id)d.", {'router_id': router_id, 'ha_vr_id': allocation.vr_id}) + router_body = {l3_apidef.ROUTER: + {l3_ext_ha_apidef.HA_INFO: True, + 'ha_vr_id': allocation.vr_id}} + registry.publish(resources.ROUTER, events.PRECOMMIT_UPDATE, + self, payload=events.DBEventPayload( + context, request_body=router_body, + states=(old_router,), + resource_id=router_id, + desired_state=router_db)) return allocation.vr_id @@ -343,7 +356,8 @@ class L3_HA_NAT_db_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin, return n_utils.create_object_with_dependency( creator, dep_getter, dep_creator, dep_id_attr, dep_deleter)[1] - @registry.receives(resources.ROUTER, [events.BEFORE_CREATE]) + @registry.receives(resources.ROUTER, [events.BEFORE_CREATE], + priority_group.PRIORITY_ROUTER_EXTENDED_ATTRIBUTE) @db_api.retry_if_session_inactive() def _before_router_create(self, resource, event, trigger, context, router, **kwargs): @@ -356,7 +370,8 @@ class L3_HA_NAT_db_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin, if not self.get_ha_network(context, router['tenant_id']): self._create_ha_network(context, router['tenant_id']) - @registry.receives(resources.ROUTER, [events.PRECOMMIT_CREATE]) + @registry.receives(resources.ROUTER, [events.PRECOMMIT_CREATE], + priority_group.PRIORITY_ROUTER_EXTENDED_ATTRIBUTE) def _precommit_router_create(self, resource, event, trigger, context, router, router_db, **kwargs): """Event handler to set ha flag and status on creation.""" @@ -375,7 +390,8 @@ class L3_HA_NAT_db_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin, l3ha_exc.HANetworkConcurrentDeletion( tenant_id=router['tenant_id'])) - @registry.receives(resources.ROUTER, [events.AFTER_CREATE]) + @registry.receives(resources.ROUTER, [events.AFTER_CREATE], + priority_group.PRIORITY_ROUTER_EXTENDED_ATTRIBUTE) def _after_router_create(self, resource, event, trigger, context, router_id, router, router_db, **kwargs): if not router['ha']: @@ -396,7 +412,8 @@ class L3_HA_NAT_db_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin, context, router_id, {'status': constants.ERROR})['status'] - @registry.receives(resources.ROUTER, [events.PRECOMMIT_UPDATE]) + @registry.receives(resources.ROUTER, [events.PRECOMMIT_UPDATE], + priority_group.PRIORITY_ROUTER_EXTENDED_ATTRIBUTE) def _validate_migration(self, resource, event, trigger, payload=None): """Event handler on precommit update to validate migration.""" @@ -443,7 +460,8 @@ class L3_HA_NAT_db_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin, self.set_extra_attr_value( payload.context, payload.desired_state, 'ha', requested_ha_state) - @registry.receives(resources.ROUTER, [events.AFTER_UPDATE]) + @registry.receives(resources.ROUTER, [events.AFTER_UPDATE], + priority_group.PRIORITY_ROUTER_EXTENDED_ATTRIBUTE) def _reconfigure_ha_resources(self, resource, event, trigger, context, router_id, old_router, router, router_db, **kwargs): @@ -498,7 +516,8 @@ class L3_HA_NAT_db_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin, "%(tenant)s.", {'network': net_id, 'tenant': tenant_id}) - @registry.receives(resources.ROUTER, [events.PRECOMMIT_DELETE]) + @registry.receives(resources.ROUTER, [events.PRECOMMIT_DELETE], + priority_group.PRIORITY_ROUTER_EXTENDED_ATTRIBUTE) def _release_router_vr_id(self, resource, event, trigger, context, router_db, **kwargs): """Event handler for removal of VRID during router delete.""" @@ -509,7 +528,8 @@ class L3_HA_NAT_db_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin, self._delete_vr_id_allocation( context, ha_network, router_db.extra_attributes.ha_vr_id) - @registry.receives(resources.ROUTER, [events.AFTER_DELETE]) + @registry.receives(resources.ROUTER, [events.AFTER_DELETE], + priority_group.PRIORITY_ROUTER_EXTENDED_ATTRIBUTE) @db_api.retry_if_session_inactive() def _cleanup_ha_network(self, resource, event, trigger, context, router_id, original, **kwargs): diff --git a/neutron/services/l3_router/service_providers/driver_controller.py b/neutron/services/l3_router/service_providers/driver_controller.py index b572b6e3985..e2e501e04a3 100644 --- a/neutron/services/l3_router/service_providers/driver_controller.py +++ b/neutron/services/l3_router/service_providers/driver_controller.py @@ -13,6 +13,7 @@ # under the License. from neutron_lib.callbacks import events +from neutron_lib.callbacks import priority_group from neutron_lib.callbacks import registry from neutron_lib.callbacks import resources from neutron_lib import constants as lib_const @@ -63,14 +64,16 @@ class DriverController(object): plugin_constants.FLAVORS) return self._flavor_plugin_ref - @registry.receives(resources.ROUTER, [events.BEFORE_CREATE]) + @registry.receives(resources.ROUTER, [events.BEFORE_CREATE], + priority_group.PRIORITY_ROUTER_CONTROLLER) def _check_router_request(self, resource, event, trigger, context, router, **kwargs): """Validates that API request is sane (flags compat with flavor).""" drv = self._get_provider_for_create(context, router) _ensure_driver_supports_request(drv, router) - @registry.receives(resources.ROUTER, [events.PRECOMMIT_CREATE]) + @registry.receives(resources.ROUTER, [events.PRECOMMIT_CREATE], + priority_group.PRIORITY_ROUTER_CONTROLLER) def _set_router_provider(self, resource, event, trigger, context, router, router_db, **kwargs): """Associates a router with a service provider. @@ -84,14 +87,26 @@ class DriverController(object): drv = self._get_provider_for_create(context, router) self._stm.add_resource_association(context, plugin_constants.L3, drv.name, router['id']) + registry.notify( + resources.ROUTER_CONTROLLER, events.PRECOMMIT_ADD_ASSOCIATION, + trigger, context=context, router=router, + router_db=router_db, old_driver=None, + new_driver=drv, **kwargs) - @registry.receives(resources.ROUTER, [events.PRECOMMIT_DELETE]) + @registry.receives(resources.ROUTER, [events.PRECOMMIT_DELETE], + priority_group.PRIORITY_ROUTER_CONTROLLER) def _clear_router_provider(self, resource, event, trigger, context, router_id, **kwargs): """Remove the association between a router and a service provider.""" + drv = self.get_provider_for_router(context, router_id) + registry.notify( + resources.ROUTER_CONTROLLER, events.PRECOMMIT_DELETE_ASSOCIATIONS, + trigger, context=context, router_id=router_id, + old_driver=drv, new_driver=None, **kwargs) self._stm.del_resource_associations(context, [router_id]) - @registry.receives(resources.ROUTER, [events.PRECOMMIT_UPDATE]) + @registry.receives(resources.ROUTER, [events.PRECOMMIT_UPDATE], + priority_group.PRIORITY_ROUTER_CONTROLLER) def _update_router_provider(self, resource, event, trigger, payload=None): """Handle transition between providers. @@ -141,11 +156,19 @@ class DriverController(object): _ensure_driver_supports_request(new_drv, payload.request_body) # TODO(kevinbenton): notify old driver explicitly of driver change with payload.context.session.begin(subtransactions=True): + registry.publish( + resources.ROUTER_CONTROLLER, + events.PRECOMMIT_DELETE_ASSOCIATIONS, + trigger, payload=payload) self._stm.del_resource_associations( payload.context, [payload.resource_id]) self._stm.add_resource_association( payload.context, plugin_constants.L3, new_drv.name, payload.resource_id) + registry.publish( + resources.ROUTER_CONTROLLER, + events.PRECOMMIT_ADD_ASSOCIATION, + trigger, payload=payload), def get_provider_for_router(self, context, router_id): """Return the provider driver handle for a router id.""" @@ -159,6 +182,10 @@ class DriverController(object): driver_name = driver.name self._stm.add_resource_association(context, plugin_constants.L3, driver_name, router_id) + registry.notify( + resources.ROUTER_CONTROLLER, events.PRECOMMIT_ADD_ASSOCIATION, + self, context=context, router_id=router_id, + router=router, old_driver=None, new_driver=driver) return self.drivers[driver_name] def _get_provider_for_create(self, context, router): diff --git a/neutron/tests/unit/db/test_extraroute_db.py b/neutron/tests/unit/db/test_extraroute_db.py index 3e300d944d5..9de46143440 100644 --- a/neutron/tests/unit/db/test_extraroute_db.py +++ b/neutron/tests/unit/db/test_extraroute_db.py @@ -14,6 +14,8 @@ # under the License. import mock +from neutron_lib.callbacks import events +from neutron_lib.callbacks import registry from neutron_lib import context from neutron_lib.plugins import constants from neutron_lib.plugins import directory @@ -64,9 +66,12 @@ class TestExtraRouteDb(testlib_api.SqlTestCase): update_request = { 'router': router, } - with mock.patch.object(self._plugin, '_validate_routes'): - updated_router = self._plugin.update_router(ctx, router_id, - update_request) + with mock.patch.object(registry, "publish") as mock_cb: + with mock.patch.object(self._plugin, '_validate_routes'): + updated_router = self._plugin.update_router(ctx, router_id, + update_request) + mock_cb.assert_called_with('router', events.PRECOMMIT_UPDATE, + self._plugin, payload=mock.ANY) self.assertItemsEqual(updated_router['routes'], routes) got_router = self._plugin.get_router(ctx, router_id) self.assertItemsEqual(got_router['routes'], routes) diff --git a/neutron/tests/unit/db/test_l3_hamode_db.py b/neutron/tests/unit/db/test_l3_hamode_db.py index 6254c22997c..4c13db369ba 100644 --- a/neutron/tests/unit/db/test_l3_hamode_db.py +++ b/neutron/tests/unit/db/test_l3_hamode_db.py @@ -1337,7 +1337,10 @@ class L3HAUserTestCase(L3HATestFramework): def test_update_router(self): router = self._create_router(ctx=self.user_ctx) - self._update_router(router['id'], ctx=self.user_ctx) + with mock.patch.object(registry, 'publish') as mock_cb: + self._update_router(router['id'], ctx=self.user_ctx) + mock_cb.assert_called_with('router', events.PRECOMMIT_UPDATE, + self.plugin, payload=mock.ANY) def test_delete_router(self): router = self._create_router(ctx=self.user_ctx) diff --git a/neutron/tests/unit/services/l3_router/service_providers/test_driver_controller.py b/neutron/tests/unit/services/l3_router/service_providers/test_driver_controller.py index 5c54d81b8dc..07955d94a49 100644 --- a/neutron/tests/unit/services/l3_router/service_providers/test_driver_controller.py +++ b/neutron/tests/unit/services/l3_router/service_providers/test_driver_controller.py @@ -14,6 +14,7 @@ import mock from neutron_lib.callbacks import events +from neutron_lib.callbacks import registry from neutron_lib import constants from neutron_lib import context from neutron_lib import exceptions as lib_exc @@ -74,7 +75,8 @@ class TestDriverController(testlib_api.SqlTestCase): self.assertFalse(self.dc.drivers['dvr'].owns_router(self.ctx, r2)) self.assertFalse(self.dc.drivers['dvr'].owns_router(self.ctx, None)) - def test__set_router_provider_flavor_specified(self): + @mock.patch('neutron_lib.callbacks.registry.notify') + def test__set_router_provider_flavor_specified(self, mock_cb): self._return_provider_for_flavor('dvrha') router_db = mock.Mock() flavor_id = uuidutils.generate_uuid() @@ -82,6 +84,10 @@ class TestDriverController(testlib_api.SqlTestCase): router = dict(id=router_id, flavor_id=flavor_id) self.dc._set_router_provider('router', 'PRECOMMIT_CREATE', self, self.ctx, router, router_db) + mock_cb.assert_called_with('router_controller', + events.PRECOMMIT_ADD_ASSOCIATION, mock.ANY, + context=self.ctx, router=mock.ANY, router_db=mock.ANY, + old_driver=mock.ANY, new_driver=mock.ANY) self.assertEqual(flavor_id, router_db.flavor_id) self.assertEqual(self.dc.drivers['dvrha'], self.dc.get_provider_for_router(self.ctx, @@ -89,21 +95,24 @@ class TestDriverController(testlib_api.SqlTestCase): def test__update_router_provider_invalid(self): test_dc = driver_controller.DriverController(self.fake_l3) - with mock.patch.object(test_dc, "get_provider_for_router"): - with mock.patch.object( - driver_controller, - "_ensure_driver_supports_request") as _ensure: - _ensure.side_effect = lib_exc.InvalidInput( - error_message='message') - self.assertRaises( - lib_exc.InvalidInput, - test_dc._update_router_provider, - None, None, None, - payload=events.DBEventPayload( - None, request_body={'name': 'testname'}, - states=({'flavor_id': 'old_fid'},))) + with mock.patch.object(registry, "publish") as mock_cb: + with mock.patch.object(test_dc, "get_provider_for_router"): + with mock.patch.object( + driver_controller, + "_ensure_driver_supports_request") as _ensure: + _ensure.side_effect = lib_exc.InvalidInput( + error_message='message') + self.assertRaises( + lib_exc.InvalidInput, + test_dc._update_router_provider, + None, None, None, + payload=events.DBEventPayload( + None, request_body={'name': 'testname'}, + states=({'flavor_id': 'old_fid'},))) + mock_cb.assert_not_called() - def test__set_router_provider_attr_lookups(self): + @mock.patch('neutron_lib.callbacks.registry.notify') + def test__set_router_provider_attr_lookups(self, mock_cb): # ensure correct drivers are looked up based on attrs router_id1 = uuidutils.generate_uuid() router_id2 = uuidutils.generate_uuid() @@ -135,26 +144,43 @@ class TestDriverController(testlib_api.SqlTestCase): for driver, body in cases: self.dc._set_router_provider('router', 'PRECOMMIT_CREATE', self, self.ctx, body, mock.Mock()) + mock_cb.assert_called_with('router_controller', + events.PRECOMMIT_ADD_ASSOCIATION, mock.ANY, + context=self.ctx, router=mock.ANY, router_db=mock.ANY, + old_driver=mock.ANY, new_driver=mock.ANY) self.assertEqual(self.dc.drivers[driver], self.dc.get_provider_for_router(self.ctx, body['id']), 'Expecting %s for body %s' % (driver, body)) - def test__clear_router_provider(self): + @mock.patch('neutron_lib.callbacks.registry.notify') + def test__clear_router_provider(self, mock_cb): # ensure correct drivers are looked up based on attrs router_id1 = uuidutils.generate_uuid() body = dict(id=router_id1, distributed=True, ha=True) self.dc._set_router_provider('router', 'PRECOMMIT_CREATE', self, self.ctx, body, mock.Mock()) + mock_cb.assert_called_with('router_controller', + events.PRECOMMIT_ADD_ASSOCIATION, mock.ANY, + context=self.ctx, router=mock.ANY, router_db=mock.ANY, + old_driver=mock.ANY, new_driver=mock.ANY) self.assertEqual(self.dc.drivers['dvrha'], self.dc.get_provider_for_router(self.ctx, body['id'])) self.dc._clear_router_provider('router', 'PRECOMMIT_DELETE', self, self.ctx, body['id']) + mock_cb.assert_called_with('router_controller', + events.PRECOMMIT_DELETE_ASSOCIATIONS, mock.ANY, + context=self.ctx, router_id=mock.ANY, old_driver=mock.ANY, + new_driver=mock.ANY) with testtools.ExpectedException(ValueError): # if association was cleared, get_router will be called self.fake_l3.get_router.side_effect = ValueError self.dc.get_provider_for_router(self.ctx, body['id']) + mock_cb.assert_called_with('router_controller', + events.PRECOMMIT_ADD_ASSOCIATION, mock.ANY, context=self.ctx, + router_id=body['id'], router=mock.ANY, old_driver=mock.ANY, + new_driver=mock.ANY) def test__flavor_plugin(self): directory.add_plugin(p_cons.FLAVORS, mock.Mock())