Add unit-tests for ListObjectParts API handler (#2826)

* Add missing uploadID test
... make variables in test code unexported.
* Add ServerNotInitialized test for ListObjectPartsHandler
* Add tests for ListObjectParts with signatureV2 and Anonymous requests
* Add failure test cases for ListObjectParts
master
Krishnan Parthasarathi 8 years ago committed by Harshavardhana
parent 61a18ed48f
commit 4f902d42b2
  1. 346
      cmd/object-handlers_test.go
  2. 4
      cmd/server_test.go
  3. 4
      cmd/server_v2_test.go
  4. 9
      cmd/test-utils_test.go

@ -41,6 +41,7 @@ const (
TooBigDecodedLength
BadSignature
BadMD5
MissingUploadID
)
// Wrapper for calling GetObject API handler tests for both XL multiple disks and FS single drive setup.
@ -1137,7 +1138,7 @@ func testAPIPutObjectPartHandlerAnon(obj ObjectLayer, instanceType, bucketName s
getPutObjectPartURL("", bucketName, testObject, mpartResp.UploadID, "1"),
int64(len("hello")), bytes.NewReader([]byte("hello")))
if aErr != nil {
t.Fatalf("Test %d %s Failed to create a signed request to upload part for %s/%s: <ERROR> %v",
t.Fatalf("Test %d %s Failed to create an anonymous request to upload part for %s/%s: <ERROR> %v",
1, instanceType, bucketName, testObject, aErr)
}
apiRouter.ServeHTTP(anonRec, anonReq)
@ -1171,7 +1172,7 @@ func testAPIPutObjectPartHandlerAnon(obj ObjectLayer, instanceType, bucketName s
getPutObjectPartURL("", bucketName, testObject, mpartResp.UploadID, "1"),
int64(len("hello")), bytes.NewReader([]byte("hello")))
if aErr != nil {
t.Fatalf("Test %d %s Failed to create a signed request to upload part for %s/%s: <ERROR> %v",
t.Fatalf("Test %d %s Failed to create an anonymous request to upload part for %s/%s: <ERROR> %v",
1, instanceType, bucketName, testObject, aErr)
}
apiRouter.ServeHTTP(anonRec, anonReq)
@ -1210,13 +1211,14 @@ func testAPIPutObjectPartHandler(obj ObjectLayer, instanceType, bucketName strin
t.Fatalf("[%s] Failed to unmarshal NewMultipartUpload response <ERROR> %v", instanceType, err)
}
NoAPIErr := APIError{}
MissingContent := getAPIError(ErrMissingContentLength)
EntityTooLarge := getAPIError(ErrEntityTooLarge)
BadSigning := getAPIError(ErrSignatureDoesNotMatch)
BadChecksum := getAPIError(ErrInvalidDigest)
InvalidPart := getAPIError(ErrInvalidPart)
InvalidMaxParts := getAPIError(ErrInvalidMaxParts)
noAPIErr := APIError{}
missingContent := getAPIError(ErrMissingContentLength)
entityTooLarge := getAPIError(ErrEntityTooLarge)
badSigning := getAPIError(ErrSignatureDoesNotMatch)
badChecksum := getAPIError(ErrInvalidDigest)
invalidPart := getAPIError(ErrInvalidPart)
invalidMaxParts := getAPIError(ErrInvalidMaxParts)
noSuchUploadID := getAPIError(ErrNoSuchUpload)
// SignatureMismatch for various signing types
testCases := []struct {
objectName string
@ -1226,19 +1228,25 @@ func testAPIPutObjectPartHandler(obj ObjectLayer, instanceType, bucketName strin
expectedAPIError APIError
}{
// Success case
{testObject, bytes.NewReader([]byte("hello")), "1", None, NoAPIErr},
{testObject, bytes.NewReader([]byte("hello")), "9999999999999999999", None, InvalidPart},
{testObject, bytes.NewReader([]byte("hello")), strconv.Itoa(maxPartID + 1), None, InvalidMaxParts},
{testObject, bytes.NewReader([]byte("hello")), "1", MissingContentLength, MissingContent},
{testObject, bytes.NewReader([]byte("hello")), "1", TooBigObject, EntityTooLarge},
{testObject, bytes.NewReader([]byte("hello")), "1", BadSignature, BadSigning},
{testObject, bytes.NewReader([]byte("hello")), "1", BadMD5, BadChecksum},
{testObject, bytes.NewReader([]byte("hello")), "1", None, noAPIErr},
{testObject, bytes.NewReader([]byte("hello")), "9999999999999999999", None, invalidPart},
{testObject, bytes.NewReader([]byte("hello")), strconv.Itoa(maxPartID + 1), None, invalidMaxParts},
{testObject, bytes.NewReader([]byte("hello")), "1", MissingContentLength, missingContent},
{testObject, bytes.NewReader([]byte("hello")), "1", TooBigObject, entityTooLarge},
{testObject, bytes.NewReader([]byte("hello")), "1", BadSignature, badSigning},
{testObject, bytes.NewReader([]byte("hello")), "1", BadMD5, badChecksum},
{testObject, bytes.NewReader([]byte("hello")), "1", MissingUploadID, noSuchUploadID},
}
for i, test := range testCases {
tRec := httptest.NewRecorder()
uploadID := mpartResp.UploadID
// To simulate PutObjectPart failure at object layer.
if test.fault == MissingUploadID {
uploadID = "upload1"
}
tReq, tErr := newTestSignedRequestV4("PUT",
getPutObjectPartURL("", bucketName, test.objectName, mpartResp.UploadID, test.partNumber),
getPutObjectPartURL("", bucketName, test.objectName, uploadID, test.partNumber),
0, test.reader, credentials.AccessKeyID, credentials.SecretAccessKey)
if tErr != nil {
t.Fatalf("Test %d %s Failed to create a signed request to upload part for %s/%s: <ERROR> %v", i+1, instanceType,
@ -1256,7 +1264,7 @@ func testAPIPutObjectPartHandler(obj ObjectLayer, instanceType, bucketName strin
tReq.Header.Set("Content-MD5", "badmd5")
}
apiRouter.ServeHTTP(tRec, tReq)
if test.expectedAPIError != NoAPIErr {
if test.expectedAPIError != noAPIErr {
errBytes, err := ioutil.ReadAll(tRec.Result().Body)
if err != nil {
t.Fatalf("Test %d %s Failed to read error response from upload part request %s/%s: <ERROR> %v",
@ -1302,3 +1310,305 @@ func TestPutObjectPartNilObjAPI(t *testing.T) {
t.Errorf("Test expected to fail with %d, but failed with %d", serverNotInitializedErr, rec.Code)
}
}
func testAPIListObjectPartsHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
credentials credential, t TestErrHandler) {
testObject := "testobject"
rec := httptest.NewRecorder()
req, err := newTestSignedRequestV4("POST", getNewMultipartURL("", bucketName, testObject),
0, nil, credentials.AccessKeyID, credentials.SecretAccessKey)
if err != nil {
t.Fatalf("[%s] - Failed to create a signed request to initiate multipart upload for %s/%s: <ERROR> %v",
instanceType, bucketName, testObject, err)
}
apiRouter.ServeHTTP(rec, req)
// Get uploadID of the mulitpart upload initiated.
var mpartResp InitiateMultipartUploadResponse
mpartRespBytes, err := ioutil.ReadAll(rec.Result().Body)
if err != nil {
t.Fatalf("[%s] Failed to read NewMultipartUpload response <ERROR> %v", instanceType, err)
}
err = xml.Unmarshal(mpartRespBytes, &mpartResp)
if err != nil {
t.Fatalf("[%s] Failed to unmarshal NewMultipartUpload response <ERROR> %v", instanceType, err)
}
// Upload a part for listing purposes.
rec = httptest.NewRecorder()
req, err = newTestSignedRequestV4("PUT",
getPutObjectPartURL("", bucketName, testObject, mpartResp.UploadID, "1"),
int64(len("hello")), bytes.NewReader([]byte("hello")), credentials.AccessKeyID, credentials.SecretAccessKey)
if err != nil {
t.Fatalf("[%s] - Failed to create a signed request to initiate multipart upload for %s/%s: <ERROR> %v",
instanceType, bucketName, testObject, err)
}
apiRouter.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("[%s] - Failed to PutObjectPart bucket: %s object: %s HTTP status code: %d",
instanceType, bucketName, testObject, rec.Code)
}
noAPIErr := APIError{}
signatureMismatchErr := getAPIError(ErrSignatureDoesNotMatch)
noSuchUploadErr := getAPIError(ErrNoSuchUpload)
invalidPartMarkerErr := getAPIError(ErrInvalidPartNumberMarker)
invalidMaxPartsErr := getAPIError(ErrInvalidMaxParts)
testCases := []struct {
fault Fault
partNumberMarker string
maxParts string
expectedErr APIError
}{
{BadSignature, "", "", signatureMismatchErr},
{MissingUploadID, "", "", noSuchUploadErr},
{None, "-1", "", invalidPartMarkerErr},
{None, "", "-1", invalidMaxPartsErr},
}
for i, test := range testCases {
uploadID := mpartResp.UploadID
tRec := httptest.NewRecorder()
if test.fault == MissingUploadID {
uploadID = "upload1"
}
tReq, tErr := newTestSignedRequestV4("GET",
getListMultipartURLWithParams("", bucketName, testObject, uploadID, test.maxParts, test.partNumberMarker, ""),
0, nil, credentials.AccessKeyID, credentials.SecretAccessKey)
if tErr != nil {
t.Fatalf("Test %d %s - Failed to create a signed request to list object parts for %s/%s: <ERROR> %v",
i+1, instanceType, bucketName, testObject, tErr)
}
if test.fault == BadSignature {
// Mangle signature
tReq.Header.Set("authorization", tReq.Header.Get("authorization")+"a")
}
apiRouter.ServeHTTP(tRec, tReq)
if test.expectedErr != noAPIErr {
errBytes, err := ioutil.ReadAll(tRec.Result().Body)
if err != nil {
t.Fatalf("Test %d %s Failed to read error response list object parts request %s/%s: <ERROR> %v",
i+1, instanceType, bucketName, testObject, err)
}
var errXML APIErrorResponse
err = xml.Unmarshal(errBytes, &errXML)
if err != nil {
t.Fatalf("Test %d %s Failed to unmarshal error response from list object partsest %s/%s: <ERROR> %v",
i+1, instanceType, bucketName, testObject, err)
}
if test.expectedErr.Code != errXML.Code {
t.Errorf("Test %d %s expected to fail with %s but received %s",
i+1, instanceType, test.expectedErr.Code, errXML.Code)
}
} else {
if tRec.Code != http.StatusOK {
t.Errorf("Test %d %s expected to succeed but failed with HTTP status code %d",
i+1, instanceType, tRec.Code)
}
}
}
}
func TestAPIListObjectPartsHandler(t *testing.T) {
ExecObjectLayerAPITest(t, testAPIListObjectPartsHandler,
[]string{"PutObjectPart", "NewMultipart", "ListObjectParts"})
}
func testAPIListObjectPartsHandlerV2(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
credentials credential, t TestErrHandler) {
testObject := "testobject"
rec := httptest.NewRecorder()
req, err := newTestSignedRequestV4("POST", getNewMultipartURL("", bucketName, testObject),
0, nil, credentials.AccessKeyID, credentials.SecretAccessKey)
if err != nil {
t.Fatalf("[%s] - Failed to create a signed request to initiate multipart upload for %s/%s: <ERROR> %v",
instanceType, bucketName, testObject, err)
}
apiRouter.ServeHTTP(rec, req)
// Get uploadID of the mulitpart upload initiated.
var mpartResp InitiateMultipartUploadResponse
mpartRespBytes, err := ioutil.ReadAll(rec.Result().Body)
if err != nil {
t.Fatalf("[%s] Failed to read NewMultipartUpload response <ERROR> %v", instanceType, err)
}
err = xml.Unmarshal(mpartRespBytes, &mpartResp)
if err != nil {
t.Fatalf("[%s] Failed to unmarshal NewMultipartUpload response <ERROR> %v", instanceType, err)
}
// Upload a part for listing purposes.
rec = httptest.NewRecorder()
req, err = newTestSignedRequestV4("PUT",
getPutObjectPartURL("", bucketName, testObject, mpartResp.UploadID, "1"),
int64(len("hello")), bytes.NewReader([]byte("hello")), credentials.AccessKeyID, credentials.SecretAccessKey)
if err != nil {
t.Fatalf("[%s] - Failed to create a signed request to initiate multipart upload for %s/%s: <ERROR> %v",
instanceType, bucketName, testObject, err)
}
apiRouter.ServeHTTP(rec, req)
rec = httptest.NewRecorder()
req, err = newTestSignedRequestV2("GET",
getListMultipartURLWithParams("", bucketName, testObject, mpartResp.UploadID, "", "", ""),
0, nil, credentials.AccessKeyID, credentials.SecretAccessKey)
if err != nil {
t.Fatalf("[%s] - Failed to create a signed request to list object parts for %s/%s: <ERROR> %v",
instanceType, bucketName, testObject, err)
}
apiRouter.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Errorf("Test %d %s expected to succeed but failed with HTTP status code %d", 1, instanceType, rec.Code)
}
// Simulate signature mismatch error for V2 request.
rec = httptest.NewRecorder()
req, err = newTestSignedRequestV2("GET",
getListMultipartURLWithParams("", bucketName, testObject, mpartResp.UploadID, "", "", ""),
0, nil, credentials.AccessKeyID, credentials.SecretAccessKey)
if err != nil {
t.Fatalf("[%s] - Failed to create a signed request to list object parts for %s/%s: <ERROR> %v",
instanceType, bucketName, testObject, err)
}
signatureMismatchErr := getAPIError(ErrSignatureDoesNotMatch)
req.Header.Set("x-amz-date", "")
apiRouter.ServeHTTP(rec, req)
errBytes, err := ioutil.ReadAll(rec.Result().Body)
if err != nil {
t.Fatalf("Test %d %s Failed to read error response list object parts request %s/%s: <ERROR> %v",
1, instanceType, bucketName, testObject, err)
}
var errXML APIErrorResponse
err = xml.Unmarshal(errBytes, &errXML)
if err != nil {
t.Fatalf("Test %d %s Failed to unmarshal error response from list object partsest %s/%s: <ERROR> %v",
1, instanceType, bucketName, testObject, err)
}
if errXML.Code != signatureMismatchErr.Code {
t.Errorf("Test %d %s expected to fail with error %s, but received %s", 1, instanceType,
signatureMismatchErr.Code, errXML.Code)
}
}
func TestListObjectPartsHandlerV2(t *testing.T) {
ExecObjectLayerAPITest(t, testAPIListObjectPartsHandlerV2, []string{"PutObjectPart", "NewMultipart", "ListObjectParts"})
}
func testAPIListObjectPartsHandlerAnon(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
credentials credential, t TestErrHandler) {
// Initialize bucket policies for anonymous request test
err := initBucketPolicies(obj)
if err != nil {
t.Fatalf("Failed to initialize bucket policies: <ERROR> %v", err)
}
testObject := "testobject"
rec := httptest.NewRecorder()
req, err := newTestSignedRequestV4("POST", getNewMultipartURL("", bucketName, testObject),
0, nil, credentials.AccessKeyID, credentials.SecretAccessKey)
if err != nil {
t.Fatalf("[%s] - Failed to create a signed request to initiate multipart upload for %s/%s: <ERROR> %v",
instanceType, bucketName, testObject, err)
}
apiRouter.ServeHTTP(rec, req)
// Get uploadID of the mulitpart upload initiated.
var mpartResp InitiateMultipartUploadResponse
mpartRespBytes, err := ioutil.ReadAll(rec.Result().Body)
if err != nil {
t.Fatalf("[%s] Failed to read NewMultipartUpload response <ERROR> %v", instanceType, err)
}
err = xml.Unmarshal(mpartRespBytes, &mpartResp)
if err != nil {
t.Fatalf("[%s] Failed to unmarshal NewMultipartUpload response <ERROR> %v", instanceType, err)
}
// Add a part to the new multipart upload created.
uploadID := mpartResp.UploadID
req, err = newTestSignedRequestV4("PUT",
getPutObjectPartURL("", bucketName, testObject, uploadID, "1"),
0, bytes.NewReader([]byte("hello")), credentials.AccessKeyID, credentials.SecretAccessKey)
if err != nil {
t.Fatalf("Test %d %s Failed to create a signed request to upload part for %s/%s: <ERROR> %v", 1, instanceType,
bucketName, testObject, err)
}
// Attempt an anonymous ListObjectParts API request to trigger AccessDenied error.
accessDeniedErr := getAPIError(ErrAccessDenied)
anonRec := httptest.NewRecorder()
anonReq, aErr := newTestRequest("GET",
getListMultipartURLWithParams("", bucketName, testObject, mpartResp.UploadID, "", "", ""),
0, nil)
if aErr != nil {
t.Fatalf("Test %d %s Failed to create an anonymous request to list multipart of an upload for %s/%s: <ERROR> %v",
1, instanceType, bucketName, testObject, aErr)
}
apiRouter.ServeHTTP(anonRec, anonReq)
anonErrBytes, err := ioutil.ReadAll(anonRec.Result().Body)
if err != nil {
t.Fatalf("Test %d %s Failed to read error response from list object parts request %s/%s: <ERROR> %v",
1, instanceType, bucketName, testObject, err)
}
var anonErrXML APIErrorResponse
err = xml.Unmarshal(anonErrBytes, &anonErrXML)
if err != nil {
t.Fatalf("Test %d %s Failed to unmarshal error response from list object parts request %s/%s: <ERROR> %v",
1, instanceType, bucketName, testObject, err)
}
if accessDeniedErr.Code != anonErrXML.Code {
t.Errorf("Test %d %s expected to fail with error %s, but received %s", 1, instanceType,
accessDeniedErr.Code, anonErrXML.Code)
}
// Set write only policy on bucket to allow anonymous ListObjectParts API
// request to go through.
writeOnlyPolicy := bucketPolicy{
Version: "1.0",
Statements: []policyStatement{getWriteOnlyObjectStatement(bucketName, "")},
}
globalBucketPolicies.SetBucketPolicy(bucketName, &writeOnlyPolicy)
anonRec = httptest.NewRecorder()
anonReq, aErr = newTestRequest("GET",
getListMultipartURLWithParams("", bucketName, testObject, mpartResp.UploadID, "", "", ""),
0, nil)
if aErr != nil {
t.Fatalf("Test %d %s Failed to create an anonymous request to list multipart of an upload for %s/%s: <ERROR> %v",
1, instanceType, bucketName, testObject, aErr)
}
apiRouter.ServeHTTP(anonRec, anonReq)
if anonRec.Code != http.StatusOK {
t.Errorf("Test %d %s expected ListObjectParts with authAnonymous type to succeed but failed with "+
"HTTP status code %d", 1, instanceType, anonRec.Code)
}
}
func TestListObjectPartsHandlerAnon(t *testing.T) {
ExecObjectLayerAPITest(t, testAPIListObjectPartsHandlerAnon, []string{"PutObjectPart", "NewMultipart", "ListObjectParts"})
}
func TestListObjectPartsHandlerNilObjAPI(t *testing.T) {
configDir, err := newTestConfig("us-east-1")
if err != nil {
t.Fatalf("Failed to create a test config: %v", err)
}
defer removeAll(configDir)
rec := httptest.NewRecorder()
req, err := newTestSignedRequestV4("GET",
getListMultipartURLWithParams("", "testbucket", "testobject", "fakeuploadId", "", "", ""),
0, bytes.NewReader([]byte("")), "abcd1", "abcd123")
if err != nil {
t.Fatal("Failed to create a signed UploadPart request.")
}
// Setup the 'nil' objectAPI router.
nilAPIRouter := initTestNilObjAPIEndPoints([]string{"ListObjectParts"})
nilAPIRouter.ServeHTTP(rec, req)
serverNotInitializedErr := getAPIError(ErrServerNotInitialized).HTTPStatusCode
if rec.Code != serverNotInitializedErr {
t.Errorf("Test expected to fail with %d, but failed with %d", serverNotInitializedErr, rec.Code)
}
}

@ -2305,7 +2305,7 @@ func (s *TestSuiteCommon) TestObjectMultipartListError(c *C) {
// HTTP request to ListMultipart Uploads.
// max-keys is set to valid value of 1
request, err = newTestSignedRequestV4("GET", getListMultipartURLWithParams(s.endPoint, bucketName, objectName, uploadID, "1"),
request, err = newTestSignedRequestV4("GET", getListMultipartURLWithParams(s.endPoint, bucketName, objectName, uploadID, "1", "", ""),
0, nil, s.accessKey, s.secretKey)
c.Assert(err, IsNil)
// execute the HTTP request.
@ -2315,7 +2315,7 @@ func (s *TestSuiteCommon) TestObjectMultipartListError(c *C) {
// HTTP request to ListMultipart Uploads.
// max-keys is set to invalid value of -2.
request, err = newTestSignedRequestV4("GET", getListMultipartURLWithParams(s.endPoint, bucketName, objectName, uploadID, "-2"),
request, err = newTestSignedRequestV4("GET", getListMultipartURLWithParams(s.endPoint, bucketName, objectName, uploadID, "-2", "", ""),
0, nil, s.accessKey, s.secretKey)
c.Assert(err, IsNil)
// execute the HTTP request.

@ -2267,7 +2267,7 @@ func (s *TestSuiteCommonV2) TestObjectMultipartListError(c *C) {
// HTTP request to ListMultipart Uploads.
// max-keys is set to valid value of 1
request, err = newTestSignedRequestV2("GET", getListMultipartURLWithParams(s.endPoint, bucketName, objectName, uploadID, "1"),
request, err = newTestSignedRequestV2("GET", getListMultipartURLWithParams(s.endPoint, bucketName, objectName, uploadID, "1", "", ""),
0, nil, s.accessKey, s.secretKey)
c.Assert(err, IsNil)
// execute the HTTP request.
@ -2277,7 +2277,7 @@ func (s *TestSuiteCommonV2) TestObjectMultipartListError(c *C) {
// HTTP request to ListMultipart Uploads.
// max-keys is set to invalid value of -2.
request, err = newTestSignedRequestV2("GET", getListMultipartURLWithParams(s.endPoint, bucketName, objectName, uploadID, "-2"),
request, err = newTestSignedRequestV2("GET", getListMultipartURLWithParams(s.endPoint, bucketName, objectName, uploadID, "-2", "", ""),
0, nil, s.accessKey, s.secretKey)
c.Assert(err, IsNil)
// execute the HTTP request.

@ -1133,10 +1133,13 @@ func getListMultipartUploadsURLWithParams(endPoint, bucketName, prefix, keyMarke
}
// return URL for a listing parts on a given upload id.
func getListMultipartURLWithParams(endPoint, bucketName, objectName, uploadID, maxParts string) string {
func getListMultipartURLWithParams(endPoint, bucketName, objectName, uploadID, maxParts, partNumberMarker, encoding string) string {
queryValues := url.Values{}
queryValues.Set("uploadId", uploadID)
queryValues.Set("max-parts", maxParts)
if partNumberMarker != "" {
queryValues.Set("part-number-marker", partNumberMarker)
}
return makeTestTargetURL(endPoint, bucketName, objectName, queryValues)
}
@ -1389,10 +1392,12 @@ func addAPIFunc(muxRouter *router.Router, apiRouter *router.Router, bucket *rout
// Register New Multipart upload handler.
case "NewMultipart":
bucket.Methods("POST").Path("/{object:.+}").HandlerFunc(api.NewMultipartUploadHandler).Queries("uploads", "")
// Register PutObjectPart handler.
case "PutObjectPart":
bucket.Methods("PUT").Path("/{object:.+}").HandlerFunc(api.PutObjectPartHandler).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
// Register ListObjectParts handler.
case "ListObjectParts":
bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(api.ListObjectPartsHandler).Queries("uploadId", "{uploadId:.*}")
// Register ListMultipartUploads handler.
case "ListMultipartUploads":
bucket.Methods("GET").HandlerFunc(api.ListMultipartUploadsHandler).Queries("uploads", "")

Loading…
Cancel
Save