diff --git a/cmd/erasure-healing.go b/cmd/erasure-healing.go index 15c9556fa..b42380f40 100644 --- a/cmd/erasure-healing.go +++ b/cmd/erasure-healing.go @@ -294,6 +294,26 @@ func shouldHealObjectOnDisk(erErr, dataErr error, meta FileInfo, latestMeta File return false } +const xMinIOHealing = ReservedMetadataPrefix + "healing" + +// SetHealing marks object (version) as being healed. +// Note: this is to be used only from healObject +func (fi *FileInfo) SetHealing() { + if fi.Metadata == nil { + fi.Metadata = make(map[string]string) + } + fi.Metadata[xMinIOHealing] = "true" +} + +// Healing returns true if object is being healed (i.e fi is being passed down +// from healObject) +func (fi FileInfo) Healing() bool { + if _, ok := fi.Metadata[xMinIOHealing]; ok { + return true + } + return false +} + // Heals an object by re-writing corrupt/missing erasure blocks. func (er *erasureObjects) healObject(ctx context.Context, bucket string, object string, versionID string, opts madmin.HealOpts) (result madmin.HealResultItem, err error) { dryRun := opts.DryRun @@ -664,6 +684,7 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object partsMetadata[i].Erasure.Index = i + 1 // Attempt a rename now from healed data to final location. + partsMetadata[i].SetHealing() if _, err = disk.RenameData(ctx, minioMetaTmpBucket, tmpID, partsMetadata[i], bucket, object); err != nil { logger.LogIf(ctx, err) return result, err diff --git a/cmd/xl-storage-format-v2.go b/cmd/xl-storage-format-v2.go index fb9be0235..8bad88041 100644 --- a/cmd/xl-storage-format-v2.go +++ b/cmd/xl-storage-format-v2.go @@ -1583,8 +1583,9 @@ func (x *xlMetaV2) AddVersion(fi FileInfo) error { if len(k) > len(ReservedMetadataPrefixLower) && strings.EqualFold(k[:len(ReservedMetadataPrefixLower)], ReservedMetadataPrefixLower) { // Skip tierFVID, tierFVMarker keys; it's used // only for creating free-version. + // Skip xMinIOHealing, it's used only in RenameData switch k { - case tierFVIDKey, tierFVMarkerKey: + case tierFVIDKey, tierFVMarkerKey, xMinIOHealing: continue } diff --git a/cmd/xl-storage-format-v2_test.go b/cmd/xl-storage-format-v2_test.go index 248648b47..f9b42c5ce 100644 --- a/cmd/xl-storage-format-v2_test.go +++ b/cmd/xl-storage-format-v2_test.go @@ -1010,3 +1010,51 @@ func Test_mergeXLV2Versions2(t *testing.T) { }) } } + +func TestXMinIOHealingSkip(t *testing.T) { + xl := xlMetaV2{} + failOnErr := func(err error) { + t.Helper() + if err != nil { + t.Fatalf("Test failed with %v", err) + } + } + + fi := FileInfo{ + Volume: "volume", + Name: "object-name", + VersionID: "756100c6-b393-4981-928a-d49bbc164741", + IsLatest: true, + Deleted: false, + ModTime: time.Now(), + Size: 1 << 10, + Mode: 0, + Erasure: ErasureInfo{ + Algorithm: ReedSolomon.String(), + DataBlocks: 4, + ParityBlocks: 2, + BlockSize: 10000, + Index: 1, + Distribution: []int{1, 2, 3, 4, 5, 6, 7, 8}, + Checksums: []ChecksumInfo{{ + PartNumber: 1, + Algorithm: HighwayHash256S, + Hash: nil, + }}, + }, + NumVersions: 1, + } + + fi.SetHealing() + failOnErr(xl.AddVersion(fi)) + + var err error + fi, err = xl.ToFileInfo(fi.Volume, fi.Name, fi.VersionID, false) + if err != nil { + t.Fatalf("xl.ToFileInfo failed with %v", err) + } + + if fi.Healing() { + t.Fatal("Expected fi.Healing to be false") + } +} diff --git a/cmd/xl-storage.go b/cmd/xl-storage.go index d64f5d3e5..8407fd62e 100644 --- a/cmd/xl-storage.go +++ b/cmd/xl-storage.go @@ -2357,7 +2357,11 @@ func (s *xlStorage) RenameData(ctx context.Context, srcVolume, srcPath string, f // suspended or disabled on this bucket. RenameData will replace // the 'null' version. We add a free-version to track its tiered // content for asynchronous deletion. - if fi.VersionID == "" && !fi.IsRestoreObjReq() { + // + // Note: RestoreObject and HealObject requests don't end up replacing the + // null version and therefore don't require the free-version to track + // anything + if fi.VersionID == "" && !fi.IsRestoreObjReq() && !fi.Healing() { // Note: Restore object request reuses PutObject/Multipart // upload to copy back its data from the remote tier. This // doesn't replace the existing version, so we don't need to add