Merge "s3api: Add basic support for ?versions bucket listings"
This commit is contained in:
commit
166b85e468
@ -110,17 +110,22 @@ class BucketController(Controller):
|
|||||||
'format': 'json',
|
'format': 'json',
|
||||||
'limit': max_keys + 1,
|
'limit': max_keys + 1,
|
||||||
}
|
}
|
||||||
if 'marker' in req.params:
|
|
||||||
query.update({'marker': req.params['marker']})
|
|
||||||
if 'prefix' in req.params:
|
if 'prefix' in req.params:
|
||||||
query.update({'prefix': req.params['prefix']})
|
query.update({'prefix': req.params['prefix']})
|
||||||
if 'delimiter' in req.params:
|
if 'delimiter' in req.params:
|
||||||
query.update({'delimiter': req.params['delimiter']})
|
query.update({'delimiter': req.params['delimiter']})
|
||||||
|
|
||||||
# GET Bucket (List Objects) Version 2 parameters
|
|
||||||
is_v2 = int(req.params.get('list-type', '1')) == 2
|
|
||||||
fetch_owner = False
|
fetch_owner = False
|
||||||
if is_v2:
|
if 'versions' in req.params:
|
||||||
|
listing_type = 'object-versions'
|
||||||
|
if 'key-marker' in req.params:
|
||||||
|
query.update({'marker': req.params['key-marker']})
|
||||||
|
elif 'version-id-marker' in req.params:
|
||||||
|
err_msg = ('A version-id marker cannot be specified without '
|
||||||
|
'a key marker.')
|
||||||
|
raise InvalidArgument('version-id-marker',
|
||||||
|
req.params['version-id-marker'], err_msg)
|
||||||
|
elif int(req.params.get('list-type', '1')) == 2:
|
||||||
|
listing_type = 'version-2'
|
||||||
if 'start-after' in req.params:
|
if 'start-after' in req.params:
|
||||||
query.update({'marker': req.params['start-after']})
|
query.update({'marker': req.params['start-after']})
|
||||||
# continuation-token overrides start-after
|
# continuation-token overrides start-after
|
||||||
@ -129,21 +134,40 @@ class BucketController(Controller):
|
|||||||
query.update({'marker': decoded})
|
query.update({'marker': decoded})
|
||||||
if 'fetch-owner' in req.params:
|
if 'fetch-owner' in req.params:
|
||||||
fetch_owner = config_true_value(req.params['fetch-owner'])
|
fetch_owner = config_true_value(req.params['fetch-owner'])
|
||||||
|
else:
|
||||||
|
listing_type = 'version-1'
|
||||||
|
if 'marker' in req.params:
|
||||||
|
query.update({'marker': req.params['marker']})
|
||||||
|
|
||||||
resp = req.get_response(self.app, query=query)
|
resp = req.get_response(self.app, query=query)
|
||||||
|
|
||||||
objects = json.loads(resp.body)
|
objects = json.loads(resp.body)
|
||||||
|
|
||||||
elem = Element('ListBucketResult')
|
|
||||||
SubElement(elem, 'Name').text = req.container_name
|
|
||||||
SubElement(elem, 'Prefix').text = req.params.get('prefix')
|
|
||||||
|
|
||||||
# in order to judge that truncated is valid, check whether
|
# in order to judge that truncated is valid, check whether
|
||||||
# max_keys + 1 th element exists in swift.
|
# max_keys + 1 th element exists in swift.
|
||||||
is_truncated = max_keys > 0 and len(objects) > max_keys
|
is_truncated = max_keys > 0 and len(objects) > max_keys
|
||||||
objects = objects[:max_keys]
|
objects = objects[:max_keys]
|
||||||
|
|
||||||
if not is_v2:
|
if listing_type == 'object-versions':
|
||||||
|
elem = Element('ListVersionsResult')
|
||||||
|
SubElement(elem, 'Name').text = req.container_name
|
||||||
|
SubElement(elem, 'Prefix').text = req.params.get('prefix')
|
||||||
|
SubElement(elem, 'KeyMarker').text = req.params.get('key-marker')
|
||||||
|
SubElement(elem, 'VersionIdMarker').text = req.params.get(
|
||||||
|
'version-id-marker')
|
||||||
|
if is_truncated:
|
||||||
|
if 'name' in objects[-1]:
|
||||||
|
SubElement(elem, 'NextKeyMarker').text = \
|
||||||
|
objects[-1]['name']
|
||||||
|
if 'subdir' in objects[-1]:
|
||||||
|
SubElement(elem, 'NextKeyMarker').text = \
|
||||||
|
objects[-1]['subdir']
|
||||||
|
SubElement(elem, 'NextVersionIdMarker').text = 'null'
|
||||||
|
else:
|
||||||
|
elem = Element('ListBucketResult')
|
||||||
|
SubElement(elem, 'Name').text = req.container_name
|
||||||
|
SubElement(elem, 'Prefix').text = req.params.get('prefix')
|
||||||
|
if listing_type == 'version-1':
|
||||||
SubElement(elem, 'Marker').text = req.params.get('marker')
|
SubElement(elem, 'Marker').text = req.params.get('marker')
|
||||||
if is_truncated and 'delimiter' in req.params:
|
if is_truncated and 'delimiter' in req.params:
|
||||||
if 'name' in objects[-1]:
|
if 'name' in objects[-1]:
|
||||||
@ -152,7 +176,7 @@ class BucketController(Controller):
|
|||||||
if 'subdir' in objects[-1]:
|
if 'subdir' in objects[-1]:
|
||||||
SubElement(elem, 'NextMarker').text = \
|
SubElement(elem, 'NextMarker').text = \
|
||||||
objects[-1]['subdir']
|
objects[-1]['subdir']
|
||||||
else:
|
elif listing_type == 'version-2':
|
||||||
if is_truncated:
|
if is_truncated:
|
||||||
if 'name' in objects[-1]:
|
if 'name' in objects[-1]:
|
||||||
SubElement(elem, 'NextContinuationToken').text = \
|
SubElement(elem, 'NextContinuationToken').text = \
|
||||||
@ -181,6 +205,12 @@ class BucketController(Controller):
|
|||||||
|
|
||||||
for o in objects:
|
for o in objects:
|
||||||
if 'subdir' not in o:
|
if 'subdir' not in o:
|
||||||
|
if listing_type == 'object-versions':
|
||||||
|
contents = SubElement(elem, 'Version')
|
||||||
|
SubElement(contents, 'Key').text = o['name']
|
||||||
|
SubElement(contents, 'VersionId').text = 'null'
|
||||||
|
SubElement(contents, 'IsLatest').text = 'true'
|
||||||
|
else:
|
||||||
contents = SubElement(elem, 'Contents')
|
contents = SubElement(elem, 'Contents')
|
||||||
SubElement(contents, 'Key').text = o['name']
|
SubElement(contents, 'Key').text = o['name']
|
||||||
SubElement(contents, 'LastModified').text = \
|
SubElement(contents, 'LastModified').text = \
|
||||||
@ -192,7 +222,7 @@ class BucketController(Controller):
|
|||||||
etag = '"%s"' % o['hash']
|
etag = '"%s"' % o['hash']
|
||||||
SubElement(contents, 'ETag').text = etag
|
SubElement(contents, 'ETag').text = etag
|
||||||
SubElement(contents, 'Size').text = str(o['bytes'])
|
SubElement(contents, 'Size').text = str(o['bytes'])
|
||||||
if fetch_owner or not is_v2:
|
if fetch_owner or listing_type != 'version-2':
|
||||||
owner = SubElement(contents, 'Owner')
|
owner = SubElement(contents, 'Owner')
|
||||||
SubElement(owner, 'ID').text = req.user_id
|
SubElement(owner, 'ID').text = req.user_id
|
||||||
SubElement(owner, 'DisplayName').text = req.user_id
|
SubElement(owner, 'DisplayName').text = req.user_id
|
||||||
|
@ -34,9 +34,9 @@ from test.unit.common.middleware.s3api.helpers import UnreadableInput
|
|||||||
|
|
||||||
class TestS3ApiBucket(S3ApiTestCase):
|
class TestS3ApiBucket(S3ApiTestCase):
|
||||||
def setup_objects(self):
|
def setup_objects(self):
|
||||||
self.objects = (('rose', '2011-01-05T02:19:14.275290', 0, 303),
|
self.objects = (('lily', '2011-01-05T02:19:14.275290', '0', '3909'),
|
||||||
|
('rose', '2011-01-05T02:19:14.275290', 0, 303),
|
||||||
('viola', '2011-01-05T02:19:14.275290', '0', 3909),
|
('viola', '2011-01-05T02:19:14.275290', '0', 3909),
|
||||||
('lily', '2011-01-05T02:19:14.275290', '0', '3909'),
|
|
||||||
('mu', '2011-01-05T02:19:14.275290',
|
('mu', '2011-01-05T02:19:14.275290',
|
||||||
'md5-of-the-manifest; s3_etag=0', '3909'),
|
'md5-of-the-manifest; s3_etag=0', '3909'),
|
||||||
('with space', '2011-01-05T02:19:14.275290', 0, 390),
|
('with space', '2011-01-05T02:19:14.275290', 0, 390),
|
||||||
@ -395,7 +395,7 @@ class TestS3ApiBucket(S3ApiTestCase):
|
|||||||
status, headers, body = self.call_s3api(req)
|
status, headers, body = self.call_s3api(req)
|
||||||
self.assertEqual(status.split()[0], '200')
|
self.assertEqual(status.split()[0], '200')
|
||||||
elem = fromstring(body, 'ListBucketResult')
|
elem = fromstring(body, 'ListBucketResult')
|
||||||
self.assertEqual(elem.find('./NextMarker').text, 'viola')
|
self.assertEqual(elem.find('./NextMarker').text, 'rose')
|
||||||
self.assertEqual(elem.find('./MaxKeys').text, '2')
|
self.assertEqual(elem.find('./MaxKeys').text, '2')
|
||||||
self.assertEqual(elem.find('./IsTruncated').text, 'true')
|
self.assertEqual(elem.find('./IsTruncated').text, 'true')
|
||||||
|
|
||||||
@ -471,6 +471,46 @@ class TestS3ApiBucket(S3ApiTestCase):
|
|||||||
for o in objects:
|
for o in objects:
|
||||||
self.assertIsNotNone(o.find('./Owner'))
|
self.assertIsNotNone(o.find('./Owner'))
|
||||||
|
|
||||||
|
def test_bucket_GET_with_versions_versioning_not_configured(self):
|
||||||
|
req = Request.blank('/junk?versions',
|
||||||
|
environ={'REQUEST_METHOD': 'GET'},
|
||||||
|
headers={'Authorization': 'AWS test:tester:hmac',
|
||||||
|
'Date': self.get_date_header()})
|
||||||
|
status, headers, body = self.call_s3api(req)
|
||||||
|
|
||||||
|
self.assertEqual(status.split()[0], '200')
|
||||||
|
elem = fromstring(body, 'ListVersionsResult')
|
||||||
|
self.assertEqual(elem.find('./Name').text, 'junk')
|
||||||
|
self.assertIsNone(elem.find('./Prefix').text)
|
||||||
|
self.assertIsNone(elem.find('./KeyMarker').text)
|
||||||
|
self.assertIsNone(elem.find('./VersionIdMarker').text)
|
||||||
|
self.assertEqual(elem.find('./MaxKeys').text, '1000')
|
||||||
|
self.assertEqual(elem.find('./IsTruncated').text, 'false')
|
||||||
|
self.assertEqual(elem.findall('./DeleteMarker'), [])
|
||||||
|
versions = elem.findall('./Version')
|
||||||
|
objects = list(self.objects)
|
||||||
|
self.assertEqual([v.find('./Key').text for v in versions],
|
||||||
|
[v[0] for v in objects])
|
||||||
|
self.assertEqual([v.find('./IsLatest').text for v in versions],
|
||||||
|
['true' for v in objects])
|
||||||
|
self.assertEqual([v.find('./VersionId').text for v in versions],
|
||||||
|
['null' for v in objects])
|
||||||
|
# Last modified in self.objects is 2011-01-05T02:19:14.275290 but
|
||||||
|
# the returned value is 2011-01-05T02:19:14.275Z
|
||||||
|
self.assertEqual([v.find('./LastModified').text for v in versions],
|
||||||
|
[v[1][:-3] + 'Z' for v in objects])
|
||||||
|
self.assertEqual([v.find('./ETag').text for v in versions],
|
||||||
|
['"0"' for v in objects])
|
||||||
|
self.assertEqual([v.find('./Size').text for v in versions],
|
||||||
|
[str(v[3]) for v in objects])
|
||||||
|
self.assertEqual([v.find('./Owner/ID').text for v in versions],
|
||||||
|
['test:tester' for v in objects])
|
||||||
|
self.assertEqual([v.find('./Owner/DisplayName').text
|
||||||
|
for v in versions],
|
||||||
|
['test:tester' for v in objects])
|
||||||
|
self.assertEqual([v.find('./StorageClass').text for v in versions],
|
||||||
|
['STANDARD' for v in objects])
|
||||||
|
|
||||||
@s3acl
|
@s3acl
|
||||||
def test_bucket_PUT_error(self):
|
def test_bucket_PUT_error(self):
|
||||||
code = self._test_method_error('PUT', '/bucket', swob.HTTPCreated,
|
code = self._test_method_error('PUT', '/bucket', swob.HTTPCreated,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user