Files
seaweedfs/weed/storage/volume_super_block_replication_test.go
Chris LuandGitHub 4dc1b70b2f test: pin that a .vif replication outranks the superblock (#10499)
* test: pin that a .vif replication outranks the superblock

Store.ConfigureVolume rewrites the .vif and never the replica-placement byte in
the .dat, so that byte keeps whatever the volume was created with for good.
readSuperBlock reads it and then overrides it from the .vif, which is what makes
a replication change take effect and survive a remount.

Invert that and every replication change silently reverts on the next mount,
while the .vif on disk still records what the operator asked for -- a durability
setting quietly going back to its old value, with nothing to indicate it.

Worth pinning rather than reading off the code, because the field beside it
resolves the other way: version takes the superblock over the .vif. Two fields,
one function, opposite precedence, each a line to invert wrongly.

Covers the empty case too, since a .vif that declares no replication has to
leave the superblock standing or a volume whose replication was never
configured would be forced to whatever the zero value parses as.

* test: drop the unreachable nil check on MaybeLoadVolumeInfo

It initialises the returned pointer before the existence check and every
return is naked, so it never yields nil. Guarding against it implied a
contract the callee does not have.
2026-07-30 17:03:38 -07:00

102 lines
3.6 KiB
Go

package storage
import (
"os"
"path/filepath"
"testing"
"github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb"
"github.com/seaweedfs/seaweedfs/weed/storage/needle"
"github.com/seaweedfs/seaweedfs/weed/storage/super_block"
"github.com/seaweedfs/seaweedfs/weed/storage/volume_info"
)
// A volume's replication lives in two places that can disagree, and the .vif is
// the one that counts.
//
// Store.ConfigureVolume rewrites the .vif and never the replica-placement byte
// in the .dat superblock, so that byte keeps whatever the volume was created
// with for good. readSuperBlock reads the byte and then overrides it from the
// .vif, which is what makes the change take effect and survive a remount.
//
// Preferring the superblock instead would silently revert every replication
// change on the next mount, while the .vif on disk still recorded what the
// operator asked for. That is worth a test rather than a reading of the code,
// because it is one line to invert and the field beside it -- version -- really
// does resolve the other way, with the superblock outranking the .vif.
func TestVifReplicationOutranksSuperBlock(t *testing.T) {
dir := t.TempDir()
// Created carrying 000: the byte written into the .dat, and left there.
created, err := super_block.NewReplicaPlacementFromString("000")
if err != nil {
t.Fatal(err)
}
v, err := NewVolume(dir, dir, "", 1, NeedleMapInMemory, created, &needle.TTL{}, 0, needle.GetCurrentVersion(), 0, 0)
if err != nil {
t.Fatalf("create volume: %v", err)
}
if got := v.SuperBlock.ReplicaPlacement.String(); got != "000" {
t.Fatalf("volume created with replication %q, want 000", got)
}
v.Close()
// What ConfigureVolume does: rewrite the .vif alone.
vifFile := filepath.Join(dir, "1.vif")
info, _, _, err := volume_info.MaybeLoadVolumeInfo(vifFile)
if err != nil {
t.Fatalf("load vif: %v", err)
}
info.Replication = "010"
if err := volume_info.SaveVolumeInfo(vifFile, info); err != nil {
t.Fatalf("save vif: %v", err)
}
// The .dat still says 000 — nothing rewrote it.
reopened, err := NewVolume(dir, dir, "", 1, NeedleMapInMemory, nil, nil, 0, needle.GetCurrentVersion(), 0, 0)
if err != nil {
t.Fatalf("reload volume: %v", err)
}
defer reopened.Close()
if got := reopened.SuperBlock.ReplicaPlacement.String(); got != "010" {
t.Errorf("replication after reload = %q, want 010 from the .vif; "+
"a reconfigured volume must not revert to the byte in its .dat", got)
}
}
// An empty replication in the .vif declares nothing, so the superblock stands.
// Without this a volume that never had its replication configured would be
// forced to whatever the zero value parses as.
func TestEmptyVifReplicationLeavesSuperBlockAlone(t *testing.T) {
dir := t.TempDir()
created, err := super_block.NewReplicaPlacementFromString("010")
if err != nil {
t.Fatal(err)
}
v, err := NewVolume(dir, dir, "", 2, NeedleMapInMemory, created, &needle.TTL{}, 0, needle.GetCurrentVersion(), 0, 0)
if err != nil {
t.Fatalf("create volume: %v", err)
}
v.Close()
vifFile := filepath.Join(dir, "2.vif")
if err := volume_info.SaveVolumeInfo(vifFile, &volume_server_pb.VolumeInfo{Version: uint32(needle.GetCurrentVersion())}); err != nil {
t.Fatalf("save vif: %v", err)
}
if _, err := os.Stat(vifFile); err != nil {
t.Fatalf("vif not written: %v", err)
}
reopened, err := NewVolume(dir, dir, "", 2, NeedleMapInMemory, nil, nil, 0, needle.GetCurrentVersion(), 0, 0)
if err != nil {
t.Fatalf("reload volume: %v", err)
}
defer reopened.Close()
if got := reopened.SuperBlock.ReplicaPlacement.String(); got != "010" {
t.Errorf("replication after reload = %q, want the superblock's 010 to stand", got)
}
}