| 
									
										
										
										
											2016-11-17 08:42:23 +08:00
										 |  |  | /* | 
					
						
							| 
									
										
										
										
											2019-04-10 02:39:42 +08:00
										 |  |  |  * MinIO Cloud Storage, (C) 2016, 2017 MinIO, Inc. | 
					
						
							| 
									
										
										
										
											2016-11-17 08:42:23 +08:00
										 |  |  |  * | 
					
						
							|  |  |  |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
					
						
							|  |  |  |  * you may not use this file except in compliance with the License. | 
					
						
							|  |  |  |  * You may obtain a copy of the License at | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *     http://www.apache.org/licenses/LICENSE-2.0
 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Unless required by applicable law or agreed to in writing, software | 
					
						
							|  |  |  |  * distributed under the License is distributed on an "AS IS" BASIS, | 
					
						
							|  |  |  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
					
						
							|  |  |  |  * See the License for the specific language governing permissions and | 
					
						
							|  |  |  |  * limitations under the License. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package cmd | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2017-03-18 00:25:49 +08:00
										 |  |  | 	"bytes" | 
					
						
							| 
									
										
										
										
											2018-03-16 04:27:16 +08:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2017-03-18 00:25:49 +08:00
										 |  |  | 	"path/filepath" | 
					
						
							| 
									
										
										
										
											2016-11-17 08:42:23 +08:00
										 |  |  | 	"testing" | 
					
						
							| 
									
										
										
										
											2019-03-15 04:08:51 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/minio/minio/pkg/madmin" | 
					
						
							| 
									
										
										
										
											2016-11-17 08:42:23 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Tests undoes and validates if the undoing completes successfully.
 | 
					
						
							|  |  |  | func TestUndoMakeBucket(t *testing.T) { | 
					
						
							|  |  |  | 	nDisks := 16 | 
					
						
							|  |  |  | 	fsDirs, err := getRandomDisks(nDisks) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer removeRoots(fsDirs) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Remove format.json on 16 disks.
 | 
					
						
							| 
									
										
										
										
											2019-11-20 09:42:27 +08:00
										 |  |  | 	obj, _, err := initObjectLayer(mustGetZoneEndpoints(fsDirs...)) | 
					
						
							| 
									
										
										
										
											2016-11-17 08:42:23 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	bucketName := getRandomBucketName() | 
					
						
							| 
									
										
										
										
											2018-03-16 04:27:16 +08:00
										 |  |  | 	if err = obj.MakeBucketWithLocation(context.Background(), bucketName, ""); err != nil { | 
					
						
							| 
									
										
										
										
											2016-11-17 08:42:23 +08:00
										 |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-11-20 09:42:27 +08:00
										 |  |  | 	z := obj.(*xlZones) | 
					
						
							|  |  |  | 	xl := z.zones[0].sets[0] | 
					
						
							|  |  |  | 	undoMakeBucket(xl.getDisks(), bucketName) | 
					
						
							| 
									
										
										
										
											2016-11-17 08:42:23 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Validate if bucket was deleted properly.
 | 
					
						
							| 
									
										
										
										
											2018-03-16 04:27:16 +08:00
										 |  |  | 	_, err = obj.GetBucketInfo(context.Background(), bucketName) | 
					
						
							| 
									
										
										
										
											2016-11-17 08:42:23 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		switch err.(type) { | 
					
						
							|  |  |  | 		case BucketNotFound: | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			t.Fatal(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-27 05:57:44 +08:00
										 |  |  | func TestHealObjectCorrupted(t *testing.T) { | 
					
						
							|  |  |  | 	nDisks := 16 | 
					
						
							|  |  |  | 	fsDirs, err := getRandomDisks(nDisks) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	defer removeRoots(fsDirs) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Everything is fine, should return nil
 | 
					
						
							| 
									
										
										
										
											2019-11-20 09:42:27 +08:00
										 |  |  | 	objLayer, _, err := initObjectLayer(mustGetZoneEndpoints(fsDirs...)) | 
					
						
							| 
									
										
										
										
											2019-03-27 05:57:44 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	bucket := "bucket" | 
					
						
							|  |  |  | 	object := "object" | 
					
						
							|  |  |  | 	data := bytes.Repeat([]byte("a"), 5*1024*1024) | 
					
						
							|  |  |  | 	var opts ObjectOptions | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-02 04:12:15 +08:00
										 |  |  | 	err = objLayer.MakeBucketWithLocation(context.Background(), bucket, "") | 
					
						
							| 
									
										
										
										
											2019-03-27 05:57:44 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to make a bucket - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Create an object with multiple parts uploaded in decreasing
 | 
					
						
							|  |  |  | 	// part number.
 | 
					
						
							| 
									
										
										
										
											2019-10-02 04:12:15 +08:00
										 |  |  | 	uploadID, err := objLayer.NewMultipartUpload(context.Background(), bucket, object, opts) | 
					
						
							| 
									
										
										
										
											2019-03-27 05:57:44 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to create a multipart upload - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var uploadedParts []CompletePart | 
					
						
							|  |  |  | 	for _, partID := range []int{2, 1} { | 
					
						
							| 
									
										
										
										
											2019-10-02 04:12:15 +08:00
										 |  |  | 		pInfo, err1 := objLayer.PutObjectPart(context.Background(), bucket, object, uploadID, partID, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", ""), opts) | 
					
						
							| 
									
										
										
										
											2019-03-27 05:57:44 +08:00
										 |  |  | 		if err1 != nil { | 
					
						
							|  |  |  | 			t.Fatalf("Failed to upload a part - %v", err1) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		uploadedParts = append(uploadedParts, CompletePart{ | 
					
						
							|  |  |  | 			PartNumber: pInfo.PartNumber, | 
					
						
							|  |  |  | 			ETag:       pInfo.ETag, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-02 04:12:15 +08:00
										 |  |  | 	_, err = objLayer.CompleteMultipartUpload(context.Background(), bucket, object, uploadID, uploadedParts, ObjectOptions{}) | 
					
						
							| 
									
										
										
										
											2019-03-27 05:57:44 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to complete multipart upload - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-13 07:29:44 +08:00
										 |  |  | 	// Test 1: Remove the object backend files from the first disk.
 | 
					
						
							| 
									
										
										
										
											2019-11-20 09:42:27 +08:00
										 |  |  | 	z := objLayer.(*xlZones) | 
					
						
							|  |  |  | 	xl := z.zones[0].sets[0] | 
					
						
							|  |  |  | 	firstDisk := xl.getDisks()[0] | 
					
						
							| 
									
										
										
										
											2019-03-27 05:57:44 +08:00
										 |  |  | 	err = firstDisk.DeleteFile(bucket, filepath.Join(object, xlMetaJSONFile)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to delete a file - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-02 04:12:15 +08:00
										 |  |  | 	_, err = objLayer.HealObject(context.Background(), bucket, object, false, false, madmin.HealNormalScan) | 
					
						
							| 
									
										
										
										
											2019-03-27 05:57:44 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to heal object - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	_, err = firstDisk.StatFile(bucket, filepath.Join(object, xlMetaJSONFile)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Errorf("Expected xl.json file to be present but stat failed - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-13 07:29:44 +08:00
										 |  |  | 	// Test 2: Heal when part.1 is empty
 | 
					
						
							|  |  |  | 	partSt1, err := firstDisk.StatFile(bucket, filepath.Join(object, "part.1")) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Errorf("Expected part.1 file to be present but stat failed - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	err = firstDisk.DeleteFile(bucket, filepath.Join(object, "part.1")) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-10-02 04:12:15 +08:00
										 |  |  | 		t.Errorf("Failure during deleting part.1 - %v", err) | 
					
						
							| 
									
										
										
										
											2019-07-13 07:29:44 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-10-02 04:12:15 +08:00
										 |  |  | 	err = firstDisk.WriteAll(bucket, filepath.Join(object, "part.1"), bytes.NewReader([]byte{})) | 
					
						
							| 
									
										
										
										
											2019-07-13 07:29:44 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Errorf("Failure during creating part.1 - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-10-02 04:12:15 +08:00
										 |  |  | 	_, err = objLayer.HealObject(context.Background(), bucket, object, false, true, madmin.HealDeepScan) | 
					
						
							| 
									
										
										
										
											2019-07-13 07:29:44 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Errorf("Expected nil but received %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	partSt2, err := firstDisk.StatFile(bucket, filepath.Join(object, "part.1")) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Errorf("Expected from part.1 file to be present but stat failed - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if partSt1.Size != partSt2.Size { | 
					
						
							|  |  |  | 		t.Errorf("part.1 file size is not the same before and after heal") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-02 04:12:15 +08:00
										 |  |  | 	// Test 3: Heal when part.1 is correct in size but corrupted
 | 
					
						
							|  |  |  | 	partSt1, err = firstDisk.StatFile(bucket, filepath.Join(object, "part.1")) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Errorf("Expected part.1 file to be present but stat failed - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	err = firstDisk.DeleteFile(bucket, filepath.Join(object, "part.1")) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Errorf("Failure during deleting part.1 - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	bdata := bytes.Repeat([]byte("b"), int(partSt1.Size)) | 
					
						
							|  |  |  | 	err = firstDisk.WriteAll(bucket, filepath.Join(object, "part.1"), bytes.NewReader(bdata)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Errorf("Failure during creating part.1 - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	_, err = objLayer.HealObject(context.Background(), bucket, object, false, true, madmin.HealDeepScan) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Errorf("Expected nil but received %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	partSt2, err = firstDisk.StatFile(bucket, filepath.Join(object, "part.1")) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Errorf("Expected from part.1 file to be present but stat failed - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if partSt1.Size != partSt2.Size { | 
					
						
							|  |  |  | 		t.Errorf("part.1 file size is not the same before and after heal") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Test 4: checks if HealObject returns an error when xl.json is not found
 | 
					
						
							| 
									
										
										
										
											2019-07-13 07:29:44 +08:00
										 |  |  | 	// in more than read quorum number of disks, to create a corrupted situation.
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-20 09:42:27 +08:00
										 |  |  | 	for i := 0; i <= len(xl.getDisks())/2; i++ { | 
					
						
							|  |  |  | 		xl.getDisks()[i].DeleteFile(bucket, filepath.Join(object, xlMetaJSONFile)) | 
					
						
							| 
									
										
										
										
											2019-03-27 05:57:44 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-22 05:18:32 +08:00
										 |  |  | 	// Try healing now, expect to receive errFileNotFound.
 | 
					
						
							| 
									
										
										
										
											2019-10-02 04:12:15 +08:00
										 |  |  | 	_, err = objLayer.HealObject(context.Background(), bucket, object, false, true, madmin.HealDeepScan) | 
					
						
							| 
									
										
										
										
											2019-03-27 05:57:44 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2019-11-22 05:18:32 +08:00
										 |  |  | 		if _, ok := err.(ObjectNotFound); !ok { | 
					
						
							|  |  |  | 			t.Errorf("Expect %v but received %v", ObjectNotFound{Bucket: bucket, Object: object}, err) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-03-27 05:57:44 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// since majority of xl.jsons are not available, object should be successfully deleted.
 | 
					
						
							| 
									
										
										
										
											2019-10-02 04:12:15 +08:00
										 |  |  | 	_, err = objLayer.GetObjectInfo(context.Background(), bucket, object, ObjectOptions{}) | 
					
						
							| 
									
										
										
										
											2019-03-27 05:57:44 +08:00
										 |  |  | 	if _, ok := err.(ObjectNotFound); !ok { | 
					
						
							|  |  |  | 		t.Errorf("Expect %v but received %v", ObjectNotFound{Bucket: bucket, Object: object}, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-18 00:25:49 +08:00
										 |  |  | // Tests healing of object.
 | 
					
						
							|  |  |  | func TestHealObjectXL(t *testing.T) { | 
					
						
							|  |  |  | 	nDisks := 16 | 
					
						
							|  |  |  | 	fsDirs, err := getRandomDisks(nDisks) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	defer removeRoots(fsDirs) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Everything is fine, should return nil
 | 
					
						
							| 
									
										
										
										
											2019-11-20 09:42:27 +08:00
										 |  |  | 	obj, _, err := initObjectLayer(mustGetZoneEndpoints(fsDirs...)) | 
					
						
							| 
									
										
										
										
											2017-03-18 00:25:49 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	bucket := "bucket" | 
					
						
							|  |  |  | 	object := "object" | 
					
						
							| 
									
										
										
										
											2017-03-23 01:15:16 +08:00
										 |  |  | 	data := bytes.Repeat([]byte("a"), 5*1024*1024) | 
					
						
							| 
									
										
										
										
											2018-09-11 00:42:43 +08:00
										 |  |  | 	var opts ObjectOptions | 
					
						
							| 
									
										
										
										
											2017-03-23 01:15:16 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-16 04:27:16 +08:00
										 |  |  | 	err = obj.MakeBucketWithLocation(context.Background(), bucket, "") | 
					
						
							| 
									
										
										
										
											2017-03-18 00:25:49 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to make a bucket - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-23 01:15:16 +08:00
										 |  |  | 	// Create an object with multiple parts uploaded in decreasing
 | 
					
						
							|  |  |  | 	// part number.
 | 
					
						
							| 
									
										
										
										
											2019-02-09 13:31:06 +08:00
										 |  |  | 	uploadID, err := obj.NewMultipartUpload(context.Background(), bucket, object, opts) | 
					
						
							| 
									
										
										
										
											2017-03-23 01:15:16 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to create a multipart upload - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-14 16:25:10 +08:00
										 |  |  | 	var uploadedParts []CompletePart | 
					
						
							| 
									
										
										
										
											2017-03-23 01:15:16 +08:00
										 |  |  | 	for _, partID := range []int{2, 1} { | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  | 		pInfo, err1 := obj.PutObjectPart(context.Background(), bucket, object, uploadID, partID, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", ""), opts) | 
					
						
							| 
									
										
										
										
											2017-04-01 16:06:06 +08:00
										 |  |  | 		if err1 != nil { | 
					
						
							|  |  |  | 			t.Fatalf("Failed to upload a part - %v", err1) | 
					
						
							| 
									
										
										
										
											2017-03-23 01:15:16 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-11-14 16:25:10 +08:00
										 |  |  | 		uploadedParts = append(uploadedParts, CompletePart{ | 
					
						
							| 
									
										
										
										
											2017-03-23 01:15:16 +08:00
										 |  |  | 			PartNumber: pInfo.PartNumber, | 
					
						
							|  |  |  | 			ETag:       pInfo.ETag, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-15 09:36:41 +08:00
										 |  |  | 	_, err = obj.CompleteMultipartUpload(context.Background(), bucket, object, uploadID, uploadedParts, ObjectOptions{}) | 
					
						
							| 
									
										
										
										
											2017-03-18 00:25:49 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2017-03-23 01:15:16 +08:00
										 |  |  | 		t.Fatalf("Failed to complete multipart upload - %v", err) | 
					
						
							| 
									
										
										
										
											2017-03-18 00:25:49 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Remove the object backend files from the first disk.
 | 
					
						
							| 
									
										
										
										
											2019-11-20 09:42:27 +08:00
										 |  |  | 	z := obj.(*xlZones) | 
					
						
							|  |  |  | 	xl := z.zones[0].sets[0] | 
					
						
							|  |  |  | 	firstDisk := xl.getDisks()[0] | 
					
						
							| 
									
										
										
										
											2017-03-18 00:25:49 +08:00
										 |  |  | 	err = firstDisk.DeleteFile(bucket, filepath.Join(object, xlMetaJSONFile)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to delete a file - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-15 04:08:51 +08:00
										 |  |  | 	_, err = obj.HealObject(context.Background(), bucket, object, false, false, madmin.HealNormalScan) | 
					
						
							| 
									
										
										
										
											2017-03-18 00:25:49 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to heal object - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	_, err = firstDisk.StatFile(bucket, filepath.Join(object, xlMetaJSONFile)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Errorf("Expected xl.json file to be present but stat failed - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-20 09:42:27 +08:00
										 |  |  | 	xlDisks := xl.getDisks() | 
					
						
							|  |  |  | 	xl.getDisks = func() []StorageAPI { | 
					
						
							|  |  |  | 		// Nil more than half the disks, to remove write quorum.
 | 
					
						
							|  |  |  | 		for i := 0; i <= len(xlDisks)/2; i++ { | 
					
						
							|  |  |  | 			xlDisks[i] = nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return xlDisks | 
					
						
							| 
									
										
										
										
											2017-03-18 00:25:49 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Try healing now, expect to receive errDiskNotFound.
 | 
					
						
							| 
									
										
										
										
											2019-03-15 04:08:51 +08:00
										 |  |  | 	_, err = obj.HealObject(context.Background(), bucket, object, false, false, madmin.HealDeepScan) | 
					
						
							| 
									
										
										
										
											2017-12-22 19:28:13 +08:00
										 |  |  | 	// since majority of xl.jsons are not available, object quorum can't be read properly and error will be errXLReadQuorum
 | 
					
						
							| 
									
										
										
										
											2018-04-26 02:56:39 +08:00
										 |  |  | 	if _, ok := err.(InsufficientReadQuorum); !ok { | 
					
						
							| 
									
										
										
										
											2018-07-31 15:23:29 +08:00
										 |  |  | 		t.Errorf("Expected %v but received %v", InsufficientReadQuorum{}, err) | 
					
						
							| 
									
										
										
										
											2017-03-18 00:25:49 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2019-08-02 05:13:06 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Tests healing of empty directories
 | 
					
						
							|  |  |  | func TestHealEmptyDirectoryXL(t *testing.T) { | 
					
						
							|  |  |  | 	nDisks := 16 | 
					
						
							|  |  |  | 	fsDirs, err := getRandomDisks(nDisks) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer removeRoots(fsDirs) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Everything is fine, should return nil
 | 
					
						
							| 
									
										
										
										
											2019-11-20 09:42:27 +08:00
										 |  |  | 	obj, _, err := initObjectLayer(mustGetZoneEndpoints(fsDirs...)) | 
					
						
							| 
									
										
										
										
											2019-08-02 05:13:06 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	bucket := "bucket" | 
					
						
							|  |  |  | 	object := "empty-dir/" | 
					
						
							|  |  |  | 	var opts ObjectOptions | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	err = obj.MakeBucketWithLocation(context.Background(), bucket, "") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to make a bucket - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Upload an empty directory
 | 
					
						
							| 
									
										
										
										
											2019-11-20 09:42:27 +08:00
										 |  |  | 	_, err = obj.PutObject(context.Background(), bucket, object, mustGetPutObjReader(t, | 
					
						
							|  |  |  | 		bytes.NewReader([]byte{}), 0, "", ""), opts) | 
					
						
							| 
									
										
										
										
											2019-08-02 05:13:06 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Remove the object backend files from the first disk.
 | 
					
						
							| 
									
										
										
										
											2019-11-20 09:42:27 +08:00
										 |  |  | 	z := obj.(*xlZones) | 
					
						
							|  |  |  | 	xl := z.zones[0].sets[0] | 
					
						
							|  |  |  | 	firstDisk := xl.getDisks()[0] | 
					
						
							| 
									
										
										
										
											2019-08-02 05:13:06 +08:00
										 |  |  | 	err = firstDisk.DeleteFile(bucket, object) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to delete a file - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Heal the object
 | 
					
						
							|  |  |  | 	hr, err := obj.HealObject(context.Background(), bucket, object, false, false, madmin.HealNormalScan) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to heal object - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check if the empty directory is restored in the first disk
 | 
					
						
							|  |  |  | 	_, err = firstDisk.StatVol(pathJoin(bucket, object)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Expected object to be present but stat failed - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check the state of the object in the first disk (should be missing)
 | 
					
						
							|  |  |  | 	if hr.Before.Drives[0].State != madmin.DriveStateMissing { | 
					
						
							|  |  |  | 		t.Fatalf("Unexpected drive state: %v", hr.Before.Drives[0].State) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check the state of all other disks (should be ok)
 | 
					
						
							|  |  |  | 	for i, h := range append(hr.Before.Drives[1:], hr.After.Drives...) { | 
					
						
							|  |  |  | 		if h.State != madmin.DriveStateOk { | 
					
						
							|  |  |  | 			t.Fatalf("Unexpected drive state (%d): %v", i+1, h.State) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Heal the same object again
 | 
					
						
							|  |  |  | 	hr, err = obj.HealObject(context.Background(), bucket, object, false, false, madmin.HealNormalScan) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to heal object - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check that Before & After states are all okay
 | 
					
						
							|  |  |  | 	for i, h := range append(hr.Before.Drives, hr.After.Drives...) { | 
					
						
							|  |  |  | 		if h.State != madmin.DriveStateOk { | 
					
						
							|  |  |  | 			t.Fatalf("Unexpected drive state (%d): %v", i+1, h.State) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |