Merge "Fix cleanup segment container"

This commit is contained in:
Jenkins 2016-06-02 02:00:58 +00:00 committed by Gerrit Code Review
commit 02d1e2c7cb
4 changed files with 128 additions and 10 deletions

View File

@ -160,12 +160,23 @@ class BucketAclHandler(BaseAclHandler):
def DELETE(self, app):
if self.container.endswith(MULTIUPLOAD_SUFFIX):
# anyways, delete multiupload container doesn't need acls
# because it depends on GET segment container result for
# cleanup
pass
else:
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):
if self.method != 'DELETE':
if self.method == 'DELETE' and \
self.container.endswith(MULTIUPLOAD_SUFFIX):
pass
else:
return self._handle_acl(app, 'GET')
def PUT(self, app):

View File

@ -41,6 +41,19 @@ class BucketController(Controller):
container = req.container_name + MULTIUPLOAD_SUFFIX
marker = ''
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:
while True:
# delete all segments
@ -187,9 +200,9 @@ class BucketController(Controller):
"""
Handle DELETE Bucket request
"""
resp = req.get_response(self.app)
if CONF.allow_multipart_uploads:
self._delete_segments_bucket(req)
resp = req.get_response(self.app)
return resp
def POST(self, req):

View File

@ -573,6 +573,60 @@ class TestSwift3MultiUpload(Swift3FunctionalTestCase):
query=query)
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__':
unittest.main()

View File

@ -74,7 +74,6 @@ class TestSwift3Bucket(Swift3TestCase):
def setUp(self):
super(TestSwift3Bucket, self).setUp()
self.setup_objects()
def test_bucket_HEAD(self):
@ -434,23 +433,34 @@ class TestSwift3Bucket(Swift3TestCase):
status, headers, body = self.call_swift3(req)
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
def test_bucket_DELETE_error(self):
code = self._test_method_error('DELETE', '/bucket',
swob.HTTPUnauthorized)
code = self._test_method_error_delete('/bucket', swob.HTTPUnauthorized)
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')
code = self._test_method_error('DELETE', '/bucket', swob.HTTPNotFound)
code = self._test_method_error_delete('/bucket', swob.HTTPNotFound)
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)
self.assertEquals(code, 'BucketNotEmpty')
code = self._test_method_error('DELETE', '/bucket',
swob.HTTPServerError)
self.assertEquals(code, 'InternalError')
@s3acl
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',
environ={'REQUEST_METHOD': 'DELETE'},
headers={'Authorization': 'AWS test:tester:hmac',
@ -458,6 +468,27 @@ class TestSwift3Bucket(Swift3TestCase):
status, headers, body = self.call_swift3(req)
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):
req = Request.blank('/bucket',
environ={'REQUEST_METHOD': method},
@ -514,18 +545,27 @@ class TestSwift3Bucket(Swift3TestCase):
status, headers, body = self._test_bucket_for_s3acl('DELETE',
'test:other')
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)
def test_bucket_DELETE_with_write_permission(self):
status, headers, body = self._test_bucket_for_s3acl('DELETE',
'test:write')
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)
def test_bucket_DELETE_with_fullcontrol_permission(self):
status, headers, body = \
self._test_bucket_for_s3acl('DELETE', 'test:full_control')
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__':
unittest.main()