| 
									
										
										
										
											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/>.
 | 
					
						
							| 
									
										
										
										
											2019-10-29 01:27:49 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | package cmd | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 	"encoding/json" | 
					
						
							| 
									
										
										
										
											2020-08-19 05:37:26 +08:00
										 |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2020-08-08 04:22:53 +08:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 	"io" | 
					
						
							| 
									
										
										
										
											2021-11-20 00:46:47 +08:00
										 |  |  | 	"os" | 
					
						
							| 
									
										
										
										
											2020-12-16 09:34:54 +08:00
										 |  |  | 	"sort" | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2023-04-19 05:49:56 +08:00
										 |  |  | 	"sync" | 
					
						
							| 
									
										
										
										
											2019-10-29 01:27:49 +08:00
										 |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-16 22:30:05 +08:00
										 |  |  | 	"github.com/dustin/go-humanize" | 
					
						
							| 
									
										
										
										
											2023-06-20 08:53:08 +08:00
										 |  |  | 	"github.com/minio/madmin-go/v3" | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 	"github.com/minio/minio-go/v7/pkg/set" | 
					
						
							| 
									
										
										
										
											2023-06-19 09:20:15 +08:00
										 |  |  | 	"github.com/minio/minio/internal/config" | 
					
						
							| 
									
										
										
										
											2021-06-02 05:59:40 +08:00
										 |  |  | 	"github.com/minio/minio/internal/logger" | 
					
						
							| 
									
										
										
										
											2023-09-05 03:57:37 +08:00
										 |  |  | 	"github.com/minio/pkg/v2/env" | 
					
						
							| 
									
										
										
										
											2019-10-29 01:27:49 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-29 10:39:32 +08:00
										 |  |  | const ( | 
					
						
							|  |  |  | 	defaultMonitorNewDiskInterval = time.Second * 10 | 
					
						
							|  |  |  | 	healingTrackerFilename        = ".healing.bin" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //go:generate msgp -file $GOFILE -unexported
 | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // healingTracker is used to persist healing information during a heal.
 | 
					
						
							| 
									
										
										
										
											2020-09-29 10:39:32 +08:00
										 |  |  | type healingTracker struct { | 
					
						
							| 
									
										
										
										
											2023-04-19 05:49:56 +08:00
										 |  |  | 	disk StorageAPI    `msg:"-"` | 
					
						
							|  |  |  | 	mu   *sync.RWMutex `msg:"-"` | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-16 13:32:06 +08:00
										 |  |  | 	ID         string | 
					
						
							|  |  |  | 	PoolIndex  int | 
					
						
							|  |  |  | 	SetIndex   int | 
					
						
							|  |  |  | 	DiskIndex  int | 
					
						
							|  |  |  | 	Path       string | 
					
						
							|  |  |  | 	Endpoint   string | 
					
						
							|  |  |  | 	Started    time.Time | 
					
						
							|  |  |  | 	LastUpdate time.Time | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ObjectsTotalCount uint64 | 
					
						
							|  |  |  | 	ObjectsTotalSize  uint64 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ItemsHealed uint64 | 
					
						
							|  |  |  | 	ItemsFailed uint64 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	BytesDone   uint64 | 
					
						
							|  |  |  | 	BytesFailed uint64 | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Last object scanned.
 | 
					
						
							|  |  |  | 	Bucket string `json:"-"` | 
					
						
							|  |  |  | 	Object string `json:"-"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Numbers when current bucket started healing,
 | 
					
						
							|  |  |  | 	// for resuming with correct numbers.
 | 
					
						
							| 
									
										
										
										
											2021-07-16 13:32:06 +08:00
										 |  |  | 	ResumeItemsHealed uint64 `json:"-"` | 
					
						
							|  |  |  | 	ResumeItemsFailed uint64 `json:"-"` | 
					
						
							|  |  |  | 	ResumeBytesDone   uint64 `json:"-"` | 
					
						
							|  |  |  | 	ResumeBytesFailed uint64 `json:"-"` | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Filled on startup/restarts.
 | 
					
						
							|  |  |  | 	QueuedBuckets []string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Filled during heal.
 | 
					
						
							|  |  |  | 	HealedBuckets []string | 
					
						
							| 
									
										
										
										
											2023-01-06 12:41:19 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// ID of the current healing operation
 | 
					
						
							|  |  |  | 	HealID string | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 	// Add future tracking capabilities
 | 
					
						
							|  |  |  | 	// Be sure that they are included in toHealingDisk
 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // loadHealingTracker will load the healing tracker from the supplied disk.
 | 
					
						
							|  |  |  | // The disk ID will be validated against the loaded one.
 | 
					
						
							|  |  |  | func loadHealingTracker(ctx context.Context, disk StorageAPI) (*healingTracker, error) { | 
					
						
							|  |  |  | 	if disk == nil { | 
					
						
							| 
									
										
										
										
											2022-08-05 07:10:08 +08:00
										 |  |  | 		return nil, errors.New("loadHealingTracker: nil drive given") | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	diskID, err := disk.GetDiskID() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	b, err := disk.ReadAll(ctx, minioMetaBucket, | 
					
						
							| 
									
										
										
										
											2022-07-16 12:03:23 +08:00
										 |  |  | 		pathJoin(bucketMetaPrefix, healingTrackerFilename)) | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	var h healingTracker | 
					
						
							|  |  |  | 	_, err = h.UnmarshalMsg(b) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if h.ID != diskID && h.ID != "" { | 
					
						
							| 
									
										
										
										
											2022-08-05 07:10:08 +08:00
										 |  |  | 		return nil, fmt.Errorf("loadHealingTracker: drive id mismatch expected %s, got %s", h.ID, diskID) | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	h.disk = disk | 
					
						
							|  |  |  | 	h.ID = diskID | 
					
						
							| 
									
										
										
										
											2023-04-19 05:49:56 +08:00
										 |  |  | 	h.mu = &sync.RWMutex{} | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 	return &h, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // newHealingTracker will create a new healing tracker for the disk.
 | 
					
						
							| 
									
										
										
										
											2023-04-19 05:49:56 +08:00
										 |  |  | func newHealingTracker() *healingTracker { | 
					
						
							|  |  |  | 	return &healingTracker{ | 
					
						
							|  |  |  | 		mu: &sync.RWMutex{}, | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-04-19 05:49:56 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func initHealingTracker(disk StorageAPI, healID string) *healingTracker { | 
					
						
							|  |  |  | 	h := newHealingTracker() | 
					
						
							|  |  |  | 	diskID, _ := disk.GetDiskID() | 
					
						
							|  |  |  | 	h.disk = disk | 
					
						
							|  |  |  | 	h.ID = diskID | 
					
						
							|  |  |  | 	h.HealID = healID | 
					
						
							|  |  |  | 	h.Path = disk.String() | 
					
						
							|  |  |  | 	h.Endpoint = disk.Endpoint().String() | 
					
						
							|  |  |  | 	h.Started = time.Now().UTC() | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 	h.PoolIndex, h.SetIndex, h.DiskIndex = disk.GetDiskLoc() | 
					
						
							| 
									
										
										
										
											2023-04-19 05:49:56 +08:00
										 |  |  | 	return h | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (h healingTracker) getLastUpdate() time.Time { | 
					
						
							|  |  |  | 	h.mu.RLock() | 
					
						
							|  |  |  | 	defer h.mu.RUnlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return h.LastUpdate | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (h healingTracker) getBucket() string { | 
					
						
							|  |  |  | 	h.mu.RLock() | 
					
						
							|  |  |  | 	defer h.mu.RUnlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return h.Bucket | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (h *healingTracker) setBucket(bucket string) { | 
					
						
							|  |  |  | 	h.mu.Lock() | 
					
						
							|  |  |  | 	defer h.mu.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	h.Bucket = bucket | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (h healingTracker) getObject() string { | 
					
						
							|  |  |  | 	h.mu.RLock() | 
					
						
							|  |  |  | 	defer h.mu.RUnlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return h.Object | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (h *healingTracker) setObject(object string) { | 
					
						
							|  |  |  | 	h.mu.Lock() | 
					
						
							|  |  |  | 	defer h.mu.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	h.Object = object | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (h *healingTracker) updateProgress(success bool, bytes uint64) { | 
					
						
							|  |  |  | 	h.mu.Lock() | 
					
						
							|  |  |  | 	defer h.mu.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if success { | 
					
						
							|  |  |  | 		h.ItemsHealed++ | 
					
						
							|  |  |  | 		h.BytesDone += bytes | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		h.ItemsFailed++ | 
					
						
							|  |  |  | 		h.BytesFailed += bytes | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // update will update the tracker on the disk.
 | 
					
						
							|  |  |  | // If the tracker has been deleted an error is returned.
 | 
					
						
							|  |  |  | func (h *healingTracker) update(ctx context.Context) error { | 
					
						
							|  |  |  | 	if h.disk.Healing() == nil { | 
					
						
							| 
									
										
										
										
											2022-08-05 07:10:08 +08:00
										 |  |  | 		return fmt.Errorf("healingTracker: drive %q is not marked as healing", h.ID) | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-04-19 05:49:56 +08:00
										 |  |  | 	h.mu.Lock() | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 	if h.ID == "" || h.PoolIndex < 0 || h.SetIndex < 0 || h.DiskIndex < 0 { | 
					
						
							|  |  |  | 		h.ID, _ = h.disk.GetDiskID() | 
					
						
							|  |  |  | 		h.PoolIndex, h.SetIndex, h.DiskIndex = h.disk.GetDiskLoc() | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-04-19 05:49:56 +08:00
										 |  |  | 	h.mu.Unlock() | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 	return h.save(ctx) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // save will unconditionally save the tracker and will be created if not existing.
 | 
					
						
							|  |  |  | func (h *healingTracker) save(ctx context.Context) error { | 
					
						
							| 
									
										
										
										
											2023-04-19 05:49:56 +08:00
										 |  |  | 	h.mu.Lock() | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 	if h.PoolIndex < 0 || h.SetIndex < 0 || h.DiskIndex < 0 { | 
					
						
							|  |  |  | 		// Attempt to get location.
 | 
					
						
							|  |  |  | 		if api := newObjectLayerFn(); api != nil { | 
					
						
							|  |  |  | 			if ep, ok := api.(*erasureServerPools); ok { | 
					
						
							|  |  |  | 				h.PoolIndex, h.SetIndex, h.DiskIndex, _ = ep.getPoolAndSet(h.ID) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	h.LastUpdate = time.Now().UTC() | 
					
						
							|  |  |  | 	htrackerBytes, err := h.MarshalMsg(nil) | 
					
						
							| 
									
										
										
										
											2023-04-19 05:49:56 +08:00
										 |  |  | 	h.mu.Unlock() | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	globalBackgroundHealState.updateHealStatus(h) | 
					
						
							|  |  |  | 	return h.disk.WriteAll(ctx, minioMetaBucket, | 
					
						
							| 
									
										
										
										
											2022-07-16 12:03:23 +08:00
										 |  |  | 		pathJoin(bucketMetaPrefix, healingTrackerFilename), | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 		htrackerBytes) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // delete the tracker on disk.
 | 
					
						
							|  |  |  | func (h *healingTracker) delete(ctx context.Context) error { | 
					
						
							|  |  |  | 	return h.disk.Delete(ctx, minioMetaBucket, | 
					
						
							| 
									
										
										
										
											2022-07-16 12:03:23 +08:00
										 |  |  | 		pathJoin(bucketMetaPrefix, healingTrackerFilename), | 
					
						
							| 
									
										
										
										
											2022-07-12 00:15:54 +08:00
										 |  |  | 		DeleteOptions{ | 
					
						
							|  |  |  | 			Recursive: false, | 
					
						
							|  |  |  | 			Force:     false, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	) | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (h *healingTracker) isHealed(bucket string) bool { | 
					
						
							| 
									
										
										
										
											2023-04-19 05:49:56 +08:00
										 |  |  | 	h.mu.RLock() | 
					
						
							|  |  |  | 	defer h.mu.RUnlock() | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 	for _, v := range h.HealedBuckets { | 
					
						
							|  |  |  | 		if v == bucket { | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return false | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2020-09-29 10:39:32 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | // resume will reset progress to the numbers at the start of the bucket.
 | 
					
						
							|  |  |  | func (h *healingTracker) resume() { | 
					
						
							| 
									
										
										
										
											2023-04-19 05:49:56 +08:00
										 |  |  | 	h.mu.Lock() | 
					
						
							|  |  |  | 	defer h.mu.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-16 13:32:06 +08:00
										 |  |  | 	h.ItemsHealed = h.ResumeItemsHealed | 
					
						
							|  |  |  | 	h.ItemsFailed = h.ResumeItemsFailed | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 	h.BytesDone = h.ResumeBytesDone | 
					
						
							|  |  |  | 	h.BytesFailed = h.ResumeBytesFailed | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // bucketDone should be called when a bucket is done healing.
 | 
					
						
							|  |  |  | // Adds the bucket to the list of healed buckets and updates resume numbers.
 | 
					
						
							|  |  |  | func (h *healingTracker) bucketDone(bucket string) { | 
					
						
							| 
									
										
										
										
											2023-04-19 05:49:56 +08:00
										 |  |  | 	h.mu.Lock() | 
					
						
							|  |  |  | 	defer h.mu.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-16 13:32:06 +08:00
										 |  |  | 	h.ResumeItemsHealed = h.ItemsHealed | 
					
						
							|  |  |  | 	h.ResumeItemsFailed = h.ItemsFailed | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 	h.ResumeBytesDone = h.BytesDone | 
					
						
							|  |  |  | 	h.ResumeBytesFailed = h.BytesFailed | 
					
						
							|  |  |  | 	h.HealedBuckets = append(h.HealedBuckets, bucket) | 
					
						
							|  |  |  | 	for i, b := range h.QueuedBuckets { | 
					
						
							|  |  |  | 		if b == bucket { | 
					
						
							|  |  |  | 			// Delete...
 | 
					
						
							|  |  |  | 			h.QueuedBuckets = append(h.QueuedBuckets[:i], h.QueuedBuckets[i+1:]...) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // setQueuedBuckets will add buckets, but exclude any that is already in h.HealedBuckets.
 | 
					
						
							|  |  |  | // Order is preserved.
 | 
					
						
							|  |  |  | func (h *healingTracker) setQueuedBuckets(buckets []BucketInfo) { | 
					
						
							| 
									
										
										
										
											2023-04-19 05:49:56 +08:00
										 |  |  | 	h.mu.Lock() | 
					
						
							|  |  |  | 	defer h.mu.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 	s := set.CreateStringSet(h.HealedBuckets...) | 
					
						
							|  |  |  | 	h.QueuedBuckets = make([]string, 0, len(buckets)) | 
					
						
							|  |  |  | 	for _, b := range buckets { | 
					
						
							|  |  |  | 		if !s.Contains(b.Name) { | 
					
						
							|  |  |  | 			h.QueuedBuckets = append(h.QueuedBuckets, b.Name) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (h *healingTracker) printTo(writer io.Writer) { | 
					
						
							| 
									
										
										
										
											2023-04-19 05:49:56 +08:00
										 |  |  | 	h.mu.RLock() | 
					
						
							|  |  |  | 	defer h.mu.RUnlock() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 	b, err := json.MarshalIndent(h, "", "  ") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		writer.Write([]byte(err.Error())) | 
					
						
							| 
									
										
										
										
											2023-05-16 01:14:48 +08:00
										 |  |  | 		return | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	writer.Write(b) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // toHealingDisk converts the information to madmin.HealingDisk
 | 
					
						
							|  |  |  | func (h *healingTracker) toHealingDisk() madmin.HealingDisk { | 
					
						
							| 
									
										
										
										
											2023-04-19 05:49:56 +08:00
										 |  |  | 	h.mu.RLock() | 
					
						
							|  |  |  | 	defer h.mu.RUnlock() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 	return madmin.HealingDisk{ | 
					
						
							| 
									
										
										
										
											2021-07-16 13:32:06 +08:00
										 |  |  | 		ID:                h.ID, | 
					
						
							| 
									
										
										
										
											2023-01-06 12:41:19 +08:00
										 |  |  | 		HealID:            h.HealID, | 
					
						
							| 
									
										
										
										
											2021-07-16 13:32:06 +08:00
										 |  |  | 		Endpoint:          h.Endpoint, | 
					
						
							|  |  |  | 		PoolIndex:         h.PoolIndex, | 
					
						
							|  |  |  | 		SetIndex:          h.SetIndex, | 
					
						
							|  |  |  | 		DiskIndex:         h.DiskIndex, | 
					
						
							|  |  |  | 		Path:              h.Path, | 
					
						
							|  |  |  | 		Started:           h.Started.UTC(), | 
					
						
							|  |  |  | 		LastUpdate:        h.LastUpdate.UTC(), | 
					
						
							|  |  |  | 		ObjectsTotalCount: h.ObjectsTotalCount, | 
					
						
							|  |  |  | 		ObjectsTotalSize:  h.ObjectsTotalSize, | 
					
						
							|  |  |  | 		ItemsHealed:       h.ItemsHealed, | 
					
						
							|  |  |  | 		ItemsFailed:       h.ItemsFailed, | 
					
						
							|  |  |  | 		BytesDone:         h.BytesDone, | 
					
						
							|  |  |  | 		BytesFailed:       h.BytesFailed, | 
					
						
							|  |  |  | 		Bucket:            h.Bucket, | 
					
						
							|  |  |  | 		Object:            h.Object, | 
					
						
							|  |  |  | 		QueuedBuckets:     h.QueuedBuckets, | 
					
						
							|  |  |  | 		HealedBuckets:     h.HealedBuckets, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		ObjectsHealed: h.ItemsHealed, // Deprecated July 2021
 | 
					
						
							|  |  |  | 		ObjectsFailed: h.ItemsFailed, // Deprecated July 2021
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-09-29 10:39:32 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2019-10-29 01:27:49 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-08 10:43:06 +08:00
										 |  |  | func initAutoHeal(ctx context.Context, objAPI ObjectLayer) { | 
					
						
							| 
									
										
										
										
											2020-12-02 05:50:33 +08:00
										 |  |  | 	z, ok := objAPI.(*erasureServerPools) | 
					
						
							| 
									
										
										
										
											2020-08-08 10:43:06 +08:00
										 |  |  | 	if !ok { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	initBackgroundHealing(ctx, objAPI) // start quick background healing
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-29 10:39:32 +08:00
										 |  |  | 	globalBackgroundHealState.pushHealLocalDisks(getLocalDisksToHeal()...) | 
					
						
							| 
									
										
										
										
											2020-09-05 08:09:02 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-12 03:19:22 +08:00
										 |  |  | 	if env.Get("_MINIO_AUTO_DRIVE_HEALING", config.EnableOn) == config.EnableOn || env.Get("_MINIO_AUTO_DISK_HEALING", config.EnableOn) == config.EnableOn { | 
					
						
							| 
									
										
										
										
											2023-06-19 09:20:15 +08:00
										 |  |  | 		go monitorLocalDisksAndHeal(ctx, z) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-10-29 01:27:49 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-05 08:09:02 +08:00
										 |  |  | func getLocalDisksToHeal() (disksToHeal Endpoints) { | 
					
						
							| 
									
										
										
										
											2023-03-13 21:04:20 +08:00
										 |  |  | 	globalLocalDrivesMu.RLock() | 
					
						
							|  |  |  | 	globalLocalDrives := globalLocalDrives | 
					
						
							|  |  |  | 	globalLocalDrivesMu.RUnlock() | 
					
						
							| 
									
										
										
										
											2022-01-25 03:28:45 +08:00
										 |  |  | 	for _, disk := range globalLocalDrives { | 
					
						
							|  |  |  | 		_, err := disk.GetDiskID() | 
					
						
							|  |  |  | 		if errors.Is(err, errUnformattedDisk) { | 
					
						
							|  |  |  | 			disksToHeal = append(disksToHeal, disk.Endpoint()) | 
					
						
							|  |  |  | 			continue | 
					
						
							| 
									
										
										
										
											2020-08-08 04:22:53 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-01-25 03:28:45 +08:00
										 |  |  | 		if disk.Healing() != nil { | 
					
						
							|  |  |  | 			disksToHeal = append(disksToHeal, disk.Endpoint()) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if len(disksToHeal) == globalEndpoints.NEndpoints() { | 
					
						
							|  |  |  | 		// When all disks == all command line endpoints
 | 
					
						
							|  |  |  | 		// this is a fresh setup, no need to trigger healing.
 | 
					
						
							|  |  |  | 		return Endpoints{} | 
					
						
							| 
									
										
										
										
											2020-08-08 04:22:53 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-09-05 08:09:02 +08:00
										 |  |  | 	return disksToHeal | 
					
						
							| 
									
										
										
										
											2020-08-08 04:22:53 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-21 22:53:55 +08:00
										 |  |  | var newDiskHealingTimeout = newDynamicTimeout(30*time.Second, 10*time.Second) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint) error { | 
					
						
							|  |  |  | 	disk, format, err := connectEndpoint(endpoint) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return fmt.Errorf("Error: %w, %s", err, endpoint) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-11-26 16:26:15 +08:00
										 |  |  | 	defer disk.Close() | 
					
						
							| 
									
										
										
										
											2022-06-21 22:53:55 +08:00
										 |  |  | 	poolIdx := globalEndpoints.GetLocalPoolIdx(disk.Endpoint()) | 
					
						
							|  |  |  | 	if poolIdx < 0 { | 
					
						
							| 
									
										
										
										
											2023-02-27 20:55:32 +08:00
										 |  |  | 		return fmt.Errorf("unexpected pool index (%d) found for %s", poolIdx, disk.Endpoint()) | 
					
						
							| 
									
										
										
										
											2022-06-21 22:53:55 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Calculate the set index where the current endpoint belongs
 | 
					
						
							|  |  |  | 	z.serverPools[poolIdx].erasureDisksMu.RLock() | 
					
						
							|  |  |  | 	setIdx, _, err := findDiskIndex(z.serverPools[poolIdx].format, format) | 
					
						
							|  |  |  | 	z.serverPools[poolIdx].erasureDisksMu.RUnlock() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if setIdx < 0 { | 
					
						
							| 
									
										
										
										
											2023-02-27 20:55:32 +08:00
										 |  |  | 		return fmt.Errorf("unexpected set index (%d) found for  %s", setIdx, disk.Endpoint()) | 
					
						
							| 
									
										
										
										
											2022-06-21 22:53:55 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Prevent parallel erasure set healing
 | 
					
						
							| 
									
										
										
										
											2022-10-27 03:43:54 +08:00
										 |  |  | 	locker := z.NewNSLock(minioMetaBucket, fmt.Sprintf("new-drive-healing/%d/%d", poolIdx, setIdx)) | 
					
						
							| 
									
										
										
										
											2022-06-21 22:53:55 +08:00
										 |  |  | 	lkctx, err := locker.GetLock(ctx, newDiskHealingTimeout) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-02-27 20:55:32 +08:00
										 |  |  | 		return fmt.Errorf("Healing of drive '%v' on %s pool, belonging to %s erasure set already in progress: %w", | 
					
						
							|  |  |  | 			disk, humanize.Ordinal(poolIdx+1), humanize.Ordinal(setIdx+1), err) | 
					
						
							| 
									
										
										
										
											2022-06-21 22:53:55 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	ctx = lkctx.Context() | 
					
						
							| 
									
										
										
										
											2022-12-24 11:49:07 +08:00
										 |  |  | 	defer locker.Unlock(lkctx) | 
					
						
							| 
									
										
										
										
											2022-06-21 22:53:55 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-06 12:41:19 +08:00
										 |  |  | 	// Load healing tracker in this disk
 | 
					
						
							|  |  |  | 	tracker, err := loadHealingTracker(ctx, disk) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-02-27 20:55:32 +08:00
										 |  |  | 		// A healing tracker may be deleted if another disk in the
 | 
					
						
							|  |  |  | 		// same erasure set with same healing-id successfully finished
 | 
					
						
							|  |  |  | 		// healing.
 | 
					
						
							|  |  |  | 		if errors.Is(err, errFileNotFound) { | 
					
						
							| 
									
										
										
										
											2023-01-06 12:41:19 +08:00
										 |  |  | 			return nil | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-02-27 20:55:32 +08:00
										 |  |  | 		logger.LogIf(ctx, fmt.Errorf("Unable to load healing tracker on '%s': %w, re-initializing..", disk, err)) | 
					
						
							| 
									
										
										
										
											2023-04-19 05:49:56 +08:00
										 |  |  | 		tracker = initHealingTracker(disk, mustGetUUID()) | 
					
						
							| 
									
										
										
										
											2023-01-06 12:41:19 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-27 20:55:32 +08:00
										 |  |  | 	logger.Info(fmt.Sprintf("Healing drive '%s' - 'mc admin heal alias/ --verbose' to check the current status.", endpoint)) | 
					
						
							| 
									
										
										
										
											2023-01-06 12:41:19 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-26 08:51:32 +08:00
										 |  |  | 	buckets, _ := z.ListBuckets(ctx, BucketOptions{}) | 
					
						
							| 
									
										
										
										
											2023-02-27 20:55:32 +08:00
										 |  |  | 	// Buckets data are dispersed in multiple pools/sets, make
 | 
					
						
							| 
									
										
										
										
											2022-06-21 22:53:55 +08:00
										 |  |  | 	// sure to heal all bucket metadata configuration.
 | 
					
						
							|  |  |  | 	buckets = append(buckets, BucketInfo{ | 
					
						
							|  |  |  | 		Name: pathJoin(minioMetaBucket, minioConfigPrefix), | 
					
						
							|  |  |  | 	}, BucketInfo{ | 
					
						
							|  |  |  | 		Name: pathJoin(minioMetaBucket, bucketMetaPrefix), | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Heal latest buckets first.
 | 
					
						
							|  |  |  | 	sort.Slice(buckets, func(i, j int) bool { | 
					
						
							|  |  |  | 		a, b := strings.HasPrefix(buckets[i].Name, minioMetaBucket), strings.HasPrefix(buckets[j].Name, minioMetaBucket) | 
					
						
							|  |  |  | 		if a != b { | 
					
						
							|  |  |  | 			return a | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return buckets[i].Created.After(buckets[j].Created) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if serverDebugLog { | 
					
						
							| 
									
										
										
										
											2023-02-27 20:55:32 +08:00
										 |  |  | 		logger.Info("Healing drive '%v' on %s pool, belonging to %s erasure set", disk, humanize.Ordinal(poolIdx+1), humanize.Ordinal(setIdx+1)) | 
					
						
							| 
									
										
										
										
											2022-06-21 22:53:55 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Load bucket totals
 | 
					
						
							|  |  |  | 	cache := dataUsageCache{} | 
					
						
							|  |  |  | 	if err := cache.load(ctx, z.serverPools[poolIdx].sets[setIdx], dataUsageCacheName); err == nil { | 
					
						
							|  |  |  | 		dataUsageInfo := cache.dui(dataUsageRoot, nil) | 
					
						
							|  |  |  | 		tracker.ObjectsTotalCount = dataUsageInfo.ObjectsTotalCount | 
					
						
							|  |  |  | 		tracker.ObjectsTotalSize = dataUsageInfo.ObjectsTotalSize | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tracker.PoolIndex, tracker.SetIndex, tracker.DiskIndex = disk.GetDiskLoc() | 
					
						
							|  |  |  | 	tracker.setQueuedBuckets(buckets) | 
					
						
							|  |  |  | 	if err := tracker.save(ctx); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Start or resume healing of this erasure set
 | 
					
						
							| 
									
										
										
										
											2022-07-16 12:03:23 +08:00
										 |  |  | 	if err = z.serverPools[poolIdx].sets[setIdx].healErasureSet(ctx, tracker.QueuedBuckets, tracker); err != nil { | 
					
						
							| 
									
										
										
										
											2022-06-21 22:53:55 +08:00
										 |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-16 12:03:23 +08:00
										 |  |  | 	if tracker.ItemsFailed > 0 { | 
					
						
							| 
									
										
										
										
											2023-02-27 20:55:32 +08:00
										 |  |  | 		logger.Info("Healing of drive '%s' failed (healed: %d, failed: %d).", disk, tracker.ItemsHealed, tracker.ItemsFailed) | 
					
						
							| 
									
										
										
										
											2022-07-16 12:03:23 +08:00
										 |  |  | 	} else { | 
					
						
							| 
									
										
										
										
											2023-02-27 20:55:32 +08:00
										 |  |  | 		logger.Info("Healing of drive '%s' complete (healed: %d, failed: %d).", disk, tracker.ItemsHealed, tracker.ItemsFailed) | 
					
						
							| 
									
										
										
										
											2022-07-16 12:03:23 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-06-21 22:53:55 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-15 04:50:13 +08:00
										 |  |  | 	if len(tracker.QueuedBuckets) > 0 { | 
					
						
							|  |  |  | 		return fmt.Errorf("not all buckets were healed: %v", tracker.QueuedBuckets) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-21 22:53:55 +08:00
										 |  |  | 	if serverDebugLog { | 
					
						
							|  |  |  | 		tracker.printTo(os.Stdout) | 
					
						
							|  |  |  | 		logger.Info("\n") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-27 20:55:32 +08:00
										 |  |  | 	if tracker.HealID == "" { // HealID was empty only before Feb 2023
 | 
					
						
							| 
									
										
										
										
											2023-01-06 12:41:19 +08:00
										 |  |  | 		logger.LogIf(ctx, tracker.delete(ctx)) | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Remove .healing.bin from all disks with similar heal-id
 | 
					
						
							| 
									
										
										
										
											2023-07-18 00:52:05 +08:00
										 |  |  | 	disks, err := z.GetDisks(poolIdx, setIdx) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, disk := range disks { | 
					
						
							| 
									
										
										
										
											2023-01-06 12:41:19 +08:00
										 |  |  | 		t, err := loadHealingTracker(ctx, disk) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2023-02-27 20:55:32 +08:00
										 |  |  | 			if !errors.Is(err, errFileNotFound) { | 
					
						
							| 
									
										
										
										
											2023-01-06 12:41:19 +08:00
										 |  |  | 				logger.LogIf(ctx, err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if t.HealID == tracker.HealID { | 
					
						
							|  |  |  | 			t.delete(ctx) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-07-16 12:03:23 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-21 22:53:55 +08:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-29 01:27:49 +08:00
										 |  |  | // monitorLocalDisksAndHeal - ensures that detected new disks are healed
 | 
					
						
							|  |  |  | //  1. Only the concerned erasure set will be listed and healed
 | 
					
						
							|  |  |  | //  2. Only the node hosting the disk is responsible to perform the heal
 | 
					
						
							| 
									
										
										
										
											2022-06-21 22:53:55 +08:00
										 |  |  | func monitorLocalDisksAndHeal(ctx context.Context, z *erasureServerPools) { | 
					
						
							| 
									
										
										
										
											2020-01-10 18:35:06 +08:00
										 |  |  | 	// Perform automatic disk healing when a disk is replaced locally.
 | 
					
						
							| 
									
										
										
										
											2020-12-18 04:35:02 +08:00
										 |  |  | 	diskCheckTimer := time.NewTimer(defaultMonitorNewDiskInterval) | 
					
						
							|  |  |  | 	defer diskCheckTimer.Stop() | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-29 01:27:49 +08:00
										 |  |  | 	for { | 
					
						
							| 
									
										
										
										
											2020-03-23 03:16:36 +08:00
										 |  |  | 		select { | 
					
						
							|  |  |  | 		case <-ctx.Done(): | 
					
						
							|  |  |  | 			return | 
					
						
							| 
									
										
										
										
											2020-12-18 04:35:02 +08:00
										 |  |  | 		case <-diskCheckTimer.C: | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 			healDisks := globalBackgroundHealState.getHealLocalDiskEndpoints() | 
					
						
							| 
									
										
										
										
											2022-06-21 22:53:55 +08:00
										 |  |  | 			if len(healDisks) == 0 { | 
					
						
							|  |  |  | 				// Reset for next interval.
 | 
					
						
							|  |  |  | 				diskCheckTimer.Reset(defaultMonitorNewDiskInterval) | 
					
						
							| 
									
										
										
										
											2022-07-16 12:03:23 +08:00
										 |  |  | 				continue | 
					
						
							| 
									
										
										
										
											2020-12-18 08:52:47 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-21 22:53:55 +08:00
										 |  |  | 			// Reformat disks immediately
 | 
					
						
							|  |  |  | 			_, err := z.HealFormat(context.Background(), false) | 
					
						
							|  |  |  | 			if err != nil && !errors.Is(err, errNoHealRequired) { | 
					
						
							|  |  |  | 				logger.LogIf(ctx, err) | 
					
						
							|  |  |  | 				// Reset for next interval.
 | 
					
						
							|  |  |  | 				diskCheckTimer.Reset(defaultMonitorNewDiskInterval) | 
					
						
							| 
									
										
										
										
											2022-07-16 12:03:23 +08:00
										 |  |  | 				continue | 
					
						
							| 
									
										
										
										
											2020-08-08 04:22:53 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-21 22:53:55 +08:00
										 |  |  | 			for _, disk := range healDisks { | 
					
						
							|  |  |  | 				go func(disk Endpoint) { | 
					
						
							| 
									
										
										
										
											2023-01-14 07:36:46 +08:00
										 |  |  | 					globalBackgroundHealState.setDiskHealingStatus(disk, true) | 
					
						
							| 
									
										
										
										
											2023-02-27 20:55:32 +08:00
										 |  |  | 					if err := healFreshDisk(ctx, z, disk); err != nil { | 
					
						
							| 
									
										
										
										
											2023-01-14 07:36:46 +08:00
										 |  |  | 						globalBackgroundHealState.setDiskHealingStatus(disk, false) | 
					
						
							| 
									
										
										
										
											2023-03-26 18:16:51 +08:00
										 |  |  | 						timedout := OperationTimedOut{} | 
					
						
							|  |  |  | 						if !errors.Is(err, context.Canceled) && !errors.As(err, &timedout) { | 
					
						
							| 
									
										
										
										
											2023-02-28 01:59:47 +08:00
										 |  |  | 							printEndpointError(disk, err, false) | 
					
						
							|  |  |  | 						} | 
					
						
							| 
									
										
										
										
											2022-06-21 22:53:55 +08:00
										 |  |  | 						return | 
					
						
							| 
									
										
										
										
											2021-03-05 06:36:23 +08:00
										 |  |  | 					} | 
					
						
							| 
									
										
										
										
											2022-06-21 22:53:55 +08:00
										 |  |  | 					// Only upon success pop the healed disk.
 | 
					
						
							|  |  |  | 					globalBackgroundHealState.popHealLocalDisks(disk) | 
					
						
							|  |  |  | 				}(disk) | 
					
						
							| 
									
										
										
										
											2019-10-29 01:27:49 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2022-05-18 13:42:59 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// Reset for next interval.
 | 
					
						
							|  |  |  | 			diskCheckTimer.Reset(defaultMonitorNewDiskInterval) | 
					
						
							| 
									
										
										
										
											2019-10-29 01:27:49 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |