mirror of
https://github.com/pgsty/minio.git
synced 2026-11-10 09:35:31 +03:00
fix(heal): classify xl.meta checksum failures as storage corruption
Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
@@ -18,11 +18,13 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
madmin "github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/bucket/lifecycle"
|
"github.com/minio/minio/internal/bucket/lifecycle"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -136,8 +138,8 @@ func TestPurgeAllVersionsRefusalReasons(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// A drive whose metadata does not decode refuses the purge, as an
|
// A drive whose metadata does not decode refuses the purge, as an
|
||||||
// unreadable one does. Object heal does not rewrite such metadata; once the
|
// unreadable one does. Healing each surviving version restores the metadata
|
||||||
// drive holds readable metadata again, the next purge removes the key.
|
// from a quorum before the next purge can remove the key.
|
||||||
func TestPurgeAllVersionsRefusesCorruptDrive(t *testing.T) {
|
func TestPurgeAllVersionsRefusesCorruptDrive(t *testing.T) {
|
||||||
z := purgeSinglePool(t, 16)
|
z := purgeSinglePool(t, 16)
|
||||||
p := newPurgeTest(t, z, "purge-corrupt", MakeBucketOptions{VersioningEnabled: true}, purgeRules)
|
p := newPurgeTest(t, z, "purge-corrupt", MakeBucketOptions{VersioningEnabled: true}, purgeRules)
|
||||||
@@ -156,14 +158,20 @@ func TestPurgeAllVersionsRefusesCorruptDrive(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
buf := slices.Clone(good)
|
buf := slices.Clone(good)
|
||||||
buf[len(buf)/2] ^= 0xff
|
meta, _, err := isIndexedMetaV2(buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
meta[len(meta)-1] ^= 1
|
||||||
if err := bad.WriteAll(ctx, p.bucket, metaPath, buf); err != nil {
|
if err := bad.WriteAll(ctx, p.bucket, metaPath, buf); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if raw, err := bad.ReadXL(ctx, p.bucket, key, false); err != nil {
|
if raw, err := bad.ReadXL(ctx, p.bucket, key, false); err != nil {
|
||||||
t.Fatalf("the corrupted metadata is not read: %v", err)
|
t.Fatalf("the corrupted metadata is not read: %v", err)
|
||||||
} else if err := (&xlMetaV2{}).LoadOrConvert(raw.Buf); err == nil {
|
} else if err := (&xlMetaV2{}).LoadOrConvert(raw.Buf); !errors.Is(err, errFileCorrupt) {
|
||||||
t.Fatal("the corrupted metadata still decodes")
|
t.Fatalf("CRC classification: %v", err)
|
||||||
|
} else if got := toStorageErr(errors.New(err.Error())); got != errFileCorrupt {
|
||||||
|
t.Fatalf("CRC classification after storage RPC: %v", got)
|
||||||
}
|
}
|
||||||
|
|
||||||
delta := incompleteDelta(func() { p.run(ops) })
|
delta := incompleteDelta(func() { p.run(ops) })
|
||||||
@@ -179,9 +187,37 @@ func TestPurgeAllVersionsRefusesCorruptDrive(t *testing.T) {
|
|||||||
t.Fatalf("the refused purge left %s readable", got)
|
t.Fatalf("the refused purge left %s readable", got)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := bad.WriteAll(ctx, p.bucket, metaPath, good); err != nil {
|
versions, err := getFileInfoVersions(good, p.bucket, key, false)
|
||||||
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
for _, mode := range []madmin.HealScanMode{madmin.HealNormalScan, madmin.HealDeepScan} {
|
||||||
|
if err := bad.WriteAll(ctx, p.bucket, metaPath, buf); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := z.HealObject(ctx, p.bucket, key, nullVersionID, madmin.HealOpts{DryRun: true, ScanMode: mode}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
unchanged, err := bad.ReadAll(ctx, p.bucket, metaPath)
|
||||||
|
if err != nil || !bytes.Equal(unchanged, buf) {
|
||||||
|
t.Fatalf("dry-run changed corrupt metadata: %v", err)
|
||||||
|
}
|
||||||
|
for _, fi := range versions.Versions {
|
||||||
|
id := fi.VersionID
|
||||||
|
if id == "" {
|
||||||
|
id = nullVersionID
|
||||||
|
}
|
||||||
|
if _, err := z.HealObject(ctx, p.bucket, key, id, madmin.HealOpts{ScanMode: mode}); err != nil {
|
||||||
|
t.Fatalf("heal %s: %v", id, err)
|
||||||
|
}
|
||||||
|
if _, err := bad.ReadVersion(ctx, "", p.bucket, key, id, ReadOptions{}); err != nil {
|
||||||
|
t.Fatalf("repaired version %s unreadable: %v", id, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if got := p.get(key); got != "v2" {
|
||||||
|
t.Fatalf("heal changed latest data: %s", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
p.run([]expiryOp{p.deleteAllTask(0, key, lifecycle.DeleteAllVersionsAction)})
|
p.run([]expiryOp{p.deleteAllTask(0, key, lifecycle.DeleteAllVersionsAction)})
|
||||||
if got := p.held(0, key); got != nil {
|
if got := p.held(0, key); got != nil {
|
||||||
t.Errorf("the purge after the repair left %v", got)
|
t.Errorf("the purge after the repair left %v", got)
|
||||||
|
|||||||
@@ -878,7 +878,9 @@ func isIndexedMetaV2(buf []byte) (meta xlMetaBuf, data xlMetaInlineData, err err
|
|||||||
// Read metadata CRC
|
// Read metadata CRC
|
||||||
buf = nbuf
|
buf = nbuf
|
||||||
if got := uint32(xxhash.Sum64(meta)); got != crc {
|
if got := uint32(xxhash.Sum64(meta)); got != crc {
|
||||||
return nil, nil, fmt.Errorf("xlMetaV2.Load version(%d), CRC mismatch, want 0x%x, got 0x%x", minor, crc, got)
|
// Use the storage sentinel so corruption retains its classification
|
||||||
|
// across both local storage and storage RPC.
|
||||||
|
return nil, nil, errFileCorrupt
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@@ -1054,7 +1056,7 @@ func (x *xlMetaV2) loadLegacy(buf []byte) error {
|
|||||||
// Read metadata CRC (added in v2)
|
// Read metadata CRC (added in v2)
|
||||||
buf = nbuf
|
buf = nbuf
|
||||||
if got := uint32(xxhash.Sum64(v)); got != crc {
|
if got := uint32(xxhash.Sum64(v)); got != crc {
|
||||||
return fmt.Errorf("xlMetaV2.Load version(%d), CRC mismatch, want 0x%x, got 0x%x", minor, crc, got)
|
return errFileCorrupt
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("xlMetaV2.Load version(%d), loading CRC: %w", minor, err)
|
return fmt.Errorf("xlMetaV2.Load version(%d), loading CRC: %w", minor, err)
|
||||||
|
|||||||
Reference in New Issue
Block a user