diff --git a/nova/compute/api.py b/nova/compute/api.py index 346c01a66e4b..09dad41dd8db 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -4269,6 +4269,12 @@ class API: if volume_backed: self._validate_flavor_image_numa_pci( image, new_flavor, validate_pci=True) + # The server that image-backed already has the verification of + # image min_ram when calling _validate_flavor_image_nostatus. + # Here, the verification is added for the server that + # volume-backed. + if new_flavor['memory_mb'] < int(image.get('min_ram', 0)): + raise exception.FlavorMemoryTooSmall() else: self._validate_flavor_image_nostatus( context, image, new_flavor, root_bdm=None, diff --git a/nova/tests/functional/regressions/test_bug_2007968.py b/nova/tests/functional/regressions/test_bug_2007968.py index cfadb8a58644..874dda2725fb 100644 --- a/nova/tests/functional/regressions/test_bug_2007968.py +++ b/nova/tests/functional/regressions/test_bug_2007968.py @@ -13,6 +13,7 @@ import fixtures from nova.tests.fixtures import libvirt as fakelibvirt +from nova.tests.functional.api import client from nova.tests.functional.libvirt import base @@ -56,5 +57,11 @@ class Bug2007968RegressionTest(base.ServersTestBase): # This can cause the instance for boot from volume to be allowed to # get to the resize verify status, but the instance's application runs # abnormally due to insufficient memory, and it may be killed by OOM. - server = self._resize_server(server, new_flavor) - self.assertEqual(server['status'], 'VERIFY_RESIZE') + + # After the fix, compute api will directly raise FlavorMemoryTooSmall + # and will not continue the resize. + ex = self.assertRaises(client.OpenStackApiException, + self._resize_server, server, new_flavor) + self.assertEqual(400, ex.response.status_code) + self.assertIn('Flavor\'s memory is too small for requested image.', + ex.response.text) diff --git a/nova/tests/unit/compute/test_api.py b/nova/tests/unit/compute/test_api.py index 01afe5ebe1c8..dd31e38556f0 100644 --- a/nova/tests/unit/compute/test_api.py +++ b/nova/tests/unit/compute/test_api.py @@ -2376,6 +2376,39 @@ class _ComputeAPIUnitTestMixIn(object): mock_resize.assert_not_called() mock_save.assert_not_called() + @mock.patch('nova.compute.utils.is_volume_backed_instance', + return_value=True) + @mock.patch('nova.servicegroup.api.API.service_is_up', + new=mock.Mock(return_value=True)) + @mock.patch.object(objects.Instance, 'save') + @mock.patch.object(compute_api.API, '_record_action_start') + @mock.patch.object(quotas_obj.Quotas, 'limit_check_project_and_user') + @mock.patch.object(quotas_obj.Quotas, 'count_as_dict') + @mock.patch.object(flavors, 'get_flavor_by_flavor_id') + def test_resize_vol_backed_smaller_min_ram(self, mock_get_flavor, + mock_count, mock_limit, + mock_record, mock_save, + mock_is_vol_backed): + mock_resize = self.useFixture(fixtures.MockPatchObject( + self.compute_api.compute_task_api, 'resize_instance')).mock + # Resize down from 512 MB to 64 MB. + params = dict(image_ref='', system_metadata={'min_ram': 512}) + fake_inst = self._create_instance_obj(params=params) + new_flavor = self._create_flavor(id=200, flavorid=200, + name='new_flavor', disabled=False, + memory_mb=64) + mock_get_flavor.return_value = new_flavor + self.assertRaises(exception.FlavorMemoryTooSmall, + self.compute_api.resize, self.context, + fake_inst, flavor_id=new_flavor.id) + mock_get_flavor.assert_called_once_with(200, read_deleted='no') + # Should never reach these. + mock_count.assert_not_called() + mock_limit.assert_not_called() + mock_record.assert_not_called() + mock_resize.assert_not_called() + mock_save.assert_not_called() + @mock.patch('nova.servicegroup.api.API.service_is_up', new=mock.Mock(return_value=True)) @mock.patch.object(objects.Instance, 'save')