diff --git a/weed/storage/volume_super_block_replication_test.go b/weed/storage/volume_super_block_replication_test.go new file mode 100644 index 000000000..f4a88ca49 --- /dev/null +++ b/weed/storage/volume_super_block_replication_test.go @@ -0,0 +1,101 @@ +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) + } +}