]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Delete image on upload-to-image failure
authorVipin Balachandran <vbala@vmware.com>
Wed, 30 Apr 2014 12:00:57 +0000 (17:30 +0530)
committerVipin Balachandran <vbala@vmware.com>
Fri, 6 Jun 2014 06:29:52 +0000 (11:59 +0530)
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

cinder/tests/test_volume.py
cinder/volume/manager.py

index e94d04cc23da24f9bf69883061da075d74c83f18..96e13c6258450935cf91953ee2f1e86cae14331f 100644 (file)
@@ -2584,8 +2584,9 @@ class CopyVolumeToImageTestCase(BaseVolumeTestCase):
 
         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'
         }
@@ -2662,6 +2663,73 @@ class CopyVolumeToImageTestCase(BaseVolumeTestCase):
         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):
index 29ad0b2ba6373731cca79f3a7dd8ac5a580dde39..5176f67406eeb0892ad3de0269e54bdd57b6d3c0 100644 (file)
@@ -676,6 +676,7 @@ class VolumeManager(manager.SchedulerDependentManager):
 
         """
         payload = {'volume_id': volume_id, 'image_id': image_meta['id']}
+        image_service = None
         try:
             volume = self.db.volume_get(context, volume_id)
 
@@ -692,6 +693,13 @@ class VolumeManager(manager.SchedulerDependentManager):
                         "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:
@@ -703,6 +711,21 @@ class VolumeManager(manager.SchedulerDependentManager):
                 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.