Send a callback before attaching a subnet to a router
Attaching a subnet to a router may be forbidden in some cases. To prevent this attachment, we need to send a BEFORE_CREATE event with "router_interface" as resource, and reraise any exception that tells neutron a third party code forbid this attachment. Change-Id: Ia71e0c5e9e3e073053ead7e1e7c3040087f5ee13 Closes-bug: 1537091
This commit is contained in:
parent
b05549f671
commit
71dd840d30
|
@ -664,7 +664,24 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
|
|||
raise n_exc.BadRequest(resource='router', msg=msg)
|
||||
return port_id_specified, subnet_id_specified
|
||||
|
||||
def _check_router_port(self, context, port_id, device_id):
|
||||
def _check_router_port(self, context, port_id, device_id,
|
||||
router_id=None):
|
||||
"""Check that a port is available for an attachment to a router
|
||||
|
||||
:param context: The context of the request.
|
||||
:param port_id: The port to be attached.
|
||||
:param device_id: This method will check that device_id corresponds to
|
||||
the device_id of the port. It raises PortInUse exception if it
|
||||
doesn't.
|
||||
:param router_id: This method will use this router_id to notify
|
||||
third party code that an attachment is occurring on this router.
|
||||
Third party code can prevent this attachment by raising an exception
|
||||
that will be caught and reraised with a
|
||||
RouterInterfaceAttachmentConflict exception.
|
||||
:returns: The port description returned by the core plugin.
|
||||
:raises: PortInUse if the device_id is not the same as the port's one.
|
||||
:raises: BadRequest if the port has no fixed IP.
|
||||
"""
|
||||
port = self._core_plugin.get_port(context, port_id)
|
||||
if port['device_id'] != device_id:
|
||||
raise n_exc.PortInUse(net_id=port['network_id'],
|
||||
|
@ -673,6 +690,9 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
|
|||
if not port['fixed_ips']:
|
||||
msg = _('Router port must have at least one fixed IP')
|
||||
raise n_exc.BadRequest(resource='router', msg=msg)
|
||||
if router_id is not None:
|
||||
self._notify_attaching_interface(context, router_id,
|
||||
port['network_id'])
|
||||
return port
|
||||
|
||||
def _validate_router_port_info(self, context, router, port_id):
|
||||
|
@ -712,6 +732,31 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
|
|||
raise n_exc.BadRequest(resource='router', msg=msg)
|
||||
return port, subnets
|
||||
|
||||
def _notify_attaching_interface(self, context, router_id, network_id):
|
||||
"""Notify third party code that an interface is being attached to a
|
||||
router
|
||||
|
||||
:param context: The context of the request.
|
||||
:param router_id: The id of the router the interface is being
|
||||
attached to.
|
||||
:param network_id: The id of the network the port belongs to.
|
||||
:raises: RouterInterfaceAttachmentConflict if a third party code
|
||||
prevent the port to be attach to the router.
|
||||
"""
|
||||
try:
|
||||
registry.notify(resources.ROUTER_INTERFACE,
|
||||
events.BEFORE_CREATE,
|
||||
self,
|
||||
context=context,
|
||||
router_id=router_id,
|
||||
network_id=network_id
|
||||
)
|
||||
except exceptions.CallbackFailure as e:
|
||||
# raise the underlying exception
|
||||
reason = (_('cannot perform router interface attachment '
|
||||
'due to %(reason)s') % {'reason': e})
|
||||
raise l3.RouterInterfaceAttachmentConflict(reason=reason)
|
||||
|
||||
def _add_interface_by_port(self, context, router, port_id, owner):
|
||||
# Update owner before actual process in order to avoid the
|
||||
# case where a port might get attached to a router without the
|
||||
|
@ -735,6 +780,8 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
|
|||
|
||||
def _add_interface_by_subnet(self, context, router, subnet_id, owner):
|
||||
subnet = self._core_plugin.get_subnet(context, subnet_id)
|
||||
self._notify_attaching_interface(context, router.id,
|
||||
subnet['network_id'])
|
||||
if not subnet['gateway_ip']:
|
||||
msg = _('Subnet for router interface must have a gateway IP')
|
||||
raise n_exc.BadRequest(resource='router', msg=msg)
|
||||
|
@ -796,7 +843,8 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
|
|||
|
||||
if add_by_port:
|
||||
port_id = interface_info['port_id']
|
||||
port = self._check_router_port(context, port_id, '')
|
||||
port = self._check_router_port(context, port_id, '',
|
||||
router_id=router_id)
|
||||
revert_value = {'device_id': '',
|
||||
'device_owner': port['device_owner']}
|
||||
with p_utils.update_port_on_error(
|
||||
|
|
|
@ -319,7 +319,8 @@ class L3_NAT_with_dvr_db_mixin(l3_db.L3_NAT_db_mixin,
|
|||
|
||||
if add_by_port:
|
||||
port_id = interface_info['port_id']
|
||||
port = self._check_router_port(context, port_id, '')
|
||||
port = self._check_router_port(context, port_id, '',
|
||||
router_id=router_id)
|
||||
revert_value = {'device_id': '',
|
||||
'device_owner': port['device_owner']}
|
||||
with p_utils.update_port_on_error(
|
||||
|
|
|
@ -79,6 +79,11 @@ class RouterExternalGatewayInUseByFloatingIp(nexception.InUse):
|
|||
"gateway to external network %(net_id)s is required by one or "
|
||||
"more floating IPs.")
|
||||
|
||||
|
||||
class RouterInterfaceAttachmentConflict(nexception.Conflict):
|
||||
message = _("Error %(reason)s while attempting the operation.")
|
||||
|
||||
|
||||
ROUTERS = 'routers'
|
||||
FLOATINGIP = 'floatingip'
|
||||
FLOATINGIPS = '%ss' % FLOATINGIP
|
||||
|
|
|
@ -16,6 +16,9 @@ import mock
|
|||
from neutron_lib import constants
|
||||
|
||||
from neutron.api.rpc.handlers import l3_rpc
|
||||
from neutron.callbacks import events
|
||||
from neutron.callbacks import registry
|
||||
from neutron.callbacks import resources
|
||||
from neutron.common import topics
|
||||
from neutron import context
|
||||
from neutron.extensions import external_net
|
||||
|
@ -1533,6 +1536,41 @@ class L3DvrTestCase(ml2_test_base.ML2TestFramework):
|
|||
self.assertEqual(1, len(agents['agents']))
|
||||
self.assertEqual(self.l3_agent['id'], agents['agents'][0]['id'])
|
||||
|
||||
def test_add_router_interface_by_subnet_notification(self):
|
||||
notif_handler = mock.Mock()
|
||||
registry.subscribe(notif_handler.callback,
|
||||
resources.ROUTER_INTERFACE,
|
||||
events.BEFORE_CREATE)
|
||||
router = self._create_router()
|
||||
with self.network() as net, \
|
||||
self.subnet(network=net) as subnet:
|
||||
self.l3_plugin.add_router_interface(
|
||||
self.context, router['id'],
|
||||
{'subnet_id': subnet['subnet']['id']})
|
||||
kwargs = {'context': self.context, 'router_id': router['id'],
|
||||
'network_id': net['network']['id']}
|
||||
notif_handler.callback.assert_called_once_with(
|
||||
resources.ROUTER_INTERFACE, events.BEFORE_CREATE,
|
||||
mock.ANY, **kwargs)
|
||||
|
||||
def test_add_router_interface_by_port_notification(self):
|
||||
notif_handler = mock.Mock()
|
||||
registry.subscribe(notif_handler.callback,
|
||||
resources.ROUTER_INTERFACE,
|
||||
events.BEFORE_CREATE)
|
||||
router = self._create_router()
|
||||
with self.network() as net, \
|
||||
self.subnet(network=net) as subnet, \
|
||||
self.port(subnet=subnet) as port:
|
||||
self.l3_plugin.add_router_interface(
|
||||
self.context, router['id'],
|
||||
{'port_id': port['port']['id']})
|
||||
kwargs = {'context': self.context, 'router_id': router['id'],
|
||||
'network_id': net['network']['id']}
|
||||
notif_handler.callback.assert_called_once_with(
|
||||
resources.ROUTER_INTERFACE, events.BEFORE_CREATE,
|
||||
mock.ANY, **kwargs)
|
||||
|
||||
|
||||
class L3DvrTestCaseMigration(L3DvrTestCase):
|
||||
def test_update_router_db_centralized_to_distributed_with_ports(self):
|
||||
|
|
|
@ -227,6 +227,18 @@ class TestL3_NAT_dbonly_mixin(base.BaseTestCase):
|
|||
mock_get_assoc_data.assert_called_once_with(
|
||||
mock.ANY, fip, floatingip_db)
|
||||
|
||||
def test__notify_attaching_interface(self):
|
||||
with mock.patch.object(l3_db.registry, 'notify') as mock_notify:
|
||||
context = mock.MagicMock()
|
||||
router_id = 'router_id'
|
||||
net_id = 'net_id'
|
||||
self.db._notify_attaching_interface(context, router_id, net_id)
|
||||
kwargs = {'context': context, 'router_id': router_id,
|
||||
'network_id': net_id}
|
||||
mock_notify.assert_called_once_with(
|
||||
resources.ROUTER_INTERFACE, events.BEFORE_CREATE, self.db,
|
||||
**kwargs)
|
||||
|
||||
|
||||
class L3_NAT_db_mixin(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
|
|
|
@ -653,6 +653,37 @@ class L3DvrTestCase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase):
|
|||
mock_notify.assert_called_once_with(
|
||||
'router', 'before_update', self.mixin, **kwargs)
|
||||
|
||||
def test_validate_add_router_interface_by_subnet_notify_advanced_services(
|
||||
self):
|
||||
router = {'name': 'foo_router', 'admin_state_up': False}
|
||||
router_db = self._create_router(router)
|
||||
with self.network() as net, \
|
||||
self.subnet(network={'network': net['network']}) as sub, \
|
||||
mock.patch.object(
|
||||
self.mixin,
|
||||
'_notify_attaching_interface') as mock_notify:
|
||||
interface_info = {'subnet_id': sub['subnet']['id']}
|
||||
self.mixin.add_router_interface(self.ctx, router_db.id,
|
||||
interface_info)
|
||||
mock_notify.assert_called_once_with(self.ctx, router_db.id,
|
||||
sub['subnet']['network_id'])
|
||||
|
||||
def test_validate_add_router_interface_by_port_notify_advanced_services(
|
||||
self):
|
||||
router = {'name': 'foo_router', 'admin_state_up': False}
|
||||
router_db = self._create_router(router)
|
||||
with self.network() as net, \
|
||||
self.subnet(network={'network': net['network']}) as sub, \
|
||||
self.port(subnet=sub) as port, \
|
||||
mock.patch.object(
|
||||
self.mixin,
|
||||
'_notify_attaching_interface') as mock_notify:
|
||||
interface_info = {'port_id': port['port']['id']}
|
||||
self.mixin.add_router_interface(self.ctx, router_db.id,
|
||||
interface_info)
|
||||
mock_notify.assert_called_once_with(self.ctx, router_db.id,
|
||||
net['network']['id'])
|
||||
|
||||
def _test_update_arp_entry_for_dvr_service_port(
|
||||
self, device_owner, action):
|
||||
router_dict = {'name': 'test_router', 'admin_state_up': True,
|
||||
|
|
Loading…
Reference in New Issue