Skip to content

Commit

Permalink
Delete image on upload-to-image failure
Browse files Browse the repository at this point in the history
On upload-to-image failure before initiating the data transfer or during
data transfer, the source volume status is restored properly whereas the
image created remains in queued or saving state. This change deletes the
image during such failures.

Change-Id: I0aa64798d2bc5bf19b79dd3b88dcd107ff369c42
Closes-Bug: #1298042
  • Loading branch information
vbalachandran committed Jun 6, 2014
1 parent 6ff7d03 commit 3156982
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 1 deletion.
70 changes: 69 additions & 1 deletion cinder/tests/test_volume.py
Original file line number Diff line number Diff line change
Expand Up @@ -2584,8 +2584,9 @@ def setUp(self):

os.close(self.dst_fd)
self.stubs.Set(self.volume.driver, 'local_path', self.fake_local_path)
self.image_id = '70a599e0-31e7-49b7-b260-868f441e862b'
self.image_meta = {
'id': '70a599e0-31e7-49b7-b260-868f441e862b',
'id': self.image_id,
'container_format': 'bare',
'disk_format': 'raw'
}
Expand Down Expand Up @@ -2662,6 +2663,73 @@ def test_copy_volume_to_image_driver_not_initialized(self):
volume = db.volume_get(self.context, self.volume_id)
self.assertEqual(volume.status, 'available')

def test_copy_volume_to_image_driver_exception(self):
self.image_meta['id'] = self.image_id

image_service = fake_image.FakeImageService()
# create new image in queued state
queued_image_id = 'd5133f15-f753-41bd-920a-06b8c49275d9'
queued_image_meta = image_service.show(self.context, self.image_id)
queued_image_meta['id'] = queued_image_id
queued_image_meta['status'] = 'queued'
image_service.create(self.context, queued_image_meta)

# create new image in saving state
saving_image_id = '5c6eec33-bab4-4e7d-b2c9-88e2d0a5f6f2'
saving_image_meta = image_service.show(self.context, self.image_id)
saving_image_meta['id'] = saving_image_id
saving_image_meta['status'] = 'saving'
image_service.create(self.context, saving_image_meta)

# create volume
self.volume_attrs['status'] = 'available'
self.volume_attrs['instance_uuid'] = None
db.volume_create(self.context, self.volume_attrs)

with mock.patch.object(self.volume.driver,
'copy_volume_to_image') as driver_copy_mock:
driver_copy_mock.side_effect = exception.VolumeDriverException(
"Error")

# test with image not in queued state
self.assertRaises(exception.VolumeDriverException,
self.volume.copy_volume_to_image,
self.context,
self.volume_id,
self.image_meta)
volume = db.volume_get(self.context, self.volume_id)
self.assertEqual(volume['status'], 'available')
# image shouldn't be deleted if it is not in queued state
image_service.show(self.context, self.image_id)

# test with image in queued state
self.assertRaises(exception.VolumeDriverException,
self.volume.copy_volume_to_image,
self.context,
self.volume_id,
queued_image_meta)
volume = db.volume_get(self.context, self.volume_id)
self.assertEqual(volume['status'], 'available')
# queued image should be deleted
self.assertRaises(exception.ImageNotFound,
image_service.show,
self.context,
queued_image_id)

# test with image in saving state
self.assertRaises(exception.VolumeDriverException,
self.volume.copy_volume_to_image,
self.context,
self.volume_id,
saving_image_meta)
volume = db.volume_get(self.context, self.volume_id)
self.assertEqual(volume['status'], 'available')
# image in saving state should be deleted
self.assertRaises(exception.ImageNotFound,
image_service.show,
self.context,
saving_image_id)


class GetActiveByWindowTestCase(BaseVolumeTestCase):
def setUp(self):
Expand Down
23 changes: 23 additions & 0 deletions cinder/volume/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,7 @@ def copy_volume_to_image(self, context, volume_id, image_meta):
"""
payload = {'volume_id': volume_id, 'image_id': image_meta['id']}
image_service = None
try:
volume = self.db.volume_get(context, volume_id)

Expand All @@ -692,6 +693,13 @@ def copy_volume_to_image(self, context, volume_id, image_meta):
"image (%(image_id)s) successfully"),
{'volume_id': volume_id, 'image_id': image_id})
except Exception as error:
LOG.error(_("Error occurred while uploading volume %(volume_id)s "
"to image %(image_id)s."),
{'volume_id': volume_id, 'image_id': image_meta['id']})
if image_service is not None:
# Deletes the image if it is in queued or saving state
self._delete_image(context, image_meta['id'], image_service)

with excutils.save_and_reraise_exception():
payload['message'] = unicode(error)
finally:
Expand All @@ -703,6 +711,21 @@ def copy_volume_to_image(self, context, volume_id, image_meta):
self.db.volume_update(context, volume_id,
{'status': 'in-use'})

def _delete_image(self, context, image_id, image_service):
"""Deletes an image stuck in queued or saving state."""
try:
image_meta = image_service.show(context, image_id)
image_status = image_meta.get('status')
if image_status == 'queued' or image_status == 'saving':
LOG.warn("Deleting image %(image_id)s in %(image_status)s "
"state.",
{'image_id': image_id,
'image_status': image_status})
image_service.delete(context, image_id)
except Exception:
LOG.warn(_("Error occurred while deleting image %s."),
image_id, exc_info=True)

def initialize_connection(self, context, volume_id, connector):
"""Prepare volume for connection from host represented by connector.
Expand Down

0 comments on commit 3156982

Please sign in to comment.