Fix the deletion of non-existent keys

On vanilla Swift, deleting an object that doesn't exist will 404.
On AWS, deleting a key that doesn't exist will either 404 if the bucket
doesn't exist (with a NoSuchBucket code) or 204 (because yep, that's not
accessible).

Change-Id: Ied2a78b56522316bb374f23961621641af3adc83
Related-Change: I6e154594dfda6c3065774c23b24f728625a842bc
This commit is contained in:
Tim Burke 2018-06-15 13:29:16 -07:00
parent 53f9fd2b61
commit bd640cdbae
4 changed files with 35 additions and 22 deletions

View File

@ -20,7 +20,7 @@ from swift.common.utils import public
from swift.common.middleware.s3api.utils import S3Timestamp from swift.common.middleware.s3api.utils import S3Timestamp
from swift.common.middleware.s3api.controllers.base import Controller from swift.common.middleware.s3api.controllers.base import Controller
from swift.common.middleware.s3api.s3response import S3NotImplemented, \ from swift.common.middleware.s3api.s3response import S3NotImplemented, \
InvalidRange, NoSuchKey, InvalidArgument InvalidRange, NoSuchKey, InvalidArgument, HTTPNoContent
class ObjectController(Controller): class ObjectController(Controller):
@ -143,5 +143,6 @@ class ObjectController(Controller):
except NoSuchKey: except NoSuchKey:
# expect to raise NoSuchBucket when the bucket doesn't exist # expect to raise NoSuchBucket when the bucket doesn't exist
req.get_container_info(self.app) req.get_container_info(self.app)
raise # else -- it's gone! Success.
return HTTPNoContent()
return resp return resp

View File

@ -139,6 +139,12 @@ class TestS3ApiObject(S3ApiBase):
self.assertEqual(status, 204) self.assertEqual(status, 204)
self.assertCommonResponseHeaders(headers) self.assertCommonResponseHeaders(headers)
# DELETE Non-Existent Object
status, headers, body = \
self.conn.make_request('DELETE', self.bucket, 'does-not-exist')
self.assertEqual(status, 204)
self.assertCommonResponseHeaders(headers)
def test_put_object_error(self): def test_put_object_error(self):
auth_error_conn = Connection(aws_secret_key='invalid') auth_error_conn = Connection(aws_secret_key='invalid')
status, headers, body = \ status, headers, body = \
@ -238,11 +244,6 @@ class TestS3ApiObject(S3ApiBase):
self.assertEqual(get_error_code(body), 'SignatureDoesNotMatch') self.assertEqual(get_error_code(body), 'SignatureDoesNotMatch')
self.assertEqual(headers['content-type'], 'application/xml') self.assertEqual(headers['content-type'], 'application/xml')
status, headers, body = \
self.conn.make_request('DELETE', self.bucket, 'invalid')
self.assertEqual(get_error_code(body), 'NoSuchKey')
self.assertEqual(headers['content-type'], 'application/xml')
status, headers, body = \ status, headers, body = \
self.conn.make_request('DELETE', 'invalid', obj) self.conn.make_request('DELETE', 'invalid', obj)
self.assertEqual(get_error_code(body), 'NoSuchBucket') self.assertEqual(get_error_code(body), 'NoSuchBucket')

View File

@ -17,7 +17,6 @@ import unittest
from datetime import datetime from datetime import datetime
from hashlib import md5 from hashlib import md5
from six.moves import urllib
from swift.common import swob from swift.common import swob
from swift.common.swob import Request from swift.common.swob import Request
@ -87,11 +86,14 @@ class TestS3ApiMultiDelete(S3ApiTestCase):
elem = fromstring(body) elem = fromstring(body)
self.assertEqual(len(elem.findall('Deleted')), 3) self.assertEqual(len(elem.findall('Deleted')), 3)
_, path, _ = self.swift.calls_with_headers[-1] self.assertEqual(self.swift.calls, [
path, query_string = path.split('?', 1) ('HEAD', '/v1/AUTH_test/bucket'),
self.assertEqual(path, '/v1/AUTH_test/bucket/Key3') ('HEAD', '/v1/AUTH_test/bucket/Key1'),
query = dict(urllib.parse.parse_qsl(query_string)) ('DELETE', '/v1/AUTH_test/bucket/Key1'),
self.assertEqual(query['multipart-manifest'], 'delete') ('HEAD', '/v1/AUTH_test/bucket/Key2'),
('HEAD', '/v1/AUTH_test/bucket/Key3'),
('DELETE', '/v1/AUTH_test/bucket/Key3?multipart-manifest=delete'),
])
@s3acl @s3acl
def test_object_multi_DELETE_quiet(self): def test_object_multi_DELETE_quiet(self):

View File

@ -767,13 +767,6 @@ class TestS3ApiObj(S3ApiTestCase):
swob.HTTPServiceUnavailable) swob.HTTPServiceUnavailable)
self.assertEqual(code, 'InternalError') self.assertEqual(code, 'InternalError')
with patch(
'swift.common.middleware.s3api.s3request.get_container_info',
return_value={'status': 204}):
code = self._test_method_error('DELETE', '/bucket/object',
swob.HTTPNotFound)
self.assertEqual(code, 'NoSuchKey')
with patch( with patch(
'swift.common.middleware.s3api.s3request.get_container_info', 'swift.common.middleware.s3api.s3request.get_container_info',
return_value={'status': 404}): return_value={'status': 404}):
@ -809,11 +802,27 @@ class TestS3ApiObj(S3ApiTestCase):
self.assertIn(('HEAD', '/v1/AUTH_test/bucket/object'), self.assertIn(('HEAD', '/v1/AUTH_test/bucket/object'),
self.swift.calls) self.swift.calls)
self.assertIn(('DELETE', '/v1/AUTH_test/bucket/object'), self.assertEqual(('DELETE', '/v1/AUTH_test/bucket/object'),
self.swift.calls) self.swift.calls[-1])
_, path = self.swift.calls[-1] _, path = self.swift.calls[-1]
self.assertEqual(path.count('?'), 0) self.assertEqual(path.count('?'), 0)
@s3acl
def test_object_DELETE_missing(self):
self.swift.register('HEAD', '/v1/AUTH_test/bucket/object',
swob.HTTPNotFound, {}, None)
req = Request.blank('/bucket/object',
environ={'REQUEST_METHOD': 'DELETE'},
headers={'Authorization': 'AWS test:tester:hmac',
'Date': self.get_date_header()})
status, headers, body = self.call_s3api(req)
self.assertEqual(status.split()[0], '204')
self.assertIn(('HEAD', '/v1/AUTH_test/bucket/object'),
self.swift.calls)
self.assertNotIn(('DELETE', '/v1/AUTH_test/bucket/object'),
self.swift.calls)
@s3acl @s3acl
def test_slo_object_DELETE(self): def test_slo_object_DELETE(self):
self.swift.register('HEAD', '/v1/AUTH_test/bucket/object', self.swift.register('HEAD', '/v1/AUTH_test/bucket/object',