storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/global-heal.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2019 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package cmd 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "sort" 24 "time" 25 26 "storj.io/minio/cmd/logger" 27 "storj.io/minio/pkg/color" 28 "storj.io/minio/pkg/console" 29 "storj.io/minio/pkg/madmin" 30 "storj.io/minio/pkg/wildcard" 31 ) 32 33 const ( 34 bgHealingUUID = "0000-0000-0000-0000" 35 ) 36 37 // NewBgHealSequence creates a background healing sequence 38 // operation which scans all objects and heal them. 39 func newBgHealSequence() *healSequence { 40 reqInfo := &logger.ReqInfo{API: "BackgroundHeal"} 41 ctx, cancelCtx := context.WithCancel(logger.SetReqInfo(GlobalContext, reqInfo)) 42 43 hs := madmin.HealOpts{ 44 // Remove objects that do not have read-quorum 45 Remove: true, 46 ScanMode: madmin.HealNormalScan, 47 } 48 49 return &healSequence{ 50 sourceCh: make(chan healSource), 51 respCh: make(chan healResult), 52 startTime: UTCNow(), 53 clientToken: bgHealingUUID, 54 // run-background heal with reserved bucket 55 bucket: minioReservedBucket, 56 settings: hs, 57 currentStatus: healSequenceStatus{ 58 Summary: healNotStartedStatus, 59 HealSettings: hs, 60 }, 61 cancelCtx: cancelCtx, 62 ctx: ctx, 63 reportProgress: false, 64 scannedItemsMap: make(map[madmin.HealItemType]int64), 65 healedItemsMap: make(map[madmin.HealItemType]int64), 66 healFailedItemsMap: make(map[string]int64), 67 } 68 } 69 70 // getBackgroundHealStatus will return the 71 func getBackgroundHealStatus(ctx context.Context, o ObjectLayer) (madmin.BgHealState, bool) { 72 if globalBackgroundHealState == nil { 73 return madmin.BgHealState{}, false 74 } 75 76 bgSeq, ok := globalBackgroundHealState.getHealSequenceByToken(bgHealingUUID) 77 if !ok { 78 return madmin.BgHealState{}, false 79 } 80 81 var healDisksMap = map[string]struct{}{} 82 for _, ep := range getLocalDisksToHeal() { 83 healDisksMap[ep.String()] = struct{}{} 84 } 85 status := madmin.BgHealState{ 86 ScannedItemsCount: bgSeq.getScannedItemsCount(), 87 } 88 89 if o == nil { 90 healing := globalBackgroundHealState.getLocalHealingDisks() 91 for _, disk := range healing { 92 status.HealDisks = append(status.HealDisks, disk.Endpoint) 93 } 94 95 return status, true 96 } 97 98 // ignores any errors here. 99 si, _ := o.StorageInfo(ctx) 100 101 indexed := make(map[string][]madmin.Disk) 102 for _, disk := range si.Disks { 103 setIdx := fmt.Sprintf("%d-%d", disk.PoolIndex, disk.SetIndex) 104 indexed[setIdx] = append(indexed[setIdx], disk) 105 } 106 107 for id, disks := range indexed { 108 ss := madmin.SetStatus{ 109 ID: id, 110 SetIndex: disks[0].SetIndex, 111 PoolIndex: disks[0].PoolIndex, 112 } 113 for _, disk := range disks { 114 ss.Disks = append(ss.Disks, disk) 115 if disk.Healing { 116 ss.HealStatus = "Healing" 117 ss.HealPriority = "high" 118 status.HealDisks = append(status.HealDisks, disk.Endpoint) 119 } 120 } 121 sortDisks(ss.Disks) 122 status.Sets = append(status.Sets, ss) 123 } 124 sort.Slice(status.Sets, func(i, j int) bool { 125 return status.Sets[i].ID < status.Sets[j].ID 126 }) 127 128 return status, true 129 130 } 131 132 func mustGetHealSequence(ctx context.Context) *healSequence { 133 // Get background heal sequence to send elements to heal 134 for { 135 globalHealStateLK.RLock() 136 hstate := globalBackgroundHealState 137 globalHealStateLK.RUnlock() 138 139 if hstate == nil { 140 time.Sleep(time.Second) 141 continue 142 } 143 144 bgSeq, ok := hstate.getHealSequenceByToken(bgHealingUUID) 145 if !ok { 146 time.Sleep(time.Second) 147 continue 148 } 149 return bgSeq 150 } 151 } 152 153 // healErasureSet lists and heals all objects in a specific erasure set 154 func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []BucketInfo, tracker *healingTracker) error { 155 bgSeq := mustGetHealSequence(ctx) 156 buckets = append(buckets, BucketInfo{ 157 Name: pathJoin(minioMetaBucket, minioConfigPrefix), 158 }) 159 160 // Try to pro-actively heal backend-encrypted file. 161 if _, err := er.HealObject(ctx, minioMetaBucket, backendEncryptedFile, "", madmin.HealOpts{}); err != nil { 162 if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) { 163 logger.LogIf(ctx, err) 164 } 165 } 166 167 // Heal all buckets with all objects 168 for _, bucket := range buckets { 169 if tracker.isHealed(bucket.Name) { 170 continue 171 } 172 var forwardTo string 173 // If we resume to the same bucket, forward to last known item. 174 if tracker.Bucket != "" { 175 if tracker.Bucket == bucket.Name { 176 forwardTo = tracker.Bucket 177 } else { 178 // Reset to where last bucket ended if resuming. 179 tracker.resume() 180 } 181 } 182 tracker.Object = "" 183 tracker.Bucket = bucket.Name 184 // Heal current bucket 185 if _, err := er.HealBucket(ctx, bucket.Name, madmin.HealOpts{}); err != nil { 186 if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) { 187 logger.LogIf(ctx, err) 188 } 189 } 190 191 if serverDebugLog { 192 console.Debugf(color.Green("healDisk:")+" healing bucket %s content on erasure set %d\n", bucket.Name, tracker.SetIndex+1) 193 } 194 195 disks, _ := er.getOnlineDisksWithHealing() 196 if len(disks) == 0 { 197 return errors.New("healErasureSet: No non-healing disks found") 198 } 199 200 // Limit listing to 3 drives. 201 if len(disks) > 3 { 202 disks = disks[:3] 203 } 204 205 healEntry := func(entry metaCacheEntry) { 206 if entry.isDir() { 207 return 208 } 209 // We might land at .metacache, .trash, .multipart 210 // no need to heal them skip, only when bucket 211 // is '.minio.sys' 212 if bucket.Name == minioMetaBucket { 213 if wildcard.Match("buckets/*/.metacache/*", entry.name) { 214 return 215 } 216 if wildcard.Match("tmp/.trash/*", entry.name) { 217 return 218 } 219 if wildcard.Match("multipart/*", entry.name) { 220 return 221 } 222 } 223 fivs, err := entry.fileInfoVersions(bucket.Name) 224 if err != nil { 225 logger.LogIf(ctx, err) 226 return 227 } 228 waitForLowHTTPReq(globalHealConfig.IOCount, globalHealConfig.Sleep) 229 for _, version := range fivs.Versions { 230 if _, err := er.HealObject(ctx, bucket.Name, version.Name, version.VersionID, madmin.HealOpts{ 231 ScanMode: madmin.HealNormalScan, Remove: healDeleteDangling}); err != nil { 232 if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) { 233 // If not deleted, assume they failed. 234 tracker.ObjectsFailed++ 235 tracker.BytesFailed += uint64(version.Size) 236 logger.LogIf(ctx, err) 237 } 238 } else { 239 tracker.ObjectsHealed++ 240 tracker.BytesDone += uint64(version.Size) 241 } 242 bgSeq.logHeal(madmin.HealItemObject) 243 } 244 tracker.Object = entry.name 245 if time.Since(tracker.LastUpdate) > time.Minute { 246 logger.LogIf(ctx, tracker.update(ctx)) 247 } 248 } 249 250 // How to resolve partial results. 251 resolver := metadataResolutionParams{ 252 dirQuorum: 1, 253 objQuorum: 1, 254 bucket: bucket.Name, 255 } 256 257 err := listPathRaw(ctx, listPathRawOptions{ 258 disks: disks, 259 bucket: bucket.Name, 260 recursive: true, 261 forwardTo: forwardTo, 262 minDisks: 1, 263 reportNotFound: false, 264 agreed: healEntry, 265 partial: func(entries metaCacheEntries, nAgreed int, errs []error) { 266 entry, ok := entries.resolve(&resolver) 267 if ok { 268 healEntry(*entry) 269 } 270 }, 271 finished: nil, 272 }) 273 274 select { 275 // If context is canceled don't mark as done... 276 case <-ctx.Done(): 277 return ctx.Err() 278 default: 279 logger.LogIf(ctx, err) 280 tracker.bucketDone(bucket.Name) 281 logger.LogIf(ctx, tracker.update(ctx)) 282 } 283 } 284 tracker.Object = "" 285 tracker.Bucket = "" 286 287 return nil 288 } 289 290 // healObject heals given object path in deep to fix bitrot. 291 func healObject(bucket, object, versionID string, scan madmin.HealScanMode) { 292 // Get background heal sequence to send elements to heal 293 bgSeq, ok := globalBackgroundHealState.getHealSequenceByToken(bgHealingUUID) 294 if ok { 295 bgSeq.sourceCh <- healSource{ 296 bucket: bucket, 297 object: object, 298 versionID: versionID, 299 opts: &madmin.HealOpts{ 300 Remove: true, // if found dangling purge it. 301 ScanMode: scan, 302 }, 303 } 304 } 305 }