diff --git a/glance/api/v2/image_data.py b/glance/api/v2/image_data.py index 2fffbc7a5c..3a31d04ef3 100644 --- a/glance/api/v2/image_data.py +++ b/glance/api/v2/image_data.py @@ -43,9 +43,12 @@ class ImageDataController(base.Controller): def upload(self, req, image_id, data): self._get_image(req.context, image_id) - size = len(data) - location, size, checksum = self.store_api.add_to_backend( - 'file', image_id, data, size) + try: + location, size, checksum = self.store_api.add_to_backend( + 'file', image_id, data, len(data)) + except exception.Duplicate: + raise webob.exc.HTTPConflict() + self.db_api.image_update(req.context, image_id, {'location': location}) def download(self, req, image_id): diff --git a/glance/tests/functional/v2/test_images.py b/glance/tests/functional/v2/test_images.py index 04bdae0719..20bbfb1c1f 100644 --- a/glance/tests/functional/v2/test_images.py +++ b/glance/tests/functional/v2/test_images.py @@ -140,3 +140,36 @@ class TestImages(functional.FunctionalTest): self.assertEqual(0, len(images)) self.stop_servers() + + def test_upload_duplicate_data(self): + # Create an image + path = self._url('/v2/images') + headers = self._headers({'content-type': 'application/json'}) + data = json.dumps({'name': 'image-1'}) + response = requests.post(path, headers=headers, data=data) + self.assertEqual(200, response.status_code) + + # Returned image entity should have a generated id + image = json.loads(response.text)['image'] + image_id = image['id'] + + # Upload some image data + path = self._url('/v2/images/%s/file' % image_id) + headers = self._headers() + response = requests.put(path, headers=headers, data='ZZZZZ') + self.assertEqual(200, response.status_code) + + # Uploading duplicate data should be rejected with a 409 + path = self._url('/v2/images/%s/file' % image_id) + headers = self._headers() + response = requests.put(path, headers=headers, data='XXX') + self.assertEqual(409, response.status_code) + + # Data should not have been overwritten + path = self._url('/v2/images/%s/file' % image_id) + headers = self._headers() + response = requests.get(path, headers=headers) + self.assertEqual(200, response.status_code) + self.assertEqual(response.text, 'ZZZZZ') + + self.stop_servers() diff --git a/glance/tests/unit/utils.py b/glance/tests/unit/utils.py index 27db2ea267..dfbcad1e89 100644 --- a/glance/tests/unit/utils.py +++ b/glance/tests/unit/utils.py @@ -52,7 +52,7 @@ class FakeDB(object): def __init__(self): self.images = { - UUID1: self._image_format(UUID1, location='1'), + UUID1: self._image_format(UUID1, location=UUID1), UUID2: self._image_format(UUID2), } self.members = { @@ -180,8 +180,7 @@ class FakeDB(object): class FakeStoreAPI(object): def __init__(self): self.data = { - '1': ('XXX', 3), - '2': ('FFFFF', 5), + UUID1: ('XXX', 3), } def create_stores(self, conf): @@ -199,7 +198,8 @@ class FakeStoreAPI(object): return self.get_from_backend(location)[1] def add_to_backend(self, scheme, image_id, data, size): - location = utils.generate_uuid() - self.data[location] = (data, size) + if image_id in self.data: + raise exception.Duplicate() + self.data[image_id] = (data, size) checksum = 'Z' - return (location, size, checksum) + return (image_id, size, checksum) diff --git a/glance/tests/unit/v2/test_image_data_resource.py b/glance/tests/unit/v2/test_image_data_resource.py index e026b45dd7..0838006ad4 100644 --- a/glance/tests/unit/v2/test_image_data_resource.py +++ b/glance/tests/unit/v2/test_image_data_resource.py @@ -63,6 +63,11 @@ class TestImagesController(unittest.TestCase): self.assertRaises(webob.exc.HTTPNotFound, self.controller.upload, request, utils.generate_uuid(), 'YYYY') + def test_upload_data_exists(self): + request = test_utils.FakeRequest() + self.assertRaises(webob.exc.HTTPConflict, self.controller.upload, + request, test_utils.UUID1, 'YYYY') + class TestImageDataDeserializer(unittest.TestCase): def setUp(self): diff --git a/glance/tests/unit/v2/test_images_resource.py b/glance/tests/unit/v2/test_images_resource.py index 85424b1ea1..17fb106959 100644 --- a/glance/tests/unit/v2/test_images_resource.py +++ b/glance/tests/unit/v2/test_images_resource.py @@ -79,7 +79,7 @@ class TestImagesController(unittest.TestCase): expected = { 'name': 'image-2', 'owner': test_utils.TENANT1, - 'location': '1', + 'location': test_utils.UUID1, 'status': 'queued', } self.assertEqual(expected, output)