From 4711ff3adff2b4c9a54f5fb4448ace6558f82f6b Mon Sep 17 00:00:00 2001 From: Mike Perez Date: Tue, 4 Feb 2014 15:00:10 -0800 Subject: [PATCH] Raise descriptive error for over volume quota If you attempt to create a new instance booting from an image and you were over your quota on cinder, the nova manager would see that as an invalid block device. This raises a more descriptive error message in the logs. Closes-bug: #1013417 Change-Id: Ie50b97c31580da7fc38c6891d1f3646b91f8aa10 --- nova/compute/manager.py | 6 ++++ nova/tests/compute/test_compute.py | 45 ++++++++++++++++++++++++++++++ nova/tests/volume/test_cinder.py | 12 ++++++++ nova/volume/cinder.py | 2 ++ 4 files changed, 65 insertions(+) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 35b11e754d..6db63a0e15 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1733,6 +1733,12 @@ class ComputeManager(manager.Manager): block_device_info['swap']) return block_device_info + except exception.OverQuota: + msg = ('Failed to create block device for instance due to being ' + 'over volume resource quota') + LOG.debug(msg, instance=instance) + raise exception.InvalidBDM() + except Exception: LOG.exception(_('Instance failed block device setup'), instance=instance) diff --git a/nova/tests/compute/test_compute.py b/nova/tests/compute/test_compute.py index a61a45fcc7..13fbc0d6cd 100644 --- a/nova/tests/compute/test_compute.py +++ b/nova/tests/compute/test_compute.py @@ -1035,6 +1035,27 @@ class ComputeVolumeTestCase(BaseTestCase): self.compute.volume_snapshot_delete, self.context, self.instance_object, 'fake_id', 'fake_id2', {}) + @mock.patch.object(cinder.API, 'create', + side_effect=exception.OverQuota(overs='volumes')) + def test_prep_block_device_over_quota_failure(self, mock_create): + instance = self._create_fake_instance() + bdms = [ + block_device.BlockDeviceDict({ + 'boot_index': 0, + 'guest_format': None, + 'connection_info': None, + 'device_type': u'disk', + 'source_type': 'image', + 'destination_type': 'volume', + 'volume_size': 1, + 'image_id': 1, + 'device_name': '/dev/vdb', + })] + self.assertRaises(exception.InvalidBDM, + compute_manager.ComputeManager()._prep_block_device, + self.context, instance, bdms) + mock_create.assert_called_once() + class ComputeTestCase(BaseTestCase): def test_wrap_instance_fault(self): @@ -1513,6 +1534,30 @@ class ComputeTestCase(BaseTestCase): self._assert_state({'vm_state': vm_states.ERROR, 'task_state': None}) + @mock.patch('nova.compute.manager.ComputeManager._prep_block_device', + side_effect=exception.OverQuota(overs='volumes')) + def test_setup_block_device_over_quota_fail(self, mock_prep_block_dev): + """block device mapping over quota failure test. + + Make sure when we're over volume quota according to Cinder client, the + appropriate exception is raised and the instances to ERROR state, keep + the task state. + """ + instance = self._create_fake_instance() + self.assertRaises(exception.OverQuota, self.compute.run_instance, + self.context, instance=instance, request_spec={}, + filter_properties={}, requested_networks=[], + injected_files=None, admin_password=None, + is_first_time=True, node=None, + legacy_bdm_in_spec=False) + #check state is failed even after the periodic poll + self._assert_state({'vm_state': vm_states.ERROR, + 'task_state': None}) + self.compute.periodic_tasks(context.get_admin_context()) + self._assert_state({'vm_state': vm_states.ERROR, + 'task_state': None}) + mock_prep_block_dev.assert_called_once() + def test_run_instance_spawn_fail(self): """spawn failure test. diff --git a/nova/tests/volume/test_cinder.py b/nova/tests/volume/test_cinder.py index c12bdc2dc8..9aabdff8c9 100644 --- a/nova/tests/volume/test_cinder.py +++ b/nova/tests/volume/test_cinder.py @@ -14,6 +14,7 @@ # under the License. from cinderclient import exceptions as cinder_exception +import mock from nova import context from nova import exception @@ -84,6 +85,17 @@ class CinderApiTestCase(test.NoDBTestCase): self.assertRaises(exception.InvalidInput, self.api.create, self.ctx, 1, '', '') + @mock.patch('nova.volume.cinder.cinderclient') + def test_create_over_quota_failed(self, mock_cinderclient): + mock_cinderclient.return_value.volumes.create.side_effect = ( + cinder_exception.OverLimit(413)) + self.assertRaises(exception.OverQuota, self.api.create, self.ctx, + 1, '', '') + mock_cinderclient.return_value.volumes.create.assert_called_once_with( + 1, user_id=None, imageRef=None, availability_zone=None, + volume_type=None, display_description='', snapshot_id=None, + display_name='', project_id=None, metadata=None) + def test_get_all(self): cinder.cinderclient(self.ctx).AndReturn(self.cinderclient) cinder._untranslate_volume_summary_view(self.ctx, diff --git a/nova/volume/cinder.py b/nova/volume/cinder.py index f55ee61d4a..48c5f5ea65 100644 --- a/nova/volume/cinder.py +++ b/nova/volume/cinder.py @@ -301,6 +301,8 @@ class API(object): try: item = cinderclient(context).volumes.create(size, **kwargs) return _untranslate_volume_summary_view(context, item) + except cinder_exception.OverLimit: + raise exception.OverQuota(overs='volumes') except cinder_exception.BadRequest as e: raise exception.InvalidInput(reason=unicode(e))