Set error state when unshelve an instance due to not found image

If shelve image created by shelve operation can not be found when
unshelve, nova should set the instance error state.

This patch add a new param show_deleted to glance api:
:param show_deleted: (Optional) show the image even the status of
                     image is deleted.

Change-Id: Ie4bc8dfec0922023dd1a4204a67acd77f7bb0498
Closes-Bug: 1371406
This commit is contained in:
Eli Qiao 2014-09-19 13:22:37 +08:00
parent 6d7f07cc7b
commit 07dee87d5a
6 changed files with 70 additions and 11 deletions

View File

@ -660,7 +660,9 @@ class ComputeTaskManager(base.Base):
def safe_image_show(ctx, image_id):
if image_id:
return self.image_api.get(ctx, image_id)
return self.image_api.get(ctx, image_id, show_deleted=False)
else:
raise exception.ImageNotFound(image_id='')
if instance.vm_state == vm_states.SHELVED:
instance.task_state = task_states.POWERING_ON
@ -678,8 +680,14 @@ class ComputeTaskManager(base.Base):
except exception.ImageNotFound:
instance.vm_state = vm_states.ERROR
instance.save()
reason = _('Unshelve attempted but the image %s '
'cannot be found.') % image_id
if image_id:
reason = _('Unshelve attempted but the image %s '
'cannot be found.') % image_id
else:
reason = _('Unshelve attempted but the image_id is '
'not provided')
LOG.error(reason, instance=instance)
raise exception.UnshelveException(
instance_id=instance.uuid, reason=reason)

View File

@ -67,7 +67,8 @@ class API(object):
session = self._get_session(context)
return session.detail(context, **kwargs)
def get(self, context, id_or_uri, include_locations=False):
def get(self, context, id_or_uri, include_locations=False,
show_deleted=True):
"""Retrieves the information record for a single disk image. If the
supplied identifier parameter is a UUID, the default driver will
be used to return information about the image. If the supplied
@ -83,10 +84,13 @@ class API(object):
not support the locations attribute, it will
still be included in the returned dict, as an
empty list.
:param show_deleted: (Optional) show the image even the status of
image is deleted.
"""
session, image_id = self._get_session_and_image_id(context, id_or_uri)
return session.show(context, image_id,
include_locations=include_locations)
include_locations=include_locations,
show_deleted=show_deleted)
def create(self, context, image_info, data=None):
"""Creates a new image record, optionally passing the image bits to

View File

@ -290,7 +290,8 @@ class GlanceImageService(object):
return _images
def show(self, context, image_id, include_locations=False):
def show(self, context, image_id, include_locations=False,
show_deleted=True):
"""Returns a dict with image data for the given opaque image id.
:param context: The context object to pass to image client
@ -301,6 +302,8 @@ class GlanceImageService(object):
not support the locations attribute, it will
still be included in the returned dict, as an
empty list.
:param show_deleted: (Optional) show the image even the status of
image is deleted.
"""
version = 1
if include_locations:
@ -310,6 +313,9 @@ class GlanceImageService(object):
except Exception:
_reraise_translated_image_exception(image_id)
if not show_deleted and getattr(image, 'deleted', False):
raise exception.ImageNotFound(image_id=image_id)
if not _is_image_available(context, image):
raise exception.ImageNotFound(image_id=image_id)

View File

@ -1415,7 +1415,7 @@ class _BaseTaskTestCase(object):
e = exc.ImageNotFound(image_id=shelved_image_id)
self.conductor_manager.image_api.get(
self.context, shelved_image_id).AndRaise(e)
self.context, shelved_image_id, show_deleted=False).AndRaise(e)
self.mox.ReplayAll()
system_metadata['shelved_at'] = timeutils.utcnow()
@ -1428,6 +1428,24 @@ class _BaseTaskTestCase(object):
self.context, instance)
self.assertEqual(instance.vm_state, vm_states.ERROR)
def test_unshelve_offloaded_instance_image_id_is_none(self):
db_instance = jsonutils.to_primitive(self._create_fake_instance())
instance = objects.Instance.get_by_uuid(
self.context,
db_instance['uuid'],
expected_attrs=['system_metadata'])
instance.vm_state = vm_states.SHELVED_OFFLOADED
instance.task_state = task_states.UNSHELVING
system_metadata = instance.system_metadata
system_metadata['shelved_image_id'] = None
instance.save()
self.assertRaises(
exc.UnshelveException,
self.conductor_manager.unshelve_instance,
self.context, instance)
self.assertEqual(instance.vm_state, vm_states.ERROR)
def test_unshelve_instance_schedule_and_rebuild(self):
db_instance = self._create_fake_instance()
instance = objects.Instance.get_by_uuid(self.context,
@ -1443,7 +1461,7 @@ class _BaseTaskTestCase(object):
'unshelve_instance')
self.conductor_manager.image_api.get(self.context,
'fake_image_id').AndReturn('fake_image')
'fake_image_id', show_deleted=False).AndReturn('fake_image')
self.conductor_manager._schedule_instances(self.context,
'fake_image', filter_properties, instance).AndReturn(
[{'host': 'fake_host',
@ -1482,7 +1500,8 @@ class _BaseTaskTestCase(object):
system_metadata['shelved_host'] = 'fake-mini'
self.conductor_manager.unshelve_instance(self.context, instance)
_get_image.assert_has_calls([mock.call(self.context,
system_metadata['shelved_image_id'])])
system_metadata['shelved_image_id'],
show_deleted=False)])
self.assertEqual(vm_states.SHELVED_OFFLOADED, instance.vm_state)
def test_unshelve_instance_schedule_and_rebuild_volume_backed(self):
@ -1500,7 +1519,7 @@ class _BaseTaskTestCase(object):
'unshelve_instance')
self.conductor_manager.image_api.get(self.context,
'fake_image_id').AndReturn(None)
'fake_image_id', show_deleted=False).AndReturn(None)
self.conductor_manager._schedule_instances(self.context,
None, filter_properties, instance).AndReturn(
[{'host': 'fake_host',

View File

@ -168,7 +168,8 @@ class _FakeImageService(object):
with open(dst_path, 'wb') as data:
data.write(self._imagedata.get(image_id, ''))
def show(self, context, image_id, include_locations=False):
def show(self, context, image_id, include_locations=False,
show_deleted=True):
"""Get data about specified image.
Returns a dict containing image data for the given opaque image id.

View File

@ -904,6 +904,27 @@ class TestShow(test.NoDBTestCase):
self.assertIn('locations', info)
self.assertEqual(expected, info['locations'])
@mock.patch('nova.image.glance._translate_from_glance')
@mock.patch('nova.image.glance._is_image_available')
def test_do_not_show_deleted_images(self, is_avail_mock, trans_from_mock):
class fake_image_cls(dict):
id = 'b31aa5dd-f07a-4748-8f15-398346887584'
deleted = True
glance_image = fake_image_cls()
client = mock.MagicMock()
client.call.return_value = glance_image
ctx = mock.sentinel.ctx
service = glance.GlanceImageService(client)
with testtools.ExpectedException(exception.ImageNotFound):
service.show(ctx, glance_image.id, show_deleted=False)
client.call.assert_called_once_with(ctx, 1, 'get',
glance_image.id)
self.assertFalse(is_avail_mock.called)
self.assertFalse(trans_from_mock.called)
class TestDetail(test.NoDBTestCase):