Skip to content

Commit 8aae3e3

Browse files
committed
Fix volume-backed resize with a smaller disk flavor
Change I06fad233006c7bab14749a51ffa226c3801f951b in Stein started calling _validate_flavor_image_nostatus during resize but did not pass the root_bdm since the underlying volume does not change. This can, however, lead to FlavorDiskSmallerThanMinDisk being raised if the new flavor has a disk size smaller than the current flavor. This is wrong in the case of a volume-backed server because we don't care about the root disk size in the flavor in that case. This fixes the bug by splitting the pci/numa validation logic out of _validate_flavor_image_nostatus into a separate method so that the resize method can call that directly for a volume-backed server rather than deal with the complicated disk size logic in _validate_flavor_image_nostatus (see bug 1646740 for details, but tl;dr if the image min_disk is less than the flavor root_gb, the min_disk stored in the instance image system metadata is the flavor['root_gb'] which could be larger than the root volume size). To see why trying to use _validate_flavor_image_nostatus during resize for a volume-backed server is a mess, look at PS1 of this change. Change-Id: I91c9c1e88afa035c757f692c78ad72d69cc3c431 Closes-Bug: #1825020
1 parent a40c7f3 commit 8aae3e3

File tree

3 files changed

+61
-21
lines changed

3 files changed

+61
-21
lines changed

