Files
minio/cmd/storage-rollback_test.go
T
2026-10-26 22:43:51 +08:00

140 lines
3.8 KiB
Go

// Copyright (c) 2026 Feng Ruohang
// SPDX-License-Identifier: AGPL-3.0-or-later
package cmd
import (
"context"
"sync"
"testing"
"testing/synctest"
"github.com/minio/minio/internal/grid"
)
type cancelableRenameDisk struct {
StorageAPI
entered chan<- struct{}
release <-chan struct{}
}
func (d cancelableRenameDisk) RenameData(ctx context.Context, _, _ string, _ FileInfo, _, _ string, _ RenameOptions) (RenameDataResp, error) {
d.entered <- struct{}{}
select {
case <-ctx.Done():
return RenameDataResp{}, ctx.Err()
case <-d.release:
return RenameDataResp{}, errDiskNotFound
}
}
func TestStorageTimeoutCoordinatorCancellation(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
entered, release := make(chan struct{}, 4), make(chan struct{})
defer close(release)
disks := make([]StorageAPI, 4)
metadata := make([]FileInfo, 4)
for i := range disks {
disks[i] = cancelableRenameDisk{entered: entered, release: release}
metadata[i] = FileInfo{Deleted: true}
}
done := make(chan error, 1)
go func() {
_, _, _, err := renameData(ctx, disks, minioMetaTmpBucket, "staged", metadata, "bucket", "object", 3)
done <- err
}()
for range disks {
<-entered
}
cancel()
synctest.Wait()
select {
case err := <-done:
if err == nil {
t.Fatal("canceled commit unexpectedly succeeded")
}
default:
t.Fatal("coordinator did not propagate cancellation to disk calls")
}
})
}
type rollbackOptionsDisk struct {
StorageAPI
mu sync.Mutex
opts DeleteOptions
}
func (d *rollbackOptionsDisk) DeleteVersion(_ context.Context, _, _ string, _ FileInfo, _ bool, opts DeleteOptions) error {
d.mu.Lock()
defer d.mu.Unlock()
d.opts = opts
return nil
}
func TestStorageRollbackRPCOptions(t *testing.T) {
endpoint, err := NewEndpoint(t.TempDir())
if err != nil {
t.Fatal(err)
}
disk := &rollbackOptionsDisk{}
globalLocalDrivesMu.Lock()
oldDrives, oldSets := globalLocalDrivesMap, globalLocalSetDrives
globalLocalDrivesMap = map[string]StorageAPI{endpoint.String(): disk}
globalLocalSetDrives = nil
globalLocalDrivesMu.Unlock()
defer func() {
globalLocalDrivesMu.Lock()
globalLocalDrivesMap, globalLocalSetDrives = oldDrives, oldSets
globalLocalDrivesMu.Unlock()
}()
tg, err := grid.SetupTestGrid(2)
if err != nil {
t.Fatal(err)
}
defer tg.Cleanup()
server := storageRESTServer{endpoint: endpoint}
const route = "/rollback-options"
if err := storageDeleteVersionRPC.Register(tg.Managers[1], server.DeleteVersionHandler, route); err != nil {
t.Fatal(err)
}
client := &storageRESTClient{gridConn: tg.Managers[0].Connection(tg.Hosts[1]).Subroute(route)}
client.diskID.Store(new(string))
want := DeleteOptions{UndoWrite: true, OldDataDir: mustGetUUID()}
if err := client.DeleteVersion(t.Context(), "bucket", "object", FileInfo{}, false, want); err != nil {
t.Fatal(err)
}
disk.mu.Lock()
defer disk.mu.Unlock()
if disk.opts != want {
t.Fatalf("remote handler lost rollback options: got %+v, want %+v", disk.opts, want)
}
}
func TestStoragePrereleaseMetadataHidden(t *testing.T) {
disk, _, err := newXLStorageTestSetup(t)
if err != nil {
t.Fatal(err)
}
defer disk.Close()
if err := disk.MakeVol(t.Context(), "bucket"); err != nil {
t.Fatal(err)
}
fi := newFileInfo("object", 2, 2)
fi.DataDir, fi.ModTime, fi.Erasure.Index = mustGetUUID(), UTCNow(), 1
fi.Metadata = map[string]string{rollbackIDKey: mustGetUUID(), "user-key": "value"}
if err := disk.WriteMetadata(t.Context(), "", "bucket", "object", fi); err != nil {
t.Fatal(err)
}
got, err := disk.ReadVersion(t.Context(), "", "bucket", "object", "", ReadOptions{})
if err != nil {
t.Fatal(err)
}
public := cleanMetadata(got.Metadata)
if _, ok := public[rollbackIDKey]; ok || public["user-key"] != "value" {
t.Fatalf("pre-release metadata exposed or user metadata lost: %+v", public)
}
}