diff --git a/cinder/exception.py b/cinder/exception.py index 33bddc3b188..feb05c35c90 100644 --- a/cinder/exception.py +++ b/cinder/exception.py @@ -716,6 +716,10 @@ class GlanceMetadataNotFound(NotFound): message = _("Glance metadata for volume/snapshot %(id)s cannot be found.") +class ImageDownloadFailed(CinderException): + message = _("Failed to download image %(image_href)s, reason: %(reason)s") + + class ExportFailure(Invalid): message = _("Failed to export for volume: %(reason)s") diff --git a/cinder/image/glance.py b/cinder/image/glance.py index ee4b2cc2ace..6792f401c86 100644 --- a/cinder/image/glance.py +++ b/cinder/image/glance.py @@ -335,6 +335,10 @@ class GlanceImageService(object): except Exception: _reraise_translated_image_exception(image_id) + if image_chunks is None: + raise exception.ImageDownloadFailed( + image_href=image_id, reason=_('image contains no data.')) + if not data: return image_chunks else: diff --git a/cinder/image/image_utils.py b/cinder/image/image_utils.py index 3417196d380..da11a678f04 100644 --- a/cinder/image/image_utils.py +++ b/cinder/image/image_utils.py @@ -232,6 +232,12 @@ def fetch(context, image_service, image_id, path, _user_id, _project_id): raise exception.ImageTooBig(image_id=image_id, reason=reason) + reason = ("IOError: %(errno)s %(strerror)s" % + {'errno': e.errno, 'strerror': e.strerror}) + LOG.error(reason) + raise exception.ImageDownloadFailed(image_href=image_id, + reason=reason) + duration = timeutils.delta_seconds(start_time, timeutils.utcnow()) # NOTE(jdg): use a default of 1, mostly for unit test, but in diff --git a/cinder/tests/unit/image/test_glance.py b/cinder/tests/unit/image/test_glance.py index 0724450a659..975773f5613 100644 --- a/cinder/tests/unit/image/test_glance.py +++ b/cinder/tests/unit/image/test_glance.py @@ -16,6 +16,7 @@ import datetime import itertools +import six import ddt import glanceclient.exc @@ -556,6 +557,20 @@ class TestGlanceImageService(test.TestCase): self.flags(glance_num_retries=1) service.download(self.context, image_id, writer) + def test_download_no_data(self): + class MyGlanceStubClient(glance_stubs.StubGlanceClient): + """Returns None instead of an iterator.""" + def data(self, image_id): + return None + + client = MyGlanceStubClient() + service = self._create_image_service(client) + image_id = 'fake-image-uuid' + e = self.assertRaises(exception.ImageDownloadFailed, service.download, + self.context, image_id) + self.assertIn('image contains no data', six.text_type(e)) + self.assertIn(image_id, six.text_type(e)) + def test_client_forbidden_converts_to_imagenotauthed(self): class MyGlanceStubClient(glance_stubs.StubGlanceClient): """A client that raises a Forbidden exception.""" diff --git a/cinder/tests/unit/test_image_utils.py b/cinder/tests/unit/test_image_utils.py index 78e99727676..186df1cceab 100644 --- a/cinder/tests/unit/test_image_utils.py +++ b/cinder/tests/unit/test_image_utils.py @@ -315,6 +315,26 @@ class TestFetch(test.TestCase): context, image_service, image_id, path, _user_id, _project_id) + def test_fetch_ioerror(self): + context = mock.sentinel.context + image_service = mock.Mock() + image_id = mock.sentinel.image_id + e = IOError() + e.errno = errno.ECONNRESET + e.strerror = 'Some descriptive message' + image_service.download.side_effect = e + path = '/test_path' + _user_id = mock.sentinel._user_id + _project_id = mock.sentinel._project_id + + with mock.patch('cinder.image.image_utils.open', + new=mock.mock_open(), create=True): + self.assertRaisesRegex(exception.ImageDownloadFailed, + e.strerror, + image_utils.fetch, + context, image_service, image_id, path, + _user_id, _project_id) + class TestVerifyImage(test.TestCase): @mock.patch('cinder.image.image_utils.qemu_img_info') diff --git a/releasenotes/notes/bug-1799221-fix-truncated-volumes-in-case-of-glance-errors-6cae19218249c3cf.yaml b/releasenotes/notes/bug-1799221-fix-truncated-volumes-in-case-of-glance-errors-6cae19218249c3cf.yaml new file mode 100644 index 00000000000..d38a4cc8966 --- /dev/null +++ b/releasenotes/notes/bug-1799221-fix-truncated-volumes-in-case-of-glance-errors-6cae19218249c3cf.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixed a bug which could create volumes with invalid content in case of + unhandled errors from glance client + (Bug `#1799221 `_).