diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 9f4df67a69..71f16f7633 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -616,6 +616,7 @@ class Executor(wsgi.Application): except (exception.CannotDisassociateAutoAssignedFloatingIP, exception.FloatingIpAssociated, exception.FloatingIpNotFound, + exception.FloatingIpBadRequest, exception.ImageNotActive, exception.InvalidInstanceIDMalformed, exception.InvalidVolumeIDMalformed, diff --git a/nova/api/openstack/compute/floating_ips.py b/nova/api/openstack/compute/floating_ips.py index 3550e18df8..d080f9acd6 100644 --- a/nova/api/openstack/compute/floating_ips.py +++ b/nova/api/openstack/compute/floating_ips.py @@ -118,7 +118,7 @@ class FloatingIPController(object): return _translate_floating_ips_view(floating_ips) - @extensions.expected_errors((403, 404)) + @extensions.expected_errors((400, 403, 404)) def create(self, req, body=None): context = req.environ['nova.context'] authorize(context) @@ -143,6 +143,8 @@ class FloatingIPController(object): raise webob.exc.HTTPForbidden(explanation=msg) except exception.FloatingIpPoolNotFound as e: raise webob.exc.HTTPNotFound(explanation=e.format_message()) + except exception.FloatingIpBadRequest as e: + raise webob.exc.HTTPBadRequest(explanation=e.format_message()) return _translate_floating_ip_view(ip) diff --git a/nova/api/openstack/compute/legacy_v2/contrib/floating_ips.py b/nova/api/openstack/compute/legacy_v2/contrib/floating_ips.py index 1a554be3ba..3fb5129e85 100644 --- a/nova/api/openstack/compute/legacy_v2/contrib/floating_ips.py +++ b/nova/api/openstack/compute/legacy_v2/contrib/floating_ips.py @@ -128,6 +128,8 @@ class FloatingIPController(object): raise webob.exc.HTTPForbidden(explanation=msg) except exception.FloatingIpPoolNotFound as e: raise webob.exc.HTTPNotFound(explanation=e.format_message()) + except exception.FloatingIpBadRequest as e: + raise webob.exc.HTTPBadRequest(explanation=e.format_message()) return _translate_floating_ip_view(ip) diff --git a/nova/exception.py b/nova/exception.py index 1e436ba9ee..9b901dc28d 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -900,6 +900,11 @@ class FloatingIpAssociateFailed(NovaException): msg_fmt = _("Floating IP %(address)s association has failed.") +class FloatingIpBadRequest(Invalid): + ec2_code = "UnsupportedOperation" + msg_fmt = _("The floating IP request failed with a BadRequest") + + class CannotDisassociateAutoAssignedFloatingIP(NovaException): ec2_code = "UnsupportedOperation" msg_fmt = _("Cannot disassociate auto assigned floating ip") diff --git a/nova/network/neutronv2/api.py b/nova/network/neutronv2/api.py index 0b503453b5..3fbaba0b2f 100644 --- a/nova/network/neutronv2/api.py +++ b/nova/network/neutronv2/api.py @@ -1399,6 +1399,8 @@ class API(base_api.NetworkAPI): raise exception.NoMoreFloatingIps(six.text_type(e)) except neutron_client_exc.OverQuotaClient as e: raise exception.FloatingIpLimitExceeded(six.text_type(e)) + except neutron_client_exc.BadRequest as e: + raise exception.FloatingIpBadRequest(six.text_type(e)) return fip['floatingip']['floating_ip_address'] diff --git a/nova/tests/unit/api/ec2/test_middleware.py b/nova/tests/unit/api/ec2/test_middleware.py index ab241821cf..20b9eccbe9 100644 --- a/nova/tests/unit/api/ec2/test_middleware.py +++ b/nova/tests/unit/api/ec2/test_middleware.py @@ -150,6 +150,13 @@ class ExecutorTestCase(test.NoDBTestCase): self.assertIn('vol-00000005', self._extract_message(result)) self.assertEqual('InvalidVolume.NotFound', self._extract_code(result)) + def test_floating_ip_bad_create_request(self): + def bad_request(context): + raise exception.FloatingIpBadRequest() + result = self._execute(bad_request) + self.assertIn('BadRequest', self._extract_message(result)) + self.assertEqual('UnsupportedOperation', self._extract_code(result)) + class FakeResponse(object): reason = "Test Reason" diff --git a/nova/tests/unit/api/openstack/compute/test_floating_ips.py b/nova/tests/unit/api/openstack/compute/test_floating_ips.py index 2b0fe0bce3..675f299cba 100644 --- a/nova/tests/unit/api/openstack/compute/test_floating_ips.py +++ b/nova/tests/unit/api/openstack/compute/test_floating_ips.py @@ -18,6 +18,7 @@ import contextlib import uuid import mock +import six import webob from nova.api.openstack.compute import floating_ips as fips_v21 @@ -428,6 +429,18 @@ class FloatingIpTestV21(test.TestCase): self.assertIn('No more floating ips in pool non_existent_pool', ex.explanation) + @mock.patch.object(network.api.API, 'allocate_floating_ip', + side_effect=exception.FloatingIpBadRequest( + 'Bad floatingip request: Network ' + 'c8f0e88f-ae41-47cb-be6c-d8256ba80576 does not contain any ' + 'IPv4 subnet')) + def test_floating_ip_allocate_no_ipv4_subnet(self, allocate_mock): + ex = self.assertRaises(webob.exc.HTTPBadRequest, + self.controller.create, self.fake_req, + {'pool': 'non_existent_pool'}) + self.assertIn("does not contain any IPv4 subnet", + six.text_type(ex)) + @mock.patch('nova.network.api.API.allocate_floating_ip', side_effect=exception.FloatingIpLimitExceeded()) def test_floating_ip_allocate_over_quota(self, allocate_mock): diff --git a/nova/tests/unit/network/test_neutronv2.py b/nova/tests/unit/network/test_neutronv2.py index 884fcbecf2..e5235fa6ed 100644 --- a/nova/tests/unit/network/test_neutronv2.py +++ b/nova/tests/unit/network/test_neutronv2.py @@ -3018,6 +3018,22 @@ class TestNeutronv2WithMock(test.TestCase): api.allocate_floating_ip, self.context, pool_name) + def test_allocate_floating_ip_no_ipv4_subnet(self): + api = neutronapi.API() + net_id = uuid.uuid4() + error_msg = ('Bad floatingip request: Network %s does not contain ' + 'any IPv4 subnet' % net_id) + with contextlib.nested( + mock.patch.object(client.Client, 'create_floatingip'), + mock.patch.object(api, + '_get_floating_ip_pool_id_by_name_or_id')) as ( + create_mock, get_mock): + create_mock.side_effect = exceptions.BadRequest(error_msg) + + self.assertRaises(exception.FloatingIpBadRequest, + api.allocate_floating_ip, self.context, + 'ext_net') + def test_create_port_for_instance_no_more_ip(self): instance = fake_instance.fake_instance_obj(self.context) net = {'id': 'my_netid1',