Add extended checksum support (#15433)

This commit is contained in:
Klaus Post
2022-08-30 01:57:16 +02:00
committed by GitHub
parent 929b9e164e
commit a9f1ad7924
44 changed files with 1560 additions and 554 deletions
+233 -122
View File
@@ -21,10 +21,13 @@ import (
"bytes"
"context"
"crypto/md5"
"crypto/sha1"
"encoding/base64"
"encoding/hex"
"encoding/xml"
"fmt"
"hash"
"hash/crc32"
"io"
"io/ioutil"
"net/http"
@@ -37,8 +40,9 @@ import (
"sync"
"testing"
humanize "github.com/dustin/go-humanize"
"github.com/dustin/go-humanize"
"github.com/minio/minio/internal/auth"
"github.com/minio/minio/internal/hash/sha256"
xhttp "github.com/minio/minio/internal/http"
ioutilx "github.com/minio/minio/internal/ioutil"
)
@@ -1295,26 +1299,29 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
// byte data for PutObject.
bytesData := generateBytesData(6 * humanize.KiByte)
copySourceHeader := http.Header{}
copySourceHeader.Set("X-Amz-Copy-Source", "somewhere")
invalidMD5Header := http.Header{}
invalidMD5Header.Set("Content-Md5", "42")
inalidStorageClassHeader := http.Header{}
inalidStorageClassHeader.Set(xhttp.AmzStorageClass, "INVALID")
copySourceHeader := map[string]string{"X-Amz-Copy-Source": "somewhere"}
invalidMD5Header := map[string]string{"Content-Md5": "42"}
inalidStorageClassHeader := map[string]string{xhttp.AmzStorageClass: "INVALID"}
addCustomHeaders := func(req *http.Request, customHeaders http.Header) {
for k, values := range customHeaders {
for _, value := range values {
req.Header.Set(k, value)
}
addCustomHeaders := func(req *http.Request, customHeaders map[string]string) {
for k, value := range customHeaders {
req.Header.Set(k, value)
}
}
checksumData := func(b []byte, h hash.Hash) string {
h.Reset()
_, err := h.Write(b)
if err != nil {
t.Fatal(err)
}
return base64.StdEncoding.EncodeToString(h.Sum(nil))
}
// test cases with inputs and expected result for GetObject.
testCases := []struct {
bucketName string
objectName string
headers http.Header
headers map[string]string
data []byte
dataLen int
accessKey string
@@ -1322,10 +1329,11 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
fault Fault
// expected output.
expectedRespStatus int // expected response status body.
wantAPICode string
wantHeaders map[string]string
}{
// Test case - 1.
// Fetching the entire object and validating its contents.
{
0: {
bucketName: bucketName,
objectName: objectName,
data: bytesData,
@@ -1335,9 +1343,8 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
expectedRespStatus: http.StatusOK,
},
// Test case - 2.
// Test Case with invalid accessID.
{
1: {
bucketName: bucketName,
objectName: objectName,
data: bytesData,
@@ -1346,10 +1353,10 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
secretKey: credentials.SecretKey,
expectedRespStatus: http.StatusForbidden,
wantAPICode: "InvalidAccessKeyId",
},
// Test case - 3.
// Test Case with invalid header key X-Amz-Copy-Source.
{
2: {
bucketName: bucketName,
objectName: objectName,
headers: copySourceHeader,
@@ -1358,10 +1365,10 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey,
expectedRespStatus: http.StatusBadRequest,
wantAPICode: "InvalidArgument",
},
// Test case - 4.
// Test Case with invalid Content-Md5 value
{
3: {
bucketName: bucketName,
objectName: objectName,
headers: invalidMD5Header,
@@ -1370,10 +1377,10 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey,
expectedRespStatus: http.StatusBadRequest,
wantAPICode: "InvalidDigest",
},
// Test case - 5.
// Test Case with object greater than maximum allowed size.
{
4: {
bucketName: bucketName,
objectName: objectName,
data: bytesData,
@@ -1382,10 +1389,10 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
secretKey: credentials.SecretKey,
fault: TooBigObject,
expectedRespStatus: http.StatusBadRequest,
wantAPICode: "EntityTooLarge",
},
// Test case - 6.
// Test Case with missing content length
{
5: {
bucketName: bucketName,
objectName: objectName,
data: bytesData,
@@ -1394,10 +1401,10 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
secretKey: credentials.SecretKey,
fault: MissingContentLength,
expectedRespStatus: http.StatusLengthRequired,
wantAPICode: "MissingContentLength",
},
// Test case - 7.
// Test Case with invalid header key X-Amz-Storage-Class
{
6: {
bucketName: bucketName,
objectName: objectName,
headers: inalidStorageClassHeader,
@@ -1406,6 +1413,92 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey,
expectedRespStatus: http.StatusBadRequest,
wantAPICode: "InvalidStorageClass",
},
// Invalid crc32
7: {
bucketName: bucketName,
objectName: objectName,
headers: map[string]string{"x-amz-checksum-crc32": "123"},
data: bytesData,
dataLen: len(bytesData),
accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey,
expectedRespStatus: http.StatusBadRequest,
wantAPICode: "InvalidArgument",
},
// Wrong crc32
8: {
bucketName: bucketName,
objectName: objectName,
headers: map[string]string{"x-amz-checksum-crc32": "MTIzNA=="},
data: bytesData,
dataLen: len(bytesData),
accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey,
expectedRespStatus: http.StatusBadRequest,
wantAPICode: "XAmzContentChecksumMismatch",
},
// Correct crc32
9: {
bucketName: bucketName,
objectName: objectName,
headers: map[string]string{"x-amz-checksum-crc32": checksumData(bytesData, crc32.New(crc32.IEEETable))},
data: bytesData,
dataLen: len(bytesData),
accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey,
expectedRespStatus: http.StatusOK,
wantHeaders: map[string]string{"x-amz-checksum-crc32": checksumData(bytesData, crc32.New(crc32.IEEETable))},
},
// Correct crc32c
10: {
bucketName: bucketName,
objectName: objectName,
headers: map[string]string{"x-amz-checksum-crc32c": checksumData(bytesData, crc32.New(crc32.MakeTable(crc32.Castagnoli)))},
data: bytesData,
dataLen: len(bytesData),
accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey,
expectedRespStatus: http.StatusOK,
wantHeaders: map[string]string{"x-amz-checksum-crc32c": checksumData(bytesData, crc32.New(crc32.MakeTable(crc32.Castagnoli)))},
},
// CRC32 as CRC32C
11: {
bucketName: bucketName,
objectName: objectName,
headers: map[string]string{"x-amz-checksum-crc32c": checksumData(bytesData, crc32.New(crc32.IEEETable))},
data: bytesData,
dataLen: len(bytesData),
accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey,
expectedRespStatus: http.StatusBadRequest,
wantAPICode: "XAmzContentChecksumMismatch",
},
// SHA1
12: {
bucketName: bucketName,
objectName: objectName,
headers: map[string]string{"x-amz-checksum-sha1": checksumData(bytesData, sha1.New())},
data: bytesData,
dataLen: len(bytesData),
accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey,
expectedRespStatus: http.StatusOK,
wantHeaders: map[string]string{"x-amz-checksum-sha1": checksumData(bytesData, sha1.New())},
},
// SHA256
13: {
bucketName: bucketName,
objectName: objectName,
headers: map[string]string{"x-amz-checksum-sha256": checksumData(bytesData, sha256.New())},
data: bytesData,
dataLen: len(bytesData),
accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey,
expectedRespStatus: http.StatusOK,
wantHeaders: map[string]string{"x-amz-checksum-sha256": checksumData(bytesData, sha256.New())},
},
}
// Iterating over the cases, fetching the object validating the response.
@@ -1415,9 +1508,9 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
rec := httptest.NewRecorder()
// construct HTTP request for Get Object end point.
req, err = newTestSignedRequestV4(http.MethodPut, getPutObjectURL("", testCase.bucketName, testCase.objectName),
int64(testCase.dataLen), bytes.NewReader(testCase.data), testCase.accessKey, testCase.secretKey, nil)
int64(testCase.dataLen), bytes.NewReader(testCase.data), testCase.accessKey, testCase.secretKey, testCase.headers)
if err != nil {
t.Fatalf("Test %d: Failed to create HTTP request for Put Object: <ERROR> %v", i+1, err)
t.Fatalf("Test %d: Failed to create HTTP request for Put Object: <ERROR> %v", i, err)
}
// Add test case specific headers to the request.
addCustomHeaders(req, testCase.headers)
@@ -1435,22 +1528,48 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
apiRouter.ServeHTTP(rec, req)
// Assert the response code with the expected status.
if rec.Code != testCase.expectedRespStatus {
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, rec.Code)
b, _ := io.ReadAll(rec.Body)
t.Fatalf("Test %d: Expected the response status to be `%d`, but instead found `%d`: %s", i, testCase.expectedRespStatus, rec.Code, string(b))
}
if testCase.expectedRespStatus != http.StatusOK {
b, err := io.ReadAll(rec.Body)
if err != nil {
t.Fatal(err)
}
var apiErr APIErrorResponse
err = xml.Unmarshal(b, &apiErr)
if err != nil {
t.Fatal(err)
}
gotErr := apiErr.Code
wantErr := testCase.wantAPICode
if gotErr != wantErr {
t.Errorf("test %d: want api error %q, got %q", i, wantErr, gotErr)
}
if testCase.wantHeaders != nil {
for k, v := range testCase.wantHeaders {
got := rec.Header().Get(k)
if got != v {
t.Errorf("Want header %s = %s, got %#v", k, v, rec.Header())
}
}
}
}
if testCase.expectedRespStatus == http.StatusOK {
buffer := new(bytes.Buffer)
// Fetch the object to check whether the content is same as the one uploaded via PutObject.
gr, err := obj.GetObjectNInfo(context.Background(), testCase.bucketName, testCase.objectName, nil, nil, readLock, opts)
if err != nil {
t.Fatalf("Test %d: %s: Failed to fetch the copied object: <ERROR> %s", i+1, instanceType, err)
t.Fatalf("Test %d: %s: Failed to fetch the copied object: <ERROR> %s", i, instanceType, err)
}
if _, err = io.Copy(buffer, gr); err != nil {
gr.Close()
t.Fatalf("Test %d: %s: Failed to fetch the copied object: <ERROR> %s", i+1, instanceType, err)
t.Fatalf("Test %d: %s: Failed to fetch the copied object: <ERROR> %s", i, instanceType, err)
}
gr.Close()
if !bytes.Equal(bytesData, buffer.Bytes()) {
t.Errorf("Test %d: %s: Data Mismatch: Data fetched back from the uploaded object doesn't match the original one.", i+1, instanceType)
t.Errorf("Test %d: %s: Data Mismatch: Data fetched back from the uploaded object doesn't match the original one.", i, instanceType)
}
buffer.Reset()
}
@@ -1460,10 +1579,10 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
recV2 := httptest.NewRecorder()
// construct HTTP request for PUT Object endpoint.
reqV2, err = newTestSignedRequestV2(http.MethodPut, getPutObjectURL("", testCase.bucketName, testCase.objectName),
int64(testCase.dataLen), bytes.NewReader(testCase.data), testCase.accessKey, testCase.secretKey, nil)
int64(testCase.dataLen), bytes.NewReader(testCase.data), testCase.accessKey, testCase.secretKey, testCase.headers)
if err != nil {
t.Fatalf("Test %d: %s: Failed to create HTTP request for PutObject: <ERROR> %v", i+1, instanceType, err)
t.Fatalf("Test %d: %s: Failed to create HTTP request for PutObject: <ERROR> %v", i, instanceType, err)
}
// Add test case specific headers to the request.
@@ -1482,7 +1601,8 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
// Call the ServeHTTP to execute the handler.
apiRouter.ServeHTTP(recV2, reqV2)
if recV2.Code != testCase.expectedRespStatus {
t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, recV2.Code)
b, _ := io.ReadAll(rec.Body)
t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`: %s", i, instanceType, testCase.expectedRespStatus, recV2.Code, string(b))
}
if testCase.expectedRespStatus == http.StatusOK {
@@ -1490,17 +1610,26 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
// Fetch the object to check whether the content is same as the one uploaded via PutObject.
gr, err := obj.GetObjectNInfo(context.Background(), testCase.bucketName, testCase.objectName, nil, nil, readLock, opts)
if err != nil {
t.Fatalf("Test %d: %s: Failed to fetch the copied object: <ERROR> %s", i+1, instanceType, err)
t.Fatalf("Test %d: %s: Failed to fetch the copied object: <ERROR> %s", i, instanceType, err)
}
if _, err = io.Copy(buffer, gr); err != nil {
gr.Close()
t.Fatalf("Test %d: %s: Failed to fetch the copied object: <ERROR> %s", i+1, instanceType, err)
t.Fatalf("Test %d: %s: Failed to fetch the copied object: <ERROR> %s", i, instanceType, err)
}
gr.Close()
if !bytes.Equal(bytesData, buffer.Bytes()) {
t.Errorf("Test %d: %s: Data Mismatch: Data fetched back from the uploaded object doesn't match the original one.", i+1, instanceType)
t.Errorf("Test %d: %s: Data Mismatch: Data fetched back from the uploaded object doesn't match the original one.", i, instanceType)
}
buffer.Reset()
if testCase.wantHeaders != nil {
for k, v := range testCase.wantHeaders {
got := recV2.Header().Get(k)
if got != v {
t.Errorf("Want header %s = %s, got %#v", k, v, recV2.Header())
}
}
}
}
}
@@ -1585,11 +1714,12 @@ func testAPICopyObjectPartHandlerSanity(obj ObjectLayer, instanceType, bucketNam
// PutObjectPart API HTTP Handler has to be tested in isolation,
// that is without any other handler being registered,
// That's why NewMultipartUpload is initiated using ObjectLayer.
uploadID, err := obj.NewMultipartUpload(context.Background(), bucketName, testObject, opts)
res, err := obj.NewMultipartUpload(context.Background(), bucketName, testObject, opts)
if err != nil {
// Failed to create NewMultipartUpload, abort.
t.Fatalf("MinIO %s : <ERROR> %s", instanceType, err)
}
uploadID := res.UploadID
a := 0
b := globalMinPartSize
@@ -1701,11 +1831,12 @@ func testAPICopyObjectPartHandler(obj ObjectLayer, instanceType, bucketName stri
// PutObjectPart API HTTP Handler has to be tested in isolation,
// that is without any other handler being registered,
// That's why NewMultipartUpload is initiated using ObjectLayer.
uploadID, err := obj.NewMultipartUpload(context.Background(), bucketName, testObject, opts)
res, err := obj.NewMultipartUpload(context.Background(), bucketName, testObject, opts)
if err != nil {
// Failed to create NewMultipartUpload, abort.
t.Fatalf("MinIO %s : <ERROR> %s", instanceType, err)
}
uploadID := res.UploadID
// test cases with inputs and expected result for Copy Object.
testCases := []struct {
@@ -2664,20 +2795,18 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s
// object used for the test.
objectName := "test-object-new-multipart"
// uploadID obtained from NewMultipart upload.
var uploadID string
// upload IDs collected.
var uploadIDs []string
for i := 0; i < 2; i++ {
// initiate new multipart uploadID.
uploadID, err = obj.NewMultipartUpload(context.Background(), bucketName, objectName, opts)
res, err := obj.NewMultipartUpload(context.Background(), bucketName, objectName, opts)
if err != nil {
// Failed to create NewMultipartUpload, abort.
t.Fatalf("MinIO %s : <ERROR> %s", instanceType, err)
}
uploadIDs = append(uploadIDs, uploadID)
uploadIDs = append(uploadIDs, res.UploadID)
}
// Parts with size greater than 5 MiB.
@@ -2778,7 +2907,7 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s
s3MD5 := getCompleteMultipartMD5(inputParts[3].parts)
// generating the response body content for the success case.
successResponse := generateCompleteMultpartUploadResponse(bucketName, objectName, getGetObjectURL("", bucketName, objectName), s3MD5)
successResponse := generateCompleteMultpartUploadResponse(bucketName, objectName, getGetObjectURL("", bucketName, objectName), ObjectInfo{ETag: s3MD5})
encodedSuccessResponse := encodeResponse(successResponse)
ctx := context.Background()
@@ -3034,20 +3163,18 @@ func testAPIAbortMultipartHandler(obj ObjectLayer, instanceType, bucketName stri
// object used for the test.
objectName := "test-object-new-multipart"
// uploadID obtained from NewMultipart upload.
var uploadID string
// upload IDs collected.
var uploadIDs []string
for i := 0; i < 2; i++ {
// initiate new multipart uploadID.
uploadID, err = obj.NewMultipartUpload(context.Background(), bucketName, objectName, opts)
res, err := obj.NewMultipartUpload(context.Background(), bucketName, objectName, opts)
if err != nil {
// Failed to create NewMultipartUpload, abort.
t.Fatalf("MinIO %s : <ERROR> %s", instanceType, err)
}
uploadIDs = append(uploadIDs, uploadID)
uploadIDs = append(uploadIDs, res.UploadID)
}
// Parts with size greater than 5 MiB.
@@ -3445,161 +3572,132 @@ func testAPIPutObjectPartHandler(obj ObjectLayer, instanceType, bucketName strin
// PutObjectPart API HTTP Handler has to be tested in isolation,
// that is without any other handler being registered,
// That's why NewMultipartUpload is initiated using ObjectLayer.
uploadID, err := obj.NewMultipartUpload(context.Background(), bucketName, testObject, opts)
res, err := obj.NewMultipartUpload(context.Background(), bucketName, testObject, opts)
if err != nil {
// Failed to create NewMultipartUpload, abort.
t.Fatalf("MinIO %s : <ERROR> %s", instanceType, err)
}
uploadID := res.UploadID
uploadIDCopy := uploadID
// expected error types for invalid inputs to PutObjectPartHandler.
noAPIErr := APIError{}
// expected error when content length is missing in the HTTP request.
missingContent := getAPIError(ErrMissingContentLength)
// expected error when content length is too large.
entityTooLarge := getAPIError(ErrEntityTooLarge)
// expected error when the signature check fails.
badSigning := getAPIError(ErrSignatureDoesNotMatch)
// expected error MD5 sum mismatch occurs.
badChecksum := getAPIError(ErrInvalidDigest)
// expected error when the part number in the request is invalid.
invalidPart := getAPIError(ErrInvalidPart)
// expected error when maxPart is beyond the limit.
invalidMaxParts := getAPIError(ErrInvalidMaxParts)
// expected error the when the uploadID is invalid.
noSuchUploadID := getAPIError(ErrNoSuchUpload)
// expected error when InvalidAccessID is set.
invalidAccessID := getAPIError(ErrInvalidAccessKeyID)
// SignatureMismatch for various signing types
testCases := []struct {
objectName string
reader io.ReadSeeker
content string
partNumber string
fault Fault
accessKey string
secretKey string
expectedAPIError APIError
expectedAPIError APIErrorCode
}{
// Test case - 1.
// Success case.
{
0: {
objectName: testObject,
reader: bytes.NewReader([]byte("hello")),
content: "hello",
partNumber: "1",
fault: None,
accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey,
expectedAPIError: noAPIErr,
expectedAPIError: -1,
},
// Test case - 2.
// Case where part number is invalid.
{
1: {
objectName: testObject,
reader: bytes.NewReader([]byte("hello")),
content: "hello",
partNumber: "9999999999999999999",
fault: None,
accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey,
expectedAPIError: invalidPart,
expectedAPIError: ErrInvalidPart,
},
// Test case - 3.
// Case where the part number has exceeded the max allowed parts in an upload.
{
2: {
objectName: testObject,
reader: bytes.NewReader([]byte("hello")),
content: "hello",
partNumber: strconv.Itoa(globalMaxPartID + 1),
fault: None,
accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey,
expectedAPIError: invalidMaxParts,
expectedAPIError: ErrInvalidMaxParts,
},
// Test case - 4.
// Case where the content length is not set in the HTTP request.
{
3: {
objectName: testObject,
reader: bytes.NewReader([]byte("hello")),
content: "hello",
partNumber: "1",
fault: MissingContentLength,
accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey,
expectedAPIError: missingContent,
expectedAPIError: ErrMissingContentLength,
},
// Test case - 5.
// case where the object size is set to a value greater than the max allowed size.
{
4: {
objectName: testObject,
reader: bytes.NewReader([]byte("hello")),
content: "hello",
partNumber: "1",
fault: TooBigObject,
accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey,
expectedAPIError: entityTooLarge,
expectedAPIError: ErrEntityTooLarge,
},
// Test case - 6.
// case where a signature mismatch is introduced and the response is validated.
{
5: {
objectName: testObject,
reader: bytes.NewReader([]byte("hello")),
content: "hello",
partNumber: "1",
fault: BadSignature,
accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey,
expectedAPIError: badSigning,
expectedAPIError: ErrSignatureDoesNotMatch,
},
// Test case - 7.
// Case where incorrect checksum is set and the error response
// is asserted with the expected error response.
{
6: {
objectName: testObject,
reader: bytes.NewReader([]byte("hello")),
content: "hello",
partNumber: "1",
fault: BadMD5,
accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey,
expectedAPIError: badChecksum,
expectedAPIError: ErrInvalidDigest,
},
// Test case - 8.
// case where the a non-existent uploadID is set.
{
7: {
objectName: testObject,
reader: bytes.NewReader([]byte("hello")),
content: "hello",
partNumber: "1",
fault: MissingUploadID,
accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey,
expectedAPIError: noSuchUploadID,
expectedAPIError: ErrNoSuchUpload,
},
// Test case - 9.
// case with invalid AccessID.
// Forcing the signature check inside the handler to fail.
{
8: {
objectName: testObject,
reader: bytes.NewReader([]byte("hello")),
content: "hello",
partNumber: "1",
fault: None,
accessKey: "Invalid-AccessID",
secretKey: credentials.SecretKey,
expectedAPIError: invalidAccessID,
expectedAPIError: ErrInvalidAccessKeyID,
},
}
reqV2Str := "V2 Signed HTTP request"
reqV4Str := "V4 Signed HTTP request"
// collection of input HTTP request, ResponseRecorder and request type.
// Used to make a collection of V4 and V4 HTTP request.
type inputReqRec struct {
req *http.Request
rec *httptest.ResponseRecorder
@@ -3608,7 +3706,9 @@ func testAPIPutObjectPartHandler(obj ObjectLayer, instanceType, bucketName strin
for i, test := range testCases {
// Using sub-tests introduced in Go 1.7.
t.Run(fmt.Sprintf("MinIO %s : Test case %d.", instanceType, i+1), func(t *testing.T) {
t.Run(fmt.Sprintf("MinIO-%s-Test-%d.", instanceType, i), func(t *testing.T) {
// collection of input HTTP request, ResponseRecorder and request type.
// Used to make a collection of V4 and V4 HTTP request.
var reqV4, reqV2 *http.Request
var recV4, recV2 *httptest.ResponseRecorder
@@ -3623,7 +3723,7 @@ func testAPIPutObjectPartHandler(obj ObjectLayer, instanceType, bucketName strin
// constructing a v4 signed HTTP request.
reqV4, err = newTestSignedRequestV4(http.MethodPut,
getPutObjectPartURL("", bucketName, test.objectName, uploadID, test.partNumber),
0, test.reader, test.accessKey, test.secretKey, nil)
int64(len(test.content)), bytes.NewReader([]byte(test.content)), test.accessKey, test.secretKey, nil)
if err != nil {
t.Fatalf("Failed to create a signed V4 request to upload part for %s/%s: <ERROR> %v",
bucketName, test.objectName, err)
@@ -3632,10 +3732,10 @@ func testAPIPutObjectPartHandler(obj ObjectLayer, instanceType, bucketName strin
// construct HTTP request for PutObject Part Object endpoint.
reqV2, err = newTestSignedRequestV2(http.MethodPut,
getPutObjectPartURL("", bucketName, test.objectName, uploadID, test.partNumber),
0, test.reader, test.accessKey, test.secretKey, nil)
int64(len(test.content)), bytes.NewReader([]byte(test.content)), test.accessKey, test.secretKey, nil)
if err != nil {
t.Fatalf("Test %d %s Failed to create a V2 signed request to upload part for %s/%s: <ERROR> %v", i+1, instanceType,
t.Fatalf("Test %d %s Failed to create a V2 signed request to upload part for %s/%s: <ERROR> %v", i, instanceType,
bucketName, test.objectName, err)
}
@@ -3661,6 +3761,9 @@ func testAPIPutObjectPartHandler(obj ObjectLayer, instanceType, bucketName strin
// HTTP request type string for V4/V2 requests.
reqType := reqRec.reqType
// Clone so we don't retain values we do not want.
req.Header = req.Header.Clone()
// introduce faults in the request.
// deliberately introducing the invalid value to be able to assert the response with the expected error response.
switch test.fault {
@@ -3684,7 +3787,13 @@ func testAPIPutObjectPartHandler(obj ObjectLayer, instanceType, bucketName strin
apiRouter.ServeHTTP(rec, req)
// validate the error response.
if test.expectedAPIError != noAPIErr {
want := getAPIError(test.expectedAPIError)
if test.expectedAPIError == -1 {
want.HTTPStatusCode = 200
want.Code = "<no error>"
want.Description = "<no error>"
}
if rec.Code != http.StatusOK {
var errBytes []byte
// read the response body.
errBytes, err = ioutil.ReadAll(rec.Result().Body)
@@ -3700,14 +3809,16 @@ func testAPIPutObjectPartHandler(obj ObjectLayer, instanceType, bucketName strin
reqType, bucketName, test.objectName, err)
}
// Validate whether the error has occurred for the expected reason.
if test.expectedAPIError.Code != errXML.Code {
t.Errorf("%s, Expected to fail with error \"%s\", but received \"%s\".",
reqType, test.expectedAPIError.Code, errXML.Code)
if want.Code != errXML.Code {
t.Errorf("%s, Expected to fail with error \"%s\", but received \"%s\": %q.",
reqType, want.Code, errXML.Code, errXML.Message)
}
// Validate the HTTP response status code with the expected one.
if test.expectedAPIError.HTTPStatusCode != rec.Code {
t.Errorf("%s, Expected the HTTP response status code to be %d, got %d.", reqType, test.expectedAPIError.HTTPStatusCode, rec.Code)
if want.HTTPStatusCode != rec.Code {
t.Errorf("%s, Expected the HTTP response status code to be %d, got %d.", reqType, want.HTTPStatusCode, rec.Code)
}
} else if want.HTTPStatusCode != http.StatusOK {
t.Errorf("got 200 ok, want %d", rec.Code)
}
}
})
@@ -3849,12 +3960,12 @@ func testAPIListObjectPartsHandler(obj ObjectLayer, instanceType, bucketName str
// PutObjectPart API HTTP Handler has to be tested in isolation,
// that is without any other handler being registered,
// That's why NewMultipartUpload is initiated using ObjectLayer.
uploadID, err := obj.NewMultipartUpload(context.Background(), bucketName, testObject, opts)
res, err := obj.NewMultipartUpload(context.Background(), bucketName, testObject, opts)
if err != nil {
// Failed to create NewMultipartUpload, abort.
t.Fatalf("MinIO %s : <ERROR> %s", instanceType, err)
}
uploadID := res.UploadID
uploadIDCopy := uploadID
// create an object Part, will be used to test list object parts.