mirror of
https://github.com/versity/versitygw.git
synced 2026-09-01 21:56:57 +00:00
Merge pull request #2064 from versity/sis/ranged-get-headobject
fix: correct 206 Partial Content response status for ranged GetObject and HeadObject
This commit is contained in:
+21
-11
@@ -491,6 +491,11 @@ func (az *Azure) GetObject(ctx context.Context, input *s3.GetObjectInput) (*s3.G
|
||||
}
|
||||
}
|
||||
|
||||
var objSize int64
|
||||
if resp.ContentLength != nil {
|
||||
objSize = *resp.ContentLength
|
||||
}
|
||||
|
||||
var opts *azblob.DownloadStreamOptions
|
||||
var partsCount *int32
|
||||
var contentRange *string
|
||||
@@ -530,14 +535,15 @@ func (az *Azure) GetObject(ctx context.Context, input *s3.GetObjectInput) (*s3.G
|
||||
}
|
||||
} else if *input.PartNumber > 1 {
|
||||
return nil, s3err.GetAPIError(s3err.ErrInvalidPartNumberRange)
|
||||
} else {
|
||||
// partNumber=1 on a non-multipart object: fall through and serve the
|
||||
// full object without a range (opts remains nil)
|
||||
if objSize != 0 {
|
||||
// if object size is 0, the whole object is served, no content range should be set
|
||||
contentRange = backend.GetPtrFromString(fmt.Sprintf("bytes 0-%d/%d", objSize-1, objSize))
|
||||
}
|
||||
}
|
||||
// partNumber=1 on a non-multipart object: fall through and serve the
|
||||
// full object without a range (opts remains nil, contentRange stays nil).
|
||||
} else if *input.Range != "" {
|
||||
var objSize int64
|
||||
if resp.ContentLength != nil {
|
||||
objSize = *resp.ContentLength
|
||||
}
|
||||
offset, count, isValid, err := backend.ParseObjectRange(objSize, *input.Range)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -612,7 +618,7 @@ func (az *Azure) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s3
|
||||
size = *resp.ContentLength
|
||||
}
|
||||
|
||||
var contentRange string
|
||||
var contentRange *string
|
||||
var length int64
|
||||
var partsCount *int32
|
||||
|
||||
@@ -638,13 +644,17 @@ func (az *Azure) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s3
|
||||
startOffset = mpMeta.Parts[partNum-2]
|
||||
}
|
||||
length = mpMeta.Parts[partNum-1] - startOffset
|
||||
contentRange = fmt.Sprintf("bytes %d-%d/%d", startOffset, startOffset+length-1, size)
|
||||
contentRange = backend.GetPtrFromString(fmt.Sprintf("bytes %d-%d/%d", startOffset, startOffset+length-1, size))
|
||||
} else if *input.PartNumber > 1 {
|
||||
return nil, s3err.GetAPIError(s3err.ErrInvalidPartNumberRange)
|
||||
} else {
|
||||
// partNumber=1 on a non-multipart object: return full object size,
|
||||
// no Content-Range, no PartsCount.
|
||||
length = size
|
||||
if length != 0 {
|
||||
// if object size is 0, the whole object is served, no content range should be set
|
||||
contentRange = backend.GetPtrFromString(fmt.Sprintf("bytes 0-%d/%d", size-1, size))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
startOffset, lgth, isValid, err := backend.ParseObjectRange(size, getString(input.Range))
|
||||
@@ -653,13 +663,13 @@ func (az *Azure) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s3
|
||||
}
|
||||
length = lgth
|
||||
if isValid {
|
||||
contentRange = fmt.Sprintf("bytes %v-%v/%v",
|
||||
startOffset, startOffset+length-1, size)
|
||||
contentRange = backend.GetPtrFromString(fmt.Sprintf("bytes %v-%v/%v",
|
||||
startOffset, startOffset+length-1, size))
|
||||
}
|
||||
}
|
||||
|
||||
result := &s3.HeadObjectOutput{
|
||||
ContentRange: &contentRange,
|
||||
ContentRange: contentRange,
|
||||
AcceptRanges: backend.GetPtrFromString("bytes"),
|
||||
ContentLength: &length,
|
||||
PartsCount: partsCount,
|
||||
|
||||
+11
-3
@@ -4446,6 +4446,10 @@ func (p *Posix) GetObject(ctx context.Context, input *s3.GetObjectInput) (*s3.Ge
|
||||
return nil, s3err.GetAPIError(s3err.ErrInvalidPartNumberRange)
|
||||
}
|
||||
length = objSize
|
||||
if objSize != 0 {
|
||||
// if object size is 0, the whole object is served, no content range should be set
|
||||
contentRange = backend.GetPtrFromString(fmt.Sprintf("bytes 0-%d/%d", objSize-1, objSize))
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("retrieve mp metadata: %w", metaErr)
|
||||
}
|
||||
@@ -4474,7 +4478,7 @@ func (p *Posix) GetObject(ctx context.Context, input *s3.GetObjectInput) (*s3.Ge
|
||||
}
|
||||
|
||||
var checksums s3response.Checksum
|
||||
// Skip the checksums retreival if object isn't requested fully
|
||||
// Return checksums only when the full object is requested
|
||||
if input.ChecksumMode == types.ChecksumModeEnabled && length-startOffset == objSize {
|
||||
checksums, err = p.retrieveChecksums(f, bucket, object)
|
||||
if err != nil && !errors.Is(err, meta.ErrNoSuchKey) {
|
||||
@@ -4681,6 +4685,10 @@ func (p *Posix) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s3.
|
||||
return nil, s3err.GetAPIError(s3err.ErrInvalidPartNumberRange)
|
||||
}
|
||||
length = size
|
||||
if length != 0 {
|
||||
// if object size is 0, the whole object is served, no content range should be set
|
||||
contentRange = backend.GetPtrFromString(fmt.Sprintf("bytes 0-%d/%d", length-1, length))
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("retrieve mp metadata: %w", metaErr)
|
||||
}
|
||||
@@ -4690,7 +4698,6 @@ func (p *Posix) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s3.
|
||||
return nil, err
|
||||
}
|
||||
startOffset, length = start, lgth
|
||||
|
||||
if isValid {
|
||||
contentRange = backend.GetPtrFromString(fmt.Sprintf("bytes %v-%v/%v", start, start+lgth-1, size))
|
||||
}
|
||||
@@ -4718,7 +4725,8 @@ func (p *Posix) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s3.
|
||||
}
|
||||
|
||||
var checksums s3response.Checksum
|
||||
if input.ChecksumMode == types.ChecksumModeEnabled {
|
||||
// Return checksums only when the full object is requested
|
||||
if input.ChecksumMode == types.ChecksumModeEnabled && length-startOffset == size {
|
||||
checksums, err = p.retrieveChecksums(nil, bucket, object)
|
||||
if err != nil && !errors.Is(err, meta.ErrNoSuchKey) {
|
||||
return nil, fmt.Errorf("get object checksums: %w", err)
|
||||
|
||||
@@ -516,7 +516,7 @@ func (c S3ApiController) GetObject(ctx *fiber.Ctx) (*Response, error) {
|
||||
utils.SetMetaHeaders(ctx, res.Metadata)
|
||||
|
||||
status := http.StatusOK
|
||||
if acceptRange != "" {
|
||||
if res.ContentRange != nil && *res.ContentRange != "" {
|
||||
status = http.StatusPartialContent
|
||||
}
|
||||
|
||||
|
||||
@@ -813,12 +813,56 @@ func TestS3ApiController_GetObject(t *testing.T) {
|
||||
},
|
||||
MetaOpts: &MetaOptions{
|
||||
BucketOwner: "root",
|
||||
Status: http.StatusPartialContent,
|
||||
Status: http.StatusOK,
|
||||
ContentLength: cLength,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "successful partial response",
|
||||
input: testInput{
|
||||
locals: defaultLocals,
|
||||
beRes: &s3.GetObjectOutput{
|
||||
ContentRange: utils.GetStringPtr("bytes 2-4/15"),
|
||||
},
|
||||
},
|
||||
output: testOutput{
|
||||
response: &Response{
|
||||
Headers: map[string]*string{
|
||||
"ETag": nil,
|
||||
"x-amz-restore": nil,
|
||||
"accept-ranges": nil,
|
||||
"Content-Range": utils.GetStringPtr("bytes 2-4/15"),
|
||||
"Content-Disposition": nil,
|
||||
"Content-Encoding": nil,
|
||||
"Content-Language": nil,
|
||||
"Cache-Control": nil,
|
||||
"Expires": nil,
|
||||
"x-amz-checksum-crc32": nil,
|
||||
"x-amz-checksum-crc64nvme": nil,
|
||||
"x-amz-checksum-crc32c": nil,
|
||||
"x-amz-checksum-sha1": nil,
|
||||
"x-amz-checksum-sha256": nil,
|
||||
"x-amz-version-id": nil,
|
||||
"x-amz-mp-parts-count": nil,
|
||||
"x-amz-object-lock-mode": nil,
|
||||
"x-amz-object-lock-legal-hold": nil,
|
||||
"x-amz-storage-class": nil,
|
||||
"x-amz-checksum-type": nil,
|
||||
"x-amz-object-lock-retain-until-date": nil,
|
||||
"Last-Modified": nil,
|
||||
"x-amz-tagging-count": nil,
|
||||
"Content-Type": nil,
|
||||
"Content-Length": nil,
|
||||
},
|
||||
MetaOpts: &MetaOptions{
|
||||
BucketOwner: "root",
|
||||
Status: http.StatusPartialContent,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
@@ -16,6 +16,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -163,6 +164,11 @@ func (c S3ApiController) HeadObject(ctx *fiber.Ctx) (*Response, error) {
|
||||
// Set the metadata headers
|
||||
utils.SetMetaHeaders(ctx, res.Metadata)
|
||||
|
||||
status := http.StatusOK
|
||||
if res.ContentRange != nil && *res.ContentRange != "" {
|
||||
status = http.StatusPartialContent
|
||||
}
|
||||
|
||||
return &Response{
|
||||
Headers: map[string]*string{
|
||||
"Content-Range": res.ContentRange,
|
||||
@@ -193,6 +199,7 @@ func (c S3ApiController) HeadObject(ctx *fiber.Ctx) (*Response, error) {
|
||||
},
|
||||
MetaOpts: &MetaOptions{
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
Status: status,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -202,6 +203,51 @@ func TestS3ApiController_HeadObject(t *testing.T) {
|
||||
},
|
||||
MetaOpts: &MetaOptions{
|
||||
BucketOwner: "root",
|
||||
Status: http.StatusOK,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "successful partial response",
|
||||
input: testInput{
|
||||
locals: defaultLocals,
|
||||
beRes: &s3.HeadObjectOutput{
|
||||
ContentRange: utils.GetStringPtr("bytes 10-20/100"),
|
||||
},
|
||||
},
|
||||
output: testOutput{
|
||||
response: &Response{
|
||||
Headers: map[string]*string{
|
||||
"ETag": nil,
|
||||
"x-amz-restore": nil,
|
||||
"accept-ranges": nil,
|
||||
"Content-Range": utils.GetStringPtr("bytes 10-20/100"),
|
||||
"Content-Disposition": nil,
|
||||
"Content-Encoding": nil,
|
||||
"Content-Language": nil,
|
||||
"Cache-Control": nil,
|
||||
"Expires": nil,
|
||||
"x-amz-checksum-crc32": nil,
|
||||
"x-amz-checksum-crc64nvme": nil,
|
||||
"x-amz-checksum-crc32c": nil,
|
||||
"x-amz-checksum-sha1": nil,
|
||||
"x-amz-checksum-sha256": nil,
|
||||
"x-amz-version-id": nil,
|
||||
"x-amz-mp-parts-count": nil,
|
||||
"x-amz-object-lock-mode": nil,
|
||||
"x-amz-object-lock-legal-hold": nil,
|
||||
"x-amz-storage-class": nil,
|
||||
"x-amz-checksum-type": nil,
|
||||
"x-amz-object-lock-retain-until-date": nil,
|
||||
"Last-Modified": nil,
|
||||
"x-amz-tagging-count": nil,
|
||||
"Content-Type": nil,
|
||||
"Content-Length": nil,
|
||||
},
|
||||
MetaOpts: &MetaOptions{
|
||||
BucketOwner: "root",
|
||||
Status: http.StatusPartialContent,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
+324
-22
@@ -25,6 +25,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||
"github.com/versity/versitygw/s3err"
|
||||
@@ -775,8 +776,9 @@ func GetObject_directory_success(s *S3Conf) error {
|
||||
func GetObject_by_range_resp_status(s *S3Conf) error {
|
||||
testName := "GetObject_by_range_resp_status"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
obj, dLen := "my-obj", int64(4000)
|
||||
_, err := putObjectWithData(dLen, &s3.PutObjectInput{
|
||||
obj := "my-obj"
|
||||
objLength := int64(100)
|
||||
_, err := putObjectWithData(objLength, &s3.PutObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
}, s3client)
|
||||
@@ -784,32 +786,119 @@ func GetObject_by_range_resp_status(s *S3Conf) error {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := createSignedReq(
|
||||
http.MethodGet,
|
||||
s.endpoint,
|
||||
fmt.Sprintf("%v/%v", bucket, obj),
|
||||
s.awsID,
|
||||
s.awsSecret,
|
||||
"s3",
|
||||
s.awsRegion,
|
||||
nil,
|
||||
time.Now(),
|
||||
map[string]string{
|
||||
"Range": "bytes=100-200",
|
||||
},
|
||||
)
|
||||
checkRangeStatus := func(rng string, expectedStatus int) error {
|
||||
req, err := createSignedReq(
|
||||
http.MethodGet,
|
||||
s.endpoint,
|
||||
fmt.Sprintf("%v/%v", bucket, obj),
|
||||
s.awsID,
|
||||
s.awsSecret,
|
||||
"s3",
|
||||
s.awsRegion,
|
||||
nil,
|
||||
time.Now(),
|
||||
map[string]string{"Range": rng},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := s.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != expectedStatus {
|
||||
return fmt.Errorf("range %q: expected status %d, instead got %d", rng, expectedStatus, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
rng string
|
||||
expectedStatus int
|
||||
}{
|
||||
// Invalid/ignored ranges → full object, no Content-Range → 200
|
||||
{"bytes=,", http.StatusOK},
|
||||
{"bytes= -1", http.StatusOK},
|
||||
{"bytes=--1", http.StatusOK},
|
||||
{"bytes=0 -1", http.StatusOK},
|
||||
{"bytes=0--1", http.StatusOK},
|
||||
{"bytes=10-5", http.StatusOK},
|
||||
{"bytes=abc", http.StatusOK},
|
||||
{"bytes=a-z", http.StatusOK},
|
||||
{"foo=0-1", http.StatusOK},
|
||||
{"bytes=abc-xyz", http.StatusOK},
|
||||
{"bytes=100-x", http.StatusOK},
|
||||
{"bytes=0-0,1-2", http.StatusOK},
|
||||
{fmt.Sprintf("bytes=%v-%v", objLength+2, objLength-100), http.StatusOK},
|
||||
|
||||
// Valid ranges → partial content, non-empty Content-Range → 206
|
||||
{"bytes=00-01", http.StatusPartialContent},
|
||||
{"bytes=-1", http.StatusPartialContent},
|
||||
{"bytes=-2", http.StatusPartialContent},
|
||||
{"bytes=-10", http.StatusPartialContent},
|
||||
{"bytes=-100", http.StatusPartialContent},
|
||||
{"bytes=-101", http.StatusPartialContent},
|
||||
{"bytes=0-0", http.StatusPartialContent},
|
||||
{"bytes=0-99", http.StatusPartialContent},
|
||||
{"bytes=0-100", http.StatusPartialContent},
|
||||
{"bytes=0-999999", http.StatusPartialContent},
|
||||
{"bytes=1-99", http.StatusPartialContent},
|
||||
{"bytes=50-99", http.StatusPartialContent},
|
||||
{"bytes=50-", http.StatusPartialContent},
|
||||
{"bytes=0-", http.StatusPartialContent},
|
||||
{"bytes=99-99", http.StatusPartialContent},
|
||||
} {
|
||||
if err := checkRangeStatus(tc.rng, tc.expectedStatus); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func GetObject_not_enabled_checksum_mode(s *S3Conf) error {
|
||||
testName := "GetObject_not_enabled_checksum_mode"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
obj := "my-obj"
|
||||
|
||||
_, err := putObjectWithData(500, &s3.PutObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
ChecksumAlgorithm: types.ChecksumAlgorithmSha1,
|
||||
}, s3client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := s.httpClient.Do(req)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
res, err := s3client.GetObject(ctx, &s3.GetObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
}, func(o *s3.Options) {
|
||||
// config sdk to not automatically set the `x-amz-checksum-mode: ENABLED` header
|
||||
o.RequestChecksumCalculation = aws.RequestChecksumCalculationUnset
|
||||
o.ResponseChecksumValidation = aws.ResponseChecksumValidationUnset
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusPartialContent {
|
||||
return fmt.Errorf("expected response status to be %v, instead got %v",
|
||||
http.StatusPartialContent, resp.StatusCode)
|
||||
if res.ChecksumCRC32 != nil {
|
||||
return fmt.Errorf("expected nil crc32 checksum, instead got %v", *res.ChecksumCRC32)
|
||||
}
|
||||
if res.ChecksumCRC32C != nil {
|
||||
return fmt.Errorf("expected nil crc32c checksum, instead got %v", *res.ChecksumCRC32C)
|
||||
}
|
||||
if res.ChecksumSHA1 != nil {
|
||||
return fmt.Errorf("expected nil sha1 checksum, instead got %v", *res.ChecksumSHA1)
|
||||
}
|
||||
if res.ChecksumSHA256 != nil {
|
||||
return fmt.Errorf("expected nil sha256 checksum, instead got %v", *res.ChecksumSHA256)
|
||||
}
|
||||
if res.ChecksumCRC64NVME != nil {
|
||||
return fmt.Errorf("expected nil crc64nvme checksum, instead got %v", *res.ChecksumCRC64NVME)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -1474,9 +1563,10 @@ func GetObject_non_mp_part_number_1_success(s *S3Conf) error {
|
||||
cancel()
|
||||
return fmt.Errorf("expected ContentLength %d, got %v", objSize, res.ContentLength)
|
||||
}
|
||||
if getString(res.ContentRange) != "" {
|
||||
expectedCRange := fmt.Sprintf("bytes 0-%d/%d", objSize-1, objSize)
|
||||
if getString(res.ContentRange) != expectedCRange {
|
||||
cancel()
|
||||
return fmt.Errorf("expected empty Content-Range for non-multipart object, got %q", getString(res.ContentRange))
|
||||
return fmt.Errorf("expected Content-Range to be %s, instead got %s", expectedCRange, getString(res.ContentRange))
|
||||
}
|
||||
if res.PartsCount != nil {
|
||||
cancel()
|
||||
@@ -1500,3 +1590,215 @@ func GetObject_non_mp_part_number_1_success(s *S3Conf) error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func GetObject_empty_object_part_number_1(s *S3Conf) error {
|
||||
testName := "GetObject_empty_object_part_number_1"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
obj := "empty-obj"
|
||||
|
||||
out, err := putObjectWithData(0, &s3.PutObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
ChecksumAlgorithm: types.ChecksumAlgorithmSha256,
|
||||
}, s3client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pn := int32(1)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
res, err := s3client.GetObject(ctx, &s3.GetObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
PartNumber: &pn,
|
||||
ChecksumMode: types.ChecksumModeEnabled,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if res.ContentRange != nil {
|
||||
return fmt.Errorf("expected nil Content-Range for empty object with partNumber=1, got %q", *res.ContentRange)
|
||||
}
|
||||
if res.ContentLength == nil || *res.ContentLength != 0 {
|
||||
return fmt.Errorf("expected ContentLength 0, got %v", res.ContentLength)
|
||||
}
|
||||
if getString(res.ChecksumSHA256) != getString(out.res.ChecksumSHA256) {
|
||||
return fmt.Errorf("expected sha256 checksum %v, got %v",
|
||||
getString(out.res.ChecksumSHA256), getString(res.ChecksumSHA256))
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func GetObject_mp_part_number_resp_status(s *S3Conf) error {
|
||||
testName := "GetObject_mp_part_number_resp_status"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
obj := "my-obj"
|
||||
out, err := createMp(s3client, bucket, obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
const partCount = int64(2)
|
||||
parts, _, err := uploadParts(s3client, partCount*5*1024*1024, partCount, bucket, obj, *out.UploadId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
compParts := make([]types.CompletedPart, len(parts))
|
||||
for i, p := range parts {
|
||||
compParts[i] = types.CompletedPart{
|
||||
ETag: p.ETag,
|
||||
PartNumber: p.PartNumber,
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
UploadId: out.UploadId,
|
||||
MultipartUpload: &types.CompletedMultipartUpload{
|
||||
Parts: compParts,
|
||||
},
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := createSignedReq(
|
||||
http.MethodGet,
|
||||
s.endpoint,
|
||||
fmt.Sprintf("%v/%v?partNumber=1", bucket, obj),
|
||||
s.awsID,
|
||||
s.awsSecret,
|
||||
"s3",
|
||||
s.awsRegion,
|
||||
nil,
|
||||
time.Now(),
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := s.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusPartialContent {
|
||||
return fmt.Errorf("expected response status to be %v, instead got %v",
|
||||
http.StatusPartialContent, resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func GetObject_ranged_with_checksum_mode(s *S3Conf) error {
|
||||
testName := "GetObject_ranged_with_checksum_mode"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
checkNoChecksums := func(res *s3.GetObjectOutput) error {
|
||||
if res.ChecksumCRC32 != nil {
|
||||
return fmt.Errorf("expected nil crc32 checksum, instead got %v", *res.ChecksumCRC32)
|
||||
}
|
||||
if res.ChecksumCRC32C != nil {
|
||||
return fmt.Errorf("expected nil crc32c checksum, instead got %v", *res.ChecksumCRC32C)
|
||||
}
|
||||
if res.ChecksumSHA1 != nil {
|
||||
return fmt.Errorf("expected nil sha1 checksum, instead got %v", *res.ChecksumSHA1)
|
||||
}
|
||||
if res.ChecksumSHA256 != nil {
|
||||
return fmt.Errorf("expected nil sha256 checksum, instead got %v", *res.ChecksumSHA256)
|
||||
}
|
||||
if res.ChecksumCRC64NVME != nil {
|
||||
return fmt.Errorf("expected nil crc64nvme checksum, instead got %v", *res.ChecksumCRC64NVME)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sub-test 1: regular object with Range header and ChecksumMode enabled
|
||||
regularObj := "regular-obj"
|
||||
_, err := putObjectWithData(500, &s3.PutObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: ®ularObj,
|
||||
ChecksumAlgorithm: types.ChecksumAlgorithmSha256,
|
||||
}, s3client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
res, err := s3client.GetObject(ctx, &s3.GetObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: ®ularObj,
|
||||
Range: getPtr("bytes=0-99"),
|
||||
ChecksumMode: types.ChecksumModeEnabled,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return fmt.Errorf("ranged GET on regular object: %w", err)
|
||||
}
|
||||
if err := checkNoChecksums(res); err != nil {
|
||||
return fmt.Errorf("ranged GET on regular object: %w", err)
|
||||
}
|
||||
|
||||
// Sub-test 2: multipart object with partNumber and ChecksumMode enabled
|
||||
mpObj := "mp-obj"
|
||||
mpOut, err := createMp(s3client, bucket, mpObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
const partCount = int64(2)
|
||||
parts, _, err := uploadParts(s3client, partCount*5*1024*1024, partCount, bucket, mpObj, *mpOut.UploadId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
compParts := make([]types.CompletedPart, len(parts))
|
||||
for i, p := range parts {
|
||||
compParts[i] = types.CompletedPart{
|
||||
ETag: p.ETag,
|
||||
PartNumber: p.PartNumber,
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{
|
||||
Bucket: &bucket,
|
||||
Key: &mpObj,
|
||||
UploadId: mpOut.UploadId,
|
||||
MultipartUpload: &types.CompletedMultipartUpload{
|
||||
Parts: compParts,
|
||||
},
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pn := int32(1)
|
||||
ctx, cancel = context.WithTimeout(context.Background(), longTimeout)
|
||||
mpRes, err := s3client.GetObject(ctx, &s3.GetObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &mpObj,
|
||||
PartNumber: &pn,
|
||||
ChecksumMode: types.ChecksumModeEnabled,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return fmt.Errorf("partNumber GET on MP object: %w", err)
|
||||
}
|
||||
if err := checkNoChecksums(mpRes); err != nil {
|
||||
return fmt.Errorf("partNumber GET on MP object: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1097,8 +1097,9 @@ func HeadObject_non_mp_part_number_1_success(s *S3Conf) error {
|
||||
if res.ContentLength == nil || *res.ContentLength != objSize {
|
||||
return fmt.Errorf("expected ContentLength %d, got %v", objSize, res.ContentLength)
|
||||
}
|
||||
if getString(res.ContentRange) != "" {
|
||||
return fmt.Errorf("expected empty Content-Range for non-multipart object, got %q", getString(res.ContentRange))
|
||||
expectedCRange := fmt.Sprintf("bytes 0-%d/%d", objSize-1, objSize)
|
||||
if getString(res.ContentRange) != expectedCRange {
|
||||
return fmt.Errorf("expected Content-Range to be %s, instead got %s", expectedCRange, getString(res.ContentRange))
|
||||
}
|
||||
if res.PartsCount != nil {
|
||||
return fmt.Errorf("expected nil PartsCount for non-multipart object, got %d", *res.PartsCount)
|
||||
@@ -1111,6 +1112,304 @@ func HeadObject_non_mp_part_number_1_success(s *S3Conf) error {
|
||||
})
|
||||
}
|
||||
|
||||
func HeadObject_by_range_resp_status(s *S3Conf) error {
|
||||
testName := "HeadObject_by_range_resp_status"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
obj := "my-obj"
|
||||
objLength := int64(100)
|
||||
_, err := putObjectWithData(objLength, &s3.PutObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
}, s3client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
checkRangeStatus := func(rng string, expectedStatus int) error {
|
||||
req, err := createSignedReq(
|
||||
http.MethodHead,
|
||||
s.endpoint,
|
||||
fmt.Sprintf("%v/%v", bucket, obj),
|
||||
s.awsID,
|
||||
s.awsSecret,
|
||||
"s3",
|
||||
s.awsRegion,
|
||||
nil,
|
||||
time.Now(),
|
||||
map[string]string{"Range": rng},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := s.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode != expectedStatus {
|
||||
return fmt.Errorf("range %q: expected status %d, instead got %d", rng, expectedStatus, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
rng string
|
||||
expectedStatus int
|
||||
}{
|
||||
// Invalid/ignored ranges → full object, no Content-Range → 200
|
||||
{"bytes=,", http.StatusOK},
|
||||
{"bytes= -1", http.StatusOK},
|
||||
{"bytes=--1", http.StatusOK},
|
||||
{"bytes=0 -1", http.StatusOK},
|
||||
{"bytes=0--1", http.StatusOK},
|
||||
{"bytes=10-5", http.StatusOK},
|
||||
{"bytes=abc", http.StatusOK},
|
||||
{"bytes=a-z", http.StatusOK},
|
||||
{"foo=0-1", http.StatusOK},
|
||||
{"bytes=abc-xyz", http.StatusOK},
|
||||
{"bytes=100-x", http.StatusOK},
|
||||
{"bytes=0-0,1-2", http.StatusOK},
|
||||
{fmt.Sprintf("bytes=%v-%v", objLength+2, objLength-100), http.StatusOK},
|
||||
|
||||
// Valid ranges → partial content, non-empty Content-Range → 206
|
||||
{"bytes=00-01", http.StatusPartialContent},
|
||||
{"bytes=-1", http.StatusPartialContent},
|
||||
{"bytes=-2", http.StatusPartialContent},
|
||||
{"bytes=-10", http.StatusPartialContent},
|
||||
{"bytes=-100", http.StatusPartialContent},
|
||||
{"bytes=-101", http.StatusPartialContent},
|
||||
{"bytes=0-0", http.StatusPartialContent},
|
||||
{"bytes=0-99", http.StatusPartialContent},
|
||||
{"bytes=0-100", http.StatusPartialContent},
|
||||
{"bytes=0-999999", http.StatusPartialContent},
|
||||
{"bytes=1-99", http.StatusPartialContent},
|
||||
{"bytes=50-99", http.StatusPartialContent},
|
||||
{"bytes=50-", http.StatusPartialContent},
|
||||
{"bytes=0-", http.StatusPartialContent},
|
||||
{"bytes=99-99", http.StatusPartialContent},
|
||||
} {
|
||||
if err := checkRangeStatus(tc.rng, tc.expectedStatus); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func HeadObject_empty_object_part_number_1(s *S3Conf) error {
|
||||
testName := "HeadObject_empty_object_part_number_1"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
obj := "empty-obj"
|
||||
|
||||
out, err := putObjectWithData(0, &s3.PutObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
ChecksumAlgorithm: types.ChecksumAlgorithmSha256,
|
||||
}, s3client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pn := int32(1)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
res, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
PartNumber: &pn,
|
||||
ChecksumMode: types.ChecksumModeEnabled,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if res.ContentRange != nil {
|
||||
return fmt.Errorf("expected nil Content-Range for empty object with partNumber=1, got %q", *res.ContentRange)
|
||||
}
|
||||
if res.ContentLength == nil || *res.ContentLength != 0 {
|
||||
return fmt.Errorf("expected ContentLength 0, got %v", res.ContentLength)
|
||||
}
|
||||
if getString(res.ChecksumSHA256) != getString(out.res.ChecksumSHA256) {
|
||||
return fmt.Errorf("expected sha256 checksum %v, got %v",
|
||||
getString(out.res.ChecksumSHA256), getString(res.ChecksumSHA256))
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func HeadObject_mp_part_number_resp_status(s *S3Conf) error {
|
||||
testName := "HeadObject_mp_part_number_resp_status"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
obj := "my-obj"
|
||||
out, err := createMp(s3client, bucket, obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
const partCount = int64(2)
|
||||
parts, _, err := uploadParts(s3client, partCount*5*1024*1024, partCount, bucket, obj, *out.UploadId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
compParts := make([]types.CompletedPart, len(parts))
|
||||
for i, p := range parts {
|
||||
compParts[i] = types.CompletedPart{
|
||||
ETag: p.ETag,
|
||||
PartNumber: p.PartNumber,
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
UploadId: out.UploadId,
|
||||
MultipartUpload: &types.CompletedMultipartUpload{
|
||||
Parts: compParts,
|
||||
},
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := createSignedReq(
|
||||
http.MethodHead,
|
||||
s.endpoint,
|
||||
fmt.Sprintf("%v/%v?partNumber=1", bucket, obj),
|
||||
s.awsID,
|
||||
s.awsSecret,
|
||||
"s3",
|
||||
s.awsRegion,
|
||||
nil,
|
||||
time.Now(),
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := s.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusPartialContent {
|
||||
return fmt.Errorf("expected response status to be %v, instead got %v",
|
||||
http.StatusPartialContent, resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func HeadObject_ranged_with_checksum_mode(s *S3Conf) error {
|
||||
testName := "HeadObject_ranged_with_checksum_mode"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
checkNoChecksums := func(res *s3.HeadObjectOutput) error {
|
||||
if res.ChecksumCRC32 != nil {
|
||||
return fmt.Errorf("expected nil crc32 checksum, instead got %v", *res.ChecksumCRC32)
|
||||
}
|
||||
if res.ChecksumCRC32C != nil {
|
||||
return fmt.Errorf("expected nil crc32c checksum, instead got %v", *res.ChecksumCRC32C)
|
||||
}
|
||||
if res.ChecksumSHA1 != nil {
|
||||
return fmt.Errorf("expected nil sha1 checksum, instead got %v", *res.ChecksumSHA1)
|
||||
}
|
||||
if res.ChecksumSHA256 != nil {
|
||||
return fmt.Errorf("expected nil sha256 checksum, instead got %v", *res.ChecksumSHA256)
|
||||
}
|
||||
if res.ChecksumCRC64NVME != nil {
|
||||
return fmt.Errorf("expected nil crc64nvme checksum, instead got %v", *res.ChecksumCRC64NVME)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sub-test 1: regular object with Range header and ChecksumMode enabled
|
||||
regularObj := "regular-obj"
|
||||
_, err := putObjectWithData(500, &s3.PutObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: ®ularObj,
|
||||
ChecksumAlgorithm: types.ChecksumAlgorithmSha256,
|
||||
}, s3client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
res, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: ®ularObj,
|
||||
Range: getPtr("bytes=0-99"),
|
||||
ChecksumMode: types.ChecksumModeEnabled,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return fmt.Errorf("ranged HEAD on regular object: %w", err)
|
||||
}
|
||||
if err := checkNoChecksums(res); err != nil {
|
||||
return fmt.Errorf("ranged HEAD on regular object: %w", err)
|
||||
}
|
||||
|
||||
// Sub-test 2: multipart object with partNumber and ChecksumMode enabled
|
||||
mpObj := "mp-obj"
|
||||
mpOut, err := createMp(s3client, bucket, mpObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
const partCount = int64(2)
|
||||
parts, _, err := uploadParts(s3client, partCount*5*1024*1024, partCount, bucket, mpObj, *mpOut.UploadId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
compParts := make([]types.CompletedPart, len(parts))
|
||||
for i, p := range parts {
|
||||
compParts[i] = types.CompletedPart{
|
||||
ETag: p.ETag,
|
||||
PartNumber: p.PartNumber,
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{
|
||||
Bucket: &bucket,
|
||||
Key: &mpObj,
|
||||
UploadId: mpOut.UploadId,
|
||||
MultipartUpload: &types.CompletedMultipartUpload{
|
||||
Parts: compParts,
|
||||
},
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pn := int32(1)
|
||||
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
||||
mpRes, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &mpObj,
|
||||
PartNumber: &pn,
|
||||
ChecksumMode: types.ChecksumModeEnabled,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return fmt.Errorf("partNumber HEAD on MP object: %w", err)
|
||||
}
|
||||
if err := checkNoChecksums(mpRes); err != nil {
|
||||
return fmt.Errorf("partNumber HEAD on MP object: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func HeadObject_overrides_fail_public(s *S3Conf) error {
|
||||
testName := "HeadObject_overrides_fail_public"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
|
||||
@@ -205,6 +205,7 @@ func TestHeadObject(ts *TestState) {
|
||||
ts.Run(HeadObject_non_existing_dir_object)
|
||||
ts.Run(HeadObject_invalid_parent_dir)
|
||||
ts.Run(HeadObject_with_range)
|
||||
ts.Run(HeadObject_by_range_resp_status)
|
||||
ts.Run(HeadObject_zero_len_with_range)
|
||||
ts.Run(HeadObject_dir_with_range)
|
||||
ts.Run(HeadObject_conditional_reads)
|
||||
@@ -212,6 +213,7 @@ func TestHeadObject(ts *TestState) {
|
||||
if !ts.conf.azureTests {
|
||||
ts.Run(HeadObject_not_enabled_checksum_mode)
|
||||
ts.Run(HeadObject_checksums)
|
||||
ts.Run(HeadObject_ranged_with_checksum_mode)
|
||||
}
|
||||
ts.Run(HeadObject_success)
|
||||
ts.Run(HeadObject_overrides_success)
|
||||
@@ -220,7 +222,9 @@ func TestHeadObject(ts *TestState) {
|
||||
ts.Run(HeadObject_range_and_part_number)
|
||||
ts.Run(HeadObject_mp_part_number_exceeds_parts_count)
|
||||
ts.Run(HeadObject_mp_part_number_success)
|
||||
ts.Run(HeadObject_mp_part_number_resp_status)
|
||||
ts.Run(HeadObject_non_mp_part_number_1_success)
|
||||
ts.Run(HeadObject_empty_object_part_number_1)
|
||||
}
|
||||
|
||||
func TestGetObjectAttributes(ts *TestState) {
|
||||
@@ -248,8 +252,10 @@ func TestGetObject(ts *TestState) {
|
||||
ts.Run(GetObject_conditional_reads)
|
||||
//TODO: remove the condition after implementing checksums in azure
|
||||
if !ts.conf.azureTests {
|
||||
ts.Run(GetObject_not_enabled_checksum_mode)
|
||||
ts.Run(GetObject_checksums)
|
||||
ts.Run(GetObject_dir_object_checksum)
|
||||
ts.Run(GetObject_ranged_with_checksum_mode)
|
||||
}
|
||||
ts.Run(GetObject_success)
|
||||
ts.Run(GetObject_directory_success)
|
||||
@@ -262,7 +268,9 @@ func TestGetObject(ts *TestState) {
|
||||
ts.Run(GetObject_range_and_part_number)
|
||||
ts.Run(GetObject_mp_part_number_exceeds_parts_count)
|
||||
ts.Run(GetObject_mp_part_number_success)
|
||||
ts.Run(GetObject_mp_part_number_resp_status)
|
||||
ts.Run(GetObject_non_mp_part_number_1_success)
|
||||
ts.Run(GetObject_empty_object_part_number_1)
|
||||
}
|
||||
|
||||
func TestListObjects(ts *TestState) {
|
||||
@@ -1395,11 +1403,13 @@ func GetIntTests() IntTests {
|
||||
"HeadObject_name_too_long": HeadObject_name_too_long,
|
||||
"HeadObject_invalid_parent_dir": HeadObject_invalid_parent_dir,
|
||||
"HeadObject_with_range": HeadObject_with_range,
|
||||
"HeadObject_by_range_resp_status": HeadObject_by_range_resp_status,
|
||||
"HeadObject_zero_len_with_range": HeadObject_zero_len_with_range,
|
||||
"HeadObject_dir_with_range": HeadObject_dir_with_range,
|
||||
"HeadObject_conditional_reads": HeadObject_conditional_reads,
|
||||
"HeadObject_not_enabled_checksum_mode": HeadObject_not_enabled_checksum_mode,
|
||||
"HeadObject_checksums": HeadObject_checksums,
|
||||
"HeadObject_ranged_with_checksum_mode": HeadObject_ranged_with_checksum_mode,
|
||||
"HeadObject_success": HeadObject_success,
|
||||
"HeadObject_overrides_success": HeadObject_overrides_success,
|
||||
"HeadObject_overrides_presign_success": HeadObject_overrides_presign_success,
|
||||
@@ -1407,7 +1417,9 @@ func GetIntTests() IntTests {
|
||||
"HeadObject_range_and_part_number": HeadObject_range_and_part_number,
|
||||
"HeadObject_mp_part_number_exceeds_parts_count": HeadObject_mp_part_number_exceeds_parts_count,
|
||||
"HeadObject_mp_part_number_success": HeadObject_mp_part_number_success,
|
||||
"HeadObject_mp_part_number_resp_status": HeadObject_mp_part_number_resp_status,
|
||||
"HeadObject_non_mp_part_number_1_success": HeadObject_non_mp_part_number_1_success,
|
||||
"HeadObject_empty_object_part_number_1": HeadObject_empty_object_part_number_1,
|
||||
"GetObjectAttributes_non_existing_bucket": GetObjectAttributes_non_existing_bucket,
|
||||
"GetObjectAttributes_non_existing_object": GetObjectAttributes_non_existing_object,
|
||||
"GetObjectAttributes_invalid_attrs": GetObjectAttributes_invalid_attrs,
|
||||
@@ -1424,8 +1436,10 @@ func GetIntTests() IntTests {
|
||||
"GetObject_invalid_parent": GetObject_invalid_parent,
|
||||
"GetObject_large_object": GetObject_large_object,
|
||||
"GetObject_conditional_reads": GetObject_conditional_reads,
|
||||
"GetObject_not_enabled_checksum_mode": GetObject_not_enabled_checksum_mode,
|
||||
"GetObject_checksums": GetObject_checksums,
|
||||
"GetObject_dir_object_checksum": GetObject_dir_object_checksum,
|
||||
"GetObject_ranged_with_checksum_mode": GetObject_ranged_with_checksum_mode,
|
||||
"GetObject_success": GetObject_success,
|
||||
"GetObject_directory_success": GetObject_directory_success,
|
||||
"GetObject_by_range_resp_status": GetObject_by_range_resp_status,
|
||||
@@ -1437,7 +1451,9 @@ func GetIntTests() IntTests {
|
||||
"GetObject_range_and_part_number": GetObject_range_and_part_number,
|
||||
"GetObject_mp_part_number_exceeds_parts_count": GetObject_mp_part_number_exceeds_parts_count,
|
||||
"GetObject_mp_part_number_success": GetObject_mp_part_number_success,
|
||||
"GetObject_mp_part_number_resp_status": GetObject_mp_part_number_resp_status,
|
||||
"GetObject_non_mp_part_number_1_success": GetObject_non_mp_part_number_1_success,
|
||||
"GetObject_empty_object_part_number_1": GetObject_empty_object_part_number_1,
|
||||
"ListObjects_non_existing_bucket": ListObjects_non_existing_bucket,
|
||||
"ListObjects_with_prefix": ListObjects_with_prefix,
|
||||
"ListObjects_truncated": ListObjects_truncated,
|
||||
|
||||
Reference in New Issue
Block a user