mirror of
https://github.com/pgsty/minio.git
synced 2026-07-24 14:36:15 +03:00
Add extended checksum support (#15433)
This commit is contained in:
+233
-122
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user