swift/test/unit/common/middleware/s3api/test_multi_get.py
indianwhocodes 46e7da97c6 s3api: Support GET/HEAD request with ?partNumber
Co-Authored-By: Alistair Coles <alistairncoles@gmail.com>
Co-Authored-By: Clay Gerrard <clay.gerrard@gmail.com>
Closes-Bug: #1735284
Change-Id: Ib396309c706fbc6bc419377fe23fcf5603a89f45
2024-03-12 13:47:55 +00:00

662 lines
29 KiB
Python

# Copyright (c) 2023 NVIDIA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import binascii
import string
import json
import mock
from swift.common import swob, utils
from swift.common.request_helpers import get_reserved_name
from swift.common.middleware import symlink
from swift.common.middleware.versioned_writes import object_versioning as ov
from test.unit import make_timestamp_iter
from test.unit.common.middleware.test_slo import slo, md5hex
from test.unit.common.middleware.s3api import (
S3ApiTestCase, S3ApiTestCaseAcl, _gen_test_headers)
def _prepare_mpu(swift, ts_iter, upload_id, num_segments,
segment_bucket='bucket+segments', segment_key='mpu'):
manifest = []
for i, letter in enumerate(string.ascii_lowercase):
if len(manifest) >= num_segments:
break
size = (i + 1) * 5
body = letter * size
etag = md5hex(body)
path = '/%s/%s/%s/%s' % (segment_bucket, segment_key, upload_id, i + 1)
swift.register('GET', '/v1/AUTH_test' + path, swob.HTTPOk, {
'Content-Length': len(body),
'Etag': etag,
}, body)
manifest.append({
"name": path,
"bytes": size,
"hash": etag,
"content_type": "application/octet-stream",
"last_modified": next(ts_iter).isoformat,
})
slo_etag = md5hex(''.join(s['hash'] for s in manifest))
s3_hash = md5hex(binascii.a2b_hex(''.join(
s['hash'] for s in manifest)))
s3_etag = "%s-%s" % (s3_hash, len(manifest))
manifest_json = json.dumps(manifest)
json_md5 = md5hex(manifest_json)
manifest_headers = {
'Content-Length': str(len(manifest_json)),
'X-Static-Large-Object': 'true',
'Etag': json_md5,
'Content-Type': 'application/octet-stream',
'X-Object-Sysmeta-Slo-Etag': slo_etag,
'X-Object-Sysmeta-Slo-Size': str(sum(
s['bytes'] for s in manifest)),
'X-Object-Sysmeta-S3Api-Etag': s3_etag,
'X-Object-Sysmeta-S3Api-Upload-Id': upload_id,
'X-Object-Sysmeta-Container-Update-Override-Etag':
'%s; s3_etag=%s; slo_etag=%s' % (json_md5, s3_etag, slo_etag),
}
return manifest_headers, manifest_json
class TestMpuGETorHEAD(S3ApiTestCase):
def _wrap_app(self, app):
self.slo = slo.filter_factory({'rate_limit_under_size': '0'})(app)
return super(TestMpuGETorHEAD, self)._wrap_app(self.slo)
def setUp(self):
# this will call our _wrap_app
super(TestMpuGETorHEAD, self).setUp()
self.ts = make_timestamp_iter()
manifest_headers, manifest_json = _prepare_mpu(
self.swift, self.ts, 'X', 3)
self.s3_etag = manifest_headers['X-Object-Sysmeta-S3Api-Etag']
self.swift.register(
'GET', '/v1/AUTH_test/bucket/mpu',
swob.HTTPOk, manifest_headers, manifest_json.encode('ascii'))
self.s3_acl = False
def test_mpu_GET(self):
req = swob.Request.blank('/bucket/mpu', headers={
'Authorization': 'AWS test:tester:hmac',
'Date': self.get_date_header()
})
status, headers, body = self.call_s3api(req)
self.assertEqual(status.split()[0], '200')
self.assertEqual(body, b'aaaaabbbbbbbbbbccccccccccccccc')
expected_calls = [
('GET', '/v1/AUTH_test/bucket/mpu'),
('GET', '/v1/AUTH_test/bucket+segments/mpu/X/1'
'?multipart-manifest=get'),
('GET', '/v1/AUTH_test/bucket+segments/mpu/X/2'
'?multipart-manifest=get'),
('GET', '/v1/AUTH_test/bucket+segments/mpu/X/3'
'?multipart-manifest=get'),
]
if self.s3_acl:
# pre-flight object ACL check
expected_calls.insert(0, ('HEAD', '/v1/AUTH_test/bucket/mpu'))
self.assertEqual(self.swift.calls, expected_calls)
self.assertEqual(headers['Content-Length'], '30')
self.assertEqual(headers['Etag'], '"%s"' % self.s3_etag)
self.assertNotIn('X-Amz-Mp-Parts-Count', headers)
def test_mpu_GET_part_num(self):
req = swob.Request.blank('/bucket/mpu', params={
'partNumber': '2',
}, headers={
'Authorization': 'AWS test:tester:hmac',
'Date': self.get_date_header()
})
status, headers, body = self.call_s3api(req)
self.assertEqual(status.split()[0], '206')
self.assertEqual(body, b'bbbbbbbbbb')
expected_calls = [
('GET', '/v1/AUTH_test/bucket/mpu?part-number=2'),
('GET', '/v1/AUTH_test/bucket+segments/mpu/X/2'
'?multipart-manifest=get'),
]
if self.s3_acl:
expected_calls.insert(0, ('HEAD', '/v1/AUTH_test/bucket/mpu'))
self.assertEqual(self.swift.calls, expected_calls)
self.assertEqual(headers['Content-Length'], '10')
self.assertEqual(headers['Content-Range'], 'bytes 5-14/30')
self.assertEqual(headers['Etag'], '"%s"' % self.s3_etag)
self.assertEqual(headers['X-Amz-Mp-Parts-Count'], '3')
def test_mpu_GET_invalid_part_num(self):
req = swob.Request.blank('/bucket/mpu', params={
'partNumber': 'foo',
}, headers={
'Authorization': 'AWS test:tester:hmac',
'Date': self.get_date_header()
})
status, headers, body = self.call_s3api(req)
self.assertEqual(status.split()[0], '400')
self.assertEqual(self._get_error_code(body), 'InvalidArgument')
self.assertEqual(self.swift.calls, [])
def test_mpu_GET_zero_part_num(self):
req = swob.Request.blank('/bucket/mpu', params={
'partNumber': '0',
}, headers={
'Authorization': 'AWS test:tester:hmac',
'Date': self.get_date_header()
})
status, headers, body = self.call_s3api(req)
self.assertEqual(status.split()[0], '400')
self.assertEqual(self._get_error_code(body), 'InvalidArgument')
self.assertEqual(self.swift.calls, [])
def _do_test_mpu_GET_out_of_range_part_num(self, part_number):
self.swift.clear_calls()
req = swob.Request.blank('/bucket/mpu', params={
'partNumber': str(part_number),
}, headers={
'Authorization': 'AWS test:tester:hmac',
'Date': self.get_date_header()
})
status, headers, body = self.call_s3api(req)
self.assertEqual(status.split()[0], '416')
self.assertEqual(self._get_error_code(body), 'InvalidPartNumber')
expected_calls = [
# s3api.controller.obj doesn't know yet if it's SLO, we delegate
# param validation
('GET', '/v1/AUTH_test/bucket/mpu?part-number=%s' % part_number),
]
if self.s3_acl:
expected_calls.insert(0, ('HEAD', '/v1/AUTH_test/bucket/mpu'))
self.assertEqual(self.swift.calls, expected_calls)
def test_mpu_GET_out_of_range_part_num(self):
self._do_test_mpu_GET_out_of_range_part_num(4)
self._do_test_mpu_GET_out_of_range_part_num(10000)
def test_existing_part_number_greater_than_max_parts_allowed(self):
part_number = 3
max_parts = 2
req = swob.Request.blank('/bucket/mpu', params={
'partNumber': str(part_number),
}, headers={
'Authorization': 'AWS test:tester:hmac',
'Date': self.get_date_header()
})
bad_req = swob.Request.blank('/bucket/mpu', params={
'partNumber': str(part_number + 1),
}, headers={
'Authorization': 'AWS test:tester:hmac',
'Date': self.get_date_header()
})
with mock.patch.object(self.s3api.conf,
'max_upload_part_num', max_parts):
# num_parts >= part number > max parts
status, headers, body = self.call_s3api(req)
self.assertEqual(status.split()[0], '206')
# part number > num parts > max parts
status, headers, body = self.call_s3api(bad_req)
self.assertEqual(status.split()[0], '400')
self.assertIn('must be an integer between 1 and 3, inclusive',
self._get_error_message(body))
max_parts = part_number + 1
with mock.patch.object(self.s3api.conf,
'max_upload_part_num', max_parts):
# max_parts > num_parts >= part number
status, headers, body = self.call_s3api(req)
self.assertEqual(status.split()[0], '206')
# max_parts >= part number > num parts
status, headers, body = self.call_s3api(bad_req)
self.assertEqual(status.split()[0], '416')
self.assertIn('The requested partnumber is not satisfiable',
self._get_error_message(body))
# part number > max_parts > num parts
bad_req.params = {'partNumber': str(max_parts + 1)}
status, headers, body = self.call_s3api(bad_req)
self.assertEqual(status.split()[0], '400')
self.assertIn('must be an integer between 1 and 4, inclusive',
self._get_error_message(body))
def test_mpu_GET_huge_part_num(self):
req = swob.Request.blank('/bucket/mpu', params={
'partNumber': '10001',
}, headers={
'Authorization': 'AWS test:tester:hmac',
'Date': self.get_date_header()
})
status, headers, body = self.call_s3api(req)
self.assertEqual(status.split()[0], '400')
self.assertEqual(self._get_error_code(body), 'InvalidArgument')
expected_calls = [
# XXX is this value configurable? do we need the SLO request?
('GET', '/v1/AUTH_test/bucket/mpu?part-number=10001'),
]
if self.s3_acl:
expected_calls.insert(0, ('HEAD', '/v1/AUTH_test/bucket/mpu'))
self.assertEqual(self.swift.calls, expected_calls)
def test_mpu_HEAD_part_num(self):
req = swob.Request.blank('/bucket/mpu', params={
'partNumber': '1',
}, headers={
'Authorization': 'AWS test:tester:hmac',
'Date': self.get_date_header()
}, method='HEAD')
status, headers, body = self.call_s3api(req)
self.assertEqual(status.split()[0], '206')
self.assertEqual(body, b'')
self.assertEqual(self.swift.calls, [
('HEAD', '/v1/AUTH_test/bucket/mpu?part-number=1'),
('GET', '/v1/AUTH_test/bucket/mpu?part-number=1'),
])
self.assertEqual(headers['Content-Length'], '5')
self.assertEqual(headers['Content-Range'], 'bytes 0-4/30')
self.assertEqual(headers['Etag'], '"%s"' % self.s3_etag)
self.assertEqual(headers['X-Amz-Mp-Parts-Count'], '3')
def test_mpu_HEAD_invalid_part_num(self):
req = swob.Request.blank('/bucket/mpu', method='HEAD', params={
'partNumber': 'foo',
}, headers={
'Authorization': 'AWS test:tester:hmac',
'Date': self.get_date_header()
})
status, headers, _ = self.call_s3api(req)
self.assertEqual(status.split()[0], '400')
self.assertEqual(self.swift.calls, [])
def test_mpu_HEAD_zero_part_num(self):
req = swob.Request.blank('/bucket/mpu', method='HEAD', params={
'partNumber': '0',
}, headers={
'Authorization': 'AWS test:tester:hmac',
'Date': self.get_date_header()
})
status, headers, _ = self.call_s3api(req)
self.assertEqual(status.split()[0], '400')
self.assertEqual(self.swift.calls, [])
def _do_test_mpu_HEAD_out_of_range_part_num(self, part_number):
self.swift.clear_calls()
req = swob.Request.blank('/bucket/mpu', method='HEAD', params={
'partNumber': str(part_number),
}, headers={
'Authorization': 'AWS test:tester:hmac',
'Date': self.get_date_header()
})
status, headers, _ = self.call_s3api(req)
self.assertEqual(status.split()[0], '416')
self.assertEqual(self.swift.calls, [
('HEAD', '/v1/AUTH_test/bucket/mpu?part-number=%s' % part_number),
# SLO has to refetch to *see* if it's out-of-bounds
('GET', '/v1/AUTH_test/bucket/mpu?part-number=%s' % part_number),
])
def test_mpu_HEAD_out_of_range_part_num(self):
self._do_test_mpu_HEAD_out_of_range_part_num(4)
self._do_test_mpu_HEAD_out_of_range_part_num(10000)
def test_mpu_HEAD_huge_part_num(self):
req = swob.Request.blank('/bucket/mpu', method='HEAD', params={
'partNumber': '10001',
}, headers={
'Authorization': 'AWS test:tester:hmac',
'Date': self.get_date_header()
})
status, headers, body = self.call_s3api(req)
self.assertEqual(status.split()[0], '400')
self.assertEqual(self.swift.calls, [
('HEAD', '/v1/AUTH_test/bucket/mpu?part-number=10001'),
# XXX were two requests worth it to 400?
# how big can you configure SLO?
# do such manifests *exist*?
('GET', '/v1/AUTH_test/bucket/mpu?part-number=10001'),
])
class TestMpuGETorHEADAcl(TestMpuGETorHEAD, S3ApiTestCaseAcl):
def setUp(self):
super(TestMpuGETorHEADAcl, self).setUp()
object_headers = _gen_test_headers(
self.default_owner, self.grants, 'object')
self.swift.update_sticky_response_headers(
'/v1/AUTH_test/bucket/mpu', object_headers)
# this is used to flag insertion of expected HEAD pre-flight request of
# object ACLs
self.s3_acl = True
class TestVersionedMpuGETorHEAD(S3ApiTestCase):
def _wrap_app(self, app):
self.sym = symlink.filter_factory({})(app)
self.sym.logger = self.swift.logger
self.ov = ov.ObjectVersioningMiddleware(self.sym, {})
self.ov.logger = self.swift.logger
self.slo = slo.filter_factory({'rate_limit_under_size': '0'})(self.ov)
self.slo.logger = self.swift.logger
return super(TestVersionedMpuGETorHEAD, self)._wrap_app(self.slo)
def setUp(self):
# this will call our _wrap_app
super(TestVersionedMpuGETorHEAD, self).setUp()
self.ts = make_timestamp_iter()
self.swift.register('HEAD', '/v1/AUTH_test/bucket+segments',
swob.HTTPNoContent, {}, None)
versions_container = get_reserved_name('versions', 'bucket')
self.swift.register(
'HEAD', '/v1/AUTH_test/bucket', swob.HTTPNoContent, {
ov.SYSMETA_VERSIONS_CONT: versions_container,
ov.SYSMETA_VERSIONS_ENABLED: True,
}, None)
self.swift.register('HEAD', '/v1/AUTH_test/%s' % versions_container,
swob.HTTPNoContent, {}, None)
num_versions = 3
self.version_ids = []
for v in range(num_versions):
upload_id = 'X%s' % v
num_segments = 3 + v
manifest_headers, manifest_json = _prepare_mpu(
self.swift, self.ts, upload_id, num_segments)
version_ts = next(self.ts)
# add in a little user-meta to keep versions stright
manifest_version_headers = dict(manifest_headers, **{
'x-object-meta-user-notes': 'version%s' % v,
'x-backend-timestamp': version_ts.internal,
})
self.version_ids.append(version_ts.normal)
obj_version_path = get_reserved_name('mpu', (~version_ts).normal)
self.swift.register(
'GET', '/v1/AUTH_test/%s/%s' % (
versions_container, obj_version_path),
swob.HTTPOk, manifest_version_headers,
manifest_json.encode('ascii'))
# TODO: make a current version symlink
symlink_target = '%s/%s' % (versions_container, obj_version_path)
slo_etag = manifest_headers['X-Object-Sysmeta-Slo-Etag']
s3_etag = manifest_headers['X-Object-Sysmeta-S3Api-Etag']
symlink_target_etag = json_md5 = manifest_headers['Etag']
symlink_target_bytes = manifest_headers['X-Object-Sysmeta-Slo-Size']
manifest_symlink_headers = dict(manifest_headers, **{
'X-Object-Sysmeta-Container-Update-Override-Etag':
'%s; s3_etag=%s; slo_etag=%s; symlink_target=%s; '
'symlink_target_etag=%s; symlink_target_bytes=%s' % (
json_md5, s3_etag, slo_etag, symlink_target,
symlink_target_etag, symlink_target_bytes),
'X-Object-Sysmeta-Allow-Reserved-Names': 'true',
'X-Object-Sysmeta-Symlink-Target': symlink_target,
'X-Object-Sysmeta-Symlink-Target-Bytes': str(symlink_target_bytes),
'X-Object-Sysmeta-Symlink-Target-Etag': symlink_target_etag,
'X-Object-Sysmeta-Symloop-Extend': 'true',
'X-Object-Sysmeta-Versions-Symlink': 'true',
})
self.swift.register(
'GET', '/v1/AUTH_test/bucket/mpu', swob.HTTPOk,
manifest_symlink_headers, '')
self.s3_acl = False
def test_mpu_GET_version(self):
req = swob.Request.blank('/bucket/mpu', params={
'versionId': self.version_ids[0],
}, headers={
'Authorization': 'AWS test:tester:hmac',
'Date': self.get_date_header()
})
status, headers, body = self.call_s3api(req)
self.assertEqual(status, '200 OK')
self.assertEqual(headers['x-amz-meta-user-notes'], 'version0')
self.assertEqual(headers['Content-Length'], '30')
self.assertEqual(body, b'aaaaabbbbbbbbbbccccccccccccccc')
expected_calls = [
('HEAD', '/v1/AUTH_test'),
('HEAD', '/v1/AUTH_test/bucket'),
('HEAD', '/v1/AUTH_test/\x00versions\x00bucket'),
('GET', '/v1/AUTH_test/\x00versions\x00bucket/\x00mpu\x00%s'
'?version-id=%s' % (
(~utils.Timestamp(self.version_ids[0])).normal,
self.version_ids[0])),
('HEAD', '/v1/AUTH_test/bucket+segments'),
('GET', '/v1/AUTH_test/bucket+segments/mpu/X0/1'
'?multipart-manifest=get'),
('GET', '/v1/AUTH_test/bucket+segments/mpu/X0/2'
'?multipart-manifest=get'),
('GET', '/v1/AUTH_test/bucket+segments/mpu/X0/3'
'?multipart-manifest=get')
]
if self.s3_acl:
expected_calls.insert(3, (
'HEAD', '/v1/AUTH_test/\x00versions\x00bucket/\x00mpu\x00%s'
'?version-id=%s' % (
(~utils.Timestamp(self.version_ids[0])).normal,
self.version_ids[0])
))
self.assertEqual(self.swift.calls, expected_calls)
def test_mpu_GET_last_version(self):
req = swob.Request.blank('/bucket/mpu', headers={
'Authorization': 'AWS test:tester:hmac',
'Date': self.get_date_header()
})
status, headers, body = self.call_s3api(req)
self.assertEqual(status, '200 OK')
self.assertEqual(headers['x-amz-meta-user-notes'], 'version2')
self.assertEqual(headers['Content-Length'], '75')
expected_calls = [
('HEAD', '/v1/AUTH_test'),
('HEAD', '/v1/AUTH_test/bucket'),
('HEAD', '/v1/AUTH_test/\x00versions\x00bucket'),
('GET', '/v1/AUTH_test/bucket/mpu'),
('GET', '/v1/AUTH_test/\x00versions\x00bucket/\x00mpu\x00%s' % (
~utils.Timestamp(self.version_ids[2])).normal),
('HEAD', '/v1/AUTH_test/bucket+segments'),
('GET', '/v1/AUTH_test/bucket+segments/mpu/X2/1'
'?multipart-manifest=get'),
('GET', '/v1/AUTH_test/bucket+segments/mpu/X2/2'
'?multipart-manifest=get'),
('GET', '/v1/AUTH_test/bucket+segments/mpu/X2/3'
'?multipart-manifest=get'),
('GET', '/v1/AUTH_test/bucket+segments/mpu/X2/4'
'?multipart-manifest=get'),
('GET', '/v1/AUTH_test/bucket+segments/mpu/X2/5'
'?multipart-manifest=get'),
]
if self.s3_acl:
# the pre-flight head on version marker get's symlinked; but I
# think maybe symlink makes metadata addative?
expected_calls = expected_calls[:3] + [
('HEAD', '/v1/AUTH_test/bucket/mpu'),
('HEAD', '/v1/AUTH_test/\x00versions\x00bucket/\x00mpu\x00'
'%s' % (~utils.Timestamp(self.version_ids[2])).normal),
] + expected_calls[3:]
self.assertEqual(expected_calls, self.swift.calls)
def test_mpu_HEAD_last_version(self):
req = swob.Request.blank('/bucket/mpu', method='HEAD', headers={
'Authorization': 'AWS test:tester:hmac',
'Date': self.get_date_header()
})
status, headers, body = self.call_s3api(req)
self.assertEqual(status, '200 OK')
self.assertEqual(headers['x-amz-meta-user-notes'], 'version2')
self.assertEqual(headers['Content-Length'], '75')
self.assertEqual([
('HEAD', '/v1/AUTH_test'),
('HEAD', '/v1/AUTH_test/bucket'),
('HEAD', '/v1/AUTH_test/\x00versions\x00bucket'),
('HEAD', '/v1/AUTH_test/bucket/mpu'),
('HEAD', '/v1/AUTH_test/\x00versions\x00bucket/\x00mpu\x00%s' % (
~utils.Timestamp(self.version_ids[2])).normal),
], self.swift.calls)
def test_mpu_HEAD_version(self):
req = swob.Request.blank('/bucket/mpu', method='HEAD', params={
'versionId': self.version_ids[1],
}, headers={
'Authorization': 'AWS test:tester:hmac',
'Date': self.get_date_header()
})
status, headers, body = self.call_s3api(req)
self.assertEqual(status, '200 OK')
self.assertEqual(headers['x-amz-meta-user-notes'], 'version1')
self.assertEqual(headers['Content-Length'], '50')
self.assertEqual(body, b'')
self.assertEqual([
('HEAD', '/v1/AUTH_test'),
('HEAD', '/v1/AUTH_test/bucket'),
('HEAD', '/v1/AUTH_test/\x00versions\x00bucket'),
('HEAD', '/v1/AUTH_test/\x00versions\x00bucket/\x00mpu\x00%s'
'?version-id=%s' % (
(~utils.Timestamp(self.version_ids[1])).normal,
self.version_ids[1])),
], self.swift.calls)
def test_mpu_GET_version_part_num(self):
req = swob.Request.blank('/bucket/mpu', params={
'versionId': self.version_ids[2],
'partNumber': 5,
}, headers={
'Authorization': 'AWS test:tester:hmac',
'Date': self.get_date_header()
})
status, headers, body = self.call_s3api(req)
self.assertEqual(status, '206 Partial Content')
self.assertEqual(headers['x-amz-meta-user-notes'], 'version2')
self.assertEqual(headers['Content-Length'], '25')
self.assertEqual(body, b'e' * 25)
expected_calls = [
('HEAD', '/v1/AUTH_test'),
('HEAD', '/v1/AUTH_test/bucket'),
('HEAD', '/v1/AUTH_test/\x00versions\x00bucket'),
('GET', '/v1/AUTH_test/\x00versions\x00bucket/\x00mpu\x00%s'
'?part-number=5&version-id=%s' % (
(~utils.Timestamp(self.version_ids[2])).normal,
self.version_ids[2])),
('HEAD', '/v1/AUTH_test/bucket+segments'),
('GET', '/v1/AUTH_test/bucket+segments/mpu/X2/5'
'?multipart-manifest=get'),
]
if self.s3_acl:
expected_calls.insert(3, (
'HEAD', '/v1/AUTH_test/\x00versions\x00bucket/\x00mpu\x00%s'
'?version-id=%s' % (
(~utils.Timestamp(self.version_ids[2])).normal,
self.version_ids[2])
))
self.assertEqual(expected_calls, self.swift.calls)
def test_mpu_HEAD_version_part_num(self):
req = swob.Request.blank('/bucket/mpu', method='HEAD', params={
'versionId': self.version_ids[2],
'partNumber': 3,
}, headers={
'Authorization': 'AWS test:tester:hmac',
'Date': self.get_date_header()
})
status, headers, body = self.call_s3api(req)
self.assertEqual(status, '206 Partial Content')
self.assertEqual(headers['x-amz-meta-user-notes'], 'version2')
self.assertEqual(headers['Content-Length'], '15')
self.assertEqual(body, b'')
self.assertEqual(self.swift.calls, [
('HEAD', '/v1/AUTH_test'),
('HEAD', '/v1/AUTH_test/bucket'),
('HEAD', '/v1/AUTH_test/\x00versions\x00bucket'),
('HEAD', '/v1/AUTH_test/\x00versions\x00bucket/\x00mpu\x00%s'
'?part-number=3&version-id=%s' % (
(~utils.Timestamp(self.version_ids[2])).normal,
self.version_ids[2])),
('GET', '/v1/AUTH_test/\x00versions\x00bucket/\x00mpu\x00%s'
'?part-number=3&version-id=%s' % (
(~utils.Timestamp(self.version_ids[2])).normal,
self.version_ids[2])),
])
def test_mpu_GET_last_version_part_num(self):
req = swob.Request.blank('/bucket/mpu', params={
'partNumber': 4,
}, headers={
'Authorization': 'AWS test:tester:hmac',
'Date': self.get_date_header()
})
status, headers, body = self.call_s3api(req)
self.assertEqual(status, '206 Partial Content')
self.assertEqual(headers['x-amz-meta-user-notes'], 'version2')
self.assertEqual(headers['Content-Length'], '20')
self.assertEqual(body, b'd' * 20)
expected_calls = [
('HEAD', '/v1/AUTH_test'),
('HEAD', '/v1/AUTH_test/bucket'),
('HEAD', '/v1/AUTH_test/\x00versions\x00bucket'),
('GET', '/v1/AUTH_test/bucket/mpu?part-number=4'),
('GET', '/v1/AUTH_test/\x00versions\x00bucket/\x00mpu\x00%s'
'?part-number=4' % (
~utils.Timestamp(self.version_ids[2])).normal),
('HEAD', '/v1/AUTH_test/bucket+segments'),
('GET', '/v1/AUTH_test/bucket+segments/mpu/X2/4'
'?multipart-manifest=get'),
]
if self.s3_acl:
expected_calls = expected_calls[:3] + [
('HEAD', '/v1/AUTH_test/bucket/mpu'),
('HEAD', '/v1/AUTH_test/\x00versions\x00bucket/\x00mpu\x00'
'%s' % (~utils.Timestamp(self.version_ids[2])).normal),
] + expected_calls[3:]
self.assertEqual(expected_calls, self.swift.calls)
def test_mpu_HEAD_last_version_part_num(self):
req = swob.Request.blank('/bucket/mpu', method='HEAD', params={
'partNumber': 5,
}, headers={
'Authorization': 'AWS test:tester:hmac',
'Date': self.get_date_header()
})
status, headers, body = self.call_s3api(req)
self.assertEqual(status, '206 Partial Content')
self.assertEqual(headers['x-amz-meta-user-notes'], 'version2')
self.assertEqual(headers['Content-Length'], '25')
self.assertEqual(self.swift.calls, [
('HEAD', '/v1/AUTH_test'),
('HEAD', '/v1/AUTH_test/bucket'),
('HEAD', '/v1/AUTH_test/\x00versions\x00bucket'),
('HEAD', '/v1/AUTH_test/bucket/mpu?part-number=5'),
('HEAD', '/v1/AUTH_test/\x00versions\x00bucket/\x00mpu\x00%s'
'?part-number=5' % (
~utils.Timestamp(self.version_ids[2])).normal),
('GET', '/v1/AUTH_test/bucket/mpu?part-number=5'),
('GET', '/v1/AUTH_test/\x00versions\x00bucket/\x00mpu\x00%s'
'?part-number=5' % (
~utils.Timestamp(self.version_ids[2])).normal),
])
class TestVersionedMpuGETorHEADAcl(TestVersionedMpuGETorHEAD,
S3ApiTestCaseAcl):
def setUp(self):
super(TestVersionedMpuGETorHEADAcl, self).setUp()
object_headers = _gen_test_headers(
self.default_owner, self.grants, 'object')
for version_id in self.version_ids:
# s3acl would add the default object ACL on PUT to each version
version_path = '/v1/AUTH_test/\x00versions\x00bucket/' \
'\x00mpu\x00%s' % (~utils.Timestamp(version_id)).normal
self.swift.update_sticky_response_headers(
version_path, object_headers)
# this is used to flag insertion of expected HEAD pre-flight request of
# object ACLs
self.s3_acl = True