Merge "Add checks for volume status when rebuilding" into stable/train
This commit is contained in:
commit
5cebdb7b71
|
@ -1139,6 +1139,7 @@ class ServersController(wsgi.Controller):
|
||||||
exception.ImageNotActive,
|
exception.ImageNotActive,
|
||||||
exception.ImageUnacceptable,
|
exception.ImageUnacceptable,
|
||||||
exception.InvalidMetadata,
|
exception.InvalidMetadata,
|
||||||
|
exception.InvalidVolume,
|
||||||
) as error:
|
) as error:
|
||||||
raise exc.HTTPBadRequest(explanation=error.format_message())
|
raise exc.HTTPBadRequest(explanation=error.format_message())
|
||||||
except INVALID_FLAVOR_IMAGE_EXCEPTIONS as error:
|
except INVALID_FLAVOR_IMAGE_EXCEPTIONS as error:
|
||||||
|
|
|
@ -3346,6 +3346,12 @@ class API(base.Base):
|
||||||
self._checks_for_create_and_rebuild(context, image_id, image,
|
self._checks_for_create_and_rebuild(context, image_id, image,
|
||||||
flavor, metadata, files_to_inject, root_bdm)
|
flavor, metadata, files_to_inject, root_bdm)
|
||||||
|
|
||||||
|
# Check the state of the volume. If it is not in-use, an exception
|
||||||
|
# will occur when creating attachment during reconstruction,
|
||||||
|
# resulting in the failure of reconstruction and the instance
|
||||||
|
# turning into an error state.
|
||||||
|
self._check_volume_status(context, bdms)
|
||||||
|
|
||||||
# NOTE(sean-k-mooney): When we rebuild with a new image we need to
|
# NOTE(sean-k-mooney): When we rebuild with a new image we need to
|
||||||
# validate that the NUMA topology does not change as we do a NOOP claim
|
# validate that the NUMA topology does not change as we do a NOOP claim
|
||||||
# in resource tracker. As such we cannot allow the resource usage or
|
# in resource tracker. As such we cannot allow the resource usage or
|
||||||
|
@ -3445,6 +3451,17 @@ class API(base.Base):
|
||||||
preserve_ephemeral=preserve_ephemeral, host=host,
|
preserve_ephemeral=preserve_ephemeral, host=host,
|
||||||
request_spec=request_spec)
|
request_spec=request_spec)
|
||||||
|
|
||||||
|
def _check_volume_status(self, context, bdms):
|
||||||
|
"""Check whether the status of the volume is "in-use".
|
||||||
|
|
||||||
|
:param context: A context.RequestContext
|
||||||
|
:param bdms: BlockDeviceMappingList of BDMs for the instance
|
||||||
|
"""
|
||||||
|
for bdm in bdms:
|
||||||
|
if bdm.volume_id:
|
||||||
|
vol = self.volume_api.get(context, bdm.volume_id)
|
||||||
|
self.volume_api.check_attached(context, vol)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _validate_numa_rebuild(instance, image, flavor):
|
def _validate_numa_rebuild(instance, image, flavor):
|
||||||
"""validates that the NUMA constraints do not change on rebuild.
|
"""validates that the NUMA constraints do not change on rebuild.
|
||||||
|
@ -3963,10 +3980,8 @@ class API(base.Base):
|
||||||
|
|
||||||
bdms = objects.BlockDeviceMappingList.get_by_instance_uuid(
|
bdms = objects.BlockDeviceMappingList.get_by_instance_uuid(
|
||||||
context, instance.uuid)
|
context, instance.uuid)
|
||||||
for bdm in bdms:
|
self._check_volume_status(context, bdms)
|
||||||
if bdm.volume_id:
|
|
||||||
vol = self.volume_api.get(context, bdm.volume_id)
|
|
||||||
self.volume_api.check_attached(context, vol)
|
|
||||||
if compute_utils.is_volume_backed_instance(context, instance, bdms):
|
if compute_utils.is_volume_backed_instance(context, instance, bdms):
|
||||||
reason = _("Cannot rescue a volume-backed instance")
|
reason = _("Cannot rescue a volume-backed instance")
|
||||||
raise exception.InstanceNotRescuable(instance_id=instance.uuid,
|
raise exception.InstanceNotRescuable(instance_id=instance.uuid,
|
||||||
|
|
|
@ -642,6 +642,21 @@ class ServerActionsControllerTestV21(test.TestCase):
|
||||||
self.controller._action_rebuild,
|
self.controller._action_rebuild,
|
||||||
self.req, FAKE_UUID, body=body)
|
self.req, FAKE_UUID, body=body)
|
||||||
|
|
||||||
|
@mock.patch.object(compute_api.API, 'rebuild')
|
||||||
|
def test_rebuild_raise_invalid_volume_exc(self, mock_rebuild):
|
||||||
|
"""Make sure that we can't rebuild with an InvalidVolume exception."""
|
||||||
|
body = {
|
||||||
|
"rebuild": {
|
||||||
|
"imageRef": self._image_href,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
mock_rebuild.side_effect = exception.InvalidVolume('error')
|
||||||
|
|
||||||
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||||
|
self.controller._action_rebuild,
|
||||||
|
self.req, FAKE_UUID, body=body)
|
||||||
|
|
||||||
def test_resize_server(self):
|
def test_resize_server(self):
|
||||||
|
|
||||||
body = dict(resize=dict(flavorRef="http://localhost/3"))
|
body = dict(resize=dict(flavorRef="http://localhost/3"))
|
||||||
|
|
|
@ -3612,6 +3612,66 @@ class _ComputeAPIUnitTestMixIn(object):
|
||||||
"new password",
|
"new password",
|
||||||
auto_disk_config=True)
|
auto_disk_config=True)
|
||||||
|
|
||||||
|
@mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid')
|
||||||
|
@mock.patch.object(objects.Instance, 'save')
|
||||||
|
@mock.patch.object(objects.Instance, 'get_flavor')
|
||||||
|
@mock.patch.object(objects.BlockDeviceMappingList, 'get_by_instance_uuid')
|
||||||
|
@mock.patch.object(compute_api.API, '_get_image')
|
||||||
|
@mock.patch.object(compute_api.API, '_check_auto_disk_config')
|
||||||
|
@mock.patch.object(compute_api.API, '_checks_for_create_and_rebuild')
|
||||||
|
@mock.patch.object(compute_api.API, '_record_action_start')
|
||||||
|
def test_rebuild_with_invalid_volume(self, _record_action_start,
|
||||||
|
_checks_for_create_and_rebuild, _check_auto_disk_config,
|
||||||
|
mock_get_image, mock_get_bdms, get_flavor,
|
||||||
|
instance_save, req_spec_get_by_inst_uuid):
|
||||||
|
"""Test a negative scenario where the instance has an
|
||||||
|
invalid volume.
|
||||||
|
"""
|
||||||
|
instance = fake_instance.fake_instance_obj(
|
||||||
|
self.context, vm_state=vm_states.ACTIVE, cell_name='fake-cell',
|
||||||
|
launched_at=timeutils.utcnow(),
|
||||||
|
system_metadata={}, image_ref='foo',
|
||||||
|
expected_attrs=['system_metadata'])
|
||||||
|
|
||||||
|
bdms = objects.BlockDeviceMappingList(objects=[
|
||||||
|
objects.BlockDeviceMapping(
|
||||||
|
boot_index=None, image_id=None,
|
||||||
|
source_type='volume', destination_type='volume',
|
||||||
|
volume_type=None, snapshot_id=None,
|
||||||
|
volume_id=uuids.volume_id, volume_size=None)])
|
||||||
|
mock_get_bdms.return_value = bdms
|
||||||
|
|
||||||
|
get_flavor.return_value = test_flavor.fake_flavor
|
||||||
|
flavor = instance.get_flavor()
|
||||||
|
|
||||||
|
image_href = 'foo'
|
||||||
|
image = {
|
||||||
|
"min_ram": 10, "min_disk": 1,
|
||||||
|
"properties": {
|
||||||
|
'architecture': fields_obj.Architecture.X86_64}}
|
||||||
|
mock_get_image.return_value = (None, image)
|
||||||
|
|
||||||
|
fake_spec = objects.RequestSpec()
|
||||||
|
req_spec_get_by_inst_uuid.return_value = fake_spec
|
||||||
|
|
||||||
|
fake_volume = {'id': uuids.volume_id, 'status': 'retyping'}
|
||||||
|
with mock.patch.object(self.compute_api.volume_api, 'get',
|
||||||
|
return_value=fake_volume) as mock_get_volume:
|
||||||
|
self.assertRaises(exception.InvalidVolume,
|
||||||
|
self.compute_api.rebuild,
|
||||||
|
self.context,
|
||||||
|
instance,
|
||||||
|
image_href,
|
||||||
|
"new password")
|
||||||
|
self.assertIsNone(instance.task_state)
|
||||||
|
mock_get_bdms.assert_called_once_with(self.context,
|
||||||
|
instance.uuid)
|
||||||
|
mock_get_volume.assert_called_once_with(self.context,
|
||||||
|
uuids.volume_id)
|
||||||
|
_check_auto_disk_config.assert_called_once_with(image=image)
|
||||||
|
_checks_for_create_and_rebuild.assert_called_once_with(
|
||||||
|
self.context, None, image, flavor, {}, [], None)
|
||||||
|
|
||||||
@mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid')
|
@mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid')
|
||||||
@mock.patch.object(objects.Instance, 'save')
|
@mock.patch.object(objects.Instance, 'save')
|
||||||
@mock.patch.object(objects.Instance, 'get_flavor')
|
@mock.patch.object(objects.Instance, 'get_flavor')
|
||||||
|
|
Loading…
Reference in New Issue