From f3eb76e57b8cafaeb892366550dc6bfaa4d2b4bb Mon Sep 17 00:00:00 2001 From: "zhong.zhou" Date: Sat, 3 Feb 2024 23:58:41 +0800 Subject: [PATCH] Validate flavor image min ram when resize volume-backed instance When resize instance, the flavors returned may not meet the image minimum memory requirement, resizing instance ignores the minimum memory limit of the image, which may cause the resizing be successfully, but the instance fails to start because the memory is too small to run the system. Related-Bug: 2007968 Change-Id: I132e444eedc10b950a2fc9ed259cd6d9aa9bed65 --- nova/compute/api.py | 6 ++++ .../regressions/test_bug_2007968.py | 11 +++++-- nova/tests/unit/compute/test_api.py | 33 +++++++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 346c01a66e..09dad41dd8 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 cfadb8a586..874dda2725 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 01afe5ebe1..dd31e38556 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')