| 
									
										
										
										
											2021-04-19 03:41:13 +08:00
										 |  |  | // Copyright (c) 2015-2021 MinIO, Inc.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This file is part of MinIO Object Storage stack
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This program is free software: you can redistribute it and/or modify
 | 
					
						
							|  |  |  | // it under the terms of the GNU Affero General Public License as published by
 | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or
 | 
					
						
							|  |  |  | // (at your option) any later version.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This program is distributed in the hope that it will be useful
 | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					
						
							|  |  |  | // GNU Affero General Public License for more details.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // You should have received a copy of the GNU Affero General Public License
 | 
					
						
							|  |  |  | // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
					
						
							| 
									
										
										
										
											2016-11-17 08:42:23 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | package cmd | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2017-03-18 00:25:49 +08:00
										 |  |  | 	"bytes" | 
					
						
							| 
									
										
										
										
											2020-04-15 08:52:38 +08:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	"crypto/rand" | 
					
						
							| 
									
										
										
										
											2021-12-04 01:26:30 +08:00
										 |  |  | 	"crypto/sha256" | 
					
						
							| 
									
										
										
										
											2021-12-26 01:01:44 +08:00
										 |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2021-12-04 01:26:30 +08:00
										 |  |  | 	"io" | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	"os" | 
					
						
							|  |  |  | 	"path" | 
					
						
							|  |  |  | 	"reflect" | 
					
						
							| 
									
										
										
										
											2016-11-17 08:42:23 +08:00
										 |  |  | 	"testing" | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2019-03-15 04:08:51 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	"github.com/dustin/go-humanize" | 
					
						
							| 
									
										
										
										
											2022-11-29 02:20:55 +08:00
										 |  |  | 	uuid2 "github.com/google/uuid" | 
					
						
							| 
									
										
										
										
											2022-12-07 05:46:50 +08:00
										 |  |  | 	"github.com/minio/madmin-go/v2" | 
					
						
							| 
									
										
										
										
											2016-11-17 08:42:23 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-08 11:11:54 +08:00
										 |  |  | // Tests isObjectDangling function
 | 
					
						
							|  |  |  | func TestIsObjectDangling(t *testing.T) { | 
					
						
							|  |  |  | 	fi := newFileInfo("test-object", 2, 2) | 
					
						
							|  |  |  | 	fi.Erasure.Index = 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	testCases := []struct { | 
					
						
							|  |  |  | 		name             string | 
					
						
							|  |  |  | 		metaArr          []FileInfo | 
					
						
							|  |  |  | 		errs             []error | 
					
						
							|  |  |  | 		dataErrs         []error | 
					
						
							|  |  |  | 		expectedMeta     FileInfo | 
					
						
							|  |  |  | 		expectedDangling bool | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name: "FileInfoExists-case1", | 
					
						
							|  |  |  | 			metaArr: []FileInfo{ | 
					
						
							|  |  |  | 				{}, | 
					
						
							|  |  |  | 				{}, | 
					
						
							|  |  |  | 				fi, | 
					
						
							|  |  |  | 				fi, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			errs: []error{ | 
					
						
							|  |  |  | 				errFileNotFound, | 
					
						
							|  |  |  | 				errDiskNotFound, | 
					
						
							|  |  |  | 				nil, | 
					
						
							|  |  |  | 				nil, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			dataErrs:         nil, | 
					
						
							|  |  |  | 			expectedMeta:     fi, | 
					
						
							|  |  |  | 			expectedDangling: false, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name: "FileInfoExists-case2", | 
					
						
							|  |  |  | 			metaArr: []FileInfo{ | 
					
						
							|  |  |  | 				{}, | 
					
						
							|  |  |  | 				{}, | 
					
						
							|  |  |  | 				fi, | 
					
						
							|  |  |  | 				fi, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			errs: []error{ | 
					
						
							|  |  |  | 				errFileNotFound, | 
					
						
							|  |  |  | 				errFileNotFound, | 
					
						
							|  |  |  | 				nil, | 
					
						
							|  |  |  | 				nil, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			dataErrs:         nil, | 
					
						
							|  |  |  | 			expectedMeta:     fi, | 
					
						
							|  |  |  | 			expectedDangling: false, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name: "FileInfoUndecided-case1", | 
					
						
							|  |  |  | 			metaArr: []FileInfo{ | 
					
						
							|  |  |  | 				{}, | 
					
						
							|  |  |  | 				{}, | 
					
						
							|  |  |  | 				{}, | 
					
						
							|  |  |  | 				fi, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			errs: []error{ | 
					
						
							|  |  |  | 				errFileNotFound, | 
					
						
							|  |  |  | 				errDiskNotFound, | 
					
						
							|  |  |  | 				errDiskNotFound, | 
					
						
							|  |  |  | 				nil, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			dataErrs:         nil, | 
					
						
							|  |  |  | 			expectedMeta:     fi, | 
					
						
							|  |  |  | 			expectedDangling: false, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:    "FileInfoUndecided-case2", | 
					
						
							|  |  |  | 			metaArr: []FileInfo{}, | 
					
						
							|  |  |  | 			errs: []error{ | 
					
						
							|  |  |  | 				errFileNotFound, | 
					
						
							|  |  |  | 				errDiskNotFound, | 
					
						
							|  |  |  | 				errDiskNotFound, | 
					
						
							|  |  |  | 				errFileNotFound, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			dataErrs:         nil, | 
					
						
							|  |  |  | 			expectedMeta:     FileInfo{}, | 
					
						
							|  |  |  | 			expectedDangling: false, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:    "FileInfoUndecided-case3(file deleted)", | 
					
						
							|  |  |  | 			metaArr: []FileInfo{}, | 
					
						
							|  |  |  | 			errs: []error{ | 
					
						
							|  |  |  | 				errFileNotFound, | 
					
						
							|  |  |  | 				errFileNotFound, | 
					
						
							|  |  |  | 				errFileNotFound, | 
					
						
							|  |  |  | 				errFileNotFound, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			dataErrs:         nil, | 
					
						
							|  |  |  | 			expectedMeta:     FileInfo{}, | 
					
						
							|  |  |  | 			expectedDangling: false, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name: "FileInfoDecided-case1", | 
					
						
							|  |  |  | 			metaArr: []FileInfo{ | 
					
						
							|  |  |  | 				{}, | 
					
						
							|  |  |  | 				{}, | 
					
						
							|  |  |  | 				{}, | 
					
						
							|  |  |  | 				fi, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			errs: []error{ | 
					
						
							|  |  |  | 				errFileNotFound, | 
					
						
							|  |  |  | 				errFileNotFound, | 
					
						
							|  |  |  | 				errFileNotFound, | 
					
						
							|  |  |  | 				nil, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			dataErrs:         nil, | 
					
						
							|  |  |  | 			expectedMeta:     fi, | 
					
						
							|  |  |  | 			expectedDangling: true, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name: "FileInfoDecided-case2", | 
					
						
							|  |  |  | 			metaArr: []FileInfo{ | 
					
						
							|  |  |  | 				{}, | 
					
						
							|  |  |  | 				{}, | 
					
						
							|  |  |  | 				{}, | 
					
						
							|  |  |  | 				fi, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			errs: []error{ | 
					
						
							|  |  |  | 				errFileNotFound, | 
					
						
							|  |  |  | 				errFileCorrupt, | 
					
						
							|  |  |  | 				errFileCorrupt, | 
					
						
							|  |  |  | 				nil, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			dataErrs:         nil, | 
					
						
							|  |  |  | 			expectedMeta:     fi, | 
					
						
							|  |  |  | 			expectedDangling: true, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name: "FileInfoDecided-case2-delete-marker", | 
					
						
							|  |  |  | 			metaArr: []FileInfo{ | 
					
						
							|  |  |  | 				{}, | 
					
						
							|  |  |  | 				{}, | 
					
						
							|  |  |  | 				{}, | 
					
						
							|  |  |  | 				{Deleted: true}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			errs: []error{ | 
					
						
							|  |  |  | 				errFileNotFound, | 
					
						
							|  |  |  | 				errFileCorrupt, | 
					
						
							|  |  |  | 				errFileCorrupt, | 
					
						
							|  |  |  | 				nil, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			dataErrs:         nil, | 
					
						
							|  |  |  | 			expectedMeta:     FileInfo{Deleted: true}, | 
					
						
							|  |  |  | 			expectedDangling: true, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name: "FileInfoDecided-case2-(duplicate data errors)", | 
					
						
							|  |  |  | 			metaArr: []FileInfo{ | 
					
						
							|  |  |  | 				{}, | 
					
						
							|  |  |  | 				{}, | 
					
						
							|  |  |  | 				{}, | 
					
						
							|  |  |  | 				{Deleted: true}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			errs: []error{ | 
					
						
							|  |  |  | 				errFileNotFound, | 
					
						
							|  |  |  | 				errFileCorrupt, | 
					
						
							|  |  |  | 				errFileCorrupt, | 
					
						
							|  |  |  | 				nil, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			dataErrs: []error{ | 
					
						
							|  |  |  | 				errFileNotFound, | 
					
						
							|  |  |  | 				errFileCorrupt, | 
					
						
							|  |  |  | 				nil, | 
					
						
							|  |  |  | 				nil, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			expectedMeta:     FileInfo{Deleted: true}, | 
					
						
							|  |  |  | 			expectedDangling: true, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		// Add new cases as seen
 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for _, testCase := range testCases { | 
					
						
							|  |  |  | 		testCase := testCase | 
					
						
							|  |  |  | 		t.Run(testCase.name, func(t *testing.T) { | 
					
						
							|  |  |  | 			gotMeta, dangling := isObjectDangling(testCase.metaArr, testCase.errs, testCase.dataErrs) | 
					
						
							|  |  |  | 			if !gotMeta.Equals(testCase.expectedMeta) { | 
					
						
							|  |  |  | 				t.Errorf("Expected %#v, got %#v", testCase.expectedMeta, gotMeta) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if dangling != testCase.expectedDangling { | 
					
						
							|  |  |  | 				t.Errorf("Expected dangling %t, got %t", testCase.expectedDangling, dangling) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | // Tests both object and bucket healing.
 | 
					
						
							|  |  |  | func TestHealing(t *testing.T) { | 
					
						
							|  |  |  | 	ctx, cancel := context.WithCancel(context.Background()) | 
					
						
							|  |  |  | 	defer cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	obj, fsDirs, err := prepareErasure16(ctx) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-09-11 00:18:19 +08:00
										 |  |  | 	defer obj.Shutdown(context.Background()) | 
					
						
							| 
									
										
										
										
											2022-09-27 00:04:54 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// initialize the server and obtain the credentials and root.
 | 
					
						
							|  |  |  | 	// credentials are necessary to sign the HTTP request.
 | 
					
						
							|  |  |  | 	if err = newTestConfig(globalMinioDefaultRegion, obj); err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Unable to initialize server config. %s", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	defer removeRoots(fsDirs) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-02 05:50:33 +08:00
										 |  |  | 	z := obj.(*erasureServerPools) | 
					
						
							|  |  |  | 	er := z.serverPools[0].sets[0] | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Create "bucket"
 | 
					
						
							| 
									
										
										
										
											2022-07-26 08:51:32 +08:00
										 |  |  | 	err = obj.MakeBucketWithLocation(ctx, "bucket", MakeBucketOptions{}) | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	bucket := "bucket" | 
					
						
							|  |  |  | 	object := "object" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	data := make([]byte, 1*humanize.MiByte) | 
					
						
							|  |  |  | 	length := int64(len(data)) | 
					
						
							|  |  |  | 	_, err = rand.Read(data) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	_, err = obj.PutObject(ctx, bucket, object, mustGetPutObjReader(t, bytes.NewReader(data), length, "", ""), ObjectOptions{}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	disk := er.getDisks()[0] | 
					
						
							| 
									
										
										
										
											2021-01-08 11:27:31 +08:00
										 |  |  | 	fileInfoPreHeal, err := disk.ReadVersion(context.Background(), bucket, object, "", false) | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Remove the object - to simulate the case where the disk was down when the object
 | 
					
						
							|  |  |  | 	// was created.
 | 
					
						
							|  |  |  | 	err = removeAll(pathJoin(disk.String(), bucket, object)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-29 02:20:55 +08:00
										 |  |  | 	// Checking abandoned parts should do nothing
 | 
					
						
							|  |  |  | 	err = er.checkAbandonedParts(ctx, bucket, object, madmin.HealOpts{ScanMode: madmin.HealNormalScan, Remove: true}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	_, err = er.HealObject(ctx, bucket, object, "", madmin.HealOpts{ScanMode: madmin.HealNormalScan}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-08 11:27:31 +08:00
										 |  |  | 	fileInfoPostHeal, err := disk.ReadVersion(context.Background(), bucket, object, "", false) | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// After heal the meta file should be as expected.
 | 
					
						
							| 
									
										
										
										
											2021-12-22 02:08:26 +08:00
										 |  |  | 	if !fileInfoPreHeal.Equals(fileInfoPostHeal) { | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 		t.Fatal("HealObject failed") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-26 01:01:44 +08:00
										 |  |  | 	err = os.RemoveAll(path.Join(fsDirs[0], bucket, object, "xl.meta")) | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-26 01:01:44 +08:00
										 |  |  | 	// Write xl.meta with different modtime to simulate the case where a disk had
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	// gone down when an object was replaced by a new object.
 | 
					
						
							|  |  |  | 	fileInfoOutDated := fileInfoPreHeal | 
					
						
							|  |  |  | 	fileInfoOutDated.ModTime = time.Now() | 
					
						
							| 
									
										
										
										
											2020-09-05 00:45:06 +08:00
										 |  |  | 	err = disk.WriteMetadata(context.Background(), bucket, object, fileInfoOutDated) | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	_, err = er.HealObject(ctx, bucket, object, "", madmin.HealOpts{ScanMode: madmin.HealDeepScan}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-08 11:27:31 +08:00
										 |  |  | 	fileInfoPostHeal, err = disk.ReadVersion(context.Background(), bucket, object, "", false) | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// After heal the meta file should be as expected.
 | 
					
						
							| 
									
										
										
										
											2021-12-22 02:08:26 +08:00
										 |  |  | 	if !fileInfoPreHeal.Equals(fileInfoPostHeal) { | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 		t.Fatal("HealObject failed") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-29 02:20:55 +08:00
										 |  |  | 	uuid, _ := uuid2.NewRandom() | 
					
						
							|  |  |  | 	for _, drive := range fsDirs { | 
					
						
							|  |  |  | 		dir := path.Join(drive, bucket, object, uuid.String()) | 
					
						
							|  |  |  | 		err = os.MkdirAll(dir, os.ModePerm) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatal(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		err = os.WriteFile(pathJoin(dir, "part.1"), []byte("some data"), os.ModePerm) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatal(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// This should remove all the unreferenced parts.
 | 
					
						
							|  |  |  | 	err = er.checkAbandonedParts(ctx, bucket, object, madmin.HealOpts{ScanMode: madmin.HealNormalScan, Remove: true}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, drive := range fsDirs { | 
					
						
							|  |  |  | 		dir := path.Join(drive, bucket, object, uuid.String()) | 
					
						
							|  |  |  | 		_, err := os.ReadFile(pathJoin(dir, "part.1")) | 
					
						
							|  |  |  | 		if err == nil { | 
					
						
							|  |  |  | 			t.Fatal("expected data dit to be cleaned up") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Remove the bucket - to simulate the case where bucket was
 | 
					
						
							|  |  |  | 	// created when the disk was down.
 | 
					
						
							|  |  |  | 	err = os.RemoveAll(path.Join(fsDirs[0], bucket)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// This would create the bucket.
 | 
					
						
							|  |  |  | 	_, err = er.HealBucket(ctx, bucket, madmin.HealOpts{ | 
					
						
							|  |  |  | 		DryRun: false, | 
					
						
							|  |  |  | 		Remove: false, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// Stat the bucket to make sure that it was created.
 | 
					
						
							|  |  |  | 	_, err = er.getDisks()[0].StatVol(context.Background(), bucket) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Tests both object and bucket healing.
 | 
					
						
							|  |  |  | func TestHealingVersioned(t *testing.T) { | 
					
						
							|  |  |  | 	ctx, cancel := context.WithCancel(context.Background()) | 
					
						
							|  |  |  | 	defer cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	obj, fsDirs, err := prepareErasure16(ctx) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer obj.Shutdown(context.Background()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// initialize the server and obtain the credentials and root.
 | 
					
						
							|  |  |  | 	// credentials are necessary to sign the HTTP request.
 | 
					
						
							|  |  |  | 	if err = newTestConfig(globalMinioDefaultRegion, obj); err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Unable to initialize server config. %s", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	defer removeRoots(fsDirs) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	z := obj.(*erasureServerPools) | 
					
						
							|  |  |  | 	er := z.serverPools[0].sets[0] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Create "bucket"
 | 
					
						
							|  |  |  | 	err = obj.MakeBucketWithLocation(ctx, "bucket", MakeBucketOptions{VersioningEnabled: true}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	bucket := "bucket" | 
					
						
							|  |  |  | 	object := "object" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	data := make([]byte, 1*humanize.MiByte) | 
					
						
							|  |  |  | 	length := int64(len(data)) | 
					
						
							|  |  |  | 	_, err = rand.Read(data) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	oi1, err := obj.PutObject(ctx, bucket, object, mustGetPutObjReader(t, bytes.NewReader(data), length, "", ""), ObjectOptions{}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// 2nd version.
 | 
					
						
							|  |  |  | 	_, _ = rand.Read(data) | 
					
						
							|  |  |  | 	oi2, err := obj.PutObject(ctx, bucket, object, mustGetPutObjReader(t, bytes.NewReader(data), length, "", ""), ObjectOptions{}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	disk := er.getDisks()[0] | 
					
						
							|  |  |  | 	fileInfoPreHeal1, err := disk.ReadVersion(context.Background(), bucket, object, oi1.VersionID, false) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	fileInfoPreHeal2, err := disk.ReadVersion(context.Background(), bucket, object, oi2.VersionID, false) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Remove the object - to simulate the case where the disk was down when the object
 | 
					
						
							|  |  |  | 	// was created.
 | 
					
						
							|  |  |  | 	err = removeAll(pathJoin(disk.String(), bucket, object)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Checking abandoned parts should do nothing
 | 
					
						
							|  |  |  | 	err = er.checkAbandonedParts(ctx, bucket, object, madmin.HealOpts{ScanMode: madmin.HealNormalScan, Remove: true}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	_, err = er.HealObject(ctx, bucket, object, "", madmin.HealOpts{ScanMode: madmin.HealNormalScan}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	fileInfoPostHeal1, err := disk.ReadVersion(context.Background(), bucket, object, oi1.VersionID, false) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	fileInfoPostHeal2, err := disk.ReadVersion(context.Background(), bucket, object, oi2.VersionID, false) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// After heal the meta file should be as expected.
 | 
					
						
							|  |  |  | 	if !fileInfoPreHeal1.Equals(fileInfoPostHeal1) { | 
					
						
							|  |  |  | 		t.Fatal("HealObject failed") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if !fileInfoPreHeal1.Equals(fileInfoPostHeal2) { | 
					
						
							|  |  |  | 		t.Fatal("HealObject failed") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	err = os.RemoveAll(path.Join(fsDirs[0], bucket, object, "xl.meta")) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Write xl.meta with different modtime to simulate the case where a disk had
 | 
					
						
							|  |  |  | 	// gone down when an object was replaced by a new object.
 | 
					
						
							|  |  |  | 	fileInfoOutDated := fileInfoPreHeal1 | 
					
						
							|  |  |  | 	fileInfoOutDated.ModTime = time.Now() | 
					
						
							|  |  |  | 	err = disk.WriteMetadata(context.Background(), bucket, object, fileInfoOutDated) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	_, err = er.HealObject(ctx, bucket, object, "", madmin.HealOpts{ScanMode: madmin.HealDeepScan}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	fileInfoPostHeal1, err = disk.ReadVersion(context.Background(), bucket, object, "", false) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// After heal the meta file should be as expected.
 | 
					
						
							|  |  |  | 	if !fileInfoPreHeal1.Equals(fileInfoPostHeal1) { | 
					
						
							|  |  |  | 		t.Fatal("HealObject failed") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	fileInfoPostHeal2, err = disk.ReadVersion(context.Background(), bucket, object, "", false) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// After heal the meta file should be as expected.
 | 
					
						
							|  |  |  | 	if !fileInfoPreHeal2.Equals(fileInfoPostHeal2) { | 
					
						
							|  |  |  | 		t.Fatal("HealObject failed") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	uuid, _ := uuid2.NewRandom() | 
					
						
							|  |  |  | 	for _, drive := range fsDirs { | 
					
						
							|  |  |  | 		dir := path.Join(drive, bucket, object, uuid.String()) | 
					
						
							|  |  |  | 		err = os.MkdirAll(dir, os.ModePerm) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatal(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		err = os.WriteFile(pathJoin(dir, "part.1"), []byte("some data"), os.ModePerm) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatal(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// This should remove all the unreferenced parts.
 | 
					
						
							|  |  |  | 	err = er.checkAbandonedParts(ctx, bucket, object, madmin.HealOpts{ScanMode: madmin.HealNormalScan, Remove: true}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, drive := range fsDirs { | 
					
						
							|  |  |  | 		dir := path.Join(drive, bucket, object, uuid.String()) | 
					
						
							|  |  |  | 		_, err := os.ReadFile(pathJoin(dir, "part.1")) | 
					
						
							|  |  |  | 		if err == nil { | 
					
						
							|  |  |  | 			t.Fatal("expected data dit to be cleaned up") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	// Remove the bucket - to simulate the case where bucket was
 | 
					
						
							|  |  |  | 	// created when the disk was down.
 | 
					
						
							|  |  |  | 	err = os.RemoveAll(path.Join(fsDirs[0], bucket)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// This would create the bucket.
 | 
					
						
							| 
									
										
										
										
											2020-12-14 03:57:08 +08:00
										 |  |  | 	_, err = er.HealBucket(ctx, bucket, madmin.HealOpts{ | 
					
						
							|  |  |  | 		DryRun: false, | 
					
						
							|  |  |  | 		Remove: false, | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// Stat the bucket to make sure that it was created.
 | 
					
						
							| 
									
										
										
										
											2020-09-05 00:45:06 +08:00
										 |  |  | 	_, err = er.getDisks()[0].StatVol(context.Background(), bucket) | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-03 08:45:30 +08:00
										 |  |  | func TestHealingDanglingObject(t *testing.T) { | 
					
						
							|  |  |  | 	ctx, cancel := context.WithCancel(context.Background()) | 
					
						
							|  |  |  | 	defer cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	resetGlobalHealState() | 
					
						
							|  |  |  | 	defer resetGlobalHealState() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	nDisks := 16 | 
					
						
							|  |  |  | 	fsDirs, err := getRandomDisks(nDisks) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-17 01:28:29 +08:00
										 |  |  | 	defer removeRoots(fsDirs) | 
					
						
							| 
									
										
										
										
											2021-09-03 08:45:30 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Everything is fine, should return nil
 | 
					
						
							|  |  |  | 	objLayer, disks, err := initObjectLayer(ctx, mustGetPoolEndpoints(fsDirs...)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	bucket := getRandomBucketName() | 
					
						
							|  |  |  | 	object := getRandomObjectName() | 
					
						
							|  |  |  | 	data := bytes.Repeat([]byte("a"), 128*1024) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-26 08:51:32 +08:00
										 |  |  | 	err = objLayer.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{}) | 
					
						
							| 
									
										
										
										
											2021-09-03 08:45:30 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to make a bucket - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-03 03:29:16 +08:00
										 |  |  | 	disks = objLayer.(*erasureServerPools).serverPools[0].erasureDisks[0] | 
					
						
							|  |  |  | 	orgDisks := append([]StorageAPI{}, disks...) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-03 08:45:30 +08:00
										 |  |  | 	// Enable versioning.
 | 
					
						
							| 
									
										
										
										
											2022-02-01 09:27:43 +08:00
										 |  |  | 	globalBucketMetadataSys.Update(ctx, bucket, bucketVersioningConfig, []byte(`<VersioningConfiguration><Status>Enabled</Status></VersioningConfiguration>`)) | 
					
						
							| 
									
										
										
										
											2021-09-03 08:45:30 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	_, err = objLayer.PutObject(ctx, bucket, object, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", ""), ObjectOptions{ | 
					
						
							|  |  |  | 		Versioned: true, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-03 03:29:16 +08:00
										 |  |  | 	setDisks := func(newDisks ...StorageAPI) { | 
					
						
							|  |  |  | 		objLayer.(*erasureServerPools).serverPools[0].erasureDisksMu.Lock() | 
					
						
							|  |  |  | 		copy(disks, newDisks) | 
					
						
							|  |  |  | 		objLayer.(*erasureServerPools).serverPools[0].erasureDisksMu.Unlock() | 
					
						
							| 
									
										
										
										
											2021-09-03 08:45:30 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-03-09 01:04:20 +08:00
										 |  |  | 	getDisk := func(n int) StorageAPI { | 
					
						
							|  |  |  | 		objLayer.(*erasureServerPools).serverPools[0].erasureDisksMu.Lock() | 
					
						
							|  |  |  | 		disk := disks[n] | 
					
						
							|  |  |  | 		objLayer.(*erasureServerPools).serverPools[0].erasureDisksMu.Unlock() | 
					
						
							|  |  |  | 		return disk | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-03 03:29:16 +08:00
										 |  |  | 	// Remove 4 disks.
 | 
					
						
							|  |  |  | 	setDisks(nil, nil, nil, nil) | 
					
						
							| 
									
										
										
										
											2021-09-03 08:45:30 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Create delete marker under quorum.
 | 
					
						
							|  |  |  | 	objInfo, err := objLayer.DeleteObject(ctx, bucket, object, ObjectOptions{Versioned: true}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-03 03:29:16 +08:00
										 |  |  | 	// Restore...
 | 
					
						
							|  |  |  | 	setDisks(orgDisks[:4]...) | 
					
						
							| 
									
										
										
										
											2021-09-03 08:45:30 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	fileInfoPreHeal, err := disks[0].ReadVersion(context.Background(), bucket, object, "", false) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if fileInfoPreHeal.NumVersions != 1 { | 
					
						
							|  |  |  | 		t.Fatalf("Expected versions 1, got %d", fileInfoPreHeal.NumVersions) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err = objLayer.HealObjects(ctx, bucket, "", madmin.HealOpts{Remove: true}, | 
					
						
							|  |  |  | 		func(bucket, object, vid string) error { | 
					
						
							|  |  |  | 			_, err := objLayer.HealObject(ctx, bucket, object, vid, madmin.HealOpts{Remove: true}) | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		}); err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	fileInfoPostHeal, err := disks[0].ReadVersion(context.Background(), bucket, object, "", false) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if fileInfoPostHeal.NumVersions != 2 { | 
					
						
							|  |  |  | 		t.Fatalf("Expected versions 2, got %d", fileInfoPreHeal.NumVersions) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if objInfo.DeleteMarker { | 
					
						
							|  |  |  | 		if _, err = objLayer.DeleteObject(ctx, bucket, object, ObjectOptions{ | 
					
						
							|  |  |  | 			Versioned: true, | 
					
						
							|  |  |  | 			VersionID: objInfo.VersionID, | 
					
						
							|  |  |  | 		}); err != nil { | 
					
						
							|  |  |  | 			t.Fatal(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-03 03:29:16 +08:00
										 |  |  | 	setDisks(nil, nil, nil, nil) | 
					
						
							| 
									
										
										
										
											2021-09-03 08:45:30 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	rd := mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", "") | 
					
						
							|  |  |  | 	_, err = objLayer.PutObject(ctx, bucket, object, rd, ObjectOptions{ | 
					
						
							|  |  |  | 		Versioned: true, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-03 03:29:16 +08:00
										 |  |  | 	setDisks(orgDisks[:4]...) | 
					
						
							| 
									
										
										
										
											2022-03-09 01:04:20 +08:00
										 |  |  | 	disk := getDisk(0) | 
					
						
							|  |  |  | 	fileInfoPreHeal, err = disk.ReadVersion(context.Background(), bucket, object, "", false) | 
					
						
							| 
									
										
										
										
											2021-09-03 08:45:30 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if fileInfoPreHeal.NumVersions != 1 { | 
					
						
							|  |  |  | 		t.Fatalf("Expected versions 1, got %d", fileInfoPreHeal.NumVersions) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err = objLayer.HealObjects(ctx, bucket, "", madmin.HealOpts{Remove: true}, | 
					
						
							|  |  |  | 		func(bucket, object, vid string) error { | 
					
						
							|  |  |  | 			_, err := objLayer.HealObject(ctx, bucket, object, vid, madmin.HealOpts{Remove: true}) | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		}); err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-09 01:04:20 +08:00
										 |  |  | 	disk = getDisk(0) | 
					
						
							|  |  |  | 	fileInfoPostHeal, err = disk.ReadVersion(context.Background(), bucket, object, "", false) | 
					
						
							| 
									
										
										
										
											2021-09-03 08:45:30 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if fileInfoPostHeal.NumVersions != 2 { | 
					
						
							|  |  |  | 		t.Fatalf("Expected versions 2, got %d", fileInfoPreHeal.NumVersions) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-09-03 11:56:13 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	rd = mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", "") | 
					
						
							|  |  |  | 	objInfo, err = objLayer.PutObject(ctx, bucket, object, rd, ObjectOptions{ | 
					
						
							|  |  |  | 		Versioned: true, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-03 03:29:16 +08:00
										 |  |  | 	setDisks(nil, nil, nil, nil) | 
					
						
							| 
									
										
										
										
											2021-09-03 11:56:13 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Create delete marker under quorum.
 | 
					
						
							|  |  |  | 	_, err = objLayer.DeleteObject(ctx, bucket, object, ObjectOptions{ | 
					
						
							|  |  |  | 		Versioned: true, | 
					
						
							|  |  |  | 		VersionID: objInfo.VersionID, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-03 03:29:16 +08:00
										 |  |  | 	setDisks(orgDisks[:4]...) | 
					
						
							| 
									
										
										
										
											2021-09-03 11:56:13 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-09 01:04:20 +08:00
										 |  |  | 	disk = getDisk(0) | 
					
						
							|  |  |  | 	fileInfoPreHeal, err = disk.ReadVersion(context.Background(), bucket, object, "", false) | 
					
						
							| 
									
										
										
										
											2021-09-03 11:56:13 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if fileInfoPreHeal.NumVersions != 3 { | 
					
						
							|  |  |  | 		t.Fatalf("Expected versions 3, got %d", fileInfoPreHeal.NumVersions) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err = objLayer.HealObjects(ctx, bucket, "", madmin.HealOpts{Remove: true}, | 
					
						
							|  |  |  | 		func(bucket, object, vid string) error { | 
					
						
							|  |  |  | 			_, err := objLayer.HealObject(ctx, bucket, object, vid, madmin.HealOpts{Remove: true}) | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		}); err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-09 01:04:20 +08:00
										 |  |  | 	disk = getDisk(0) | 
					
						
							|  |  |  | 	fileInfoPostHeal, err = disk.ReadVersion(context.Background(), bucket, object, "", false) | 
					
						
							| 
									
										
										
										
											2021-09-03 11:56:13 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if fileInfoPostHeal.NumVersions != 2 { | 
					
						
							|  |  |  | 		t.Fatalf("Expected versions 2, got %d", fileInfoPreHeal.NumVersions) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-09-03 08:45:30 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-29 07:33:03 +08:00
										 |  |  | func TestHealCorrectQuorum(t *testing.T) { | 
					
						
							|  |  |  | 	ctx, cancel := context.WithCancel(context.Background()) | 
					
						
							|  |  |  | 	defer cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	resetGlobalHealState() | 
					
						
							|  |  |  | 	defer resetGlobalHealState() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	nDisks := 32 | 
					
						
							|  |  |  | 	fsDirs, err := getRandomDisks(nDisks) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	defer removeRoots(fsDirs) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pools := mustGetPoolEndpoints(fsDirs[:16]...) | 
					
						
							|  |  |  | 	pools = append(pools, mustGetPoolEndpoints(fsDirs[16:]...)...) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Everything is fine, should return nil
 | 
					
						
							|  |  |  | 	objLayer, _, err := initObjectLayer(ctx, pools) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	bucket := getRandomBucketName() | 
					
						
							|  |  |  | 	object := getRandomObjectName() | 
					
						
							|  |  |  | 	data := bytes.Repeat([]byte("a"), 5*1024*1024) | 
					
						
							|  |  |  | 	var opts ObjectOptions | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-26 08:51:32 +08:00
										 |  |  | 	err = objLayer.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{}) | 
					
						
							| 
									
										
										
										
											2021-12-29 07:33:03 +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.
 | 
					
						
							| 
									
										
										
										
											2022-08-30 07:57:16 +08:00
										 |  |  | 	res, err := objLayer.NewMultipartUpload(ctx, bucket, object, opts) | 
					
						
							| 
									
										
										
										
											2021-12-29 07:33:03 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to create a multipart upload - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var uploadedParts []CompletePart | 
					
						
							|  |  |  | 	for _, partID := range []int{2, 1} { | 
					
						
							| 
									
										
										
										
											2022-08-30 07:57:16 +08:00
										 |  |  | 		pInfo, err1 := objLayer.PutObjectPart(ctx, bucket, object, res.UploadID, partID, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", ""), opts) | 
					
						
							| 
									
										
										
										
											2021-12-29 07:33:03 +08:00
										 |  |  | 		if err1 != nil { | 
					
						
							|  |  |  | 			t.Fatalf("Failed to upload a part - %v", err1) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		uploadedParts = append(uploadedParts, CompletePart{ | 
					
						
							|  |  |  | 			PartNumber: pInfo.PartNumber, | 
					
						
							|  |  |  | 			ETag:       pInfo.ETag, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-30 07:57:16 +08:00
										 |  |  | 	_, err = objLayer.CompleteMultipartUpload(ctx, bucket, object, res.UploadID, uploadedParts, ObjectOptions{}) | 
					
						
							| 
									
										
										
										
											2021-12-29 07:33:03 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2022-07-19 23:35:29 +08:00
										 |  |  | 		t.Fatalf("Failed to complete multipart upload - got: %v", err) | 
					
						
							| 
									
										
										
										
											2021-12-29 07:33:03 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-04 02:22:58 +08:00
										 |  |  | 	cfgFile := pathJoin(bucketMetaPrefix, bucket, ".test.bin") | 
					
						
							| 
									
										
										
										
											2021-12-29 07:33:03 +08:00
										 |  |  | 	if err = saveConfig(ctx, objLayer, cfgFile, data); err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	hopts := madmin.HealOpts{ | 
					
						
							|  |  |  | 		DryRun:   false, | 
					
						
							|  |  |  | 		Remove:   true, | 
					
						
							|  |  |  | 		ScanMode: madmin.HealNormalScan, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Test 1: Remove the object backend files from the first disk.
 | 
					
						
							|  |  |  | 	z := objLayer.(*erasureServerPools) | 
					
						
							|  |  |  | 	for _, set := range z.serverPools { | 
					
						
							|  |  |  | 		er := set.sets[0] | 
					
						
							|  |  |  | 		erasureDisks := er.getDisks() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		fileInfos, errs := readAllFileInfo(ctx, erasureDisks, bucket, object, "", false) | 
					
						
							| 
									
										
										
										
											2022-07-21 22:25:54 +08:00
										 |  |  | 		nfi, err := getLatestFileInfo(ctx, fileInfos, er.defaultParityCount, errs) | 
					
						
							| 
									
										
										
										
											2021-12-29 07:33:03 +08:00
										 |  |  | 		if errors.Is(err, errFileNotFound) { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatalf("Failed to getLatestFileInfo - %v", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		for i := 0; i < nfi.Erasure.ParityBlocks; i++ { | 
					
						
							| 
									
										
										
										
											2022-07-12 00:15:54 +08:00
										 |  |  | 			erasureDisks[i].Delete(context.Background(), bucket, pathJoin(object, xlStorageFormatFile), DeleteOptions{ | 
					
						
							|  |  |  | 				Recursive: false, | 
					
						
							|  |  |  | 				Force:     false, | 
					
						
							|  |  |  | 			}) | 
					
						
							| 
									
										
										
										
											2021-12-29 07:33:03 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Try healing now, it should heal the content properly.
 | 
					
						
							|  |  |  | 		_, err = objLayer.HealObject(ctx, bucket, object, "", hopts) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatal(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		fileInfos, errs = readAllFileInfo(ctx, erasureDisks, bucket, object, "", false) | 
					
						
							|  |  |  | 		if countErrs(errs, nil) != len(fileInfos) { | 
					
						
							|  |  |  | 			t.Fatal("Expected all xl.meta healed, but partial heal detected") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		fileInfos, errs = readAllFileInfo(ctx, erasureDisks, minioMetaBucket, cfgFile, "", false) | 
					
						
							| 
									
										
										
										
											2022-07-21 22:25:54 +08:00
										 |  |  | 		nfi, err = getLatestFileInfo(ctx, fileInfos, er.defaultParityCount, errs) | 
					
						
							| 
									
										
										
										
											2021-12-29 07:33:03 +08:00
										 |  |  | 		if errors.Is(err, errFileNotFound) { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatalf("Failed to getLatestFileInfo - %v", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		for i := 0; i < nfi.Erasure.ParityBlocks; i++ { | 
					
						
							| 
									
										
										
										
											2022-07-12 00:15:54 +08:00
										 |  |  | 			erasureDisks[i].Delete(context.Background(), minioMetaBucket, pathJoin(cfgFile, xlStorageFormatFile), DeleteOptions{ | 
					
						
							|  |  |  | 				Recursive: false, | 
					
						
							|  |  |  | 				Force:     false, | 
					
						
							|  |  |  | 			}) | 
					
						
							| 
									
										
										
										
											2021-12-29 07:33:03 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Try healing now, it should heal the content properly.
 | 
					
						
							|  |  |  | 		_, err = objLayer.HealObject(ctx, minioMetaBucket, cfgFile, "", hopts) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatal(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		fileInfos, errs = readAllFileInfo(ctx, erasureDisks, minioMetaBucket, cfgFile, "", false) | 
					
						
							|  |  |  | 		if countErrs(errs, nil) != len(fileInfos) { | 
					
						
							|  |  |  | 			t.Fatal("Expected all xl.meta healed, but partial heal detected") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-26 01:01:44 +08:00
										 |  |  | func TestHealObjectCorruptedPools(t *testing.T) { | 
					
						
							|  |  |  | 	ctx, cancel := context.WithCancel(context.Background()) | 
					
						
							|  |  |  | 	defer cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	resetGlobalHealState() | 
					
						
							|  |  |  | 	defer resetGlobalHealState() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	nDisks := 32 | 
					
						
							|  |  |  | 	fsDirs, err := getRandomDisks(nDisks) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	defer removeRoots(fsDirs) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pools := mustGetPoolEndpoints(fsDirs[:16]...) | 
					
						
							|  |  |  | 	pools = append(pools, mustGetPoolEndpoints(fsDirs[16:]...)...) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Everything is fine, should return nil
 | 
					
						
							|  |  |  | 	objLayer, _, err := initObjectLayer(ctx, pools) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	bucket := getRandomBucketName() | 
					
						
							|  |  |  | 	object := getRandomObjectName() | 
					
						
							|  |  |  | 	data := bytes.Repeat([]byte("a"), 5*1024*1024) | 
					
						
							|  |  |  | 	var opts ObjectOptions | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-26 08:51:32 +08:00
										 |  |  | 	err = objLayer.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{}) | 
					
						
							| 
									
										
										
										
											2021-12-26 01:01:44 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to make a bucket - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-20 23:07:45 +08:00
										 |  |  | 	// Upload a multipart object in the second pool
 | 
					
						
							|  |  |  | 	z := objLayer.(*erasureServerPools) | 
					
						
							|  |  |  | 	set := z.serverPools[1] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-30 07:57:16 +08:00
										 |  |  | 	res, err := set.NewMultipartUpload(ctx, bucket, object, opts) | 
					
						
							| 
									
										
										
										
											2021-12-26 01:01:44 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to create a multipart upload - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-08-30 07:57:16 +08:00
										 |  |  | 	uploadID := res.UploadID | 
					
						
							| 
									
										
										
										
											2021-12-26 01:01:44 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	var uploadedParts []CompletePart | 
					
						
							|  |  |  | 	for _, partID := range []int{2, 1} { | 
					
						
							| 
									
										
										
										
											2022-06-20 23:07:45 +08:00
										 |  |  | 		pInfo, err1 := set.PutObjectPart(ctx, bucket, object, uploadID, partID, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", ""), opts) | 
					
						
							| 
									
										
										
										
											2021-12-26 01:01: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, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-20 23:07:45 +08:00
										 |  |  | 	_, err = set.CompleteMultipartUpload(ctx, bucket, object, uploadID, uploadedParts, ObjectOptions{}) | 
					
						
							| 
									
										
										
										
											2021-12-26 01:01:44 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to complete multipart upload - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Test 1: Remove the object backend files from the first disk.
 | 
					
						
							| 
									
										
										
										
											2022-06-20 23:07:45 +08:00
										 |  |  | 	er := set.sets[0] | 
					
						
							|  |  |  | 	erasureDisks := er.getDisks() | 
					
						
							|  |  |  | 	firstDisk := erasureDisks[0] | 
					
						
							| 
									
										
										
										
											2022-07-12 00:15:54 +08:00
										 |  |  | 	err = firstDisk.Delete(context.Background(), bucket, pathJoin(object, xlStorageFormatFile), DeleteOptions{ | 
					
						
							|  |  |  | 		Recursive: false, | 
					
						
							|  |  |  | 		Force:     false, | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2022-06-20 23:07:45 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to delete a file - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-12-26 01:01:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-20 23:07:45 +08:00
										 |  |  | 	_, err = objLayer.HealObject(ctx, bucket, object, "", madmin.HealOpts{ScanMode: madmin.HealNormalScan}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to heal object - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-12-26 01:01:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-20 23:07:45 +08:00
										 |  |  | 	fileInfos, errs := readAllFileInfo(ctx, erasureDisks, bucket, object, "", false) | 
					
						
							| 
									
										
										
										
											2022-07-21 22:25:54 +08:00
										 |  |  | 	fi, err := getLatestFileInfo(ctx, fileInfos, er.defaultParityCount, errs) | 
					
						
							| 
									
										
										
										
											2022-06-20 23:07:45 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to getLatestFileInfo - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-12-26 01:01:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-20 23:07:45 +08:00
										 |  |  | 	if _, err = firstDisk.StatInfoFile(context.Background(), bucket, object+"/"+xlStorageFormatFile, false); err != nil { | 
					
						
							|  |  |  | 		t.Errorf("Expected xl.meta file to be present but stat failed - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-12-26 01:01:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-12 00:15:54 +08:00
										 |  |  | 	err = firstDisk.Delete(context.Background(), bucket, pathJoin(object, fi.DataDir, "part.1"), DeleteOptions{ | 
					
						
							|  |  |  | 		Recursive: false, | 
					
						
							|  |  |  | 		Force:     false, | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2022-06-20 23:07:45 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Errorf("Failure during deleting part.1 - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-12-26 01:01:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-20 23:07:45 +08:00
										 |  |  | 	err = firstDisk.WriteAll(context.Background(), bucket, pathJoin(object, fi.DataDir, "part.1"), []byte{}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Errorf("Failure during creating part.1 - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-12-26 01:01:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-20 23:07:45 +08:00
										 |  |  | 	_, err = objLayer.HealObject(ctx, bucket, object, "", madmin.HealOpts{DryRun: false, Remove: true, ScanMode: madmin.HealDeepScan}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Errorf("Expected nil but received %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-12-26 01:01:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-20 23:07:45 +08:00
										 |  |  | 	fileInfos, errs = readAllFileInfo(ctx, erasureDisks, bucket, object, "", false) | 
					
						
							| 
									
										
										
										
											2022-07-21 22:25:54 +08:00
										 |  |  | 	nfi, err := getLatestFileInfo(ctx, fileInfos, er.defaultParityCount, errs) | 
					
						
							| 
									
										
										
										
											2022-06-20 23:07:45 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to getLatestFileInfo - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-12-26 01:01:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-20 23:07:45 +08:00
										 |  |  | 	fi.DiskMTime = time.Time{} | 
					
						
							|  |  |  | 	nfi.DiskMTime = time.Time{} | 
					
						
							|  |  |  | 	if !reflect.DeepEqual(fi, nfi) { | 
					
						
							|  |  |  | 		t.Fatalf("FileInfo not equal after healing: %v != %v", fi, nfi) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-12-26 01:01:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-12 00:15:54 +08:00
										 |  |  | 	err = firstDisk.Delete(context.Background(), bucket, pathJoin(object, fi.DataDir, "part.1"), DeleteOptions{ | 
					
						
							|  |  |  | 		Recursive: false, | 
					
						
							|  |  |  | 		Force:     false, | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2022-06-20 23:07:45 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Errorf("Failure during deleting part.1 - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-12-26 01:01:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-20 23:07:45 +08:00
										 |  |  | 	bdata := bytes.Repeat([]byte("b"), int(nfi.Size)) | 
					
						
							|  |  |  | 	err = firstDisk.WriteAll(context.Background(), bucket, pathJoin(object, fi.DataDir, "part.1"), bdata) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Errorf("Failure during creating part.1 - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-12-26 01:01:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-20 23:07:45 +08:00
										 |  |  | 	_, err = objLayer.HealObject(ctx, bucket, object, "", madmin.HealOpts{DryRun: false, Remove: true, ScanMode: madmin.HealDeepScan}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Errorf("Expected nil but received %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-12-26 01:01:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-20 23:07:45 +08:00
										 |  |  | 	fileInfos, errs = readAllFileInfo(ctx, erasureDisks, bucket, object, "", false) | 
					
						
							| 
									
										
										
										
											2022-07-21 22:25:54 +08:00
										 |  |  | 	nfi, err = getLatestFileInfo(ctx, fileInfos, er.defaultParityCount, errs) | 
					
						
							| 
									
										
										
										
											2022-06-20 23:07:45 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to getLatestFileInfo - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-12-26 01:01:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-20 23:07:45 +08:00
										 |  |  | 	fi.DiskMTime = time.Time{} | 
					
						
							|  |  |  | 	nfi.DiskMTime = time.Time{} | 
					
						
							|  |  |  | 	if !reflect.DeepEqual(fi, nfi) { | 
					
						
							|  |  |  | 		t.Fatalf("FileInfo not equal after healing: %v != %v", fi, nfi) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-12-26 01:01:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-20 23:07:45 +08:00
										 |  |  | 	// Test 4: checks if HealObject returns an error when xl.meta is not found
 | 
					
						
							|  |  |  | 	// in more than read quorum number of disks, to create a corrupted situation.
 | 
					
						
							|  |  |  | 	for i := 0; i <= nfi.Erasure.DataBlocks; i++ { | 
					
						
							| 
									
										
										
										
											2022-07-12 00:15:54 +08:00
										 |  |  | 		erasureDisks[i].Delete(context.Background(), bucket, pathJoin(object, xlStorageFormatFile), DeleteOptions{ | 
					
						
							|  |  |  | 			Recursive: false, | 
					
						
							|  |  |  | 			Force:     false, | 
					
						
							|  |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2022-06-20 23:07:45 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-12-26 01:01:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-20 23:07:45 +08:00
										 |  |  | 	// Try healing now, expect to receive errFileNotFound.
 | 
					
						
							|  |  |  | 	_, err = objLayer.HealObject(ctx, bucket, object, "", madmin.HealOpts{DryRun: false, Remove: true, ScanMode: madmin.HealDeepScan}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2021-12-26 01:01:44 +08:00
										 |  |  | 		if _, ok := err.(ObjectNotFound); !ok { | 
					
						
							|  |  |  | 			t.Errorf("Expect %v but received %v", ObjectNotFound{Bucket: bucket, Object: object}, err) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-06-20 23:07:45 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-12-26 01:01:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-20 23:07:45 +08:00
										 |  |  | 	// since majority of xl.meta's are not available, object should be successfully deleted.
 | 
					
						
							|  |  |  | 	_, err = objLayer.GetObjectInfo(ctx, bucket, object, ObjectOptions{}) | 
					
						
							|  |  |  | 	if _, ok := err.(ObjectNotFound); !ok { | 
					
						
							|  |  |  | 		t.Errorf("Expect %v but received %v", ObjectNotFound{Bucket: bucket, Object: object}, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for i := 0; i < (nfi.Erasure.DataBlocks + nfi.Erasure.ParityBlocks); i++ { | 
					
						
							|  |  |  | 		_, err = erasureDisks[i].StatInfoFile(context.Background(), bucket, pathJoin(object, xlStorageFormatFile), false) | 
					
						
							|  |  |  | 		if err == nil { | 
					
						
							|  |  |  | 			t.Errorf("Expected xl.meta file to be not present, but succeeeded") | 
					
						
							| 
									
										
										
										
											2021-12-26 01:01:44 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-05 10:24:34 +08:00
										 |  |  | func TestHealObjectCorruptedXLMeta(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2020-04-15 08:52:38 +08:00
										 |  |  | 	ctx, cancel := context.WithCancel(context.Background()) | 
					
						
							|  |  |  | 	defer cancel() | 
					
						
							| 
									
										
										
										
											2020-03-03 08:29:30 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-15 08:52:38 +08:00
										 |  |  | 	resetGlobalHealState() | 
					
						
							| 
									
										
										
										
											2020-03-03 08:29:30 +08:00
										 |  |  | 	defer resetGlobalHealState() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-27 05:57:44 +08:00
										 |  |  | 	nDisks := 16 | 
					
						
							|  |  |  | 	fsDirs, err := getRandomDisks(nDisks) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	defer removeRoots(fsDirs) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Everything is fine, should return nil
 | 
					
						
							| 
									
										
										
										
											2021-01-27 12:47:42 +08:00
										 |  |  | 	objLayer, _, err := initObjectLayer(ctx, mustGetPoolEndpoints(fsDirs...)) | 
					
						
							| 
									
										
										
										
											2019-03-27 05:57:44 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-03 08:45:30 +08:00
										 |  |  | 	bucket := getRandomBucketName() | 
					
						
							|  |  |  | 	object := getRandomObjectName() | 
					
						
							| 
									
										
										
										
											2019-03-27 05:57:44 +08:00
										 |  |  | 	data := bytes.Repeat([]byte("a"), 5*1024*1024) | 
					
						
							|  |  |  | 	var opts ObjectOptions | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-26 08:51:32 +08:00
										 |  |  | 	err = objLayer.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{}) | 
					
						
							| 
									
										
										
										
											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.
 | 
					
						
							| 
									
										
										
										
											2022-08-30 07:57:16 +08:00
										 |  |  | 	res, err := objLayer.NewMultipartUpload(ctx, 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} { | 
					
						
							| 
									
										
										
										
											2022-08-30 07:57:16 +08:00
										 |  |  | 		pInfo, err1 := objLayer.PutObjectPart(ctx, bucket, object, res.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, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-30 07:57:16 +08:00
										 |  |  | 	_, err = objLayer.CompleteMultipartUpload(ctx, bucket, object, res.UploadID, uploadedParts, ObjectOptions{}) | 
					
						
							| 
									
										
										
										
											2019-03-27 05:57:44 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to complete multipart upload - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-02 05:50:33 +08:00
										 |  |  | 	z := objLayer.(*erasureServerPools) | 
					
						
							|  |  |  | 	er := z.serverPools[0].sets[0] | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	erasureDisks := er.getDisks() | 
					
						
							|  |  |  | 	firstDisk := erasureDisks[0] | 
					
						
							| 
									
										
										
										
											2019-03-27 05:57:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-05 10:24:34 +08:00
										 |  |  | 	// Test 1: Remove the object backend files from the first disk.
 | 
					
						
							| 
									
										
										
										
											2021-01-08 11:27:31 +08:00
										 |  |  | 	fileInfos, errs := readAllFileInfo(ctx, erasureDisks, bucket, object, "", false) | 
					
						
							| 
									
										
										
										
											2022-07-21 22:25:54 +08:00
										 |  |  | 	fi, err := getLatestFileInfo(ctx, fileInfos, er.defaultParityCount, errs) | 
					
						
							| 
									
										
										
										
											2019-03-27 05:57:44 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 		t.Fatalf("Failed to getLatestFileInfo - %v", err) | 
					
						
							| 
									
										
										
										
											2019-03-27 05:57:44 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-12 00:15:54 +08:00
										 |  |  | 	err = firstDisk.Delete(context.Background(), bucket, pathJoin(object, xlStorageFormatFile), DeleteOptions{ | 
					
						
							|  |  |  | 		Recursive: false, | 
					
						
							|  |  |  | 		Force:     false, | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2019-07-13 07:29:44 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2022-03-05 10:24:34 +08:00
										 |  |  | 		t.Fatalf("Failed to delete a file - %v", err) | 
					
						
							| 
									
										
										
										
											2019-07-13 07:29:44 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-05 10:24:34 +08:00
										 |  |  | 	_, err = objLayer.HealObject(ctx, bucket, object, "", madmin.HealOpts{ScanMode: madmin.HealNormalScan}) | 
					
						
							| 
									
										
										
										
											2019-07-13 07:29:44 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2022-03-05 10:24:34 +08:00
										 |  |  | 		t.Fatalf("Failed to heal object - %v", err) | 
					
						
							| 
									
										
										
										
											2019-07-13 07:29:44 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-05 10:24:34 +08:00
										 |  |  | 	if _, err = firstDisk.StatInfoFile(context.Background(), bucket, object+"/"+xlStorageFormatFile, false); err != nil { | 
					
						
							|  |  |  | 		t.Errorf("Expected xl.meta file to be present but stat failed - %v", err) | 
					
						
							| 
									
										
										
										
											2019-07-13 07:29:44 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-08 11:27:31 +08:00
										 |  |  | 	fileInfos, errs = readAllFileInfo(ctx, erasureDisks, bucket, object, "", false) | 
					
						
							| 
									
										
										
										
											2022-07-21 22:25:54 +08:00
										 |  |  | 	nfi1, err := getLatestFileInfo(ctx, fileInfos, er.defaultParityCount, errs) | 
					
						
							| 
									
										
										
										
											2019-07-13 07:29:44 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 		t.Fatalf("Failed to getLatestFileInfo - %v", err) | 
					
						
							| 
									
										
										
										
											2019-07-13 07:29:44 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-21 03:49:05 +08:00
										 |  |  | 	fi.DiskMTime = time.Time{} | 
					
						
							|  |  |  | 	nfi1.DiskMTime = time.Time{} | 
					
						
							| 
									
										
										
										
											2022-03-05 10:24:34 +08:00
										 |  |  | 	if !reflect.DeepEqual(fi, nfi1) { | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 		t.Fatalf("FileInfo not equal after healing") | 
					
						
							| 
									
										
										
										
											2019-10-02 04:12:15 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-05 10:24:34 +08:00
										 |  |  | 	// Test 2: Test with a corrupted xl.meta
 | 
					
						
							|  |  |  | 	err = firstDisk.WriteAll(context.Background(), bucket, pathJoin(object, xlStorageFormatFile), []byte("abcd")) | 
					
						
							| 
									
										
										
										
											2019-10-02 04:12:15 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Errorf("Failure during creating part.1 - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-05 10:24:34 +08:00
										 |  |  | 	_, err = objLayer.HealObject(ctx, bucket, object, "", madmin.HealOpts{ScanMode: madmin.HealNormalScan}) | 
					
						
							| 
									
										
										
										
											2019-10-02 04:12:15 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Errorf("Expected nil but received %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-08 11:27:31 +08:00
										 |  |  | 	fileInfos, errs = readAllFileInfo(ctx, erasureDisks, bucket, object, "", false) | 
					
						
							| 
									
										
										
										
											2022-07-21 22:25:54 +08:00
										 |  |  | 	nfi2, err := getLatestFileInfo(ctx, fileInfos, er.defaultParityCount, errs) | 
					
						
							| 
									
										
										
										
											2019-10-02 04:12:15 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 		t.Fatalf("Failed to getLatestFileInfo - %v", err) | 
					
						
							| 
									
										
										
										
											2019-10-02 04:12:15 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-21 03:49:05 +08:00
										 |  |  | 	fi.DiskMTime = time.Time{} | 
					
						
							|  |  |  | 	nfi2.DiskMTime = time.Time{} | 
					
						
							| 
									
										
										
										
											2022-03-05 10:24:34 +08:00
										 |  |  | 	if !reflect.DeepEqual(fi, nfi2) { | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 		t.Fatalf("FileInfo not equal after healing") | 
					
						
							| 
									
										
										
										
											2019-10-02 04:12:15 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-05 10:24:34 +08:00
										 |  |  | 	// Test 3: checks if HealObject returns an error when xl.meta is not found
 | 
					
						
							| 
									
										
										
										
											2019-07-13 07:29:44 +08:00
										 |  |  | 	// in more than read quorum number of disks, to create a corrupted situation.
 | 
					
						
							| 
									
										
										
										
											2022-03-05 10:24:34 +08:00
										 |  |  | 	for i := 0; i <= nfi2.Erasure.DataBlocks; i++ { | 
					
						
							| 
									
										
										
										
											2022-07-12 00:15:54 +08:00
										 |  |  | 		erasureDisks[i].Delete(context.Background(), bucket, pathJoin(object, xlStorageFormatFile), DeleteOptions{ | 
					
						
							|  |  |  | 			Recursive: false, | 
					
						
							|  |  |  | 			Force:     false, | 
					
						
							|  |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2019-03-27 05:57:44 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-22 05:18:32 +08:00
										 |  |  | 	// Try healing now, expect to receive errFileNotFound.
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	_, err = objLayer.HealObject(ctx, bucket, object, "", madmin.HealOpts{DryRun: false, Remove: true, ScanMode: 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
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	// since majority of xl.meta's are not available, object should be successfully deleted.
 | 
					
						
							| 
									
										
										
										
											2020-04-15 08:52:38 +08:00
										 |  |  | 	_, err = objLayer.GetObjectInfo(ctx, 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) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-05 10:24:34 +08:00
										 |  |  | func TestHealObjectCorruptedParts(t *testing.T) { | 
					
						
							|  |  |  | 	ctx, cancel := context.WithCancel(context.Background()) | 
					
						
							|  |  |  | 	defer cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	resetGlobalHealState() | 
					
						
							|  |  |  | 	defer resetGlobalHealState() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	nDisks := 16 | 
					
						
							|  |  |  | 	fsDirs, err := getRandomDisks(nDisks) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	defer removeRoots(fsDirs) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Everything is fine, should return nil
 | 
					
						
							|  |  |  | 	objLayer, _, err := initObjectLayer(ctx, mustGetPoolEndpoints(fsDirs...)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	bucket := getRandomBucketName() | 
					
						
							|  |  |  | 	object := getRandomObjectName() | 
					
						
							|  |  |  | 	data := bytes.Repeat([]byte("a"), 5*1024*1024) | 
					
						
							|  |  |  | 	var opts ObjectOptions | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-26 08:51:32 +08:00
										 |  |  | 	err = objLayer.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{}) | 
					
						
							| 
									
										
										
										
											2022-03-05 10:24:34 +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.
 | 
					
						
							| 
									
										
										
										
											2022-08-30 07:57:16 +08:00
										 |  |  | 	res, err := objLayer.NewMultipartUpload(ctx, bucket, object, opts) | 
					
						
							| 
									
										
										
										
											2022-03-05 10:24:34 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to create a multipart upload - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var uploadedParts []CompletePart | 
					
						
							|  |  |  | 	for _, partID := range []int{2, 1} { | 
					
						
							| 
									
										
										
										
											2022-08-30 07:57:16 +08:00
										 |  |  | 		pInfo, err1 := objLayer.PutObjectPart(ctx, bucket, object, res.UploadID, partID, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", ""), opts) | 
					
						
							| 
									
										
										
										
											2022-03-05 10:24:34 +08:00
										 |  |  | 		if err1 != nil { | 
					
						
							|  |  |  | 			t.Fatalf("Failed to upload a part - %v", err1) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		uploadedParts = append(uploadedParts, CompletePart{ | 
					
						
							|  |  |  | 			PartNumber: pInfo.PartNumber, | 
					
						
							|  |  |  | 			ETag:       pInfo.ETag, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-30 07:57:16 +08:00
										 |  |  | 	_, err = objLayer.CompleteMultipartUpload(ctx, bucket, object, res.UploadID, uploadedParts, ObjectOptions{}) | 
					
						
							| 
									
										
										
										
											2022-03-05 10:24:34 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to complete multipart upload - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Test 1: Remove the object backend files from the first disk.
 | 
					
						
							|  |  |  | 	z := objLayer.(*erasureServerPools) | 
					
						
							|  |  |  | 	er := z.serverPools[0].sets[0] | 
					
						
							|  |  |  | 	erasureDisks := er.getDisks() | 
					
						
							|  |  |  | 	firstDisk := erasureDisks[0] | 
					
						
							|  |  |  | 	secondDisk := erasureDisks[1] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	fileInfos, errs := readAllFileInfo(ctx, erasureDisks, bucket, object, "", false) | 
					
						
							| 
									
										
										
										
											2022-07-21 22:25:54 +08:00
										 |  |  | 	fi, err := getLatestFileInfo(ctx, fileInfos, er.defaultParityCount, errs) | 
					
						
							| 
									
										
										
										
											2022-03-05 10:24:34 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to getLatestFileInfo - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	part1Disk1Origin, err := firstDisk.ReadAll(context.Background(), bucket, pathJoin(object, fi.DataDir, "part.1")) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to read a file - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	part1Disk2Origin, err := secondDisk.ReadAll(context.Background(), bucket, pathJoin(object, fi.DataDir, "part.1")) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to read a file - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Test 1, remove part.1
 | 
					
						
							| 
									
										
										
										
											2022-07-12 00:15:54 +08:00
										 |  |  | 	err = firstDisk.Delete(context.Background(), bucket, pathJoin(object, fi.DataDir, "part.1"), DeleteOptions{ | 
					
						
							|  |  |  | 		Recursive: false, | 
					
						
							|  |  |  | 		Force:     false, | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2022-03-05 10:24:34 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to delete a file - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	_, err = objLayer.HealObject(ctx, bucket, object, "", madmin.HealOpts{ScanMode: madmin.HealNormalScan}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to heal object - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	part1Replaced, err := firstDisk.ReadAll(context.Background(), bucket, pathJoin(object, fi.DataDir, "part.1")) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to read a file - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if !reflect.DeepEqual(part1Disk1Origin, part1Replaced) { | 
					
						
							|  |  |  | 		t.Fatalf("part.1 not healed correctly") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Test 2, Corrupt part.1
 | 
					
						
							|  |  |  | 	err = firstDisk.WriteAll(context.Background(), bucket, pathJoin(object, fi.DataDir, "part.1"), []byte("foobytes")) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to write a file - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	_, err = objLayer.HealObject(ctx, bucket, object, "", madmin.HealOpts{ScanMode: madmin.HealNormalScan}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to heal object - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	part1Replaced, err = firstDisk.ReadAll(context.Background(), bucket, pathJoin(object, fi.DataDir, "part.1")) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to read a file - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if !reflect.DeepEqual(part1Disk1Origin, part1Replaced) { | 
					
						
							|  |  |  | 		t.Fatalf("part.1 not healed correctly") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Test 3, Corrupt one part and remove data in another disk
 | 
					
						
							|  |  |  | 	err = firstDisk.WriteAll(context.Background(), bucket, pathJoin(object, fi.DataDir, "part.1"), []byte("foobytes")) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to write a file - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-12 00:15:54 +08:00
										 |  |  | 	err = secondDisk.Delete(context.Background(), bucket, object, DeleteOptions{ | 
					
						
							|  |  |  | 		Recursive: true, | 
					
						
							|  |  |  | 		Force:     false, | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2022-03-05 10:24:34 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to delete a file - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	_, err = objLayer.HealObject(ctx, bucket, object, "", madmin.HealOpts{ScanMode: madmin.HealNormalScan}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to heal object - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	partReconstructed, err := firstDisk.ReadAll(context.Background(), bucket, pathJoin(object, fi.DataDir, "part.1")) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to read a file - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if !reflect.DeepEqual(part1Disk1Origin, partReconstructed) { | 
					
						
							|  |  |  | 		t.Fatalf("part.1 not healed correctly") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	partReconstructed, err = secondDisk.ReadAll(context.Background(), bucket, pathJoin(object, fi.DataDir, "part.1")) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to read a file - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if !reflect.DeepEqual(part1Disk2Origin, partReconstructed) { | 
					
						
							|  |  |  | 		t.Fatalf("part.1 not healed correctly") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-18 00:25:49 +08:00
										 |  |  | // Tests healing of object.
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | func TestHealObjectErasure(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2020-04-15 08:52:38 +08:00
										 |  |  | 	ctx, cancel := context.WithCancel(context.Background()) | 
					
						
							|  |  |  | 	defer cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-18 00:25:49 +08:00
										 |  |  | 	nDisks := 16 | 
					
						
							|  |  |  | 	fsDirs, err := getRandomDisks(nDisks) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	defer removeRoots(fsDirs) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Everything is fine, should return nil
 | 
					
						
							| 
									
										
										
										
											2021-01-27 12:47:42 +08:00
										 |  |  | 	obj, _, err := initObjectLayer(ctx, mustGetPoolEndpoints(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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-26 08:51:32 +08:00
										 |  |  | 	err = obj.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{}) | 
					
						
							| 
									
										
										
										
											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.
 | 
					
						
							| 
									
										
										
										
											2022-08-30 07:57:16 +08:00
										 |  |  | 	res, err := obj.NewMultipartUpload(ctx, 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} { | 
					
						
							| 
									
										
										
										
											2022-08-30 07:57:16 +08:00
										 |  |  | 		pInfo, err1 := obj.PutObjectPart(ctx, bucket, object, res.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, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	// Remove the object backend files from the first disk.
 | 
					
						
							| 
									
										
										
										
											2020-12-02 05:50:33 +08:00
										 |  |  | 	z := obj.(*erasureServerPools) | 
					
						
							|  |  |  | 	er := z.serverPools[0].sets[0] | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	firstDisk := er.getDisks()[0] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-30 07:57:16 +08:00
										 |  |  | 	_, err = obj.CompleteMultipartUpload(ctx, bucket, object, res.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
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-05 10:24:34 +08:00
										 |  |  | 	// Delete the whole object folder
 | 
					
						
							| 
									
										
										
										
											2022-07-12 00:15:54 +08:00
										 |  |  | 	err = firstDisk.Delete(context.Background(), bucket, object, DeleteOptions{ | 
					
						
							|  |  |  | 		Recursive: true, | 
					
						
							|  |  |  | 		Force:     false, | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2017-03-18 00:25:49 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to delete a file - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	_, err = obj.HealObject(ctx, bucket, object, "", madmin.HealOpts{ScanMode: madmin.HealNormalScan}) | 
					
						
							| 
									
										
										
										
											2017-03-18 00:25:49 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to heal object - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-02 02:50:00 +08:00
										 |  |  | 	if _, err = firstDisk.StatInfoFile(context.Background(), bucket, object+"/"+xlStorageFormatFile, false); err != nil { | 
					
						
							| 
									
										
										
										
											2021-12-26 01:01:44 +08:00
										 |  |  | 		t.Errorf("Expected xl.meta file to be present but stat failed - %v", err) | 
					
						
							| 
									
										
										
										
											2017-03-18 00:25:49 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	erasureDisks := er.getDisks() | 
					
						
							| 
									
										
										
										
											2020-12-02 05:50:33 +08:00
										 |  |  | 	z.serverPools[0].erasureDisksMu.Lock() | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	er.getDisks = func() []StorageAPI { | 
					
						
							| 
									
										
										
										
											2019-11-20 09:42:27 +08:00
										 |  |  | 		// Nil more than half the disks, to remove write quorum.
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 		for i := 0; i <= len(erasureDisks)/2; i++ { | 
					
						
							|  |  |  | 			erasureDisks[i] = nil | 
					
						
							| 
									
										
										
										
											2019-11-20 09:42:27 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 		return erasureDisks | 
					
						
							| 
									
										
										
										
											2017-03-18 00:25:49 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-12-02 05:50:33 +08:00
										 |  |  | 	z.serverPools[0].erasureDisksMu.Unlock() | 
					
						
							| 
									
										
										
										
											2017-03-18 00:25:49 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Try healing now, expect to receive errDiskNotFound.
 | 
					
						
							| 
									
										
										
										
											2022-02-09 12:08:23 +08:00
										 |  |  | 	_, err = obj.HealObject(ctx, bucket, object, "", madmin.HealOpts{ | 
					
						
							|  |  |  | 		ScanMode: madmin.HealDeepScan, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	// since majority of xl.meta's are not available, object quorum
 | 
					
						
							|  |  |  | 	// can't be read properly will be deleted automatically and
 | 
					
						
							|  |  |  | 	// err is nil
 | 
					
						
							| 
									
										
										
										
											2022-06-20 23:07:45 +08:00
										 |  |  | 	if !isErrObjectNotFound(err) { | 
					
						
							| 
									
										
										
										
											2022-02-09 12:08:23 +08:00
										 |  |  | 		t.Fatal(err) | 
					
						
							| 
									
										
										
										
											2017-03-18 00:25:49 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2019-08-02 05:13:06 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Tests healing of empty directories
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | func TestHealEmptyDirectoryErasure(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2020-04-15 08:52:38 +08:00
										 |  |  | 	ctx, cancel := context.WithCancel(context.Background()) | 
					
						
							|  |  |  | 	defer cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-02 05:13:06 +08:00
										 |  |  | 	nDisks := 16 | 
					
						
							|  |  |  | 	fsDirs, err := getRandomDisks(nDisks) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer removeRoots(fsDirs) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Everything is fine, should return nil
 | 
					
						
							| 
									
										
										
										
											2021-01-27 12:47:42 +08:00
										 |  |  | 	obj, _, err := initObjectLayer(ctx, mustGetPoolEndpoints(fsDirs...)) | 
					
						
							| 
									
										
										
										
											2019-08-02 05:13:06 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	bucket := "bucket" | 
					
						
							|  |  |  | 	object := "empty-dir/" | 
					
						
							|  |  |  | 	var opts ObjectOptions | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-26 08:51:32 +08:00
										 |  |  | 	err = obj.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{}) | 
					
						
							| 
									
										
										
										
											2019-08-02 05:13:06 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to make a bucket - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Upload an empty directory
 | 
					
						
							| 
									
										
										
										
											2020-04-15 08:52:38 +08:00
										 |  |  | 	_, err = obj.PutObject(ctx, bucket, object, mustGetPutObjReader(t, | 
					
						
							| 
									
										
										
										
											2019-11-20 09:42:27 +08:00
										 |  |  | 		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.
 | 
					
						
							| 
									
										
										
										
											2020-12-02 05:50:33 +08:00
										 |  |  | 	z := obj.(*erasureServerPools) | 
					
						
							|  |  |  | 	er := z.serverPools[0].sets[0] | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	firstDisk := er.getDisks()[0] | 
					
						
							| 
									
										
										
										
											2020-09-19 23:39:41 +08:00
										 |  |  | 	err = firstDisk.DeleteVol(context.Background(), pathJoin(bucket, encodeDirObject(object)), true) | 
					
						
							| 
									
										
										
										
											2019-08-02 05:13:06 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to delete a file - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Heal the object
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	hr, err := obj.HealObject(ctx, bucket, object, "", madmin.HealOpts{ScanMode: madmin.HealNormalScan}) | 
					
						
							| 
									
										
										
										
											2019-08-02 05:13:06 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to heal object - %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check if the empty directory is restored in the first disk
 | 
					
						
							| 
									
										
										
										
											2020-09-19 23:39:41 +08:00
										 |  |  | 	_, err = firstDisk.StatVol(context.Background(), pathJoin(bucket, encodeDirObject(object))) | 
					
						
							| 
									
										
										
										
											2019-08-02 05:13:06 +08:00
										 |  |  | 	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
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	hr, err = obj.HealObject(ctx, bucket, object, "", madmin.HealOpts{ScanMode: madmin.HealNormalScan}) | 
					
						
							| 
									
										
										
										
											2019-08-02 05:13:06 +08:00
										 |  |  | 	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) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-12-04 01:26:30 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | func TestHealLastDataShard(t *testing.T) { | 
					
						
							|  |  |  | 	tests := []struct { | 
					
						
							|  |  |  | 		name     string | 
					
						
							|  |  |  | 		dataSize int64 | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{"4KiB", 4 * humanize.KiByte}, | 
					
						
							|  |  |  | 		{"64KiB", 64 * humanize.KiByte}, | 
					
						
							|  |  |  | 		{"128KiB", 128 * humanize.KiByte}, | 
					
						
							|  |  |  | 		{"1MiB", 1 * humanize.MiByte}, | 
					
						
							|  |  |  | 		{"5MiB", 5 * humanize.MiByte}, | 
					
						
							|  |  |  | 		{"10MiB", 10 * humanize.MiByte}, | 
					
						
							| 
									
										
										
										
											2021-12-12 01:04:07 +08:00
										 |  |  | 		{"5MiB-1KiB", 5*humanize.MiByte - 1*humanize.KiByte}, | 
					
						
							|  |  |  | 		{"10MiB-1Kib", 10*humanize.MiByte - 1*humanize.KiByte}, | 
					
						
							| 
									
										
										
										
											2021-12-04 01:26:30 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, test := range tests { | 
					
						
							|  |  |  | 		t.Run(test.name, func(t *testing.T) { | 
					
						
							|  |  |  | 			ctx, cancel := context.WithCancel(context.Background()) | 
					
						
							|  |  |  | 			defer cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			nDisks := 16 | 
					
						
							|  |  |  | 			fsDirs, err := getRandomDisks(nDisks) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				t.Fatal(err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			defer removeRoots(fsDirs) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			obj, _, err := initObjectLayer(ctx, mustGetPoolEndpoints(fsDirs...)) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				t.Fatal(err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			bucket := "bucket" | 
					
						
							|  |  |  | 			object := "object" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			data := make([]byte, test.dataSize) | 
					
						
							|  |  |  | 			_, err = rand.Read(data) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				t.Fatal(err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			var opts ObjectOptions | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-26 08:51:32 +08:00
										 |  |  | 			err = obj.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{}) | 
					
						
							| 
									
										
										
										
											2021-12-04 01:26:30 +08:00
										 |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				t.Fatalf("Failed to make a bucket - %v", err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-22 02:08:26 +08:00
										 |  |  | 			_, err = obj.PutObject(ctx, bucket, object, | 
					
						
							|  |  |  | 				mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", ""), opts) | 
					
						
							| 
									
										
										
										
											2021-12-04 01:26:30 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				t.Fatal(err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			actualH := sha256.New() | 
					
						
							|  |  |  | 			_, err = io.Copy(actualH, bytes.NewReader(data)) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			actualSha256 := actualH.Sum(nil) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			z := obj.(*erasureServerPools) | 
					
						
							|  |  |  | 			er := z.serverPools[0].getHashedSet(object) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			disks := er.getDisks() | 
					
						
							|  |  |  | 			distribution := hashOrder(pathJoin(bucket, object), nDisks) | 
					
						
							|  |  |  | 			shuffledDisks := shuffleDisks(disks, distribution) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// remove last data shard
 | 
					
						
							|  |  |  | 			err = removeAll(pathJoin(shuffledDisks[11].String(), bucket, object)) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				t.Fatalf("Failed to delete a file - %v", err) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2021-12-22 02:08:26 +08:00
										 |  |  | 			_, err = obj.HealObject(ctx, bucket, object, "", madmin.HealOpts{ | 
					
						
							|  |  |  | 				ScanMode: madmin.HealNormalScan, | 
					
						
							|  |  |  | 			}) | 
					
						
							| 
									
										
										
										
											2021-12-04 01:26:30 +08:00
										 |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				t.Fatal(err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			firstGr, err := obj.GetObjectNInfo(ctx, bucket, object, nil, nil, noLock, ObjectOptions{}) | 
					
						
							|  |  |  | 			defer firstGr.Close() | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				t.Fatal(err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			firstHealedH := sha256.New() | 
					
						
							|  |  |  | 			_, err = io.Copy(firstHealedH, firstGr) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2021-12-22 02:08:26 +08:00
										 |  |  | 				t.Fatal(err) | 
					
						
							| 
									
										
										
										
											2021-12-04 01:26:30 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 			firstHealedDataSha256 := firstHealedH.Sum(nil) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if !bytes.Equal(actualSha256, firstHealedDataSha256) { | 
					
						
							| 
									
										
										
										
											2021-12-22 02:08:26 +08:00
										 |  |  | 				t.Fatalf("object healed wrong, expected %x, got %x", | 
					
						
							|  |  |  | 					actualSha256, firstHealedDataSha256) | 
					
						
							| 
									
										
										
										
											2021-12-04 01:26:30 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// remove another data shard
 | 
					
						
							| 
									
										
										
										
											2021-12-22 02:08:26 +08:00
										 |  |  | 			if err = removeAll(pathJoin(shuffledDisks[1].String(), bucket, object)); err != nil { | 
					
						
							| 
									
										
										
										
											2021-12-04 01:26:30 +08:00
										 |  |  | 				t.Fatalf("Failed to delete a file - %v", err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-22 02:08:26 +08:00
										 |  |  | 			_, err = obj.HealObject(ctx, bucket, object, "", madmin.HealOpts{ | 
					
						
							|  |  |  | 				ScanMode: madmin.HealNormalScan, | 
					
						
							|  |  |  | 			}) | 
					
						
							| 
									
										
										
										
											2021-12-04 01:26:30 +08:00
										 |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				t.Fatal(err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			secondGr, err := obj.GetObjectNInfo(ctx, bucket, object, nil, nil, noLock, ObjectOptions{}) | 
					
						
							|  |  |  | 			defer secondGr.Close() | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				t.Fatal(err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			secondHealedH := sha256.New() | 
					
						
							|  |  |  | 			_, err = io.Copy(secondHealedH, secondGr) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2021-12-22 02:08:26 +08:00
										 |  |  | 				t.Fatal(err) | 
					
						
							| 
									
										
										
										
											2021-12-04 01:26:30 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 			secondHealedDataSha256 := secondHealedH.Sum(nil) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if !bytes.Equal(actualSha256, secondHealedDataSha256) { | 
					
						
							| 
									
										
										
										
											2021-12-22 02:08:26 +08:00
										 |  |  | 				t.Fatalf("object healed wrong, expected %x, got %x", | 
					
						
							|  |  |  | 					actualSha256, secondHealedDataSha256) | 
					
						
							| 
									
										
										
										
											2021-12-04 01:26:30 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |