github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/object-api-multipart_test.go (about) 1 // Copyright (c) 2015-2021 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cmd 19 20 import ( 21 "bytes" 22 "context" 23 "fmt" 24 "math/rand" 25 "reflect" 26 "runtime" 27 "strings" 28 "testing" 29 30 "github.com/dustin/go-humanize" 31 "github.com/minio/minio/internal/config/storageclass" 32 "github.com/minio/minio/internal/hash" 33 "github.com/minio/minio/internal/ioutil" 34 ) 35 36 // Wrapper for calling NewMultipartUpload tests for both Erasure multiple disks and single node setup. 37 func TestObjectNewMultipartUpload(t *testing.T) { 38 if runtime.GOOS == globalWindowsOSName { 39 t.Skip() 40 } 41 ExecObjectLayerTest(t, testObjectNewMultipartUpload) 42 } 43 44 // Tests validate creation of new multipart upload instance. 45 func testObjectNewMultipartUpload(obj ObjectLayer, instanceType string, t TestErrHandler) { 46 bucket := "minio-bucket" 47 object := "minio-object" 48 opts := ObjectOptions{} 49 _, err := obj.NewMultipartUpload(context.Background(), "--", object, opts) 50 if err == nil { 51 t.Fatalf("%s: Expected to fail since bucket name is invalid.", instanceType) 52 } 53 54 errMsg := "Bucket not found: minio-bucket" 55 // operation expected to fail since the bucket on which NewMultipartUpload is being initiated doesn't exist. 56 _, err = obj.NewMultipartUpload(context.Background(), bucket, object, opts) 57 if err == nil { 58 t.Fatalf("%s: Expected to fail since the NewMultipartUpload is initialized on a non-existent bucket.", instanceType) 59 } 60 if errMsg != err.Error() { 61 t.Errorf("%s, Expected to fail with Error \"%s\", but instead found \"%s\".", instanceType, errMsg, err.Error()) 62 } 63 64 // Create bucket before initiating NewMultipartUpload. 65 err = obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{}) 66 if err != nil { 67 // failed to create newbucket, abort. 68 t.Fatalf("%s : %s", instanceType, err.Error()) 69 } 70 71 res, err := obj.NewMultipartUpload(context.Background(), bucket, "\\", opts) 72 if err != nil { 73 t.Fatalf("%s : %s", instanceType, err.Error()) 74 } 75 76 err = obj.AbortMultipartUpload(context.Background(), bucket, "\\", res.UploadID, opts) 77 if err != nil { 78 switch err.(type) { 79 case InvalidUploadID: 80 t.Fatalf("%s: New Multipart upload failed to create uuid file.", instanceType) 81 default: 82 t.Fatalf(err.Error()) 83 } 84 } 85 } 86 87 // Wrapper for calling AbortMultipartUpload tests for both Erasure multiple disks and single node setup. 88 func TestObjectAbortMultipartUpload(t *testing.T) { 89 ExecObjectLayerTest(t, testObjectAbortMultipartUpload) 90 } 91 92 // Tests validate creation of abort multipart upload instance. 93 func testObjectAbortMultipartUpload(obj ObjectLayer, instanceType string, t TestErrHandler) { 94 bucket := "minio-bucket" 95 object := "minio-object" 96 opts := ObjectOptions{} 97 // Create bucket before initiating NewMultipartUpload. 98 err := obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{}) 99 if err != nil { 100 // failed to create newbucket, abort. 101 t.Fatalf("%s : %s", instanceType, err.Error()) 102 } 103 104 res, err := obj.NewMultipartUpload(context.Background(), bucket, object, opts) 105 if err != nil { 106 t.Fatalf("%s : %s", instanceType, err.Error()) 107 } 108 uploadID := res.UploadID 109 110 abortTestCases := []struct { 111 bucketName string 112 objName string 113 uploadID string 114 expectedErrType error 115 }{ 116 {"--", object, uploadID, BucketNameInvalid{}}, 117 {"foo", object, uploadID, BucketNotFound{}}, 118 {bucket, object, "foo-foo", InvalidUploadID{}}, 119 {bucket, object, uploadID, nil}, 120 } 121 122 if runtime.GOOS != globalWindowsOSName { 123 abortTestCases = append(abortTestCases, struct { 124 bucketName string 125 objName string 126 uploadID string 127 expectedErrType error 128 }{bucket, "\\", uploadID, InvalidUploadID{}}) 129 } 130 131 // Iterating over creatPartCases to generate multipart chunks. 132 for i, testCase := range abortTestCases { 133 err = obj.AbortMultipartUpload(context.Background(), testCase.bucketName, testCase.objName, testCase.uploadID, opts) 134 if testCase.expectedErrType == nil && err != nil { 135 t.Errorf("Test %d, unexpected err is received: %v, expected:%v\n", i+1, err, testCase.expectedErrType) 136 } 137 if testCase.expectedErrType != nil && !isSameType(err, testCase.expectedErrType) { 138 t.Errorf("Test %d, unexpected err is received: %v, expected:%v\n", i+1, err, testCase.expectedErrType) 139 } 140 } 141 } 142 143 // Wrapper for calling isUploadIDExists tests for both Erasure multiple disks and single node setup. 144 func TestObjectAPIIsUploadIDExists(t *testing.T) { 145 ExecObjectLayerTest(t, testObjectAPIIsUploadIDExists) 146 } 147 148 // Tests validates the validator for existence of uploadID. 149 func testObjectAPIIsUploadIDExists(obj ObjectLayer, instanceType string, t TestErrHandler) { 150 bucket := "minio-bucket" 151 object := "minio-object" 152 153 // Create bucket before initiating NewMultipartUpload. 154 err := obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{}) 155 if err != nil { 156 // Failed to create newbucket, abort. 157 t.Fatalf("%s : %s", instanceType, err.Error()) 158 } 159 160 _, err = obj.NewMultipartUpload(context.Background(), bucket, object, ObjectOptions{}) 161 if err != nil { 162 t.Fatalf("%s : %s", instanceType, err.Error()) 163 } 164 165 opts := ObjectOptions{} 166 err = obj.AbortMultipartUpload(context.Background(), bucket, object, "abc", opts) 167 switch err.(type) { 168 case InvalidUploadID: 169 default: 170 t.Fatalf("%s: Expected uploadIDPath to exist.", instanceType) 171 } 172 } 173 174 // Wrapper for calling PutObjectPart tests for both Erasure multiple disks and single node setup. 175 func TestObjectAPIPutObjectPart(t *testing.T) { 176 ExecExtendedObjectLayerTest(t, testObjectAPIPutObjectPart) 177 } 178 179 // Tests validate correctness of PutObjectPart. 180 func testObjectAPIPutObjectPart(obj ObjectLayer, instanceType string, t TestErrHandler) { 181 // Generating cases for which the PutObjectPart fails. 182 bucket := "minio-bucket" 183 object := "minio-object" 184 opts := ObjectOptions{} 185 // Create bucket before initiating NewMultipartUpload. 186 err := obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{}) 187 if err != nil { 188 // Failed to create newbucket, abort. 189 t.Fatalf("%s : %s", instanceType, err.Error()) 190 } 191 // Initiate Multipart Upload on the above created bucket. 192 res, err := obj.NewMultipartUpload(context.Background(), bucket, object, opts) 193 if err != nil { 194 // Failed to create NewMultipartUpload, abort. 195 t.Fatalf("%s : %s", instanceType, err.Error()) 196 } 197 198 err = obj.MakeBucket(context.Background(), "abc", MakeBucketOptions{}) 199 if err != nil { 200 // Failed to create newbucket, abort. 201 t.Fatalf("%s : %s", instanceType, err.Error()) 202 } 203 204 resN, err := obj.NewMultipartUpload(context.Background(), "abc", "def", opts) 205 if err != nil { 206 // Failed to create NewMultipartUpload, abort. 207 t.Fatalf("%s : %s", instanceType, err.Error()) 208 } 209 210 uploadID := res.UploadID 211 // Creating a dummy bucket for tests. 212 err = obj.MakeBucket(context.Background(), "unused-bucket", MakeBucketOptions{}) 213 if err != nil { 214 // Failed to create newbucket, abort. 215 t.Fatalf("%s : %s", instanceType, err.Error()) 216 } 217 218 obj.DeleteBucket(context.Background(), "abc", DeleteBucketOptions{}) 219 220 // Collection of non-exhaustive PutObjectPart test cases, valid errors 221 // and success responses. 222 testCases := []struct { 223 bucketName string 224 objName string 225 uploadID string 226 PartID int 227 inputReaderData string 228 inputMd5 string 229 inputSHA256 string 230 inputDataSize int64 231 // flag indicating whether the test should pass. 232 shouldPass bool 233 // expected error output. 234 expectedMd5 string 235 expectedError error 236 }{ 237 // Test case 1-4. 238 // Cases with invalid bucket name. 239 {bucketName: ".test", objName: "obj", PartID: 1, expectedError: fmt.Errorf("%s", "Bucket name invalid: .test")}, 240 {bucketName: "------", objName: "obj", PartID: 1, expectedError: fmt.Errorf("%s", "Bucket name invalid: ------")}, 241 { 242 bucketName: "$this-is-not-valid-too", objName: "obj", PartID: 1, 243 expectedError: fmt.Errorf("%s", "Bucket name invalid: $this-is-not-valid-too"), 244 }, 245 {bucketName: "a", objName: "obj", PartID: 1, expectedError: fmt.Errorf("%s", "Bucket name invalid: a")}, 246 // Test case - 5. 247 // Case with invalid object names. 248 {bucketName: bucket, PartID: 1, expectedError: fmt.Errorf("%s", "Object name invalid: minio-bucket/")}, 249 // Test case - 6. 250 // Valid object and bucket names but non-existent bucket. 251 {bucketName: "abc", objName: "def", uploadID: resN.UploadID, PartID: 1, expectedError: fmt.Errorf("%s", "Bucket not found: abc")}, 252 // Test Case - 7. 253 // Existing bucket, but using a bucket on which NewMultipartUpload is not Initiated. 254 {bucketName: "unused-bucket", objName: "def", uploadID: "xyz", PartID: 1, expectedError: fmt.Errorf("%s", "Invalid upload id xyz")}, 255 // Test Case - 8. 256 // Existing bucket, object name different from which NewMultipartUpload is constructed from. 257 // Expecting "Invalid upload id". 258 {bucketName: bucket, objName: "def", uploadID: "xyz", PartID: 1, expectedError: fmt.Errorf("%s", "Invalid upload id xyz")}, 259 // Test Case - 9. 260 // Existing bucket, bucket and object name are the ones from which NewMultipartUpload is constructed from. 261 // But the uploadID is invalid. 262 // Expecting "Invalid upload id". 263 {bucketName: bucket, objName: object, uploadID: "xyz", PartID: 1, expectedError: fmt.Errorf("%s", "Invalid upload id xyz")}, 264 // Test Case - 10. 265 // Case with valid UploadID, existing bucket name. 266 // But using the bucket name from which NewMultipartUpload is not constructed from. 267 {bucketName: "unused-bucket", objName: object, uploadID: uploadID, PartID: 1, expectedError: fmt.Errorf("%s", "Invalid upload id "+uploadID)}, 268 // Test Case - 11. 269 // Case with valid UploadID, existing bucket name. 270 // But using the object name from which NewMultipartUpload is not constructed from. 271 {bucketName: bucket, objName: "none-object", uploadID: uploadID, PartID: 1, expectedError: fmt.Errorf("%s", "Invalid upload id "+uploadID)}, 272 // Test case - 12. 273 // Input to replicate Md5 mismatch. 274 { 275 bucketName: bucket, objName: object, uploadID: uploadID, PartID: 1, inputMd5: "d41d8cd98f00b204e9800998ecf8427f", 276 expectedError: hash.BadDigest{ExpectedMD5: "d41d8cd98f00b204e9800998ecf8427f", CalculatedMD5: "d41d8cd98f00b204e9800998ecf8427e"}, 277 }, 278 // Test case - 13. 279 // When incorrect sha256 is provided. 280 { 281 bucketName: bucket, objName: object, uploadID: uploadID, PartID: 1, inputSHA256: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b854", 282 expectedError: hash.SHA256Mismatch{ 283 ExpectedSHA256: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b854", 284 CalculatedSHA256: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", 285 }, 286 }, 287 // Test case - 14. 288 // Input with size more than the size of actual data inside the reader. 289 { 290 bucketName: bucket, objName: object, uploadID: uploadID, PartID: 1, inputReaderData: "abcd", inputMd5: "e2fc714c4727ee9395f324cd2e7f3335", inputDataSize: int64(len("abcd") + 1), 291 expectedError: hash.BadDigest{ExpectedMD5: "e2fc714c4727ee9395f324cd2e7f3335", CalculatedMD5: "e2fc714c4727ee9395f324cd2e7f331f"}, 292 }, 293 // Test case - 15. 294 // Input with size less than the size of actual data inside the reader. 295 { 296 bucketName: bucket, objName: object, uploadID: uploadID, PartID: 1, inputReaderData: "abcd", inputMd5: "900150983cd24fb0d6963f7d28e17f73", inputDataSize: int64(len("abcd") - 1), 297 expectedError: ioutil.ErrOverread, 298 }, 299 300 // Test case - 16-19. 301 // Validating for success cases. 302 {bucketName: bucket, objName: object, uploadID: uploadID, PartID: 1, inputReaderData: "abcd", inputMd5: "e2fc714c4727ee9395f324cd2e7f331f", inputSHA256: "88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589", inputDataSize: int64(len("abcd")), shouldPass: true}, 303 {bucketName: bucket, objName: object, uploadID: uploadID, PartID: 2, inputReaderData: "efgh", inputMd5: "1f7690ebdd9b4caf8fab49ca1757bf27", inputSHA256: "e5e088a0b66163a0a26a5e053d2a4496dc16ab6e0e3dd1adf2d16aa84a078c9d", inputDataSize: int64(len("efgh")), shouldPass: true}, 304 {bucketName: bucket, objName: object, uploadID: uploadID, PartID: 3, inputReaderData: "ijkl", inputMd5: "09a0877d04abf8759f99adec02baf579", inputSHA256: "005c19658919186b85618c5870463eec8d9b8c1a9d00208a5352891ba5bbe086", inputDataSize: int64(len("abcd")), shouldPass: true}, 305 {bucketName: bucket, objName: object, uploadID: uploadID, PartID: 4, inputReaderData: "mnop", inputMd5: "e132e96a5ddad6da8b07bba6f6131fef", inputSHA256: "f1afc31479522d6cff1ed068f93998f05a8cd3b22f5c37d7f307084f62d1d270", inputDataSize: int64(len("abcd")), shouldPass: true}, 306 } 307 308 // Validate all the test cases. 309 for i, testCase := range testCases { 310 actualInfo, actualErr := obj.PutObjectPart(context.Background(), testCase.bucketName, testCase.objName, testCase.uploadID, testCase.PartID, mustGetPutObjReader(t, bytes.NewBufferString(testCase.inputReaderData), testCase.inputDataSize, testCase.inputMd5, testCase.inputSHA256), opts) 311 // All are test cases above are expected to fail. 312 if actualErr != nil && testCase.shouldPass { 313 t.Errorf("Test %d: %s: Expected to pass, but failed with: <ERROR> %s.", i+1, instanceType, actualErr.Error()) 314 } 315 if actualErr == nil && !testCase.shouldPass { 316 t.Errorf("Test %d: %s: Expected to fail with <ERROR> \"%s\", but passed instead.", i+1, instanceType, testCase.expectedError.Error()) 317 } 318 // Failed as expected, but does it fail for the expected reason. 319 if actualErr != nil && !testCase.shouldPass { 320 if testCase.expectedError.Error() != actualErr.Error() { 321 t.Errorf("Test %d: %s: Expected to fail with error \"%s\", but instead failed with error \"%s\" instead.", i+1, instanceType, testCase.expectedError.Error(), actualErr.Error()) 322 } 323 } 324 // Test passes as expected, but the output values are verified for correctness here. 325 if actualErr == nil && testCase.shouldPass { 326 // Asserting whether the md5 output is correct. 327 if testCase.inputMd5 != actualInfo.ETag { 328 t.Errorf("Test %d: %s: Calculated Md5 different from the actual one %s.", i+1, instanceType, actualInfo.ETag) 329 } 330 } 331 } 332 } 333 334 // Wrapper for calling TestListMultipartUploads tests for both Erasure multiple disks and single node setup. 335 func TestListMultipartUploads(t *testing.T) { 336 ExecExtendedObjectLayerTest(t, testListMultipartUploads) 337 } 338 339 // testListMultipartUploads - Tests validate listing of multipart uploads. 340 func testListMultipartUploads(obj ObjectLayer, instanceType string, t TestErrHandler) { 341 bucketNames := []string{"minio-bucket", "minio-2-bucket", "minio-3-bucket"} 342 objectNames := []string{"minio-object-1.txt", "minio-object.txt", "neymar-1.jpeg", "neymar.jpeg", "parrot-1.png", "parrot.png"} 343 uploadIDs := []string{} 344 opts := ObjectOptions{} 345 // bucketnames[0]. 346 // objectNames[0]. 347 // uploadIds [0]. 348 // Create bucket before initiating NewMultipartUpload. 349 err := obj.MakeBucket(context.Background(), bucketNames[0], MakeBucketOptions{}) 350 if err != nil { 351 // Failed to create newbucket, abort. 352 t.Fatalf("%s : %s", instanceType, err.Error()) 353 } 354 // Initiate Multipart Upload on the above created bucket. 355 res, err := obj.NewMultipartUpload(context.Background(), bucketNames[0], objectNames[0], opts) 356 if err != nil { 357 // Failed to create NewMultipartUpload, abort. 358 t.Fatalf("%s : %s", instanceType, err.Error()) 359 } 360 361 uploadIDs = append(uploadIDs, res.UploadID) 362 363 // bucketnames[1]. 364 // objectNames[0]. 365 // uploadIds [1-3]. 366 // Bucket to test for multiple upload Id's for a given object. 367 err = obj.MakeBucket(context.Background(), bucketNames[1], MakeBucketOptions{}) 368 if err != nil { 369 // Failed to create newbucket, abort. 370 t.Fatalf("%s : %s", instanceType, err.Error()) 371 } 372 for i := 0; i < 3; i++ { 373 // Initiate Multipart Upload on bucketNames[1] for the same object 3 times. 374 // Used to test the listing for the case of multiple uploadID's for a given object. 375 res, err = obj.NewMultipartUpload(context.Background(), bucketNames[1], objectNames[0], opts) 376 if err != nil { 377 // Failed to create NewMultipartUpload, abort. 378 t.Fatalf("%s : %s", instanceType, err.Error()) 379 } 380 381 uploadIDs = append(uploadIDs, res.UploadID) 382 } 383 384 // Bucket to test for multiple objects, each with unique UUID. 385 // bucketnames[2]. 386 // objectNames[0-2]. 387 // uploadIds [4-9]. 388 err = obj.MakeBucket(context.Background(), bucketNames[2], MakeBucketOptions{}) 389 if err != nil { 390 // Failed to create newbucket, abort. 391 t.Fatalf("%s : %s", instanceType, err.Error()) 392 } 393 // Initiate Multipart Upload on bucketNames[2]. 394 // Used to test the listing for the case of multiple objects for a given bucket. 395 for i := 0; i < 6; i++ { 396 res, err = obj.NewMultipartUpload(context.Background(), bucketNames[2], objectNames[i], opts) 397 if err != nil { 398 // Failed to create NewMultipartUpload, abort. 399 t.Fatalf("%s : %s", instanceType, err.Error()) 400 } 401 // uploadIds [4-9]. 402 uploadIDs = append(uploadIDs, res.UploadID) 403 } 404 // Create multipart parts. 405 // Need parts to be uploaded before MultipartLists can be called and tested. 406 createPartCases := []struct { 407 bucketName string 408 objName string 409 uploadID string 410 PartID int 411 inputReaderData string 412 inputMd5 string 413 inputDataSize int64 414 expectedMd5 string 415 }{ 416 // Case 1-4. 417 // Creating sequence of parts for same uploadID. 418 // Used to ensure that the ListMultipartResult produces one output for the four parts uploaded below for the given upload ID. 419 {bucketNames[0], objectNames[0], uploadIDs[0], 1, "abcd", "e2fc714c4727ee9395f324cd2e7f331f", int64(len("abcd")), "e2fc714c4727ee9395f324cd2e7f331f"}, 420 {bucketNames[0], objectNames[0], uploadIDs[0], 2, "efgh", "1f7690ebdd9b4caf8fab49ca1757bf27", int64(len("efgh")), "1f7690ebdd9b4caf8fab49ca1757bf27"}, 421 {bucketNames[0], objectNames[0], uploadIDs[0], 3, "ijkl", "09a0877d04abf8759f99adec02baf579", int64(len("abcd")), "09a0877d04abf8759f99adec02baf579"}, 422 {bucketNames[0], objectNames[0], uploadIDs[0], 4, "mnop", "e132e96a5ddad6da8b07bba6f6131fef", int64(len("abcd")), "e132e96a5ddad6da8b07bba6f6131fef"}, 423 // Cases 5-7. 424 // Create parts with 3 uploadID's for the same object. 425 // Testing for listing of all the uploadID's for given object. 426 // Insertion with 3 different uploadID's are done for same bucket and object. 427 {bucketNames[1], objectNames[0], uploadIDs[1], 1, "abcd", "e2fc714c4727ee9395f324cd2e7f331f", int64(len("abcd")), "e2fc714c4727ee9395f324cd2e7f331f"}, 428 {bucketNames[1], objectNames[0], uploadIDs[2], 1, "abcd", "e2fc714c4727ee9395f324cd2e7f331f", int64(len("abcd")), "e2fc714c4727ee9395f324cd2e7f331f"}, 429 {bucketNames[1], objectNames[0], uploadIDs[3], 1, "abcd", "e2fc714c4727ee9395f324cd2e7f331f", int64(len("abcd")), "e2fc714c4727ee9395f324cd2e7f331f"}, 430 // Case 8-13. 431 // Generating parts for different objects. 432 {bucketNames[2], objectNames[0], uploadIDs[4], 1, "abcd", "e2fc714c4727ee9395f324cd2e7f331f", int64(len("abcd")), "e2fc714c4727ee9395f324cd2e7f331f"}, 433 {bucketNames[2], objectNames[1], uploadIDs[5], 1, "abcd", "e2fc714c4727ee9395f324cd2e7f331f", int64(len("abcd")), "e2fc714c4727ee9395f324cd2e7f331f"}, 434 {bucketNames[2], objectNames[2], uploadIDs[6], 1, "abcd", "e2fc714c4727ee9395f324cd2e7f331f", int64(len("abcd")), "e2fc714c4727ee9395f324cd2e7f331f"}, 435 {bucketNames[2], objectNames[3], uploadIDs[7], 1, "abcd", "e2fc714c4727ee9395f324cd2e7f331f", int64(len("abcd")), "e2fc714c4727ee9395f324cd2e7f331f"}, 436 {bucketNames[2], objectNames[4], uploadIDs[8], 1, "abcd", "e2fc714c4727ee9395f324cd2e7f331f", int64(len("abcd")), "e2fc714c4727ee9395f324cd2e7f331f"}, 437 {bucketNames[2], objectNames[5], uploadIDs[9], 1, "abcd", "e2fc714c4727ee9395f324cd2e7f331f", int64(len("abcd")), "e2fc714c4727ee9395f324cd2e7f331f"}, 438 } 439 sha256sum := "" 440 // Iterating over creatPartCases to generate multipart chunks. 441 for _, testCase := range createPartCases { 442 _, err := obj.PutObjectPart(context.Background(), testCase.bucketName, testCase.objName, testCase.uploadID, testCase.PartID, mustGetPutObjReader(t, bytes.NewBufferString(testCase.inputReaderData), testCase.inputDataSize, testCase.inputMd5, sha256sum), opts) 443 if err != nil { 444 t.Fatalf("%s : %s", instanceType, err.Error()) 445 } 446 447 } 448 449 // Expected Results set for asserting ListObjectMultipart test. 450 listMultipartResults := []ListMultipartsInfo{ 451 // listMultipartResults - 1. 452 // Used to check that the result produces only one output for the 4 parts uploaded in cases 1-4 of createPartCases above. 453 // ListMultipartUploads doesn't list the parts. 454 { 455 MaxUploads: 100, 456 Uploads: []MultipartInfo{ 457 { 458 Object: objectNames[0], 459 UploadID: uploadIDs[0], 460 }, 461 }, 462 }, 463 // listMultipartResults - 2. 464 // Used to check that the result produces if keyMarker is set to the only available object. 465 // `KeyMarker` is set. 466 // ListMultipartUploads doesn't list the parts. 467 { 468 MaxUploads: 100, 469 KeyMarker: "minio-object-1.txt", 470 }, 471 // listMultipartResults - 3. 472 // `KeyMarker` is set, no MultipartInfo expected. 473 // ListMultipartUploads doesn't list the parts. 474 // `Maxupload` value is asserted. 475 { 476 MaxUploads: 100, 477 KeyMarker: "orange", 478 }, 479 // listMultipartResults - 4. 480 // `KeyMarker` is set, no MultipartInfo expected. 481 // Maxupload value is asserted. 482 { 483 MaxUploads: 1, 484 KeyMarker: "orange", 485 }, 486 // listMultipartResults - 5. 487 // `KeyMarker` is set. It contains part of the objectname as `KeyPrefix`. 488 // Expecting the result to contain one MultipartInfo entry and Istruncated to be false. 489 { 490 MaxUploads: 10, 491 KeyMarker: "min", 492 IsTruncated: false, 493 Uploads: []MultipartInfo{ 494 { 495 Object: objectNames[0], 496 UploadID: uploadIDs[0], 497 }, 498 }, 499 }, 500 // listMultipartResults - 6. 501 // `KeyMarker` is set. It contains part of the objectname as `KeyPrefix`. 502 // `MaxUploads` is set equal to the number of meta data entries in the result, the result contains only one entry. 503 // Expecting the result to contain one MultipartInfo entry and IsTruncated to be false. 504 { 505 MaxUploads: 1, 506 KeyMarker: "min", 507 IsTruncated: false, 508 Uploads: []MultipartInfo{ 509 { 510 Object: objectNames[0], 511 UploadID: uploadIDs[0], 512 }, 513 }, 514 }, 515 // listMultipartResults - 7. 516 // `KeyMarker` is set. It contains part of the objectname as `KeyPrefix`. 517 // Testing for the case with `MaxUploads` set to 0. 518 // Expecting the result to contain no MultipartInfo entry since `MaxUploads` is set to 0. 519 // Expecting `IsTruncated` to be true. 520 { 521 MaxUploads: 0, 522 KeyMarker: "min", 523 IsTruncated: true, 524 }, 525 // listMultipartResults - 8. 526 // `KeyMarker` is set. It contains part of the objectname as KeyPrefix. 527 // Testing for the case with `MaxUploads` set to 0. 528 // Expecting the result to contain no MultipartInfo entry since `MaxUploads` is set to 0. 529 // Expecting `isTruncated` to be true. 530 { 531 MaxUploads: 0, 532 KeyMarker: "min", 533 IsTruncated: true, 534 }, 535 // listMultipartResults - 9. 536 // `KeyMarker` is set. It contains part of the objectname as KeyPrefix. 537 // `KeyMarker` is set equal to the object name in the result. 538 // Expecting the result to contain one MultipartInfo entry and IsTruncated to be false. 539 { 540 MaxUploads: 2, 541 KeyMarker: "minio-object", 542 IsTruncated: false, 543 Uploads: []MultipartInfo{ 544 { 545 Object: objectNames[0], 546 UploadID: uploadIDs[0], 547 }, 548 }, 549 }, 550 // listMultipartResults - 10. 551 // Prefix is set. It is set equal to the object name. 552 // MaxUploads is set more than number of meta data entries in the result. 553 // Expecting the result to contain one MultipartInfo entry and IsTruncated to be false. 554 { 555 MaxUploads: 2, 556 Prefix: "minio-object-1.txt", 557 IsTruncated: false, 558 Uploads: []MultipartInfo{ 559 { 560 Object: objectNames[0], 561 UploadID: uploadIDs[0], 562 }, 563 }, 564 }, 565 // listMultipartResults - 11. 566 // Setting `Prefix` to contain the object name as its prefix. 567 // MaxUploads is set more than number of meta data entries in the result. 568 // Expecting the result to contain one MultipartInfo entry and IsTruncated to be false. 569 { 570 MaxUploads: 2, 571 Prefix: "min", 572 IsTruncated: false, 573 Uploads: []MultipartInfo{ 574 { 575 Object: objectNames[0], 576 UploadID: uploadIDs[0], 577 }, 578 }, 579 }, 580 // listMultipartResults - 12. 581 // Setting `Prefix` to contain the object name as its prefix. 582 // MaxUploads is set equal to number of meta data entries in the result. 583 // Expecting the result to contain one MultipartInfo entry and IsTruncated to be false. 584 { 585 MaxUploads: 1, 586 Prefix: "min", 587 IsTruncated: false, 588 Uploads: []MultipartInfo{ 589 { 590 Object: objectNames[0], 591 UploadID: uploadIDs[0], 592 }, 593 }, 594 }, 595 // listMultipartResults - 13. 596 // `Prefix` is set. It doesn't contain object name as its preifx. 597 // MaxUploads is set more than number of meta data entries in the result. 598 // Expecting no `Uploads` metadata. 599 { 600 MaxUploads: 2, 601 Prefix: "orange", 602 IsTruncated: false, 603 }, 604 // listMultipartResults - 14. 605 // `Prefix` is set. It doesn't contain object name as its preifx. 606 // MaxUploads is set more than number of meta data entries in the result. 607 // Expecting the result to contain 0 uploads and isTruncated to false. 608 { 609 MaxUploads: 2, 610 Prefix: "Asia", 611 IsTruncated: false, 612 }, 613 // listMultipartResults - 15. 614 // Setting `Delimiter`. 615 // MaxUploads is set more than number of meta data entries in the result. 616 // Expecting the result to contain one MultipartInfo entry and IsTruncated to be false. 617 { 618 MaxUploads: 2, 619 Delimiter: SlashSeparator, 620 Prefix: "", 621 IsTruncated: false, 622 Uploads: []MultipartInfo{ 623 { 624 Object: objectNames[0], 625 UploadID: uploadIDs[0], 626 }, 627 }, 628 }, 629 // listMultipartResults - 16. 630 // Testing for listing of 3 uploadID's for a given object. 631 // Will be used to list on bucketNames[1]. 632 { 633 MaxUploads: 100, 634 Uploads: []MultipartInfo{ 635 { 636 Object: objectNames[0], 637 UploadID: uploadIDs[1], 638 }, 639 { 640 Object: objectNames[0], 641 UploadID: uploadIDs[2], 642 }, 643 { 644 Object: objectNames[0], 645 UploadID: uploadIDs[3], 646 }, 647 }, 648 }, 649 // listMultipartResults - 17. 650 // Testing for listing of 3 uploadID's (uploadIDs[1-3]) for a given object with uploadID Marker set. 651 // uploadIDs[1] is set as UploadMarker, Expecting it to be skipped in the result. 652 // uploadIDs[2] and uploadIDs[3] are expected to be in the result. 653 // Istruncted is expected to be false. 654 // Will be used to list on bucketNames[1]. 655 { 656 MaxUploads: 100, 657 KeyMarker: "minio-object-1.txt", 658 UploadIDMarker: uploadIDs[1], 659 IsTruncated: false, 660 Uploads: []MultipartInfo{ 661 { 662 Object: objectNames[0], 663 UploadID: uploadIDs[2], 664 }, 665 { 666 Object: objectNames[0], 667 UploadID: uploadIDs[3], 668 }, 669 }, 670 }, 671 // listMultipartResults - 18. 672 // Testing for listing of 3 uploadID's (uploadIDs[1-3]) for a given object with uploadID Marker set. 673 // uploadIDs[2] is set as UploadMarker, Expecting it to be skipped in the result. 674 // Only uploadIDs[3] are expected to be in the result. 675 // Istruncted is expected to be false. 676 // Will be used to list on bucketNames[1]. 677 { 678 MaxUploads: 100, 679 KeyMarker: "minio-object-1.txt", 680 UploadIDMarker: uploadIDs[2], 681 IsTruncated: false, 682 Uploads: []MultipartInfo{ 683 { 684 Object: objectNames[0], 685 UploadID: uploadIDs[3], 686 }, 687 }, 688 }, 689 // listMultipartResults - 19. 690 // Testing for listing of 3 uploadID's for a given object, setting maxKeys to be 2. 691 // There are 3 MultipartInfo in the result (uploadIDs[1-3]), it should be truncated to 2. 692 // Since there is only single object for bucketNames[1], the NextKeyMarker is set to its name. 693 // The last entry in the result, uploadIDs[2], that is should be set as NextUploadIDMarker. 694 // Will be used to list on bucketNames[1]. 695 { 696 MaxUploads: 2, 697 IsTruncated: true, 698 NextKeyMarker: objectNames[0], 699 NextUploadIDMarker: uploadIDs[2], 700 Uploads: []MultipartInfo{ 701 { 702 Object: objectNames[0], 703 UploadID: uploadIDs[1], 704 }, 705 { 706 Object: objectNames[0], 707 UploadID: uploadIDs[2], 708 }, 709 }, 710 }, 711 // listMultipartResults - 20. 712 // Testing for listing of 3 uploadID's for a given object, setting maxKeys to be 1. 713 // There are 3 MultipartInfo in the result (uploadIDs[1-3]), it should be truncated to 1. 714 // The last entry in the result, uploadIDs[1], that is should be set as NextUploadIDMarker. 715 // Will be used to list on bucketNames[1]. 716 { 717 MaxUploads: 1, 718 IsTruncated: true, 719 NextKeyMarker: objectNames[0], 720 NextUploadIDMarker: uploadIDs[1], 721 Uploads: []MultipartInfo{ 722 { 723 Object: objectNames[0], 724 UploadID: uploadIDs[1], 725 }, 726 }, 727 }, 728 // listMultipartResults - 21. 729 // Testing for listing of 3 uploadID's for a given object, setting maxKeys to be 3. 730 // There are 3 MultipartInfo in the result (uploadIDs[1-3]), hence no truncation is expected. 731 // Since all the MultipartInfo is listed, expecting no values for NextUploadIDMarker and NextKeyMarker. 732 // Will be used to list on bucketNames[1]. 733 { 734 MaxUploads: 3, 735 IsTruncated: false, 736 Uploads: []MultipartInfo{ 737 { 738 Object: objectNames[0], 739 UploadID: uploadIDs[1], 740 }, 741 { 742 Object: objectNames[0], 743 UploadID: uploadIDs[2], 744 }, 745 { 746 Object: objectNames[0], 747 UploadID: uploadIDs[3], 748 }, 749 }, 750 }, 751 // listMultipartResults - 22. 752 // Testing for listing of 3 uploadID's for a given object, setting `prefix` to be "min". 753 // Will be used to list on bucketNames[1]. 754 { 755 MaxUploads: 10, 756 IsTruncated: false, 757 Prefix: "min", 758 Uploads: []MultipartInfo{ 759 { 760 Object: objectNames[0], 761 UploadID: uploadIDs[1], 762 }, 763 { 764 Object: objectNames[0], 765 UploadID: uploadIDs[2], 766 }, 767 { 768 Object: objectNames[0], 769 UploadID: uploadIDs[3], 770 }, 771 }, 772 }, 773 // listMultipartResults - 23. 774 // Testing for listing of 3 uploadID's for a given object 775 // setting `prefix` to be "orange". 776 // Will be used to list on bucketNames[1]. 777 { 778 MaxUploads: 10, 779 IsTruncated: false, 780 Prefix: "orange", 781 }, 782 // listMultipartResults - 24. 783 // Testing for listing of 3 uploadID's for a given object. 784 // setting `prefix` to be "Asia". 785 // Will be used to list on bucketNames[1]. 786 { 787 MaxUploads: 10, 788 IsTruncated: false, 789 Prefix: "Asia", 790 }, 791 // listMultipartResults - 25. 792 // Testing for listing of 3 uploadID's for a given object. 793 // setting `prefix` and uploadIDMarker. 794 // Will be used to list on bucketNames[1]. 795 { 796 MaxUploads: 10, 797 KeyMarker: "minio-object-1.txt", 798 IsTruncated: false, 799 Prefix: "min", 800 UploadIDMarker: uploadIDs[1], 801 Uploads: []MultipartInfo{ 802 { 803 Object: objectNames[0], 804 UploadID: uploadIDs[2], 805 }, 806 { 807 Object: objectNames[0], 808 UploadID: uploadIDs[3], 809 }, 810 }, 811 }, 812 813 // Operations on bucket 2. 814 // listMultipartResults - 26. 815 // checking listing everything. 816 { 817 MaxUploads: 100, 818 IsTruncated: false, 819 820 Uploads: []MultipartInfo{ 821 { 822 Object: objectNames[0], 823 UploadID: uploadIDs[4], 824 }, 825 { 826 Object: objectNames[1], 827 UploadID: uploadIDs[5], 828 }, 829 { 830 Object: objectNames[2], 831 UploadID: uploadIDs[6], 832 }, 833 { 834 Object: objectNames[3], 835 UploadID: uploadIDs[7], 836 }, 837 { 838 Object: objectNames[4], 839 UploadID: uploadIDs[8], 840 }, 841 { 842 Object: objectNames[5], 843 UploadID: uploadIDs[9], 844 }, 845 }, 846 }, 847 // listMultipartResults - 27. 848 // listing with `prefix` "min". 849 { 850 MaxUploads: 100, 851 IsTruncated: false, 852 Prefix: "min", 853 Uploads: []MultipartInfo{ 854 { 855 Object: objectNames[0], 856 UploadID: uploadIDs[4], 857 }, 858 { 859 Object: objectNames[1], 860 UploadID: uploadIDs[5], 861 }, 862 }, 863 }, 864 // listMultipartResults - 28. 865 // listing with `prefix` "ney". 866 { 867 MaxUploads: 100, 868 IsTruncated: false, 869 Prefix: "ney", 870 Uploads: []MultipartInfo{ 871 { 872 Object: objectNames[2], 873 UploadID: uploadIDs[6], 874 }, 875 { 876 Object: objectNames[3], 877 UploadID: uploadIDs[7], 878 }, 879 }, 880 }, 881 // listMultipartResults - 29. 882 // listing with `prefix` "parrot". 883 { 884 MaxUploads: 100, 885 IsTruncated: false, 886 Prefix: "parrot", 887 Uploads: []MultipartInfo{ 888 { 889 Object: objectNames[4], 890 UploadID: uploadIDs[8], 891 }, 892 { 893 Object: objectNames[5], 894 UploadID: uploadIDs[9], 895 }, 896 }, 897 }, 898 // listMultipartResults - 30. 899 // listing with `prefix` "neymar.jpeg". 900 // prefix set to object name. 901 { 902 MaxUploads: 100, 903 IsTruncated: false, 904 Prefix: "neymar.jpeg", 905 Uploads: []MultipartInfo{ 906 { 907 Object: objectNames[3], 908 UploadID: uploadIDs[7], 909 }, 910 }, 911 }, 912 913 // listMultipartResults - 31. 914 // checking listing with marker set to 3. 915 // `NextUploadIDMarker` is expected to be set on last uploadID in the result. 916 // `NextKeyMarker` is expected to be set on the last object key in the list. 917 { 918 MaxUploads: 3, 919 IsTruncated: true, 920 NextUploadIDMarker: uploadIDs[6], 921 NextKeyMarker: objectNames[2], 922 Uploads: []MultipartInfo{ 923 { 924 Object: objectNames[0], 925 UploadID: uploadIDs[4], 926 }, 927 { 928 Object: objectNames[1], 929 UploadID: uploadIDs[5], 930 }, 931 { 932 Object: objectNames[2], 933 UploadID: uploadIDs[6], 934 }, 935 }, 936 }, 937 // listMultipartResults - 32. 938 // checking listing with marker set to no of objects in the list. 939 // `NextUploadIDMarker` is expected to be empty since all results are listed. 940 // `NextKeyMarker` is expected to be empty since all results are listed. 941 { 942 MaxUploads: 6, 943 IsTruncated: false, 944 Uploads: []MultipartInfo{ 945 { 946 Object: objectNames[0], 947 UploadID: uploadIDs[4], 948 }, 949 { 950 Object: objectNames[1], 951 UploadID: uploadIDs[5], 952 }, 953 { 954 Object: objectNames[2], 955 UploadID: uploadIDs[6], 956 }, 957 { 958 Object: objectNames[3], 959 UploadID: uploadIDs[7], 960 }, 961 { 962 Object: objectNames[4], 963 UploadID: uploadIDs[8], 964 }, 965 { 966 Object: objectNames[5], 967 UploadID: uploadIDs[9], 968 }, 969 }, 970 }, 971 // listMultipartResults - 33. 972 // checking listing with `UploadIDMarker` set. 973 { 974 MaxUploads: 10, 975 IsTruncated: false, 976 UploadIDMarker: uploadIDs[6], 977 Uploads: []MultipartInfo{ 978 { 979 Object: objectNames[3], 980 UploadID: uploadIDs[7], 981 }, 982 { 983 Object: objectNames[4], 984 UploadID: uploadIDs[8], 985 }, 986 { 987 Object: objectNames[5], 988 UploadID: uploadIDs[9], 989 }, 990 }, 991 }, 992 // listMultipartResults - 34. 993 // checking listing with `KeyMarker` set. 994 { 995 MaxUploads: 10, 996 IsTruncated: false, 997 KeyMarker: objectNames[3], 998 Uploads: []MultipartInfo{ 999 { 1000 Object: objectNames[4], 1001 UploadID: uploadIDs[8], 1002 }, 1003 { 1004 Object: objectNames[5], 1005 UploadID: uploadIDs[9], 1006 }, 1007 }, 1008 }, 1009 // listMultipartResults - 35. 1010 // Checking listing with `Prefix` and `KeyMarker`. 1011 // No upload MultipartInfo in the result expected since KeyMarker is set to last Key in the result. 1012 { 1013 MaxUploads: 10, 1014 IsTruncated: false, 1015 Prefix: "minio-object", 1016 KeyMarker: objectNames[1], 1017 }, 1018 // listMultipartResults - 36. 1019 // checking listing with `Prefix` and `UploadIDMarker` set. 1020 { 1021 MaxUploads: 10, 1022 IsTruncated: false, 1023 Prefix: "minio", 1024 UploadIDMarker: uploadIDs[4], 1025 Uploads: []MultipartInfo{ 1026 { 1027 Object: objectNames[1], 1028 UploadID: uploadIDs[5], 1029 }, 1030 }, 1031 }, 1032 // listMultipartResults - 37. 1033 // Checking listing with `KeyMarker` and `UploadIDMarker` set. 1034 { 1035 MaxUploads: 10, 1036 IsTruncated: false, 1037 KeyMarker: "minio-object.txt", 1038 UploadIDMarker: uploadIDs[5], 1039 }, 1040 } 1041 1042 // Collection of non-exhaustive ListMultipartUploads test cases, valid errors 1043 // and success responses. 1044 testCases := []struct { 1045 // Inputs to ListMultipartUploads. 1046 bucket string 1047 prefix string 1048 keyMarker string 1049 uploadIDMarker string 1050 delimiter string 1051 maxUploads int 1052 // Expected output of ListMultipartUploads. 1053 expectedResult ListMultipartsInfo 1054 expectedErr error 1055 // Flag indicating whether the test is expected to pass or not. 1056 shouldPass bool 1057 }{ 1058 // Test cases with invalid bucket names ( Test number 1-4 ). 1059 {".test", "", "", "", "", 0, ListMultipartsInfo{}, BucketNameInvalid{Bucket: ".test"}, false}, 1060 {"Test", "", "", "", "", 0, ListMultipartsInfo{}, BucketNameInvalid{Bucket: "Test"}, false}, 1061 {"---", "", "", "", "", 0, ListMultipartsInfo{}, BucketNameInvalid{Bucket: "---"}, false}, 1062 {"ad", "", "", "", "", 0, ListMultipartsInfo{}, BucketNameInvalid{Bucket: "ad"}, false}, 1063 // Valid bucket names, but they do not exist (Test number 5-7). 1064 {"volatile-bucket-1", "", "", "", "", 0, ListMultipartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-1"}, false}, 1065 {"volatile-bucket-2", "", "", "", "", 0, ListMultipartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-2"}, false}, 1066 {"volatile-bucket-3", "", "", "", "", 0, ListMultipartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-3"}, false}, 1067 // Valid, existing bucket, delimiter not supported, returns empty values (Test number 8-9). 1068 {bucketNames[0], "", "", "", "*", 0, ListMultipartsInfo{Delimiter: "*"}, nil, true}, 1069 {bucketNames[0], "", "", "", "-", 0, ListMultipartsInfo{Delimiter: "-"}, nil, true}, 1070 // If marker is *after* the last possible object from the prefix it should return an empty list. 1071 { 1072 bucketNames[0], "Asia", "europe-object", "", "", 0, 1073 ListMultipartsInfo{KeyMarker: "europe-object", Prefix: "Asia", IsTruncated: false}, 1074 nil, true, 1075 }, 1076 // Setting an invalid combination of uploadIDMarker and Marker (Test number 11-12). 1077 { 1078 bucketNames[0], "asia", "asia/europe/", "abc", "", 0, 1079 ListMultipartsInfo{}, 1080 fmt.Errorf("Invalid combination of uploadID marker '%s' and marker '%s'", "abc", "asia/europe/"), false, 1081 }, 1082 { 1083 // Contains a base64 padding character 1084 bucketNames[0], "asia", "asia/europe", "abc=", "", 0, 1085 ListMultipartsInfo{}, 1086 fmt.Errorf("Malformed upload id %s", "abc="), false, 1087 }, 1088 1089 // Setting up valid case of ListMultiPartUploads. 1090 // Test case with multiple parts for a single uploadID (Test number 13). 1091 {bucketNames[0], "", "", "", "", 100, listMultipartResults[0], nil, true}, 1092 // Test with a KeyMarker (Test number 14-17). 1093 {bucketNames[0], "", "minio-object-1.txt", "", "", 100, listMultipartResults[1], nil, true}, 1094 {bucketNames[0], "", "orange", "", "", 100, listMultipartResults[2], nil, true}, 1095 {bucketNames[0], "", "orange", "", "", 1, listMultipartResults[3], nil, true}, 1096 {bucketNames[0], "", "min", "", "", 10, listMultipartResults[4], nil, true}, 1097 // Test case with keyMarker set equal to number of parts in the result. (Test number 18). 1098 {bucketNames[0], "", "min", "", "", 1, listMultipartResults[5], nil, true}, 1099 // Test case with keyMarker set to 0. (Test number 19). 1100 {bucketNames[0], "", "min", "", "", 0, listMultipartResults[6], nil, true}, 1101 // Test case with keyMarker less than 0. (Test number 20). 1102 // {bucketNames[0], "", "min", "", "", -1, listMultipartResults[7], nil, true}, 1103 // The result contains only one entry. The KeyPrefix is set to the object name in the result. 1104 // Expecting the result to skip the KeyPrefix entry in the result (Test number 21). 1105 {bucketNames[0], "", "minio-object", "", "", 2, listMultipartResults[8], nil, true}, 1106 // Test case containing prefix values. 1107 // Setting prefix to be equal to object name.(Test number 22). 1108 {bucketNames[0], "minio-object-1.txt", "", "", "", 2, listMultipartResults[9], nil, true}, 1109 // Setting `prefix` to contain the object name as its prefix (Test number 23). 1110 {bucketNames[0], "min", "", "", "", 2, listMultipartResults[10], nil, true}, 1111 // Setting `prefix` to contain the object name as its prefix (Test number 24). 1112 {bucketNames[0], "min", "", "", "", 1, listMultipartResults[11], nil, true}, 1113 // Setting `prefix` to not to contain the object name as its prefix (Test number 25-26). 1114 {bucketNames[0], "orange", "", "", "", 2, listMultipartResults[12], nil, true}, 1115 {bucketNames[0], "Asia", "", "", "", 2, listMultipartResults[13], nil, true}, 1116 // setting delimiter (Test number 27). 1117 {bucketNames[0], "", "", "", SlashSeparator, 2, listMultipartResults[14], nil, true}, 1118 // Test case with multiple uploadID listing for given object (Test number 28). 1119 {bucketNames[1], "", "", "", "", 100, listMultipartResults[15], nil, true}, 1120 // Test case with multiple uploadID listing for given object, but uploadID marker set. 1121 // Testing whether the marker entry is skipped (Test number 29-30). 1122 {bucketNames[1], "", "minio-object-1.txt", uploadIDs[1], "", 100, listMultipartResults[16], nil, true}, 1123 {bucketNames[1], "", "minio-object-1.txt", uploadIDs[2], "", 100, listMultipartResults[17], nil, true}, 1124 // Test cases with multiple uploadID listing for a given object (Test number 31-32). 1125 // MaxKeys set to values lesser than the number of entries in the MultipartInfo. 1126 // IsTruncated is expected to be true. 1127 {bucketNames[1], "", "", "", "", 2, listMultipartResults[18], nil, true}, 1128 {bucketNames[1], "", "", "", "", 1, listMultipartResults[19], nil, true}, 1129 // MaxKeys set to the value which is equal to no of entries in the MultipartInfo (Test number 33). 1130 // In case of bucketNames[1], there are 3 entries. 1131 // Since all available entries are listed, IsTruncated is expected to be false 1132 // and NextMarkers are expected to empty. 1133 {bucketNames[1], "", "", "", "", 3, listMultipartResults[20], nil, true}, 1134 // Adding prefix (Test number 34-36). 1135 {bucketNames[1], "min", "", "", "", 10, listMultipartResults[21], nil, true}, 1136 {bucketNames[1], "orange", "", "", "", 10, listMultipartResults[22], nil, true}, 1137 {bucketNames[1], "Asia", "", "", "", 10, listMultipartResults[23], nil, true}, 1138 // Test case with `Prefix` and `UploadIDMarker` (Test number 37). 1139 {bucketNames[1], "min", "minio-object-1.txt", uploadIDs[1], "", 10, listMultipartResults[24], nil, true}, 1140 // Test case for bucket with multiple objects in it. 1141 // Bucket used : `bucketNames[2]`. 1142 // Objects used: `objectNames[1-5]`. 1143 // UploadId's used: uploadIds[4-8]. 1144 // (Test number 39). 1145 {bucketNames[2], "", "", "", "", 100, listMultipartResults[25], nil, true}, 1146 // Test cases with prefixes. 1147 // Testing listing with prefix set to "min" (Test number 40) . 1148 {bucketNames[2], "min", "", "", "", 100, listMultipartResults[26], nil, true}, 1149 // Testing listing with prefix set to "ney" (Test number 41). 1150 {bucketNames[2], "ney", "", "", "", 100, listMultipartResults[27], nil, true}, 1151 // Testing listing with prefix set to "par" (Test number 42). 1152 {bucketNames[2], "parrot", "", "", "", 100, listMultipartResults[28], nil, true}, 1153 // Testing listing with prefix set to object name "neymar.jpeg" (Test number 43). 1154 {bucketNames[2], "neymar.jpeg", "", "", "", 100, listMultipartResults[29], nil, true}, 1155 // Testing listing with `MaxUploads` set to 3 (Test number 44). 1156 {bucketNames[2], "", "", "", "", 3, listMultipartResults[30], nil, true}, 1157 // In case of bucketNames[2], there are 6 entries (Test number 45). 1158 // Since all available entries are listed, IsTruncated is expected to be false 1159 // and NextMarkers are expected to empty. 1160 {bucketNames[2], "", "", "", "", 6, listMultipartResults[31], nil, true}, 1161 // Test case with `KeyMarker` (Test number 47). 1162 {bucketNames[2], "", objectNames[3], "", "", 10, listMultipartResults[33], nil, true}, 1163 // Test case with `prefix` and `KeyMarker` (Test number 48). 1164 {bucketNames[2], "minio-object", objectNames[1], "", "", 10, listMultipartResults[34], nil, true}, 1165 } 1166 1167 for i, testCase := range testCases { 1168 // fmt.Println(i+1, testCase) // uncomment to peek into the test cases. 1169 actualResult, actualErr := obj.ListMultipartUploads(context.Background(), testCase.bucket, testCase.prefix, testCase.keyMarker, testCase.uploadIDMarker, testCase.delimiter, testCase.maxUploads) 1170 if actualErr != nil && testCase.shouldPass { 1171 t.Errorf("Test %d: %s: Expected to pass, but failed with: <ERROR> %s", i+1, instanceType, actualErr.Error()) 1172 } 1173 if actualErr == nil && !testCase.shouldPass { 1174 t.Errorf("Test %d: %s: Expected to fail with <ERROR> \"%s\", but passed instead", i+1, instanceType, testCase.expectedErr.Error()) 1175 } 1176 // Failed as expected, but does it fail for the expected reason. 1177 if actualErr != nil && !testCase.shouldPass { 1178 if !strings.Contains(actualErr.Error(), testCase.expectedErr.Error()) { 1179 t.Errorf("Test %d: %s: Expected to fail with error \"%s\", but instead failed with error \"%s\" instead", i+1, instanceType, testCase.expectedErr.Error(), actualErr.Error()) 1180 } 1181 } 1182 // Passes as expected, but asserting the results. 1183 if actualErr == nil && testCase.shouldPass { 1184 expectedResult := testCase.expectedResult 1185 // Asserting the MaxUploads. 1186 if actualResult.MaxUploads != expectedResult.MaxUploads { 1187 t.Errorf("Test %d: %s: Expected the MaxUploads to be %d, but instead found it to be %d", i+1, instanceType, expectedResult.MaxUploads, actualResult.MaxUploads) 1188 } 1189 // Asserting Prefix. 1190 if actualResult.Prefix != expectedResult.Prefix { 1191 t.Errorf("Test %d: %s: Expected Prefix to be \"%s\", but instead found it to be \"%s\"", i+1, instanceType, expectedResult.Prefix, actualResult.Prefix) 1192 } 1193 // Asserting Delimiter. 1194 if actualResult.Delimiter != expectedResult.Delimiter { 1195 t.Errorf("Test %d: %s: Expected Delimiter to be \"%s\", but instead found it to be \"%s\"", i+1, instanceType, expectedResult.Delimiter, actualResult.Delimiter) 1196 } 1197 // Asserting the keyMarker. 1198 if actualResult.KeyMarker != expectedResult.KeyMarker { 1199 t.Errorf("Test %d: %s: Expected keyMarker to be \"%s\", but instead found it to be \"%s\"", i+1, instanceType, expectedResult.KeyMarker, actualResult.KeyMarker) 1200 } 1201 } 1202 } 1203 } 1204 1205 // Wrapper for calling TestListObjectPartsDiskNotFound tests for both Erasure multiple disks and single node setup. 1206 func TestListObjectPartsDiskNotFound(t *testing.T) { 1207 ExecObjectLayerDiskAlteredTest(t, testListObjectPartsDiskNotFound) 1208 } 1209 1210 // testListObjectParts - Tests validate listing of object parts when disks go offline. 1211 func testListObjectPartsDiskNotFound(obj ObjectLayer, instanceType string, disks []string, t *testing.T) { 1212 bucketNames := []string{"minio-bucket", "minio-2-bucket"} 1213 objectNames := []string{"minio-object-1.txt"} 1214 uploadIDs := []string{} 1215 1216 globalStorageClass.Update(storageclass.Config{ 1217 RRS: storageclass.StorageClass{ 1218 Parity: 2, 1219 }, 1220 Standard: storageclass.StorageClass{ 1221 Parity: 4, 1222 }, 1223 }) 1224 1225 // bucketnames[0]. 1226 // objectNames[0]. 1227 // uploadIds [0]. 1228 // Create bucket before initiating NewMultipartUpload. 1229 err := obj.MakeBucket(context.Background(), bucketNames[0], MakeBucketOptions{}) 1230 if err != nil { 1231 // Failed to create newbucket, abort. 1232 t.Fatalf("%s : %s", instanceType, err.Error()) 1233 } 1234 opts := ObjectOptions{} 1235 // Initiate Multipart Upload on the above created bucket. 1236 res, err := obj.NewMultipartUpload(context.Background(), bucketNames[0], objectNames[0], opts) 1237 if err != nil { 1238 // Failed to create NewMultipartUpload, abort. 1239 t.Fatalf("%s : %s", instanceType, err.Error()) 1240 } 1241 1242 z := obj.(*erasureServerPools) 1243 er := z.serverPools[0].sets[0] 1244 1245 erasureDisks := er.getDisks() 1246 ridx := rand.Intn(len(erasureDisks)) 1247 1248 z.serverPools[0].erasureDisksMu.Lock() 1249 er.getDisks = func() []StorageAPI { 1250 erasureDisks[ridx] = newNaughtyDisk(erasureDisks[ridx], nil, errFaultyDisk) 1251 return erasureDisks 1252 } 1253 z.serverPools[0].erasureDisksMu.Unlock() 1254 1255 uploadIDs = append(uploadIDs, res.UploadID) 1256 1257 // Create multipart parts. 1258 // Need parts to be uploaded before MultipartLists can be called and tested. 1259 createPartCases := []struct { 1260 bucketName string 1261 objName string 1262 uploadID string 1263 PartID int 1264 inputReaderData string 1265 inputMd5 string 1266 inputDataSize int64 1267 expectedMd5 string 1268 }{ 1269 // Case 1-4. 1270 // Creating sequence of parts for same uploadID. 1271 // Used to ensure that the ListMultipartResult produces one output for the four parts uploaded below for the given upload ID. 1272 {bucketNames[0], objectNames[0], uploadIDs[0], 1, "abcd", "e2fc714c4727ee9395f324cd2e7f331f", int64(len("abcd")), "e2fc714c4727ee9395f324cd2e7f331f"}, 1273 {bucketNames[0], objectNames[0], uploadIDs[0], 2, "efgh", "1f7690ebdd9b4caf8fab49ca1757bf27", int64(len("efgh")), "1f7690ebdd9b4caf8fab49ca1757bf27"}, 1274 {bucketNames[0], objectNames[0], uploadIDs[0], 3, "ijkl", "09a0877d04abf8759f99adec02baf579", int64(len("abcd")), "09a0877d04abf8759f99adec02baf579"}, 1275 {bucketNames[0], objectNames[0], uploadIDs[0], 4, "mnop", "e132e96a5ddad6da8b07bba6f6131fef", int64(len("abcd")), "e132e96a5ddad6da8b07bba6f6131fef"}, 1276 } 1277 sha256sum := "" 1278 // Iterating over creatPartCases to generate multipart chunks. 1279 for _, testCase := range createPartCases { 1280 _, err := obj.PutObjectPart(context.Background(), testCase.bucketName, testCase.objName, testCase.uploadID, testCase.PartID, mustGetPutObjReader(t, bytes.NewBufferString(testCase.inputReaderData), testCase.inputDataSize, testCase.inputMd5, sha256sum), opts) 1281 if err != nil { 1282 t.Fatalf("%s : %s", instanceType, err.Error()) 1283 } 1284 } 1285 1286 partInfos := []ListPartsInfo{ 1287 // partinfos - 0. 1288 { 1289 Bucket: bucketNames[0], 1290 Object: objectNames[0], 1291 MaxParts: 10, 1292 UploadID: uploadIDs[0], 1293 Parts: []PartInfo{ 1294 { 1295 PartNumber: 1, 1296 Size: 4, 1297 ETag: "e2fc714c4727ee9395f324cd2e7f331f", 1298 }, 1299 { 1300 PartNumber: 2, 1301 Size: 4, 1302 ETag: "1f7690ebdd9b4caf8fab49ca1757bf27", 1303 }, 1304 { 1305 PartNumber: 3, 1306 Size: 4, 1307 ETag: "09a0877d04abf8759f99adec02baf579", 1308 }, 1309 { 1310 PartNumber: 4, 1311 Size: 4, 1312 ETag: "e132e96a5ddad6da8b07bba6f6131fef", 1313 }, 1314 }, 1315 }, 1316 // partinfos - 1. 1317 { 1318 Bucket: bucketNames[0], 1319 Object: objectNames[0], 1320 MaxParts: 3, 1321 NextPartNumberMarker: 3, 1322 IsTruncated: true, 1323 UploadID: uploadIDs[0], 1324 Parts: []PartInfo{ 1325 { 1326 PartNumber: 1, 1327 Size: 4, 1328 ETag: "e2fc714c4727ee9395f324cd2e7f331f", 1329 }, 1330 { 1331 PartNumber: 2, 1332 Size: 4, 1333 ETag: "1f7690ebdd9b4caf8fab49ca1757bf27", 1334 }, 1335 { 1336 PartNumber: 3, 1337 Size: 4, 1338 ETag: "09a0877d04abf8759f99adec02baf579", 1339 }, 1340 }, 1341 }, 1342 // partinfos - 2. 1343 { 1344 Bucket: bucketNames[0], 1345 Object: objectNames[0], 1346 MaxParts: 2, 1347 IsTruncated: false, 1348 UploadID: uploadIDs[0], 1349 PartNumberMarker: 3, 1350 Parts: []PartInfo{ 1351 { 1352 PartNumber: 4, 1353 Size: 4, 1354 ETag: "e132e96a5ddad6da8b07bba6f6131fef", 1355 }, 1356 }, 1357 }, 1358 } 1359 1360 // Collection of non-exhaustive ListObjectParts test cases, valid errors 1361 // and success responses. 1362 testCases := []struct { 1363 bucket string 1364 object string 1365 uploadID string 1366 partNumberMarker int 1367 maxParts int 1368 // Expected output of ListPartsInfo. 1369 expectedResult ListPartsInfo 1370 expectedErr error 1371 // Flag indicating whether the test is expected to pass or not. 1372 shouldPass bool 1373 }{ 1374 // Test cases with invalid bucket names (Test number 1-4). 1375 {".test", "", "", 0, 0, ListPartsInfo{}, BucketNameInvalid{Bucket: ".test"}, false}, 1376 {"Test", "", "", 0, 0, ListPartsInfo{}, BucketNameInvalid{Bucket: "Test"}, false}, 1377 {"---", "", "", 0, 0, ListPartsInfo{}, BucketNameInvalid{Bucket: "---"}, false}, 1378 {"ad", "", "", 0, 0, ListPartsInfo{}, BucketNameInvalid{Bucket: "ad"}, false}, 1379 // Test cases for listing uploadID with single part. 1380 // Valid bucket names, but they do not exist (Test number 5-7). 1381 {"volatile-bucket-1", "test1", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-1"}, false}, 1382 {"volatile-bucket-2", "test1", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-2"}, false}, 1383 {"volatile-bucket-3", "test1", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-3"}, false}, 1384 // Test case for Asserting for invalid objectName (Test number 8). 1385 {bucketNames[0], "", "", 0, 0, ListPartsInfo{}, ObjectNameInvalid{Bucket: bucketNames[0]}, false}, 1386 // Asserting for Invalid UploadID (Test number 9). 1387 {bucketNames[0], objectNames[0], "abc", 0, 0, ListPartsInfo{}, InvalidUploadID{UploadID: "abc"}, false}, 1388 // Test case for uploadID with multiple parts (Test number 12). 1389 {bucketNames[0], objectNames[0], uploadIDs[0], 0, 10, partInfos[0], nil, true}, 1390 // Test case with maxParts set to less than number of parts (Test number 13). 1391 {bucketNames[0], objectNames[0], uploadIDs[0], 0, 3, partInfos[1], nil, true}, 1392 // Test case with partNumberMarker set (Test number 14)-. 1393 {bucketNames[0], objectNames[0], uploadIDs[0], 3, 2, partInfos[2], nil, true}, 1394 } 1395 1396 for i, testCase := range testCases { 1397 actualResult, actualErr := obj.ListObjectParts(context.Background(), testCase.bucket, testCase.object, testCase.uploadID, testCase.partNumberMarker, testCase.maxParts, ObjectOptions{}) 1398 if actualErr != nil && testCase.shouldPass { 1399 t.Errorf("Test %d: %s: Expected to pass, but failed with: <ERROR> %s", i+1, instanceType, actualErr.Error()) 1400 } 1401 if actualErr == nil && !testCase.shouldPass { 1402 t.Errorf("Test %d: %s: Expected to fail with <ERROR> \"%s\", but passed instead", i+1, instanceType, testCase.expectedErr.Error()) 1403 } 1404 // Failed as expected, but does it fail for the expected reason. 1405 if actualErr != nil && !testCase.shouldPass { 1406 if !strings.Contains(actualErr.Error(), testCase.expectedErr.Error()) { 1407 t.Errorf("Test %d: %s: Expected to fail with error \"%s\", but instead failed with error \"%s\" instead", i+1, instanceType, testCase.expectedErr, actualErr) 1408 } 1409 } 1410 // Passes as expected, but asserting the results. 1411 if actualErr == nil && testCase.shouldPass { 1412 expectedResult := testCase.expectedResult 1413 // Asserting the MaxParts. 1414 if actualResult.MaxParts != expectedResult.MaxParts { 1415 t.Errorf("Test %d: %s: Expected the MaxParts to be %d, but instead found it to be %d", i+1, instanceType, expectedResult.MaxParts, actualResult.MaxParts) 1416 } 1417 // Asserting Object Name. 1418 if actualResult.Object != expectedResult.Object { 1419 t.Errorf("Test %d: %s: Expected Object name to be \"%s\", but instead found it to be \"%s\"", i+1, instanceType, expectedResult.Object, actualResult.Object) 1420 } 1421 // Asserting UploadID. 1422 if actualResult.UploadID != expectedResult.UploadID { 1423 t.Errorf("Test %d: %s: Expected UploadID to be \"%s\", but instead found it to be \"%s\"", i+1, instanceType, expectedResult.UploadID, actualResult.UploadID) 1424 } 1425 // Asserting NextPartNumberMarker. 1426 if actualResult.NextPartNumberMarker != expectedResult.NextPartNumberMarker { 1427 t.Errorf("Test %d: %s: Expected NextPartNumberMarker to be \"%d\", but instead found it to be \"%d\"", i+1, instanceType, expectedResult.NextPartNumberMarker, actualResult.NextPartNumberMarker) 1428 } 1429 // Asserting PartNumberMarker. 1430 if actualResult.PartNumberMarker != expectedResult.PartNumberMarker { 1431 t.Errorf("Test %d: %s: Expected PartNumberMarker to be \"%d\", but instead found it to be \"%d\"", i+1, instanceType, expectedResult.PartNumberMarker, actualResult.PartNumberMarker) 1432 } 1433 // Asserting the BucketName. 1434 if actualResult.Bucket != expectedResult.Bucket { 1435 t.Errorf("Test %d: %s: Expected Bucket to be \"%s\", but instead found it to be \"%s\"", i+1, instanceType, expectedResult.Bucket, actualResult.Bucket) 1436 } 1437 // Asserting IsTruncated. 1438 if actualResult.IsTruncated != testCase.expectedResult.IsTruncated { 1439 t.Errorf("Test %d: %s: Expected IsTruncated to be \"%v\", but found it to \"%v\"", i+1, instanceType, expectedResult.IsTruncated, actualResult.IsTruncated) 1440 } 1441 // Asserting the number of Parts. 1442 if len(expectedResult.Parts) != len(actualResult.Parts) { 1443 t.Errorf("Test %d: %s: Expected the result to contain info of %d Parts, but found %d instead", i+1, instanceType, len(expectedResult.Parts), len(actualResult.Parts)) 1444 } else { 1445 // Iterating over the partInfos and asserting the fields. 1446 for j, actualMetaData := range actualResult.Parts { 1447 // Asserting the PartNumber in the PartInfo. 1448 if actualMetaData.PartNumber != expectedResult.Parts[j].PartNumber { 1449 t.Errorf("Test %d: %s: Part %d: Expected PartNumber to be \"%d\", but instead found \"%d\"", i+1, instanceType, j+1, expectedResult.Parts[j].PartNumber, actualMetaData.PartNumber) 1450 } 1451 // Asserting the Size in the PartInfo. 1452 if actualMetaData.Size != expectedResult.Parts[j].Size { 1453 t.Errorf("Test %d: %s: Part %d: Expected Part Size to be \"%d\", but instead found \"%d\"", i+1, instanceType, j+1, expectedResult.Parts[j].Size, actualMetaData.Size) 1454 } 1455 // Asserting the ETag in the PartInfo. 1456 if actualMetaData.ETag != expectedResult.Parts[j].ETag { 1457 t.Errorf("Test %d: %s: Part %d: Expected Etag to be \"%s\", but instead found \"%s\"", i+1, instanceType, j+1, expectedResult.Parts[j].ETag, actualMetaData.ETag) 1458 } 1459 } 1460 } 1461 } 1462 } 1463 } 1464 1465 // Wrapper for calling TestListObjectParts tests for both Erasure multiple disks and single node setup. 1466 func TestListObjectParts(t *testing.T) { 1467 ExecObjectLayerTest(t, testListObjectParts) 1468 } 1469 1470 // testListObjectParts - test validate listing of object parts. 1471 func testListObjectParts(obj ObjectLayer, instanceType string, t TestErrHandler) { 1472 bucketNames := []string{"minio-bucket", "minio-2-bucket"} 1473 objectNames := []string{"minio-object-1.txt"} 1474 uploadIDs := []string{} 1475 opts := ObjectOptions{} 1476 // bucketnames[0]. 1477 // objectNames[0]. 1478 // uploadIds [0]. 1479 // Create bucket before initiating NewMultipartUpload. 1480 err := obj.MakeBucket(context.Background(), bucketNames[0], MakeBucketOptions{}) 1481 if err != nil { 1482 // Failed to create newbucket, abort. 1483 t.Fatalf("%s : %s", instanceType, err.Error()) 1484 } 1485 // Initiate Multipart Upload on the above created bucket. 1486 res, err := obj.NewMultipartUpload(context.Background(), bucketNames[0], objectNames[0], opts) 1487 if err != nil { 1488 // Failed to create NewMultipartUpload, abort. 1489 t.Fatalf("%s : %s", instanceType, err.Error()) 1490 } 1491 1492 uploadIDs = append(uploadIDs, res.UploadID) 1493 1494 // Create multipart parts. 1495 // Need parts to be uploaded before MultipartLists can be called and tested. 1496 createPartCases := []struct { 1497 bucketName string 1498 objName string 1499 uploadID string 1500 PartID int 1501 inputReaderData string 1502 inputMd5 string 1503 inputDataSize int64 1504 expectedMd5 string 1505 }{ 1506 // Case 1-4. 1507 // Creating sequence of parts for same uploadID. 1508 // Used to ensure that the ListMultipartResult produces one output for the four parts uploaded below for the given upload ID. 1509 {bucketNames[0], objectNames[0], uploadIDs[0], 1, "abcd", "e2fc714c4727ee9395f324cd2e7f331f", int64(len("abcd")), "e2fc714c4727ee9395f324cd2e7f331f"}, 1510 {bucketNames[0], objectNames[0], uploadIDs[0], 2, "efgh", "1f7690ebdd9b4caf8fab49ca1757bf27", int64(len("efgh")), "1f7690ebdd9b4caf8fab49ca1757bf27"}, 1511 {bucketNames[0], objectNames[0], uploadIDs[0], 3, "ijkl", "09a0877d04abf8759f99adec02baf579", int64(len("abcd")), "09a0877d04abf8759f99adec02baf579"}, 1512 {bucketNames[0], objectNames[0], uploadIDs[0], 4, "mnop", "e132e96a5ddad6da8b07bba6f6131fef", int64(len("abcd")), "e132e96a5ddad6da8b07bba6f6131fef"}, 1513 } 1514 sha256sum := "" 1515 // Iterating over creatPartCases to generate multipart chunks. 1516 for _, testCase := range createPartCases { 1517 _, err := obj.PutObjectPart(context.Background(), testCase.bucketName, testCase.objName, testCase.uploadID, testCase.PartID, mustGetPutObjReader(t, bytes.NewBufferString(testCase.inputReaderData), testCase.inputDataSize, testCase.inputMd5, sha256sum), opts) 1518 if err != nil { 1519 t.Fatalf("%s : %s", instanceType, err.Error()) 1520 } 1521 } 1522 1523 partInfos := []ListPartsInfo{ 1524 // partinfos - 0. 1525 { 1526 Bucket: bucketNames[0], 1527 Object: objectNames[0], 1528 MaxParts: 10, 1529 UploadID: uploadIDs[0], 1530 Parts: []PartInfo{ 1531 { 1532 PartNumber: 1, 1533 Size: 4, 1534 ETag: "e2fc714c4727ee9395f324cd2e7f331f", 1535 }, 1536 { 1537 PartNumber: 2, 1538 Size: 4, 1539 ETag: "1f7690ebdd9b4caf8fab49ca1757bf27", 1540 }, 1541 { 1542 PartNumber: 3, 1543 Size: 4, 1544 ETag: "09a0877d04abf8759f99adec02baf579", 1545 }, 1546 { 1547 PartNumber: 4, 1548 Size: 4, 1549 ETag: "e132e96a5ddad6da8b07bba6f6131fef", 1550 }, 1551 }, 1552 }, 1553 // partinfos - 1. 1554 { 1555 Bucket: bucketNames[0], 1556 Object: objectNames[0], 1557 MaxParts: 3, 1558 NextPartNumberMarker: 3, 1559 IsTruncated: true, 1560 UploadID: uploadIDs[0], 1561 Parts: []PartInfo{ 1562 { 1563 PartNumber: 1, 1564 Size: 4, 1565 ETag: "e2fc714c4727ee9395f324cd2e7f331f", 1566 }, 1567 { 1568 PartNumber: 2, 1569 Size: 4, 1570 ETag: "1f7690ebdd9b4caf8fab49ca1757bf27", 1571 }, 1572 { 1573 PartNumber: 3, 1574 Size: 4, 1575 ETag: "09a0877d04abf8759f99adec02baf579", 1576 }, 1577 }, 1578 }, 1579 // partinfos - 2. 1580 { 1581 Bucket: bucketNames[0], 1582 Object: objectNames[0], 1583 MaxParts: 2, 1584 IsTruncated: false, 1585 UploadID: uploadIDs[0], 1586 PartNumberMarker: 3, 1587 Parts: []PartInfo{ 1588 { 1589 PartNumber: 4, 1590 Size: 4, 1591 ETag: "e132e96a5ddad6da8b07bba6f6131fef", 1592 }, 1593 }, 1594 }, 1595 } 1596 1597 // Collection of non-exhaustive ListObjectParts test cases, valid errors 1598 // and success responses. 1599 testCases := []struct { 1600 bucket string 1601 object string 1602 uploadID string 1603 partNumberMarker int 1604 maxParts int 1605 // Expected output of ListPartsInfo. 1606 expectedResult ListPartsInfo 1607 expectedErr error 1608 // Flag indicating whether the test is expected to pass or not. 1609 shouldPass bool 1610 }{ 1611 // Test cases with invalid bucket names (Test number 1-4). 1612 {".test", "", "", 0, 0, ListPartsInfo{}, BucketNameInvalid{Bucket: ".test"}, false}, 1613 {"Test", "", "", 0, 0, ListPartsInfo{}, BucketNameInvalid{Bucket: "Test"}, false}, 1614 {"---", "", "", 0, 0, ListPartsInfo{}, BucketNameInvalid{Bucket: "---"}, false}, 1615 {"ad", "", "", 0, 0, ListPartsInfo{}, BucketNameInvalid{Bucket: "ad"}, false}, 1616 // Test cases for listing uploadID with single part. 1617 // Valid bucket names, but they do not exist (Test number 5-7). 1618 {"volatile-bucket-1", "test1", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-1"}, false}, 1619 {"volatile-bucket-2", "test1", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-2"}, false}, 1620 {"volatile-bucket-3", "test1", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-3"}, false}, 1621 // Test case for Asserting for invalid objectName (Test number 8). 1622 {bucketNames[0], "", "", 0, 0, ListPartsInfo{}, ObjectNameInvalid{Bucket: bucketNames[0]}, false}, 1623 // Asserting for Invalid UploadID (Test number 9). 1624 {bucketNames[0], objectNames[0], "abc", 0, 0, ListPartsInfo{}, InvalidUploadID{UploadID: "abc"}, false}, 1625 // Test case for uploadID with multiple parts (Test number 12). 1626 {bucketNames[0], objectNames[0], uploadIDs[0], 0, 10, partInfos[0], nil, true}, 1627 // Test case with maxParts set to less than number of parts (Test number 13). 1628 {bucketNames[0], objectNames[0], uploadIDs[0], 0, 3, partInfos[1], nil, true}, 1629 // Test case with partNumberMarker set (Test number 14)-. 1630 {bucketNames[0], objectNames[0], uploadIDs[0], 3, 2, partInfos[2], nil, true}, 1631 } 1632 1633 for i, testCase := range testCases { 1634 actualResult, actualErr := obj.ListObjectParts(context.Background(), testCase.bucket, testCase.object, testCase.uploadID, testCase.partNumberMarker, testCase.maxParts, opts) 1635 if actualErr != nil && testCase.shouldPass { 1636 t.Errorf("Test %d: %s: Expected to pass, but failed with: <ERROR> %s", i+1, instanceType, actualErr.Error()) 1637 } 1638 if actualErr == nil && !testCase.shouldPass { 1639 t.Errorf("Test %d: %s: Expected to fail with <ERROR> \"%s\", but passed instead", i+1, instanceType, testCase.expectedErr.Error()) 1640 } 1641 // Failed as expected, but does it fail for the expected reason. 1642 if actualErr != nil && !testCase.shouldPass { 1643 if !strings.Contains(actualErr.Error(), testCase.expectedErr.Error()) { 1644 t.Errorf("Test %d: %s: Expected to fail with error \"%s\", but instead failed with error \"%s\" instead", i+1, instanceType, testCase.expectedErr.Error(), actualErr.Error()) 1645 } 1646 } 1647 // Passes as expected, but asserting the results. 1648 if actualErr == nil && testCase.shouldPass { 1649 expectedResult := testCase.expectedResult 1650 // Asserting the MaxParts. 1651 if actualResult.MaxParts != expectedResult.MaxParts { 1652 t.Errorf("Test %d: %s: Expected the MaxParts to be %d, but instead found it to be %d", i+1, instanceType, expectedResult.MaxParts, actualResult.MaxParts) 1653 } 1654 // Asserting Object Name. 1655 if actualResult.Object != expectedResult.Object { 1656 t.Errorf("Test %d: %s: Expected Object name to be \"%s\", but instead found it to be \"%s\"", i+1, instanceType, expectedResult.Object, actualResult.Object) 1657 } 1658 // Asserting UploadID. 1659 if actualResult.UploadID != expectedResult.UploadID { 1660 t.Errorf("Test %d: %s: Expected UploadID to be \"%s\", but instead found it to be \"%s\"", i+1, instanceType, expectedResult.UploadID, actualResult.UploadID) 1661 } 1662 // Asserting PartNumberMarker. 1663 if actualResult.PartNumberMarker != expectedResult.PartNumberMarker { 1664 t.Errorf("Test %d: %s: Expected PartNumberMarker to be \"%d\", but instead found it to be \"%d\"", i+1, instanceType, expectedResult.PartNumberMarker, actualResult.PartNumberMarker) 1665 } 1666 // Asserting the BucketName. 1667 if actualResult.Bucket != expectedResult.Bucket { 1668 t.Errorf("Test %d: %s: Expected Bucket to be \"%s\", but instead found it to be \"%s\"", i+1, instanceType, expectedResult.Bucket, actualResult.Bucket) 1669 } 1670 1671 // Asserting IsTruncated. 1672 if actualResult.IsTruncated != testCase.expectedResult.IsTruncated { 1673 t.Errorf("Test %d: %s: Expected IsTruncated to be \"%v\", but found it to \"%v\"", i+1, instanceType, expectedResult.IsTruncated, actualResult.IsTruncated) 1674 continue 1675 } 1676 // Asserting NextPartNumberMarker. 1677 if actualResult.NextPartNumberMarker != expectedResult.NextPartNumberMarker { 1678 t.Errorf("Test %d: %s: Expected NextPartNumberMarker to be \"%d\", but instead found it to be \"%d\"", i+1, instanceType, expectedResult.NextPartNumberMarker, actualResult.NextPartNumberMarker) 1679 continue 1680 } 1681 // Asserting the number of Parts. 1682 if len(expectedResult.Parts) != len(actualResult.Parts) { 1683 t.Errorf("Test %d: %s: Expected the result to contain info of %d Parts, but found %d instead", i+1, instanceType, len(expectedResult.Parts), len(actualResult.Parts)) 1684 continue 1685 } 1686 // Iterating over the partInfos and asserting the fields. 1687 for j, actualMetaData := range actualResult.Parts { 1688 // Asserting the PartNumber in the PartInfo. 1689 if actualMetaData.PartNumber != expectedResult.Parts[j].PartNumber { 1690 t.Errorf("Test %d: %s: Part %d: Expected PartNumber to be \"%d\", but instead found \"%d\"", i+1, instanceType, j+1, expectedResult.Parts[j].PartNumber, actualMetaData.PartNumber) 1691 } 1692 // Asserting the Size in the PartInfo. 1693 if actualMetaData.Size != expectedResult.Parts[j].Size { 1694 t.Errorf("Test %d: %s: Part %d: Expected Part Size to be \"%d\", but instead found \"%d\"", i+1, instanceType, j+1, expectedResult.Parts[j].Size, actualMetaData.Size) 1695 } 1696 // Asserting the ETag in the PartInfo. 1697 if actualMetaData.ETag != expectedResult.Parts[j].ETag { 1698 t.Errorf("Test %d: %s: Part %d: Expected Etag to be \"%s\", but instead found \"%s\"", i+1, instanceType, j+1, expectedResult.Parts[j].ETag, actualMetaData.ETag) 1699 } 1700 } 1701 } 1702 } 1703 } 1704 1705 // Test for validating complete Multipart upload. 1706 func TestObjectCompleteMultipartUpload(t *testing.T) { 1707 ExecExtendedObjectLayerTest(t, testObjectCompleteMultipartUpload) 1708 } 1709 1710 // Tests validate CompleteMultipart functionality. 1711 func testObjectCompleteMultipartUpload(obj ObjectLayer, instanceType string, t TestErrHandler) { 1712 var err error 1713 bucketNames := []string{"minio-bucket", "minio-2-bucket"} 1714 objectNames := []string{"minio-object-1.txt"} 1715 uploadIDs := []string{} 1716 1717 // bucketnames[0]. 1718 // objectNames[0]. 1719 // uploadIds [0]. 1720 // Create bucket before initiating NewMultipartUpload. 1721 err = obj.MakeBucket(context.Background(), bucketNames[0], MakeBucketOptions{}) 1722 if err != nil { 1723 // Failed to create newbucket, abort. 1724 t.Fatalf("%s : %s", instanceType, err) 1725 } 1726 // Initiate Multipart Upload on the above created bucket. 1727 res, err := obj.NewMultipartUpload(context.Background(), bucketNames[0], objectNames[0], ObjectOptions{UserDefined: map[string]string{"X-Amz-Meta-Id": "id"}}) 1728 if err != nil { 1729 // Failed to create NewMultipartUpload, abort. 1730 t.Fatalf("%s : %s", instanceType, err) 1731 } 1732 1733 uploadIDs = append(uploadIDs, res.UploadID) 1734 // Parts with size greater than 5 MiB. 1735 // Generating a 6MiB byte array. 1736 validPart := bytes.Repeat([]byte("abcdef"), 1*humanize.MiByte) 1737 validPartMD5 := getMD5Hash(validPart) 1738 // Create multipart parts. 1739 // Need parts to be uploaded before CompleteMultiPartUpload can be called tested. 1740 parts := []struct { 1741 bucketName string 1742 objName string 1743 uploadID string 1744 PartID int 1745 inputReaderData string 1746 inputMd5 string 1747 inputDataSize int64 1748 }{ 1749 // Case 1-4. 1750 // Creating sequence of parts for same uploadID. 1751 {bucketNames[0], objectNames[0], uploadIDs[0], 1, "abcd", "e2fc714c4727ee9395f324cd2e7f331f", int64(len("abcd"))}, 1752 {bucketNames[0], objectNames[0], uploadIDs[0], 2, "efgh", "1f7690ebdd9b4caf8fab49ca1757bf27", int64(len("efgh"))}, 1753 {bucketNames[0], objectNames[0], uploadIDs[0], 3, "ijkl", "09a0877d04abf8759f99adec02baf579", int64(len("abcd"))}, 1754 {bucketNames[0], objectNames[0], uploadIDs[0], 4, "mnop", "e132e96a5ddad6da8b07bba6f6131fef", int64(len("abcd"))}, 1755 // Part with size larger than 5Mb. 1756 {bucketNames[0], objectNames[0], uploadIDs[0], 5, string(validPart), validPartMD5, int64(len(validPart))}, 1757 {bucketNames[0], objectNames[0], uploadIDs[0], 6, string(validPart), validPartMD5, int64(len(validPart))}, 1758 {bucketNames[0], objectNames[0], uploadIDs[0], 7, string(validPart), validPartMD5, int64(len(validPart))}, 1759 } 1760 sha256sum := "" 1761 var opts ObjectOptions 1762 // Iterating over creatPartCases to generate multipart chunks. 1763 for _, part := range parts { 1764 _, err = obj.PutObjectPart(context.Background(), part.bucketName, part.objName, part.uploadID, part.PartID, mustGetPutObjReader(t, bytes.NewBufferString(part.inputReaderData), part.inputDataSize, part.inputMd5, sha256sum), opts) 1765 if err != nil { 1766 t.Fatalf("%s : %s", instanceType, err) 1767 } 1768 } 1769 // Parts to be sent as input for CompleteMultipartUpload. 1770 inputParts := []struct { 1771 parts []CompletePart 1772 }{ 1773 // inputParts - 0. 1774 // Case for replicating ETag mismatch. 1775 { 1776 []CompletePart{ 1777 {ETag: "abcd", PartNumber: 1}, 1778 }, 1779 }, 1780 // inputParts - 1. 1781 // should error out with part too small. 1782 { 1783 []CompletePart{ 1784 {ETag: "e2fc714c4727ee9395f324cd2e7f331f", PartNumber: 1}, 1785 {ETag: "1f7690ebdd9b4caf8fab49ca1757bf27", PartNumber: 2}, 1786 }, 1787 }, 1788 // inputParts - 2. 1789 // Case with invalid Part number. 1790 { 1791 []CompletePart{ 1792 {ETag: "e2fc714c4727ee9395f324cd2e7f331f", PartNumber: 10}, 1793 }, 1794 }, 1795 // inputParts - 3. 1796 // Case with valid part. 1797 // Part size greater than 5MB. 1798 { 1799 []CompletePart{ 1800 {ETag: fmt.Sprintf("\"\"\"\"\"%s\"\"\"", validPartMD5), PartNumber: 5}, 1801 }, 1802 }, 1803 // inputParts - 4. 1804 // Used to verify that the other remaining parts are deleted after 1805 // a successful call to CompleteMultipartUpload. 1806 { 1807 []CompletePart{ 1808 {ETag: validPartMD5, PartNumber: 6}, 1809 }, 1810 }, 1811 } 1812 s3MD5 := getCompleteMultipartMD5(inputParts[3].parts) 1813 1814 // Test cases with sample input values for CompleteMultipartUpload. 1815 testCases := []struct { 1816 bucket string 1817 object string 1818 uploadID string 1819 parts []CompletePart 1820 // Expected output of CompleteMultipartUpload. 1821 expectedS3MD5 string 1822 expectedErr error 1823 // Flag indicating whether the test is expected to pass or not. 1824 shouldPass bool 1825 }{ 1826 // Test cases with invalid bucket names (Test number 1-4). 1827 {".test", "", "", []CompletePart{}, "", BucketNameInvalid{Bucket: ".test"}, false}, 1828 {"Test", "", "", []CompletePart{}, "", BucketNameInvalid{Bucket: "Test"}, false}, 1829 {"---", "", "", []CompletePart{}, "", BucketNameInvalid{Bucket: "---"}, false}, 1830 {"ad", "", "", []CompletePart{}, "", BucketNameInvalid{Bucket: "ad"}, false}, 1831 // Test cases for listing uploadID with single part. 1832 // Valid bucket names, but they do not exist (Test number 5-7). 1833 {"volatile-bucket-1", "test1", "", []CompletePart{}, "", BucketNotFound{Bucket: "volatile-bucket-1"}, false}, 1834 {"volatile-bucket-2", "test1", "", []CompletePart{}, "", BucketNotFound{Bucket: "volatile-bucket-2"}, false}, 1835 {"volatile-bucket-3", "test1", "", []CompletePart{}, "", BucketNotFound{Bucket: "volatile-bucket-3"}, false}, 1836 // Test case for Asserting for invalid objectName (Test number 8). 1837 {bucketNames[0], "", "", []CompletePart{}, "", ObjectNameInvalid{Bucket: bucketNames[0]}, false}, 1838 // Asserting for Invalid UploadID (Test number 9). 1839 {bucketNames[0], objectNames[0], "abc", []CompletePart{}, "", InvalidUploadID{UploadID: "abc"}, false}, 1840 // Test case with invalid Part Etag (Test number 10-11). 1841 {bucketNames[0], objectNames[0], uploadIDs[0], []CompletePart{{ETag: "abc"}}, "", InvalidPart{}, false}, 1842 {bucketNames[0], objectNames[0], uploadIDs[0], []CompletePart{{ETag: "abcz"}}, "", InvalidPart{}, false}, 1843 // Part number 0 doesn't exist, expecting InvalidPart error (Test number 12). 1844 {bucketNames[0], objectNames[0], uploadIDs[0], []CompletePart{{ETag: "abcd", PartNumber: 0}}, "", InvalidPart{}, false}, 1845 // // Upload and PartNumber exists, But a deliberate ETag mismatch is introduced (Test number 13). 1846 {bucketNames[0], objectNames[0], uploadIDs[0], inputParts[0].parts, "", InvalidPart{}, false}, 1847 // Test case with non existent object name (Test number 14). 1848 {bucketNames[0], "my-object", uploadIDs[0], []CompletePart{{ETag: "abcd", PartNumber: 1}}, "", InvalidUploadID{UploadID: uploadIDs[0]}, false}, 1849 // Testing for Part being too small (Test number 15). 1850 {bucketNames[0], objectNames[0], uploadIDs[0], inputParts[1].parts, "", PartTooSmall{PartNumber: 1}, false}, 1851 // TestCase with invalid Part Number (Test number 16). 1852 // Should error with Invalid Part . 1853 {bucketNames[0], objectNames[0], uploadIDs[0], inputParts[2].parts, "", InvalidPart{}, false}, 1854 // Test case with unsorted parts (Test number 17). 1855 {bucketNames[0], objectNames[0], uploadIDs[0], inputParts[3].parts, s3MD5, nil, true}, 1856 // The other parts will be flushed after a successful CompletePart (Test number 18). 1857 // the case above successfully completes CompleteMultipartUpload, the remaining Parts will be flushed. 1858 // Expecting to fail with Invalid UploadID. 1859 {bucketNames[0], objectNames[0], uploadIDs[0], inputParts[4].parts, "", InvalidUploadID{UploadID: uploadIDs[0]}, false}, 1860 } 1861 1862 for _, testCase := range testCases { 1863 testCase := testCase 1864 t.(*testing.T).Run("", func(t *testing.T) { 1865 opts = ObjectOptions{} 1866 actualResult, actualErr := obj.CompleteMultipartUpload(context.Background(), testCase.bucket, testCase.object, testCase.uploadID, testCase.parts, ObjectOptions{}) 1867 if actualErr != nil && testCase.shouldPass { 1868 t.Errorf("%s: Expected to pass, but failed with: <ERROR> %s", instanceType, actualErr) 1869 } 1870 if actualErr == nil && !testCase.shouldPass { 1871 t.Errorf("%s: Expected to fail with <ERROR> \"%s\", but passed instead", instanceType, testCase.expectedErr) 1872 } 1873 // Failed as expected, but does it fail for the expected reason. 1874 if actualErr != nil && !testCase.shouldPass { 1875 if reflect.TypeOf(actualErr) != reflect.TypeOf(testCase.expectedErr) { 1876 t.Errorf("%s: Expected to fail with error \"%s\", but instead failed with error \"%s\"", instanceType, testCase.expectedErr, actualErr) 1877 } 1878 } 1879 // Passes as expected, but asserting the results. 1880 if actualErr == nil && testCase.shouldPass { 1881 // Asserting IsTruncated. 1882 if actualResult.ETag != testCase.expectedS3MD5 { 1883 t.Errorf("%s: Expected the result to be \"%v\", but found it to \"%v\"", instanceType, testCase.expectedS3MD5, actualResult) 1884 } 1885 } 1886 }) 1887 } 1888 } 1889 1890 // Benchmarks for ObjectLayer.PutObjectPart(). 1891 // The intent is to benchmark PutObjectPart for various sizes ranging from few bytes to 100MB. 1892 // Also each of these Benchmarks are run both Erasure and FS backends. 1893 1894 // BenchmarkPutObjectPart5MbFS - Benchmark FS.PutObjectPart() for object size of 5MB. 1895 func BenchmarkPutObjectPart5MbFS(b *testing.B) { 1896 benchmarkPutObjectPart(b, "FS", 5*humanize.MiByte) 1897 } 1898 1899 // BenchmarkPutObjectPart5MbErasure - Benchmark Erasure.PutObjectPart() for object size of 5MB. 1900 func BenchmarkPutObjectPart5MbErasure(b *testing.B) { 1901 benchmarkPutObjectPart(b, "Erasure", 5*humanize.MiByte) 1902 } 1903 1904 // BenchmarkPutObjectPart10MbFS - Benchmark FS.PutObjectPart() for object size of 10MB. 1905 func BenchmarkPutObjectPart10MbFS(b *testing.B) { 1906 benchmarkPutObjectPart(b, "FS", 10*humanize.MiByte) 1907 } 1908 1909 // BenchmarkPutObjectPart10MbErasure - Benchmark Erasure.PutObjectPart() for object size of 10MB. 1910 func BenchmarkPutObjectPart10MbErasure(b *testing.B) { 1911 benchmarkPutObjectPart(b, "Erasure", 10*humanize.MiByte) 1912 } 1913 1914 // BenchmarkPutObjectPart25MbFS - Benchmark FS.PutObjectPart() for object size of 25MB. 1915 func BenchmarkPutObjectPart25MbFS(b *testing.B) { 1916 benchmarkPutObjectPart(b, "FS", 25*humanize.MiByte) 1917 } 1918 1919 // BenchmarkPutObjectPart25MbErasure - Benchmark Erasure.PutObjectPart() for object size of 25MB. 1920 func BenchmarkPutObjectPart25MbErasure(b *testing.B) { 1921 benchmarkPutObjectPart(b, "Erasure", 25*humanize.MiByte) 1922 } 1923 1924 // BenchmarkPutObjectPart50MbFS - Benchmark FS.PutObjectPart() for object size of 50MB. 1925 func BenchmarkPutObjectPart50MbFS(b *testing.B) { 1926 benchmarkPutObjectPart(b, "FS", 50*humanize.MiByte) 1927 } 1928 1929 // BenchmarkPutObjectPart50MbErasure - Benchmark Erasure.PutObjectPart() for object size of 50MB. 1930 func BenchmarkPutObjectPart50MbErasure(b *testing.B) { 1931 benchmarkPutObjectPart(b, "Erasure", 50*humanize.MiByte) 1932 }