Merge "Fix cleanup segment container"
This commit is contained in:
commit
02d1e2c7cb
@ -160,12 +160,23 @@ class BucketAclHandler(BaseAclHandler):
|
|||||||
def DELETE(self, app):
|
def DELETE(self, app):
|
||||||
if self.container.endswith(MULTIUPLOAD_SUFFIX):
|
if self.container.endswith(MULTIUPLOAD_SUFFIX):
|
||||||
# anyways, delete multiupload container doesn't need acls
|
# anyways, delete multiupload container doesn't need acls
|
||||||
|
# because it depends on GET segment container result for
|
||||||
|
# cleanup
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
return self._handle_acl(app, 'DELETE')
|
return self._handle_acl(app, 'DELETE')
|
||||||
|
|
||||||
|
def HEAD(self, app):
|
||||||
|
if self.method == 'DELETE':
|
||||||
|
return self._handle_acl(app, 'DELETE')
|
||||||
|
else:
|
||||||
|
return self._handle_acl(app, 'HEAD')
|
||||||
|
|
||||||
def GET(self, app):
|
def GET(self, app):
|
||||||
if self.method != 'DELETE':
|
if self.method == 'DELETE' and \
|
||||||
|
self.container.endswith(MULTIUPLOAD_SUFFIX):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
return self._handle_acl(app, 'GET')
|
return self._handle_acl(app, 'GET')
|
||||||
|
|
||||||
def PUT(self, app):
|
def PUT(self, app):
|
||||||
|
@ -41,6 +41,19 @@ class BucketController(Controller):
|
|||||||
container = req.container_name + MULTIUPLOAD_SUFFIX
|
container = req.container_name + MULTIUPLOAD_SUFFIX
|
||||||
marker = ''
|
marker = ''
|
||||||
seg = ''
|
seg = ''
|
||||||
|
|
||||||
|
try:
|
||||||
|
resp = req.get_response(self.app, 'HEAD')
|
||||||
|
if int(resp.sw_headers['X-Container-Object-Count']) > 0:
|
||||||
|
raise BucketNotEmpty()
|
||||||
|
# FIXME: This extra HEAD saves unexpected segment deletion
|
||||||
|
# but if a complete multipart upload happen while cleanup
|
||||||
|
# segment container below, completed object may be missing its
|
||||||
|
# segments unfortunately. To be safer, it might be good
|
||||||
|
# to handle if the segments can be deleted for each object.
|
||||||
|
except NoSuchBucket:
|
||||||
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
# delete all segments
|
# delete all segments
|
||||||
@ -187,9 +200,9 @@ class BucketController(Controller):
|
|||||||
"""
|
"""
|
||||||
Handle DELETE Bucket request
|
Handle DELETE Bucket request
|
||||||
"""
|
"""
|
||||||
resp = req.get_response(self.app)
|
|
||||||
if CONF.allow_multipart_uploads:
|
if CONF.allow_multipart_uploads:
|
||||||
self._delete_segments_bucket(req)
|
self._delete_segments_bucket(req)
|
||||||
|
resp = req.get_response(self.app)
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
def POST(self, req):
|
def POST(self, req):
|
||||||
|
@ -573,6 +573,60 @@ class TestSwift3MultiUpload(Swift3FunctionalTestCase):
|
|||||||
query=query)
|
query=query)
|
||||||
self.assertEquals(status, 200)
|
self.assertEquals(status, 200)
|
||||||
|
|
||||||
|
def test_delete_bucket_multi_upload_object_exisiting(self):
|
||||||
|
bucket = 'bucket'
|
||||||
|
keys = ['obj1']
|
||||||
|
uploads = []
|
||||||
|
|
||||||
|
results_generator = self._initiate_multi_uploads_result_generator(
|
||||||
|
bucket, keys)
|
||||||
|
|
||||||
|
# Initiate Multipart Upload
|
||||||
|
for expected_key, (status, _, body) in \
|
||||||
|
izip(keys, results_generator):
|
||||||
|
self.assertEquals(status, 200) # sanity
|
||||||
|
elem = fromstring(body, 'InitiateMultipartUploadResult')
|
||||||
|
key = elem.find('Key').text
|
||||||
|
self.assertEquals(expected_key, key) # sanity
|
||||||
|
upload_id = elem.find('UploadId').text
|
||||||
|
self.assertTrue(upload_id is not None) # sanity
|
||||||
|
self.assertTrue((key, upload_id) not in uploads)
|
||||||
|
uploads.append((key, upload_id))
|
||||||
|
|
||||||
|
self.assertEquals(len(uploads), len(keys)) # sanity
|
||||||
|
|
||||||
|
# Upload Part
|
||||||
|
key, upload_id = uploads[0]
|
||||||
|
content = 'a' * MIN_SEGMENT_SIZE
|
||||||
|
status, headers, body = \
|
||||||
|
self._upload_part(bucket, key, upload_id, content)
|
||||||
|
self.assertEquals(status, 200)
|
||||||
|
|
||||||
|
# Complete Multipart Upload
|
||||||
|
key, upload_id = uploads[0]
|
||||||
|
etags = [md5(content).hexdigest()]
|
||||||
|
xml = self._gen_comp_xml(etags)
|
||||||
|
status, headers, body = \
|
||||||
|
self._complete_multi_upload(bucket, key, upload_id, xml)
|
||||||
|
self.assertEquals(status, 200) # sanity
|
||||||
|
|
||||||
|
# GET multipart object
|
||||||
|
status, headers, body = \
|
||||||
|
self.conn.make_request('GET', bucket, key)
|
||||||
|
self.assertEquals(status, 200) # sanity
|
||||||
|
self.assertEquals(content, body) # sanity
|
||||||
|
|
||||||
|
# DELETE bucket while the object existing
|
||||||
|
status, headers, body = \
|
||||||
|
self.conn.make_request('DELETE', bucket)
|
||||||
|
self.assertEquals(status, 409) # sanity
|
||||||
|
|
||||||
|
# The object must still be there.
|
||||||
|
status, headers, body = \
|
||||||
|
self.conn.make_request('GET', bucket, key)
|
||||||
|
self.assertEquals(status, 200) # sanity
|
||||||
|
self.assertEquals(content, body) # sanity
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -74,7 +74,6 @@ class TestSwift3Bucket(Swift3TestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestSwift3Bucket, self).setUp()
|
super(TestSwift3Bucket, self).setUp()
|
||||||
|
|
||||||
self.setup_objects()
|
self.setup_objects()
|
||||||
|
|
||||||
def test_bucket_HEAD(self):
|
def test_bucket_HEAD(self):
|
||||||
@ -434,23 +433,34 @@ class TestSwift3Bucket(Swift3TestCase):
|
|||||||
status, headers, body = self.call_swift3(req)
|
status, headers, body = self.call_swift3(req)
|
||||||
self.assertEquals(self._get_error_code(body), 'MalformedXML')
|
self.assertEquals(self._get_error_code(body), 'MalformedXML')
|
||||||
|
|
||||||
|
def _test_method_error_delete(self, path, sw_resp):
|
||||||
|
self.swift.register('HEAD', '/v1/AUTH_test' + path, sw_resp, {}, None)
|
||||||
|
return self._test_method_error('DELETE', path, sw_resp)
|
||||||
|
|
||||||
@s3acl
|
@s3acl
|
||||||
def test_bucket_DELETE_error(self):
|
def test_bucket_DELETE_error(self):
|
||||||
code = self._test_method_error('DELETE', '/bucket',
|
code = self._test_method_error_delete('/bucket', swob.HTTPUnauthorized)
|
||||||
swob.HTTPUnauthorized)
|
|
||||||
self.assertEquals(code, 'SignatureDoesNotMatch')
|
self.assertEquals(code, 'SignatureDoesNotMatch')
|
||||||
code = self._test_method_error('DELETE', '/bucket', swob.HTTPForbidden)
|
code = self._test_method_error_delete('/bucket', swob.HTTPForbidden)
|
||||||
self.assertEquals(code, 'AccessDenied')
|
self.assertEquals(code, 'AccessDenied')
|
||||||
code = self._test_method_error('DELETE', '/bucket', swob.HTTPNotFound)
|
code = self._test_method_error_delete('/bucket', swob.HTTPNotFound)
|
||||||
self.assertEquals(code, 'NoSuchBucket')
|
self.assertEquals(code, 'NoSuchBucket')
|
||||||
|
code = self._test_method_error_delete('/bucket', swob.HTTPServerError)
|
||||||
|
self.assertEquals(code, 'InternalError')
|
||||||
|
|
||||||
|
# bucket not empty is now validated at swift3
|
||||||
|
self.swift.register('HEAD', '/v1/AUTH_test/bucket', swob.HTTPNoContent,
|
||||||
|
{'X-Container-Object-Count': '1'}, None)
|
||||||
code = self._test_method_error('DELETE', '/bucket', swob.HTTPConflict)
|
code = self._test_method_error('DELETE', '/bucket', swob.HTTPConflict)
|
||||||
self.assertEquals(code, 'BucketNotEmpty')
|
self.assertEquals(code, 'BucketNotEmpty')
|
||||||
code = self._test_method_error('DELETE', '/bucket',
|
|
||||||
swob.HTTPServerError)
|
|
||||||
self.assertEquals(code, 'InternalError')
|
|
||||||
|
|
||||||
@s3acl
|
@s3acl
|
||||||
def test_bucket_DELETE(self):
|
def test_bucket_DELETE(self):
|
||||||
|
# overwrite default HEAD to return x-container-object-count
|
||||||
|
self.swift.register(
|
||||||
|
'HEAD', '/v1/AUTH_test/bucket', swob.HTTPNoContent,
|
||||||
|
{'X-Container-Object-Count': 0}, None)
|
||||||
|
|
||||||
req = Request.blank('/bucket',
|
req = Request.blank('/bucket',
|
||||||
environ={'REQUEST_METHOD': 'DELETE'},
|
environ={'REQUEST_METHOD': 'DELETE'},
|
||||||
headers={'Authorization': 'AWS test:tester:hmac',
|
headers={'Authorization': 'AWS test:tester:hmac',
|
||||||
@ -458,6 +468,27 @@ class TestSwift3Bucket(Swift3TestCase):
|
|||||||
status, headers, body = self.call_swift3(req)
|
status, headers, body = self.call_swift3(req)
|
||||||
self.assertEquals(status.split()[0], '204')
|
self.assertEquals(status.split()[0], '204')
|
||||||
|
|
||||||
|
@s3acl
|
||||||
|
def test_bucket_DELETE_error_while_segment_bucket_delete(self):
|
||||||
|
# An error occured while deleting segment objects
|
||||||
|
self.swift.register('DELETE', '/v1/AUTH_test/bucket+segments/lily',
|
||||||
|
swob.HTTPServiceUnavailable, {}, json.dumps([]))
|
||||||
|
# overwrite default HEAD to return x-container-object-count
|
||||||
|
self.swift.register(
|
||||||
|
'HEAD', '/v1/AUTH_test/bucket', swob.HTTPNoContent,
|
||||||
|
{'X-Container-Object-Count': 0}, None)
|
||||||
|
|
||||||
|
req = Request.blank('/bucket',
|
||||||
|
environ={'REQUEST_METHOD': 'DELETE'},
|
||||||
|
headers={'Authorization': 'AWS test:tester:hmac',
|
||||||
|
'Date': self.get_date_header()})
|
||||||
|
status, headers, body = self.call_swift3(req)
|
||||||
|
self.assertEquals(status.split()[0], '503')
|
||||||
|
called = [(method, path) for method, path, _ in
|
||||||
|
self.swift.calls_with_headers]
|
||||||
|
# Don't delete original bucket when error occured in segment container
|
||||||
|
self.assertNotIn(('DELETE', '/v1/AUTH_test/bucket'), called)
|
||||||
|
|
||||||
def _test_bucket_for_s3acl(self, method, account):
|
def _test_bucket_for_s3acl(self, method, account):
|
||||||
req = Request.blank('/bucket',
|
req = Request.blank('/bucket',
|
||||||
environ={'REQUEST_METHOD': method},
|
environ={'REQUEST_METHOD': method},
|
||||||
@ -514,18 +545,27 @@ class TestSwift3Bucket(Swift3TestCase):
|
|||||||
status, headers, body = self._test_bucket_for_s3acl('DELETE',
|
status, headers, body = self._test_bucket_for_s3acl('DELETE',
|
||||||
'test:other')
|
'test:other')
|
||||||
self.assertEquals(self._get_error_code(body), 'AccessDenied')
|
self.assertEquals(self._get_error_code(body), 'AccessDenied')
|
||||||
|
# Don't delete anything in backend Swift
|
||||||
|
called = [method for method, _, _ in self.swift.calls_with_headers]
|
||||||
|
self.assertNotIn('DELETE', called)
|
||||||
|
|
||||||
@s3acl(s3acl_only=True)
|
@s3acl(s3acl_only=True)
|
||||||
def test_bucket_DELETE_with_write_permission(self):
|
def test_bucket_DELETE_with_write_permission(self):
|
||||||
status, headers, body = self._test_bucket_for_s3acl('DELETE',
|
status, headers, body = self._test_bucket_for_s3acl('DELETE',
|
||||||
'test:write')
|
'test:write')
|
||||||
self.assertEquals(self._get_error_code(body), 'AccessDenied')
|
self.assertEquals(self._get_error_code(body), 'AccessDenied')
|
||||||
|
# Don't delete anything in backend Swift
|
||||||
|
called = [method for method, _, _ in self.swift.calls_with_headers]
|
||||||
|
self.assertNotIn('DELETE', called)
|
||||||
|
|
||||||
@s3acl(s3acl_only=True)
|
@s3acl(s3acl_only=True)
|
||||||
def test_bucket_DELETE_with_fullcontrol_permission(self):
|
def test_bucket_DELETE_with_fullcontrol_permission(self):
|
||||||
status, headers, body = \
|
status, headers, body = \
|
||||||
self._test_bucket_for_s3acl('DELETE', 'test:full_control')
|
self._test_bucket_for_s3acl('DELETE', 'test:full_control')
|
||||||
self.assertEquals(self._get_error_code(body), 'AccessDenied')
|
self.assertEquals(self._get_error_code(body), 'AccessDenied')
|
||||||
|
# Don't delete anything in backend Swift
|
||||||
|
called = [method for method, _, _ in self.swift.calls_with_headers]
|
||||||
|
self.assertNotIn('DELETE', called)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user