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