46e7da97c6
Co-Authored-By: Alistair Coles <alistairncoles@gmail.com> Co-Authored-By: Clay Gerrard <clay.gerrard@gmail.com> Closes-Bug: #1735284 Change-Id: Ib396309c706fbc6bc419377fe23fcf5603a89f45
662 lines
29 KiB
Python
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
|