diff --git a/cmd/gateway-s3.go b/cmd/gateway-s3.go index 454fcae98..d36c9bcef 100644 --- a/cmd/gateway-s3.go +++ b/cmd/gateway-s3.go @@ -85,6 +85,10 @@ func s3ToObjectError(err error, params ...string) error { } case "XAmzContentSHA256Mismatch": err = SHA256Mismatch{} + case "NoSuchUpload": + err = InvalidUploadID{} + case "EntityTooSmall": + err = PartTooSmall{} } e.e = err @@ -195,7 +199,7 @@ func (l *s3Objects) GetBucketInfo(bucket string) (bi BucketInfo, e error) { func (l *s3Objects) ListBuckets() ([]BucketInfo, error) { buckets, err := l.Client.ListBuckets() if err != nil { - return nil, err + return nil, s3ToObjectError(traceError(err)) } b := make([]BucketInfo, len(buckets)) @@ -450,7 +454,11 @@ func toMinioClientMetadata(metadata map[string]string) map[string]string { func (l *s3Objects) NewMultipartUpload(bucket string, object string, metadata map[string]string) (uploadID string, err error) { // Create PutObject options opts := minio.PutObjectOptions{UserMetadata: metadata} - return l.Client.NewMultipartUpload(bucket, object, opts) + uploadID, err = l.Client.NewMultipartUpload(bucket, object, opts) + if err != nil { + return uploadID, s3ToObjectError(traceError(err), bucket, object) + } + return uploadID, nil } // CopyObjectPart copy part of object to other bucket and object @@ -473,7 +481,7 @@ func fromMinioClientObjectPart(op minio.ObjectPart) PartInfo { func (l *s3Objects) PutObjectPart(bucket string, object string, uploadID string, partID int, data *HashReader) (pi PartInfo, e error) { md5HexBytes, err := hex.DecodeString(data.md5Sum) if err != nil { - return pi, err + return pi, s3ToObjectError(traceError(err), bucket, object) } sha256sumBytes, err := hex.DecodeString(data.sha256Sum) @@ -483,7 +491,7 @@ func (l *s3Objects) PutObjectPart(bucket string, object string, uploadID string, info, err := l.Client.PutObjectPart(bucket, object, uploadID, partID, data, data.Size(), md5HexBytes, sha256sumBytes) if err != nil { - return pi, err + return pi, s3ToObjectError(traceError(err), bucket, object) } return fromMinioClientObjectPart(info), nil @@ -526,7 +534,8 @@ func (l *s3Objects) ListObjectParts(bucket string, object string, uploadID strin // AbortMultipartUpload aborts a ongoing multipart upload func (l *s3Objects) AbortMultipartUpload(bucket string, object string, uploadID string) error { - return l.Client.AbortMultipartUpload(bucket, object, uploadID) + err := l.Client.AbortMultipartUpload(bucket, object, uploadID) + return s3ToObjectError(traceError(err), bucket, object) } // toMinioClientCompletePart converts completePart to minio CompletePart diff --git a/cmd/gateway-s3_test.go b/cmd/gateway-s3_test.go new file mode 100644 index 000000000..cf0fe01be --- /dev/null +++ b/cmd/gateway-s3_test.go @@ -0,0 +1,120 @@ +/* + * Minio Cloud Storage, (C) 2017 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import ( + "errors" + "testing" + + minio "github.com/minio/minio-go" +) + +func errResponse(code string) minio.ErrorResponse { + return minio.ErrorResponse{ + Code: code, + } +} + +func TestS3ToObjectError(t *testing.T) { + testCases := []struct { + inputErr error + expectedErr error + bucket, object string + }{ + { + inputErr: errResponse("BucketAlreadyOwnedByYou"), + expectedErr: BucketAlreadyOwnedByYou{}, + }, + { + inputErr: errResponse("BucketNotEmpty"), + expectedErr: BucketNotEmpty{}, + }, + { + inputErr: errResponse("InvalidBucketName"), + expectedErr: BucketNameInvalid{}, + }, + { + inputErr: errResponse("NoSuchBucketPolicy"), + expectedErr: PolicyNotFound{}, + }, + { + inputErr: errResponse("NoSuchBucket"), + expectedErr: BucketNotFound{}, + }, + // with empty Object in minio.ErrorRepsonse, NoSuchKey + // is interpreted as BucketNotFound + { + inputErr: errResponse("NoSuchKey"), + expectedErr: BucketNotFound{}, + }, + { + inputErr: errResponse("NoSuchUpload"), + expectedErr: InvalidUploadID{}, + }, + { + inputErr: errResponse("XMinioInvalidObjectName"), + expectedErr: ObjectNameInvalid{}, + }, + { + inputErr: errResponse("AccessDenied"), + expectedErr: PrefixAccessDenied{}, + }, + { + inputErr: errResponse("XAmzContentSHA256Mismatch"), + expectedErr: SHA256Mismatch{}, + }, + { + inputErr: errResponse("EntityTooSmall"), + expectedErr: PartTooSmall{}, + }, + { + inputErr: nil, + expectedErr: nil, + }, + // Special test case for NoSuchKey with object name + { + inputErr: minio.ErrorResponse{ + Code: "NoSuchKey", + }, + expectedErr: ObjectNotFound{}, + bucket: "bucket", + object: "obbject", + }, + + // N B error values that aren't of expected types + // should be left untouched. + // Special test case for error that is not of type + // minio.ErrorResponse + { + inputErr: errors.New("not a minio.ErrorResponse"), + expectedErr: errors.New("not a minio.ErrorResponse"), + }, + // Special test case for error value that is not of + // type (*Error) + { + inputErr: errors.New("not a *Error"), + expectedErr: errors.New("not a *Error"), + }, + } + + for i, tc := range testCases { + actualErr := s3ToObjectError(tc.inputErr, tc.bucket, tc.object) + if e, ok := actualErr.(*Error); ok && e.e != tc.expectedErr { + t.Errorf("Test case %d: Expected error %v but received error %v", i+1, tc.expectedErr, e.e) + } + } +}