nova/compute/api.py

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -570,8 +570,7 @@ def _validate_flavor_image_nostatus(context, image, instance_type,
570570
with each other.
571571
572572
:param context: A context.RequestContext
573-
:param image: a dict representation of the image including properties,
574-
enforces the image status is active.
573+
:param image: a dict representation of the image including properties
575574
:param instance_type: Flavor object
576575
:param root_bdm: BlockDeviceMapping for root disk. Will be None for
577576
the resize case.
@@ -664,6 +663,30 @@ def _validate_flavor_image_nostatus(context, image, instance_type,
664663
servers_policies.ZERO_DISK_FLAVOR, fatal=False):
665664
raise exception.BootFromVolumeRequiredForZeroDiskFlavor()
666665

666+
API._validate_flavor_image_numa_pci(
667+
image, instance_type, validate_numa=validate_numa,
668+
validate_pci=validate_pci)
669+
670+
@staticmethod
671+
def _validate_flavor_image_numa_pci(image, instance_type,
672+
validate_numa=True,
673+
validate_pci=False):
674+
"""Validate the flavor and image NUMA/PCI values.
675+
676+
This is called from the API service to ensure that the flavor
677+
extra-specs and image properties are self-consistent and compatible
678+
with each other.
679+
680+
:param image: a dict representation of the image including properties
681+
:param instance_type: Flavor object
682+
:param validate_numa: Flag to indicate whether or not to validate
683+
the NUMA-related metadata.
684+
:param validate_pci: Flag to indicate whether or not to validate
685+
the PCI-related metadata.
686+
:raises: Many different possible exceptions. See
687+
api.openstack.compute.servers.INVALID_FLAVOR_IMAGE_EXCEPTIONS
688+
for the full list.
689+
"""
667690
image_meta = _get_image_meta_obj(image)
668691

669692
# Only validate values of flavor/image so the return results of
@@ -3603,19 +3626,23 @@ def resize(self, context, instance, flavor_id=None, clean_shutdown=True,
36033626
current_instance_type = instance.get_flavor()
36043627

36053628
# If flavor_id is not provided, only migrate the instance.
3629+
volume_backed = None
36063630
if not flavor_id:
36073631
LOG.debug("flavor_id is None. Assuming migration.",
36083632
instance=instance)
36093633
new_instance_type = current_instance_type
36103634
else:
36113635
new_instance_type = flavors.get_flavor_by_flavor_id(
36123636
flavor_id, read_deleted="no")
3637+
# Check to see if we're resizing to a zero-disk flavor which is
3638+
# only supported with volume-backed servers.
36133639
if (new_instance_type.get('root_gb') == 0 and
3614-
current_instance_type.get('root_gb') != 0 and
3615-
not compute_utils.is_volume_backed_instance(context,
3616-
instance)):
3617-
reason = _('Resize to zero disk flavor is not allowed.')
3618-
raise exception.CannotResizeDisk(reason=reason)
3640+
current_instance_type.get('root_gb') != 0):
3641+
volume_backed = compute_utils.is_volume_backed_instance(
3642+
context, instance)
3643+
if not volume_backed:
3644+
reason = _('Resize to zero disk flavor is not allowed.')
3645+
raise exception.CannotResizeDisk(reason=reason)
36193646

36203647
if not new_instance_type:
36213648
raise exception.FlavorNotFound(flavor_id=flavor_id)
@@ -3648,10 +3675,23 @@ def resize(self, context, instance, flavor_id=None, clean_shutdown=True,
36483675
if not same_instance_type:
36493676
image = utils.get_image_from_system_metadata(
36503677
instance.system_metadata)
3651-
# Can skip root_bdm check since it will not change during resize.
3652-
self._validate_flavor_image_nostatus(
3653-
context, image, new_instance_type, root_bdm=None,
3654-
validate_pci=True)
3678+
# Figure out if the instance is volume-backed but only if we didn't
3679+
# already figure that out above (avoid the extra db hit).
3680+
if volume_backed is None:
3681+
volume_backed = compute_utils.is_volume_backed_instance(
3682+
context, instance)
3683+
# If the server is volume-backed, we still want to validate numa
3684+
# and pci information in the new flavor, but we don't call
3685+
# _validate_flavor_image_nostatus because how it handles checking
3686+
# disk size validation was not intended for a volume-backed
3687+
# resize case.
3688+
if volume_backed:
3689+
self._validate_flavor_image_numa_pci(
3690+
image, new_instance_type, validate_pci=True)
3691+
else:
3692+
self._validate_flavor_image_nostatus(
3693+
context, image, new_instance_type, root_bdm=None,
3694+
validate_pci=True)
36553695

36563696
filter_properties = {'ignore_hosts': []}
36573697

nova/tests/functional/regressions/test_bug_1825020.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,8 @@
1010
# License for the specific language governing permissions and limitations
1111
# under the License.
1212

13-
import six
14-
1513
from nova import test
1614
from nova.tests import fixtures as nova_fixtures
17-
from nova.tests.functional.api import client as api_client
1815
from nova.tests.functional import fixtures as func_fixtures
1916
from nova.tests.functional import integrated_helpers
2017
from nova.tests.unit.image import fake as fake_image
@@ -73,10 +70,11 @@ def test_volume_backed_resize_disk_down(self):
7370
self._wait_for_state_change(self.api, server, 'ACTIVE')
7471

7572
# Now try to resize the server with the flavor that has smaller disk.
73+
# This should be allowed since the server is volume-backed and the
74+
# disk size in the flavor shouldn't matter.
7675
data = {'resize': {'flavorRef': flavor1['id']}}
77-
# FIXME(mriedem): This will raise FlavorDiskSmallerThanMinDisk as a 500
78-
# error until bug 1825020 is fixed.
79-
ex = self.assertRaises(api_client.OpenStackApiException,
80-
self.api.post_server_action, server['id'], data)
81-
self.assertEqual(500, ex.response.status_code)
82-
self.assertIn('FlavorDiskSmallerThanMinDisk', six.text_type(ex))
76+
self.api.post_server_action(server['id'], data)
77+
self._wait_for_state_change(self.api, server, 'VERIFY_RESIZE')
78+
# Now confirm the resize just to complete the operation.
79+
self.api.post_server_action(server['id'], {'confirmResize': None})
80+
self._wait_for_state_change(self.api, server, 'ACTIVE')

nova/tests/unit/compute/test_compute_api.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1932,6 +1932,8 @@ def test_revert_resize_concurrent_fail(self, mock_reqspec, mock_elevated,
19321932
self.context, fake_inst['uuid'], 'finished')
19331933
mock_inst_save.assert_called_once_with(expected_task_state=[None])
19341934

1935+
@mock.patch('nova.compute.utils.is_volume_backed_instance',
1936+
return_value=False)
19351937
@mock.patch('nova.compute.api.API._validate_flavor_image_nostatus')
19361938
@mock.patch('nova.objects.Migration')
19371939
@mock.patch.object(compute_api.API, '_record_action_start')
@@ -1945,7 +1947,7 @@ def test_revert_resize_concurrent_fail(self, mock_reqspec, mock_elevated,
19451947
def _test_resize(self, mock_get_all_by_host,
19461948
mock_get_by_instance_uuid, mock_get_flavor, mock_upsize,
19471949
mock_inst_save, mock_count, mock_limit, mock_record,
1948-
mock_migration, mock_validate,
1950+
mock_migration, mock_validate, mock_is_vol_backed,
19491951
flavor_id_passed=True,
19501952
same_host=False, allow_same_host=False,
19511953
project_id=None,

0 commit comments

Comments
 (0)