Catch neutronclient.NotFound on floating deletion
In some cases, trying to delete a floating IP multiple times in a short
delay can trigger an exception beacause the floating ip deletion
operation is not atomic. If neutronclient's call to delete fails with a
NotFound error, we raise a 404 error to nova's client instead of a 500.
Change-Id: I49ea7e52073148457e794d641ed17d4ef58616f8
Co-Authored-By: Stephen Finucane <sfinucan@redhat.com>
Closes-Bug: #1649852
(cherry picked from commit d99197aece
)
This commit is contained in:
parent
47774935ac
commit
1052ad4d5a
|
@ -201,6 +201,8 @@ class FloatingIPController(wsgi.Controller):
|
||||||
except exception.CannotDisassociateAutoAssignedFloatingIP:
|
except exception.CannotDisassociateAutoAssignedFloatingIP:
|
||||||
msg = _('Cannot disassociate auto assigned floating IP')
|
msg = _('Cannot disassociate auto assigned floating IP')
|
||||||
raise webob.exc.HTTPForbidden(explanation=msg)
|
raise webob.exc.HTTPForbidden(explanation=msg)
|
||||||
|
except exception.FloatingIpNotFoundForAddress as exc:
|
||||||
|
raise webob.exc.HTTPNotFound(explanation=exc.format_message())
|
||||||
|
|
||||||
|
|
||||||
class FloatingIPActionController(wsgi.Controller):
|
class FloatingIPActionController(wsgi.Controller):
|
||||||
|
|
|
@ -2008,7 +2008,12 @@ class API(base_api.NetworkAPI):
|
||||||
|
|
||||||
if raise_if_associated and fip['port_id']:
|
if raise_if_associated and fip['port_id']:
|
||||||
raise exception.FloatingIpAssociated(address=address)
|
raise exception.FloatingIpAssociated(address=address)
|
||||||
client.delete_floatingip(fip['id'])
|
try:
|
||||||
|
client.delete_floatingip(fip['id'])
|
||||||
|
except neutron_client_exc.NotFound:
|
||||||
|
raise exception.FloatingIpNotFoundForAddress(
|
||||||
|
address=address
|
||||||
|
)
|
||||||
|
|
||||||
@base_api.refresh_cache
|
@base_api.refresh_cache
|
||||||
def disassociate_floating_ip(self, context, instance, address,
|
def disassociate_floating_ip(self, context, instance, address,
|
||||||
|
|
|
@ -159,6 +159,44 @@ class FloatingIpTestNeutronV21(test.NoDBTestCase):
|
||||||
ex = exception.InvalidID(id=1)
|
ex = exception.InvalidID(id=1)
|
||||||
self._test_floatingip_delete_not_found(ex, webob.exc.HTTPBadRequest)
|
self._test_floatingip_delete_not_found(ex, webob.exc.HTTPBadRequest)
|
||||||
|
|
||||||
|
def _test_floatingip_delete_error_disassociate(self, raised_exc,
|
||||||
|
expected_exc):
|
||||||
|
"""Ensure that various exceptions are correctly transformed.
|
||||||
|
|
||||||
|
Handle the myriad exceptions that could be raised from the
|
||||||
|
'disassociate_and_release_floating_ip' call.
|
||||||
|
"""
|
||||||
|
req = fakes.HTTPRequest.blank('')
|
||||||
|
with mock.patch.object(self.controller.network_api,
|
||||||
|
'get_floating_ip',
|
||||||
|
return_value={'address': 'foo'}), \
|
||||||
|
mock.patch.object(self.controller.network_api,
|
||||||
|
'get_instance_id_by_floating_address',
|
||||||
|
return_value=None), \
|
||||||
|
mock.patch.object(self.controller.network_api,
|
||||||
|
'disassociate_and_release_floating_ip',
|
||||||
|
side_effect=raised_exc):
|
||||||
|
self.assertRaises(expected_exc,
|
||||||
|
self.controller.delete, req, 1)
|
||||||
|
|
||||||
|
def test_floatingip_delete_error_disassociate_1(self):
|
||||||
|
raised_exc = exception.Forbidden
|
||||||
|
expected_exc = webob.exc.HTTPForbidden
|
||||||
|
self._test_floatingip_delete_error_disassociate(raised_exc,
|
||||||
|
expected_exc)
|
||||||
|
|
||||||
|
def test_floatingip_delete_error_disassociate_2(self):
|
||||||
|
raised_exc = exception.CannotDisassociateAutoAssignedFloatingIP
|
||||||
|
expected_exc = webob.exc.HTTPForbidden
|
||||||
|
self._test_floatingip_delete_error_disassociate(raised_exc,
|
||||||
|
expected_exc)
|
||||||
|
|
||||||
|
def test_floatingip_delete_error_disassociate_3(self):
|
||||||
|
raised_exc = exception.FloatingIpNotFoundForAddress(address='1.1.1.1')
|
||||||
|
expected_exc = webob.exc.HTTPNotFound
|
||||||
|
self._test_floatingip_delete_error_disassociate(raised_exc,
|
||||||
|
expected_exc)
|
||||||
|
|
||||||
|
|
||||||
class FloatingIpTestV21(test.TestCase):
|
class FloatingIpTestV21(test.TestCase):
|
||||||
floating_ip = "10.10.10.10"
|
floating_ip = "10.10.10.10"
|
||||||
|
|
|
@ -3516,6 +3516,26 @@ class TestNeutronv2WithMock(test.TestCase):
|
||||||
api.allocate_floating_ip, self.context,
|
api.allocate_floating_ip, self.context,
|
||||||
'ext_net')
|
'ext_net')
|
||||||
|
|
||||||
|
@mock.patch('nova.network.neutronv2.api.get_client')
|
||||||
|
@mock.patch('nova.network.neutronv2.api.API._get_floating_ip_by_address',
|
||||||
|
return_value={'port_id': None, 'id': 'abc'})
|
||||||
|
def test_release_floating_ip_not_found(self, mock_get_ip, mock_ntrn):
|
||||||
|
"""Ensure neutron's NotFound exception is correctly handled.
|
||||||
|
|
||||||
|
Sometimes, trying to delete a floating IP multiple times in a short
|
||||||
|
delay can trigger an exception because the operation is not atomic. If
|
||||||
|
neutronclient's call to delete fails with a NotFound error, then we
|
||||||
|
should correctly handle this.
|
||||||
|
"""
|
||||||
|
mock_nc = mock.Mock()
|
||||||
|
mock_ntrn.return_value = mock_nc
|
||||||
|
mock_nc.delete_floatingip.side_effect = exceptions.NotFound()
|
||||||
|
address = '172.24.4.227'
|
||||||
|
|
||||||
|
self.assertRaises(exception.FloatingIpNotFoundForAddress,
|
||||||
|
self.api.release_floating_ip,
|
||||||
|
self.context, address)
|
||||||
|
|
||||||
@mock.patch.object(client.Client, 'create_port')
|
@mock.patch.object(client.Client, 'create_port')
|
||||||
def test_create_port_minimal_raise_no_more_ip(self, create_port_mock):
|
def test_create_port_minimal_raise_no_more_ip(self, create_port_mock):
|
||||||
instance = fake_instance.fake_instance_obj(self.context)
|
instance = fake_instance.fake_instance_obj(self.context)
|
||||||
|
|
Loading…
Reference in New Issue