| 
									
										
										
										
											2022-08-26 04:07:15 +08:00
										 |  |  | // Copyright (c) 2015-2022 MinIO, Inc.
 | 
					
						
							| 
									
										
										
										
											2021-04-19 03:41:13 +08:00
										 |  |  | //
 | 
					
						
							|  |  |  | // 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/>.
 | 
					
						
							| 
									
										
										
										
											2019-10-29 01:27:49 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | package cmd | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"sort" | 
					
						
							| 
									
										
										
										
											2022-08-26 04:07:15 +08:00
										 |  |  | 	"strconv" | 
					
						
							| 
									
										
										
										
											2019-10-29 01:27:49 +08:00
										 |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-20 00:46:47 +08:00
										 |  |  | 	"github.com/dustin/go-humanize" | 
					
						
							| 
									
										
										
										
											2022-12-07 05:46:50 +08:00
										 |  |  | 	"github.com/minio/madmin-go/v2" | 
					
						
							| 
									
										
										
										
											2021-06-02 05:59:40 +08:00
										 |  |  | 	"github.com/minio/minio/internal/color" | 
					
						
							| 
									
										
										
										
											2021-08-23 23:50:35 +08:00
										 |  |  | 	"github.com/minio/minio/internal/config/storageclass" | 
					
						
							| 
									
										
										
										
											2022-08-26 04:07:15 +08:00
										 |  |  | 	"github.com/minio/minio/internal/jobtokens" | 
					
						
							| 
									
										
										
										
											2021-06-02 05:59:40 +08:00
										 |  |  | 	"github.com/minio/minio/internal/logger" | 
					
						
							| 
									
										
										
										
											2021-05-29 06:17:01 +08:00
										 |  |  | 	"github.com/minio/pkg/console" | 
					
						
							| 
									
										
										
										
											2022-08-26 04:07:15 +08:00
										 |  |  | 	"github.com/minio/pkg/env" | 
					
						
							| 
									
										
										
										
											2021-05-29 06:17:01 +08:00
										 |  |  | 	"github.com/minio/pkg/wildcard" | 
					
						
							| 
									
										
										
										
											2019-10-29 01:27:49 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 	bgHealingUUID = "0000-0000-0000-0000" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewBgHealSequence creates a background healing sequence
 | 
					
						
							| 
									
										
										
										
											2021-02-27 07:11:42 +08:00
										 |  |  | // operation which scans all objects and heal them.
 | 
					
						
							| 
									
										
										
										
											2020-06-30 04:07:26 +08:00
										 |  |  | func newBgHealSequence() *healSequence { | 
					
						
							| 
									
										
										
										
											2019-10-29 01:27:49 +08:00
										 |  |  | 	reqInfo := &logger.ReqInfo{API: "BackgroundHeal"} | 
					
						
							| 
									
										
										
										
											2020-05-04 13:35:40 +08:00
										 |  |  | 	ctx, cancelCtx := context.WithCancel(logger.SetReqInfo(GlobalContext, reqInfo)) | 
					
						
							| 
									
										
										
										
											2019-10-29 01:27:49 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	hs := madmin.HealOpts{ | 
					
						
							|  |  |  | 		// Remove objects that do not have read-quorum
 | 
					
						
							| 
									
										
										
										
											2022-04-07 23:10:40 +08:00
										 |  |  | 		Remove: healDeleteDangling, | 
					
						
							| 
									
										
										
										
											2019-10-29 01:27:49 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return &healSequence{ | 
					
						
							| 
									
										
										
										
											2021-08-27 05:06:04 +08:00
										 |  |  | 		respCh:      make(chan healResult), | 
					
						
							| 
									
										
										
										
											2019-10-29 01:27:49 +08:00
										 |  |  | 		startTime:   UTCNow(), | 
					
						
							|  |  |  | 		clientToken: bgHealingUUID, | 
					
						
							| 
									
										
										
										
											2020-08-08 10:43:06 +08:00
										 |  |  | 		// run-background heal with reserved bucket
 | 
					
						
							|  |  |  | 		bucket:   minioReservedBucket, | 
					
						
							|  |  |  | 		settings: hs, | 
					
						
							| 
									
										
										
										
											2019-10-29 01:27:49 +08:00
										 |  |  | 		currentStatus: healSequenceStatus{ | 
					
						
							|  |  |  | 			Summary:      healNotStartedStatus, | 
					
						
							|  |  |  | 			HealSettings: hs, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2020-05-04 13:35:40 +08:00
										 |  |  | 		cancelCtx:          cancelCtx, | 
					
						
							|  |  |  | 		ctx:                ctx, | 
					
						
							|  |  |  | 		reportProgress:     false, | 
					
						
							|  |  |  | 		scannedItemsMap:    make(map[madmin.HealItemType]int64), | 
					
						
							|  |  |  | 		healedItemsMap:     make(map[madmin.HealItemType]int64), | 
					
						
							|  |  |  | 		healFailedItemsMap: make(map[string]int64), | 
					
						
							| 
									
										
										
										
											2019-10-29 01:27:49 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | // getBackgroundHealStatus will return the
 | 
					
						
							|  |  |  | func getBackgroundHealStatus(ctx context.Context, o ObjectLayer) (madmin.BgHealState, bool) { | 
					
						
							| 
									
										
										
										
											2020-09-11 00:16:26 +08:00
										 |  |  | 	if globalBackgroundHealState == nil { | 
					
						
							|  |  |  | 		return madmin.BgHealState{}, false | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-09-25 06:36:47 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-29 01:27:49 +08:00
										 |  |  | 	bgSeq, ok := globalBackgroundHealState.getHealSequenceByToken(bgHealingUUID) | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							| 
									
										
										
										
											2020-08-08 10:43:06 +08:00
										 |  |  | 		return madmin.BgHealState{}, false | 
					
						
							| 
									
										
										
										
											2019-10-29 01:27:49 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-16 13:32:06 +08:00
										 |  |  | 	status := madmin.BgHealState{ | 
					
						
							|  |  |  | 		ScannedItemsCount: bgSeq.getScannedItemsCount(), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-26 23:00:59 +08:00
										 |  |  | 	if globalMRFState.initialized() { | 
					
						
							| 
									
										
										
										
											2021-07-16 13:32:06 +08:00
										 |  |  | 		status.MRF = map[string]madmin.MRFStatus{ | 
					
						
							| 
									
										
										
										
											2021-07-26 23:00:59 +08:00
										 |  |  | 			globalLocalNodeName: globalMRFState.getCurrentMRFRoundInfo(), | 
					
						
							| 
									
										
										
										
											2021-07-16 13:32:06 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-03 01:15:06 +08:00
										 |  |  | 	healDisksMap := map[string]struct{}{} | 
					
						
							| 
									
										
										
										
											2020-09-25 06:36:47 +08:00
										 |  |  | 	for _, ep := range getLocalDisksToHeal() { | 
					
						
							|  |  |  | 		healDisksMap[ep.String()] = struct{}{} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 	if o == nil { | 
					
						
							|  |  |  | 		healing := globalBackgroundHealState.getLocalHealingDisks() | 
					
						
							|  |  |  | 		for _, disk := range healing { | 
					
						
							|  |  |  | 			status.HealDisks = append(status.HealDisks, disk.Endpoint) | 
					
						
							| 
									
										
										
										
											2020-09-25 06:36:47 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		return status, true | 
					
						
							| 
									
										
										
										
											2020-09-05 08:09:02 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-02 06:31:35 +08:00
										 |  |  | 	si := o.StorageInfo(ctx) | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	indexed := make(map[string][]madmin.Disk) | 
					
						
							|  |  |  | 	for _, disk := range si.Disks { | 
					
						
							|  |  |  | 		setIdx := fmt.Sprintf("%d-%d", disk.PoolIndex, disk.SetIndex) | 
					
						
							|  |  |  | 		indexed[setIdx] = append(indexed[setIdx], disk) | 
					
						
							| 
									
										
										
										
											2020-08-08 04:22:53 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 	for id, disks := range indexed { | 
					
						
							|  |  |  | 		ss := madmin.SetStatus{ | 
					
						
							|  |  |  | 			ID:        id, | 
					
						
							|  |  |  | 			SetIndex:  disks[0].SetIndex, | 
					
						
							|  |  |  | 			PoolIndex: disks[0].PoolIndex, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		for _, disk := range disks { | 
					
						
							|  |  |  | 			ss.Disks = append(ss.Disks, disk) | 
					
						
							|  |  |  | 			if disk.Healing { | 
					
						
							|  |  |  | 				ss.HealStatus = "Healing" | 
					
						
							|  |  |  | 				ss.HealPriority = "high" | 
					
						
							|  |  |  | 				status.HealDisks = append(status.HealDisks, disk.Endpoint) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		sortDisks(ss.Disks) | 
					
						
							|  |  |  | 		status.Sets = append(status.Sets, ss) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	sort.Slice(status.Sets, func(i, j int) bool { | 
					
						
							|  |  |  | 		return status.Sets[i].ID < status.Sets[j].ID | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-23 23:50:35 +08:00
										 |  |  | 	backendInfo := o.BackendInfo() | 
					
						
							|  |  |  | 	status.SCParity = make(map[string]int) | 
					
						
							|  |  |  | 	status.SCParity[storageclass.STANDARD] = backendInfo.StandardSCParity | 
					
						
							|  |  |  | 	status.SCParity[storageclass.RRS] = backendInfo.RRSCParity | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 	return status, true | 
					
						
							| 
									
										
										
										
											2019-10-29 01:27:49 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-14 03:57:08 +08:00
										 |  |  | func mustGetHealSequence(ctx context.Context) *healSequence { | 
					
						
							| 
									
										
										
										
											2019-10-29 01:27:49 +08:00
										 |  |  | 	// Get background heal sequence to send elements to heal
 | 
					
						
							|  |  |  | 	for { | 
					
						
							| 
									
										
										
										
											2020-12-14 03:57:08 +08:00
										 |  |  | 		globalHealStateLK.RLock() | 
					
						
							|  |  |  | 		hstate := globalBackgroundHealState | 
					
						
							|  |  |  | 		globalHealStateLK.RUnlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if hstate == nil { | 
					
						
							|  |  |  | 			time.Sleep(time.Second) | 
					
						
							|  |  |  | 			continue | 
					
						
							| 
									
										
										
										
											2019-10-29 01:27:49 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-12-14 03:57:08 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		bgSeq, ok := hstate.getHealSequenceByToken(bgHealingUUID) | 
					
						
							|  |  |  | 		if !ok { | 
					
						
							|  |  |  | 			time.Sleep(time.Second) | 
					
						
							| 
									
										
										
										
											2020-05-04 13:35:40 +08:00
										 |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-12-14 03:57:08 +08:00
										 |  |  | 		return bgSeq | 
					
						
							| 
									
										
										
										
											2019-10-29 01:27:49 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-12-14 03:57:08 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-26 04:07:15 +08:00
										 |  |  | const envHealWorkers = "_MINIO_HEAL_WORKERS" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-14 03:57:08 +08:00
										 |  |  | // healErasureSet lists and heals all objects in a specific erasure set
 | 
					
						
							| 
									
										
										
										
											2021-11-20 00:46:47 +08:00
										 |  |  | func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string, tracker *healingTracker) error { | 
					
						
							| 
									
										
										
										
											2020-12-14 03:57:08 +08:00
										 |  |  | 	bgSeq := mustGetHealSequence(ctx) | 
					
						
							| 
									
										
										
										
											2022-04-07 23:10:40 +08:00
										 |  |  | 	scanMode := madmin.HealNormalScan | 
					
						
							| 
									
										
										
										
											2021-08-26 08:46:20 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-16 01:18:09 +08:00
										 |  |  | 	// Make sure to copy since `buckets slice`
 | 
					
						
							|  |  |  | 	// is modified in place by tracker.
 | 
					
						
							|  |  |  | 	healBuckets := make([]string, len(buckets)) | 
					
						
							|  |  |  | 	copy(healBuckets, buckets) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-21 22:53:55 +08:00
										 |  |  | 	// Heal all buckets first in this erasure set - this is useful
 | 
					
						
							|  |  |  | 	// for new objects upload in different buckets to be successful
 | 
					
						
							|  |  |  | 	for _, bucket := range healBuckets { | 
					
						
							|  |  |  | 		_, err := er.HealBucket(ctx, bucket, madmin.HealOpts{ScanMode: scanMode}) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			// Log bucket healing error if any, we shall retry again.
 | 
					
						
							|  |  |  | 			logger.LogIf(ctx, err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-26 04:07:15 +08:00
										 |  |  | 	// numHealers - number of concurrent heal jobs, defaults to 1
 | 
					
						
							|  |  |  | 	numHealers, err := strconv.Atoi(env.Get(envHealWorkers, "1")) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		logger.LogIf(ctx, fmt.Errorf("invalid %s value %v, defaulting to 1", envHealWorkers, err)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if numHealers < 1 { | 
					
						
							|  |  |  | 		numHealers = 1 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// jt will never be nil since we ensure that numHealers > 0
 | 
					
						
							|  |  |  | 	jt, _ := jobtokens.New(numHealers) | 
					
						
							| 
									
										
										
										
											2021-11-20 00:46:47 +08:00
										 |  |  | 	var retErr error | 
					
						
							| 
									
										
										
										
											2019-10-29 01:27:49 +08:00
										 |  |  | 	// Heal all buckets with all objects
 | 
					
						
							| 
									
										
										
										
											2021-12-16 01:18:09 +08:00
										 |  |  | 	for _, bucket := range healBuckets { | 
					
						
							| 
									
										
										
										
											2021-11-20 00:46:47 +08:00
										 |  |  | 		if tracker.isHealed(bucket) { | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		var forwardTo string | 
					
						
							|  |  |  | 		// If we resume to the same bucket, forward to last known item.
 | 
					
						
							|  |  |  | 		if tracker.Bucket != "" { | 
					
						
							| 
									
										
										
										
											2021-11-20 00:46:47 +08:00
										 |  |  | 			if tracker.Bucket == bucket { | 
					
						
							| 
									
										
										
										
											2021-11-06 04:10:41 +08:00
										 |  |  | 				forwardTo = tracker.Object | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 			} else { | 
					
						
							|  |  |  | 				// Reset to where last bucket ended if resuming.
 | 
					
						
							|  |  |  | 				tracker.resume() | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		tracker.Object = "" | 
					
						
							| 
									
										
										
										
											2021-11-20 00:46:47 +08:00
										 |  |  | 		tracker.Bucket = bucket | 
					
						
							| 
									
										
										
										
											2022-06-21 22:53:55 +08:00
										 |  |  | 		// Heal current bucket again in case if it is failed
 | 
					
						
							|  |  |  | 		// in the  being of erasure set healing
 | 
					
						
							| 
									
										
										
										
											2021-11-20 00:46:47 +08:00
										 |  |  | 		if _, err := er.HealBucket(ctx, bucket, madmin.HealOpts{ | 
					
						
							| 
									
										
										
										
											2021-08-26 08:46:20 +08:00
										 |  |  | 			ScanMode: scanMode, | 
					
						
							|  |  |  | 		}); err != nil { | 
					
						
							| 
									
										
										
										
											2021-09-03 11:56:13 +08:00
										 |  |  | 			logger.LogIf(ctx, err) | 
					
						
							| 
									
										
										
										
											2021-11-20 00:46:47 +08:00
										 |  |  | 			continue | 
					
						
							| 
									
										
										
										
											2020-04-02 03:14:00 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-10-29 01:27:49 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-19 18:40:52 +08:00
										 |  |  | 		if serverDebugLog { | 
					
						
							| 
									
										
										
										
											2022-08-05 07:10:08 +08:00
										 |  |  | 			console.Debugf(color.Green("healDrive:")+" healing bucket %s content on %s erasure set\n", | 
					
						
							| 
									
										
										
										
											2021-11-20 00:46:47 +08:00
										 |  |  | 				bucket, humanize.Ordinal(tracker.SetIndex+1)) | 
					
						
							| 
									
										
										
										
											2021-01-19 18:40:52 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-19 03:06:54 +08:00
										 |  |  | 		disks, _ := er.getOnlineDisksWithHealing() | 
					
						
							|  |  |  | 		if len(disks) == 0 { | 
					
						
							| 
									
										
										
										
											2021-08-25 03:43:57 +08:00
										 |  |  | 			// all disks are healing in this set, this is allowed
 | 
					
						
							|  |  |  | 			// so we simply proceed to next bucket, marking the bucket
 | 
					
						
							|  |  |  | 			// as done as there are no objects to heal.
 | 
					
						
							| 
									
										
										
										
											2021-11-20 00:46:47 +08:00
										 |  |  | 			tracker.bucketDone(bucket) | 
					
						
							| 
									
										
										
										
											2021-08-25 03:43:57 +08:00
										 |  |  | 			logger.LogIf(ctx, tracker.update(ctx)) | 
					
						
							|  |  |  | 			continue | 
					
						
							| 
									
										
										
										
											2020-06-10 08:09:19 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-03-07 01:25:48 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-19 03:06:54 +08:00
										 |  |  | 		// Limit listing to 3 drives.
 | 
					
						
							|  |  |  | 		if len(disks) > 3 { | 
					
						
							|  |  |  | 			disks = disks[:3] | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-03-07 01:25:48 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-07 23:47:21 +08:00
										 |  |  | 		type healEntryResult struct { | 
					
						
							|  |  |  | 			bytes     uint64 | 
					
						
							|  |  |  | 			success   bool | 
					
						
							|  |  |  | 			entryDone bool | 
					
						
							|  |  |  | 			name      string | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		healEntryDone := func(name string) healEntryResult { | 
					
						
							|  |  |  | 			return healEntryResult{ | 
					
						
							|  |  |  | 				entryDone: true, | 
					
						
							|  |  |  | 				name:      name, | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		healEntrySuccess := func(sz uint64) healEntryResult { | 
					
						
							|  |  |  | 			return healEntryResult{ | 
					
						
							|  |  |  | 				bytes:   sz, | 
					
						
							|  |  |  | 				success: true, | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		healEntryFailure := func(sz uint64) healEntryResult { | 
					
						
							|  |  |  | 			return healEntryResult{ | 
					
						
							|  |  |  | 				bytes: sz, | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Collect updates to tracker from concurrent healEntry calls
 | 
					
						
							|  |  |  | 		results := make(chan healEntryResult) | 
					
						
							|  |  |  | 		go func() { | 
					
						
							|  |  |  | 			for res := range results { | 
					
						
							|  |  |  | 				if res.entryDone { | 
					
						
							|  |  |  | 					tracker.Object = res.name | 
					
						
							|  |  |  | 					if time.Since(tracker.LastUpdate) > time.Minute { | 
					
						
							|  |  |  | 						logger.LogIf(ctx, tracker.update(ctx)) | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					continue | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				if res.success { | 
					
						
							|  |  |  | 					tracker.ItemsHealed++ | 
					
						
							|  |  |  | 					tracker.BytesDone += res.bytes | 
					
						
							|  |  |  | 				} else { | 
					
						
							|  |  |  | 					tracker.ItemsFailed++ | 
					
						
							|  |  |  | 					tracker.BytesFailed += res.bytes | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Note: updates from healEntry to tracker must be sent on results channel.
 | 
					
						
							| 
									
										
										
										
											2021-02-19 03:06:54 +08:00
										 |  |  | 		healEntry := func(entry metaCacheEntry) { | 
					
						
							| 
									
										
										
										
											2022-08-26 04:07:15 +08:00
										 |  |  | 			defer jt.Give() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-11 13:22:17 +08:00
										 |  |  | 			if entry.name == "" && len(entry.metadata) == 0 { | 
					
						
							|  |  |  | 				// ignore entries that don't have metadata.
 | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2021-02-19 03:06:54 +08:00
										 |  |  | 			if entry.isDir() { | 
					
						
							| 
									
										
										
										
											2022-01-11 13:22:17 +08:00
										 |  |  | 				// ignore healing entry.name's with `/` suffix.
 | 
					
						
							| 
									
										
										
										
											2021-02-19 03:06:54 +08:00
										 |  |  | 				return | 
					
						
							| 
									
										
										
										
											2020-06-10 08:09:19 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2021-03-08 01:38:31 +08:00
										 |  |  | 			// We might land at .metacache, .trash, .multipart
 | 
					
						
							|  |  |  | 			// no need to heal them skip, only when bucket
 | 
					
						
							|  |  |  | 			// is '.minio.sys'
 | 
					
						
							| 
									
										
										
										
											2021-11-20 00:46:47 +08:00
										 |  |  | 			if bucket == minioMetaBucket { | 
					
						
							| 
									
										
										
										
											2021-03-08 01:38:31 +08:00
										 |  |  | 				if wildcard.Match("buckets/*/.metacache/*", entry.name) { | 
					
						
							|  |  |  | 					return | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				if wildcard.Match("tmp/.trash/*", entry.name) { | 
					
						
							|  |  |  | 					return | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				if wildcard.Match("multipart/*", entry.name) { | 
					
						
							|  |  |  | 					return | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2021-08-27 11:32:58 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-07 23:47:21 +08:00
										 |  |  | 			var result healEntryResult | 
					
						
							| 
									
										
										
										
											2021-11-20 00:46:47 +08:00
										 |  |  | 			fivs, err := entry.fileInfoVersions(bucket) | 
					
						
							| 
									
										
										
										
											2021-02-19 03:06:54 +08:00
										 |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2021-08-27 11:32:58 +08:00
										 |  |  | 				err := bgSeq.queueHealTask(healSource{ | 
					
						
							| 
									
										
										
										
											2021-11-20 00:46:47 +08:00
										 |  |  | 					bucket:    bucket, | 
					
						
							| 
									
										
										
										
											2021-08-27 11:32:58 +08:00
										 |  |  | 					object:    entry.name, | 
					
						
							|  |  |  | 					versionID: "", | 
					
						
							|  |  |  | 				}, madmin.HealItemObject) | 
					
						
							| 
									
										
										
										
											2021-11-20 00:46:47 +08:00
										 |  |  | 				if err != nil { | 
					
						
							| 
									
										
										
										
											2022-09-07 23:47:21 +08:00
										 |  |  | 					result = healEntryFailure(0) | 
					
						
							| 
									
										
										
										
											2022-01-11 13:22:17 +08:00
										 |  |  | 					logger.LogIf(ctx, fmt.Errorf("unable to heal object %s/%s: %w", bucket, entry.name, err)) | 
					
						
							| 
									
										
										
										
											2021-11-20 00:46:47 +08:00
										 |  |  | 				} else { | 
					
						
							| 
									
										
										
										
											2022-09-07 23:47:21 +08:00
										 |  |  | 					result = healEntrySuccess(0) | 
					
						
							| 
									
										
										
										
											2021-11-20 00:46:47 +08:00
										 |  |  | 				} | 
					
						
							| 
									
										
										
										
											2022-09-07 23:47:21 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				select { | 
					
						
							|  |  |  | 				case <-ctx.Done(): | 
					
						
							|  |  |  | 					return | 
					
						
							|  |  |  | 				case results <- result: | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-19 03:06:54 +08:00
										 |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2021-08-26 08:46:20 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-23 21:47:33 +08:00
										 |  |  | 			// erasureObjects layer needs object names to be encoded
 | 
					
						
							|  |  |  | 			encodedEntryName := encodeDirObject(entry.name) | 
					
						
							| 
									
										
										
										
											2022-11-29 02:20:55 +08:00
										 |  |  | 			if healDeleteDangling { | 
					
						
							|  |  |  | 				err := er.checkAbandonedParts(ctx, bucket, encodedEntryName, madmin.HealOpts{Remove: healDeleteDangling}) | 
					
						
							|  |  |  | 				if err != nil { | 
					
						
							|  |  |  | 					logger.LogIf(ctx, fmt.Errorf("unable to check object %s/%s for abandoned data: %w", bucket, entry.name, err)) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2021-02-19 03:06:54 +08:00
										 |  |  | 			for _, version := range fivs.Versions { | 
					
						
							| 
									
										
										
										
											2022-06-23 21:47:33 +08:00
										 |  |  | 				if _, err := er.HealObject(ctx, bucket, encodedEntryName, | 
					
						
							| 
									
										
										
										
											2021-08-27 11:32:58 +08:00
										 |  |  | 					version.VersionID, madmin.HealOpts{ | 
					
						
							|  |  |  | 						ScanMode: scanMode, | 
					
						
							|  |  |  | 						Remove:   healDeleteDangling, | 
					
						
							|  |  |  | 					}); err != nil { | 
					
						
							| 
									
										
										
										
											2021-09-03 11:56:13 +08:00
										 |  |  | 					// If not deleted, assume they failed.
 | 
					
						
							| 
									
										
										
										
											2022-09-07 23:47:21 +08:00
										 |  |  | 					result = healEntryFailure(uint64(version.Size)) | 
					
						
							| 
									
										
										
										
											2022-01-11 13:22:17 +08:00
										 |  |  | 					if version.VersionID != "" { | 
					
						
							|  |  |  | 						logger.LogIf(ctx, fmt.Errorf("unable to heal object %s/%s-v(%s): %w", bucket, version.Name, version.VersionID, err)) | 
					
						
							|  |  |  | 					} else { | 
					
						
							|  |  |  | 						logger.LogIf(ctx, fmt.Errorf("unable to heal object %s/%s: %w", bucket, version.Name, err)) | 
					
						
							|  |  |  | 					} | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 				} else { | 
					
						
							| 
									
										
										
										
											2022-09-07 23:47:21 +08:00
										 |  |  | 					result = healEntrySuccess(uint64(version.Size)) | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 				} | 
					
						
							| 
									
										
										
										
											2021-02-19 03:06:54 +08:00
										 |  |  | 				bgSeq.logHeal(madmin.HealItemObject) | 
					
						
							| 
									
										
										
										
											2022-09-07 23:47:21 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				select { | 
					
						
							|  |  |  | 				case <-ctx.Done(): | 
					
						
							|  |  |  | 					return | 
					
						
							|  |  |  | 				case results <- result: | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2020-04-02 03:14:00 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2022-09-07 23:47:21 +08:00
										 |  |  | 			select { | 
					
						
							|  |  |  | 			case <-ctx.Done(): | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			case results <- healEntryDone(entry.name): | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2021-08-26 08:46:20 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// Wait and proceed if there are active requests
 | 
					
						
							|  |  |  | 			waitForLowHTTPReq() | 
					
						
							| 
									
										
										
										
											2019-10-29 01:27:49 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-03-07 01:25:48 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// How to resolve partial results.
 | 
					
						
							|  |  |  | 		resolver := metadataResolutionParams{ | 
					
						
							|  |  |  | 			dirQuorum: 1, | 
					
						
							|  |  |  | 			objQuorum: 1, | 
					
						
							| 
									
										
										
										
											2021-11-20 00:46:47 +08:00
										 |  |  | 			bucket:    bucket, | 
					
						
							| 
									
										
										
										
											2021-03-07 01:25:48 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-26 04:07:15 +08:00
										 |  |  | 		err = listPathRaw(ctx, listPathRawOptions{ | 
					
						
							| 
									
										
										
										
											2021-02-19 03:06:54 +08:00
										 |  |  | 			disks:          disks, | 
					
						
							| 
									
										
										
										
											2021-11-20 00:46:47 +08:00
										 |  |  | 			bucket:         bucket, | 
					
						
							| 
									
										
										
										
											2021-02-19 03:06:54 +08:00
										 |  |  | 			recursive:      true, | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 			forwardTo:      forwardTo, | 
					
						
							| 
									
										
										
										
											2021-02-19 03:06:54 +08:00
										 |  |  | 			minDisks:       1, | 
					
						
							|  |  |  | 			reportNotFound: false, | 
					
						
							| 
									
										
										
										
											2022-08-26 04:07:15 +08:00
										 |  |  | 			agreed: func(entry metaCacheEntry) { | 
					
						
							|  |  |  | 				jt.Take() | 
					
						
							|  |  |  | 				go healEntry(entry) | 
					
						
							|  |  |  | 			}, | 
					
						
							| 
									
										
										
										
											2022-07-08 04:45:34 +08:00
										 |  |  | 			partial: func(entries metaCacheEntries, _ []error) { | 
					
						
							| 
									
										
										
										
											2021-03-07 01:25:48 +08:00
										 |  |  | 				entry, ok := entries.resolve(&resolver) | 
					
						
							| 
									
										
										
										
											2021-08-27 11:32:58 +08:00
										 |  |  | 				if !ok { | 
					
						
							|  |  |  | 					// check if we can get one entry atleast
 | 
					
						
							|  |  |  | 					// proceed to heal nonetheless.
 | 
					
						
							|  |  |  | 					entry, _ = entries.firstFound() | 
					
						
							| 
									
										
										
										
											2021-02-19 03:06:54 +08:00
										 |  |  | 				} | 
					
						
							| 
									
										
										
										
											2022-08-26 04:07:15 +08:00
										 |  |  | 				jt.Take() | 
					
						
							|  |  |  | 				go healEntry(*entry) | 
					
						
							| 
									
										
										
										
											2021-02-19 03:06:54 +08:00
										 |  |  | 			}, | 
					
						
							|  |  |  | 			finished: nil, | 
					
						
							|  |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2022-08-26 04:07:15 +08:00
										 |  |  | 		jt.Wait() // synchronize all the concurrent heal jobs
 | 
					
						
							| 
									
										
										
										
											2022-09-07 23:47:21 +08:00
										 |  |  | 		close(results) | 
					
						
							| 
									
										
										
										
											2021-07-23 03:14:44 +08:00
										 |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2021-11-20 00:46:47 +08:00
										 |  |  | 			// Set this such that when we return this function
 | 
					
						
							|  |  |  | 			// we let the caller retry this disk again for the
 | 
					
						
							|  |  |  | 			// buckets it failed to list.
 | 
					
						
							|  |  |  | 			retErr = err | 
					
						
							| 
									
										
										
										
											2021-07-23 03:14:44 +08:00
										 |  |  | 			logger.LogIf(ctx, err) | 
					
						
							| 
									
										
										
										
											2021-11-20 00:46:47 +08:00
										 |  |  | 			continue | 
					
						
							| 
									
										
										
										
											2021-07-23 03:14:44 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 		select { | 
					
						
							|  |  |  | 		// If context is canceled don't mark as done...
 | 
					
						
							|  |  |  | 		case <-ctx.Done(): | 
					
						
							|  |  |  | 			return ctx.Err() | 
					
						
							|  |  |  | 		default: | 
					
						
							| 
									
										
										
										
											2021-11-20 00:46:47 +08:00
										 |  |  | 			tracker.bucketDone(bucket) | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 			logger.LogIf(ctx, tracker.update(ctx)) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-10-29 01:27:49 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 	tracker.Object = "" | 
					
						
							|  |  |  | 	tracker.Bucket = "" | 
					
						
							| 
									
										
										
										
											2019-10-29 01:27:49 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-20 00:46:47 +08:00
										 |  |  | 	return retErr | 
					
						
							| 
									
										
										
										
											2019-10-29 01:27:49 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-28 02:21:14 +08:00
										 |  |  | // healObject heals given object path in deep to fix bitrot.
 | 
					
						
							|  |  |  | func healObject(bucket, object, versionID string, scan madmin.HealScanMode) { | 
					
						
							| 
									
										
										
										
											2020-04-02 03:14:00 +08:00
										 |  |  | 	// Get background heal sequence to send elements to heal
 | 
					
						
							| 
									
										
										
										
											2022-05-31 01:58:37 +08:00
										 |  |  | 	globalHealStateLK.Lock() | 
					
						
							| 
									
										
										
										
											2020-08-08 10:43:06 +08:00
										 |  |  | 	bgSeq, ok := globalBackgroundHealState.getHealSequenceByToken(bgHealingUUID) | 
					
						
							| 
									
										
										
										
											2022-05-31 01:58:37 +08:00
										 |  |  | 	globalHealStateLK.Unlock() | 
					
						
							| 
									
										
										
										
											2021-08-27 05:06:04 +08:00
										 |  |  | 	if ok { | 
					
						
							|  |  |  | 		bgSeq.queueHealTask(healSource{ | 
					
						
							|  |  |  | 			bucket:    bucket, | 
					
						
							|  |  |  | 			object:    object, | 
					
						
							|  |  |  | 			versionID: versionID, | 
					
						
							|  |  |  | 			opts: &madmin.HealOpts{ | 
					
						
							|  |  |  | 				Remove:   healDeleteDangling, // if found dangling purge it.
 | 
					
						
							|  |  |  | 				ScanMode: scan, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, madmin.HealItemObject) | 
					
						
							| 
									
										
										
										
											2020-04-02 03:14:00 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } |