// Copyright (c) 2026 Feng Ruohang // SPDX-License-Identifier: AGPL-3.0-or-later package cmd import ( "bytes" "context" "io" "testing" ) type compatibilityFailedOverwrite struct { StorageAPI commit bool } func (d compatibilityFailedOverwrite) RenameData(ctx context.Context, sv, sp string, fi FileInfo, dv, dp string, opts RenameOptions) (RenameDataResp, error) { if d.commit { if _, err := d.StorageAPI.RenameData(ctx, sv, sp, fi, dv, dp, opts); err != nil { return RenameDataResp{}, err } } return RenameDataResp{}, errDiskNotFound } // Maintenance compatibility requirement: after a completely joined failed // overwrite with one residue, keep the previously acknowledged quorum readable. // This tests a known history; it does not claim the resulting snapshot proves it. func TestMaintenancePartialFailedOverwriteAvailability(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() dirs, err := getRandomDisks(16) if err != nil { t.Fatal(err) } obj, _, err := initObjectLayer(ctx, mustGetPoolEndpoints(0, dirs...)) if err != nil { removeRoots(dirs) t.Fatal(err) } z := obj.(*erasureServerPools) previous := newObjectLayerFn() setObjectLayer(z) defer func() { cancel(); z.Shutdown(context.Background()); removeRoots(dirs); setObjectLayer(previous) }() bucket := "maintenance-compatibility" key := "partial-overwrite" if err := z.MakeBucket(ctx, bucket, MakeBucketOptions{}); err != nil { t.Fatal(err) } oldBody := bytes.Repeat([]byte("old-A"), 65536) newBody := bytes.Repeat([]byte("new-B"), 65536) put := func(body []byte) error { _, err := z.PutObject(ctx, bucket, key, mustGetPutObjReader(t, bytes.NewReader(body), int64(len(body)), "", ""), ObjectOptions{}) return err } if err := put(oldBody); err != nil { t.Fatal(err) } set := z.serverPools[0].getHashedSet(key) getDisks := set.getDisks disks := getDisks() wrapped := append([]StorageAPI(nil), disks...) for i, d := range disks { wrapped[i] = compatibilityFailedOverwrite{d, i == 0} } set.getDisks = func() []StorageAPI { return wrapped } err = put(newBody) set.getDisks = getDisks if err == nil { t.Fatal("partial overwrite must fail") } t.Logf("Overwrite completed with failure; one new residue, fifteen original shards: %v", err) r, err := z.GetObjectNInfo(ctx, bucket, key, nil, nil, ObjectOptions{}) if err != nil { t.Fatalf("MAINTENANCE COMPATIBILITY: previously readable acknowledged value now refused: %v", err) } data, err := io.ReadAll(r) r.Close() if err != nil || !bytes.Equal(data, oldBody) { t.Fatalf("old acknowledged payload mismatch: bytes=%d err=%v", len(data), err) } }