Merge "Fix call to store.safe_delete_from_backend"

This commit is contained in:
Jenkins 2014-01-12 01:23:07 +00:00 committed by Gerrit Code Review
commit 9e1f01e1ef
3 changed files with 46 additions and 36 deletions

View File

@ -275,9 +275,28 @@ class ImageProxy(glance.domain.proxy.Image):
remaining=remaining)
# NOTE(jbresnah) If two uploads happen at the same time and neither
# properly sets the size attribute than there is a race condition
# that will allow for the quota to be broken. Thus we must recheck
# the quota after the upload and thus after we know the size
# properly sets the size attribute[1] then there is a race condition
# that will allow for the quota to be broken[2]. Thus we must recheck
# the quota after the upload and thus after we know the size.
#
# Also, when an upload doesn't set the size properly then the call to
# check_quota above returns None and so utils.LimitingReader is not
# used above. Hence the store (e.g. filesystem store) may have to
# download the entire file before knowing the actual file size. Here
# also we need to check for the quota again after the image has been
# downloaded to the store.
#
# [1] For e.g. when using chunked transfers the 'Content-Length'
# header is not set.
# [2] For e.g.:
# - Upload 1 does not exceed quota but upload 2 exceeds quota.
# Both uploads are to different locations
# - Upload 2 completes before upload 1 and writes image.size.
# - Immediately, upload 1 completes and (over)writes image.size
# with the smaller size.
# - Now, to glance, image has not exceeded quota but, in
# reality, the quota has been exceeded.
try:
glance.api.common.check_quota(
self.context, self.image.size, self.db_api,
@ -287,7 +306,7 @@ class ImageProxy(glance.domain.proxy.Image):
% self.image.image_id)
location = self.image.locations[0]['url']
glance.store.safe_delete_from_backend(
location, self.context, self.image.image_id)
self.context, location, self.image.image_id)
raise
@property

View File

@ -2284,7 +2284,7 @@ class TestQuotas(functional.FunctionalTest):
base_headers.update(custom_headers or {})
return base_headers
def test_image_upload_under_quota(self):
def _upload_image_test(self, data_src, expected_status):
# Image list should be empty
path = self._url('/v2/images')
response = requests.get(path, headers=self._headers())
@ -2295,7 +2295,9 @@ class TestQuotas(functional.FunctionalTest):
# Create an image (with a deployer-defined property)
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json'})
data = jsonutils.dumps({'name': 'image-2',
data = jsonutils.dumps({'name': 'testimg',
'type': 'kernel',
'foo': 'bar',
'disk_format': 'aki',
'container_format': 'aki'})
response = requests.post(path, headers=headers, data=data)
@ -2304,43 +2306,32 @@ class TestQuotas(functional.FunctionalTest):
image_id = image['id']
# upload data
data = 'x' * (self.user_storage_quota - 1)
path = self._url('/v2/images/%s/file' % image_id)
headers = self._headers({'Content-Type': 'application/octet-stream'})
response = requests.put(path, headers=headers, data=data)
self.assertEqual(204, response.status_code)
response = requests.put(path, headers=headers, data=data_src)
self.assertEqual(expected_status, response.status_code)
# Deletion should work
path = self._url('/v2/images/%s' % image_id)
response = requests.delete(path, headers=self._headers())
self.assertEqual(204, response.status_code)
def test_image_upload_under_quota(self):
data = 'x' * (self.user_storage_quota - 1)
self._upload_image_test(data, 204)
def test_image_upload_exceed_quota(self):
# Image list should be empty
path = self._url('/v2/images')
response = requests.get(path, headers=self._headers())
self.assertEqual(200, response.status_code)
images = jsonutils.loads(response.text)['images']
self.assertEqual(0, len(images))
# Create an image (with a deployer-defined property)
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json'})
data = jsonutils.dumps({'name': 'image-1', 'type': 'kernel',
'foo': 'bar', 'disk_format': 'aki',
'container_format': 'aki'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(201, response.status_code)
image = jsonutils.loads(response.text)
image_id = image['id']
# upload data
data = 'x' * (self.user_storage_quota + 1)
path = self._url('/v2/images/%s/file' % image_id)
headers = self._headers({'Content-Type': 'application/octet-stream'})
response = requests.put(path, headers=headers, data=data)
self.assertEqual(413, response.status_code)
self._upload_image_test(data, 413)
path = self._url('/v2/images/%s' % image_id)
response = requests.delete(path, headers=self._headers())
self.assertEqual(204, response.status_code)
def test_chunked_image_upload_under_quota(self):
def data_gen():
yield 'x' * (self.user_storage_quota - 1)
self._upload_image_test(data_gen(), 204)
def test_chunked_image_upload_exceed_quota(self):
def data_gen():
yield 'x' * (self.user_storage_quota + 1)
self._upload_image_test(data_gen(), 413)

View File

@ -131,8 +131,8 @@ class TestImageQuota(test_utils.BaseTestCase):
if deleted:
self.mox.StubOutWithMock(glance.store, 'safe_delete_from_backend')
glance.store.safe_delete_from_backend(
base_image.locations[0]['url'],
context,
base_image.locations[0]['url'],
image.image_id)
self.mox.ReplayAll()