Merge "Fix call to store.safe_delete_from_backend"
This commit is contained in:
commit
9e1f01e1ef
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue