Merge "l3 flavor: more events/notifications and callback priority"

This commit is contained in:
Zuul 2018-06-23 02:19:12 +00:00 committed by Gerrit Code Review
commit 14cfb7181d
8 changed files with 149 additions and 36 deletions

View File

@ -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):

View File

@ -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,

View File

@ -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(

View File

@ -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):

View File

@ -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):

View File

@ -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)

View File

@ -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)

View File

@ -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())