You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
163 lines
4.5 KiB
163 lines
4.5 KiB
package blockvol
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestParseDurabilityMode_Valid(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
want DurabilityMode
|
|
}{
|
|
{"best_effort", DurabilityBestEffort},
|
|
{"sync_all", DurabilitySyncAll},
|
|
{"sync_quorum", DurabilitySyncQuorum},
|
|
{"", DurabilityBestEffort}, // empty = backward compat
|
|
}
|
|
for _, tt := range tests {
|
|
got, err := ParseDurabilityMode(tt.input)
|
|
if err != nil {
|
|
t.Errorf("ParseDurabilityMode(%q) error: %v", tt.input, err)
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("ParseDurabilityMode(%q) = %v, want %v", tt.input, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseDurabilityMode_Invalid(t *testing.T) {
|
|
_, err := ParseDurabilityMode("invalid_mode")
|
|
if !errors.Is(err, ErrInvalidDurabilityMode) {
|
|
t.Errorf("ParseDurabilityMode(invalid) error = %v, want ErrInvalidDurabilityMode", err)
|
|
}
|
|
}
|
|
|
|
func TestDurabilityMode_StringRoundTrip(t *testing.T) {
|
|
for _, m := range []DurabilityMode{DurabilityBestEffort, DurabilitySyncAll, DurabilitySyncQuorum} {
|
|
s := m.String()
|
|
got, err := ParseDurabilityMode(s)
|
|
if err != nil {
|
|
t.Errorf("round-trip %v -> %q: parse error: %v", m, s, err)
|
|
}
|
|
if got != m {
|
|
t.Errorf("round-trip %v -> %q -> %v", m, s, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDurabilityMode_Validate_SyncQuorum_RF2_Rejected(t *testing.T) {
|
|
err := DurabilitySyncQuorum.Validate(2)
|
|
if !errors.Is(err, ErrSyncQuorumRequiresRF3) {
|
|
t.Errorf("Validate(sync_quorum, RF=2) = %v, want ErrSyncQuorumRequiresRF3", err)
|
|
}
|
|
}
|
|
|
|
func TestDurabilityMode_Validate_SyncQuorum_RF3_OK(t *testing.T) {
|
|
if err := DurabilitySyncQuorum.Validate(3); err != nil {
|
|
t.Errorf("Validate(sync_quorum, RF=3) = %v, want nil", err)
|
|
}
|
|
}
|
|
|
|
func TestDurabilityMode_Validate_BestEffort_RF1_OK(t *testing.T) {
|
|
if err := DurabilityBestEffort.Validate(1); err != nil {
|
|
t.Errorf("Validate(best_effort, RF=1) = %v, want nil", err)
|
|
}
|
|
}
|
|
|
|
func TestDurabilityMode_RequiredReplicas_SyncAll_RF2(t *testing.T) {
|
|
got := DurabilitySyncAll.RequiredReplicas(2)
|
|
if got != 1 {
|
|
t.Errorf("RequiredReplicas(sync_all, RF=2) = %d, want 1", got)
|
|
}
|
|
}
|
|
|
|
func TestDurabilityMode_RequiredReplicas_SyncQuorum_RF3(t *testing.T) {
|
|
got := DurabilitySyncQuorum.RequiredReplicas(3)
|
|
if got != 1 { // quorum=2, primary counts as 1, need 1 replica
|
|
t.Errorf("RequiredReplicas(sync_quorum, RF=3) = %d, want 1", got)
|
|
}
|
|
}
|
|
|
|
func TestDurabilityMode_IsStrict(t *testing.T) {
|
|
if DurabilityBestEffort.IsStrict() {
|
|
t.Error("best_effort should not be strict")
|
|
}
|
|
if !DurabilitySyncAll.IsStrict() {
|
|
t.Error("sync_all should be strict")
|
|
}
|
|
if !DurabilitySyncQuorum.IsStrict() {
|
|
t.Error("sync_quorum should be strict")
|
|
}
|
|
}
|
|
|
|
func TestDurabilityMode_CreateCloseOpenRoundTrip(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "test.blk")
|
|
|
|
vol, err := CreateBlockVol(path, CreateOptions{
|
|
VolumeSize: 4 * 1024 * 1024,
|
|
DurabilityMode: DurabilitySyncAll,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Create: %v", err)
|
|
}
|
|
if vol.DurabilityMode() != DurabilitySyncAll {
|
|
t.Fatalf("DurabilityMode() = %v, want sync_all", vol.DurabilityMode())
|
|
}
|
|
vol.Close()
|
|
|
|
vol2, err := OpenBlockVol(path)
|
|
if err != nil {
|
|
t.Fatalf("Open: %v", err)
|
|
}
|
|
defer vol2.Close()
|
|
if vol2.DurabilityMode() != DurabilitySyncAll {
|
|
t.Errorf("DurabilityMode() after reopen = %v, want sync_all", vol2.DurabilityMode())
|
|
}
|
|
}
|
|
|
|
func TestDurabilityMode_Accessor_Default(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "test.blk")
|
|
|
|
vol, err := CreateBlockVol(path, CreateOptions{VolumeSize: 4 * 1024 * 1024})
|
|
if err != nil {
|
|
t.Fatalf("Create: %v", err)
|
|
}
|
|
defer vol.Close()
|
|
if vol.DurabilityMode() != DurabilityBestEffort {
|
|
t.Errorf("default DurabilityMode() = %v, want best_effort", vol.DurabilityMode())
|
|
}
|
|
}
|
|
|
|
func TestDurabilityMode_InvalidOnDisk_OpenFails(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "test.blk")
|
|
|
|
vol, err := CreateBlockVol(path, CreateOptions{VolumeSize: 4 * 1024 * 1024})
|
|
if err != nil {
|
|
t.Fatalf("Create: %v", err)
|
|
}
|
|
vol.Close()
|
|
|
|
// Corrupt the DurabilityMode byte on disk.
|
|
// Epoch ends at offset 104. DurabilityMode is at offset 104.
|
|
// Actually, let me compute: 4(magic)+2(ver)+2(flags)+16(uuid)+8(volsz)+4(ext)+4(blk)+
|
|
// 8(waloff)+8(walsz)+8(walhead)+8(waltail)+8(walcplsn)+4(repl)+8(created)+4(snapcnt)+8(epoch) = 104
|
|
f, err := os.OpenFile(path, os.O_RDWR, 0644)
|
|
if err != nil {
|
|
t.Fatalf("open: %v", err)
|
|
}
|
|
if _, err := f.WriteAt([]byte{99}, 104); err != nil {
|
|
t.Fatalf("write corrupt byte: %v", err)
|
|
}
|
|
f.Close()
|
|
|
|
_, err = OpenBlockVol(path)
|
|
if err == nil {
|
|
t.Fatal("OpenBlockVol should fail with invalid DurabilityMode")
|
|
}
|
|
}
|