diff --git a/glance/api/v1/upload_utils.py b/glance/api/v1/upload_utils.py index e58731964b..f9bdc29635 100644 --- a/glance/api/v1/upload_utils.py +++ b/glance/api/v1/upload_utils.py @@ -171,6 +171,14 @@ def upload_data_to_store(req, image_meta, image_data, store, notifier): raise exception.NotFound() else: raise + + except exception.NotAuthenticated as e: + # Delete image data due to possible token expiration. + LOG.debug("Authentication error - the token may have " + "expired during file upload. Deleting image data for " + " %s " % image_id) + initiate_deletion(req, location_data, image_id) + raise webob.exc.HTTPUnauthorized(explanation=e.msg, request=req) except exception.NotFound: msg = _LI("Image %s could not be found after upload. The image may" " have been deleted during the upload.") % image_id diff --git a/glance/api/v2/image_data.py b/glance/api/v2/image_data.py index cdfa34b97d..ee9d0ba455 100644 --- a/glance/api/v2/image_data.py +++ b/glance/api/v2/image_data.py @@ -90,7 +90,19 @@ class ImageDataController(object): raise webob.exc.HTTPGone(explanation=msg, request=req, content_type='text/plain') - + except exception.NotAuthenticated: + msg = (_("Authentication error - the token may have " + "expired during file upload. Deleting image data for " + "%s.") % image_id) + LOG.debug(msg) + try: + image.delete() + except exception.NotAuthenticated: + # NOTE: Ignore this exception + pass + raise webob.exc.HTTPUnauthorized(explanation=msg, + request=req, + content_type='text/plain') except ValueError as e: LOG.debug("Cannot save data for image %(id)s: %(e)s", {'id': image_id, 'e': utils.exception_to_str(e)}) diff --git a/glance/tests/unit/v1/test_upload_utils.py b/glance/tests/unit/v1/test_upload_utils.py index 083cda34d9..f561dbe1b1 100644 --- a/glance/tests/unit/v1/test_upload_utils.py +++ b/glance/tests/unit/v1/test_upload_utils.py @@ -323,3 +323,29 @@ class TestUploadUtils(base.StoreClearingUnitTest): 'metadata': {}}, image_meta['id']) mock_safe_kill.assert_called_once_with( req, image_meta['id'], 'saving') + + @mock.patch.object(registry, 'update_image_metadata', + side_effect=exception.NotAuthenticated) + @mock.patch.object(upload_utils, 'initiate_deletion') + def test_activate_image_with_expired_token( + self, mocked_delete, mocked_update): + """Test token expiration during image upload. + + If users token expired before image was uploaded then if auth error + was caught from registry during changing image status from 'saving' + to 'active' then it's required to delete all image data. + """ + context = mock.Mock() + req = mock.Mock() + req.context = context + with self._get_store_and_notifier() as (location, checksum, image_meta, + image_data, store, notifier, + update_data): + self.assertRaises(webob.exc.HTTPUnauthorized, + upload_utils.upload_data_to_store, + req, image_meta, image_data, store, notifier) + self.assertEqual(2, mocked_update.call_count) + mocked_delete.assert_called_once_with( + req, + {'url': 'file://foo/bar', 'status': 'active', 'metadata': {}}, + 'c80a1a6c-bd1f-41c5-90ee-81afedb1d58d') diff --git a/glance/tests/unit/v2/test_image_data_resource.py b/glance/tests/unit/v2/test_image_data_resource.py index a121e82e72..b7bacab4c5 100644 --- a/glance/tests/unit/v2/test_image_data_resource.py +++ b/glance/tests/unit/v2/test_image_data_resource.py @@ -183,6 +183,23 @@ class TestImagesController(base.StoreClearingUnitTest): self.assertRaises(webob.exc.HTTPBadRequest, self.controller.upload, request, unit_test_utils.UUID1, 'YYYY', 4) + def test_upload_with_expired_token(self): + def side_effect(image, from_state=None): + if from_state == 'saving': + raise exception.NotAuthenticated() + + mocked_save = mock.Mock(side_effect=side_effect) + mocked_delete = mock.Mock() + request = unit_test_utils.get_fake_request() + image = FakeImage('abcd') + image.delete = mocked_delete + self.image_repo.result = image + self.image_repo.save = mocked_save + self.assertRaises(webob.exc.HTTPUnauthorized, self.controller.upload, + request, unit_test_utils.UUID1, 'YYYY', 4) + self.assertEqual(3, mocked_save.call_count) + mocked_delete.assert_called_once_with() + def test_upload_non_existent_image_during_save_initiates_deletion(self): def fake_save_not_found(self): raise exception.NotFound()