Files
seaweedfs/weed/storage/blockvol/preset_test.go
T
Ping QiuandClaude Opus 4.6 683969086c feat: CP11B-1 provisioning presets + review fixes
Preset system: ResolvePolicy resolves named presets (database, general,
throughput) with per-field overrides into concrete volume parameters.
Create path now uses resolved policy instead of ad-hoc validation.
New /block/volume/resolve diagnostic endpoint for dry-run resolution.

Review fix 1 (MED): HasNVMeCapableServer now derives NVMe capability
from server-level heartbeat attribute (block_nvme_addr proto field)
instead of scanning volume entries. Fixes false "no NVMe" warning on
fresh clusters with NVMe-capable servers but no volumes yet.

Review fix 2 (LOW): /block/volume/resolve no longer proxied to leader —
read-only diagnostic endpoint can be served by any master.

Engine fix: ReadLBA retry loop closes stale dirty-map race when WAL
entry is recycled between lookup and read.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 14:44:24 -07:00

186 lines
5.5 KiB
Go

package blockvol
import (
"strings"
"testing"
)
func TestResolvePolicy_DatabaseDefaults(t *testing.T) {
r := ResolvePolicy(PresetDatabase, "", 0, "", EnvironmentInfo{
NVMeAvailable: true, ServerCount: 3, WALSizeDefault: 128 << 20, BlockSizeDefault: 4096,
})
if len(r.Errors) > 0 {
t.Fatalf("unexpected errors: %v", r.Errors)
}
p := r.Policy
if p.DurabilityMode != "sync_all" {
t.Errorf("durability_mode = %q, want sync_all", p.DurabilityMode)
}
if p.ReplicaFactor != 2 {
t.Errorf("replica_factor = %d, want 2", p.ReplicaFactor)
}
if p.DiskType != "ssd" {
t.Errorf("disk_type = %q, want ssd", p.DiskType)
}
if p.TransportPref != "nvme" {
t.Errorf("transport_preference = %q, want nvme", p.TransportPref)
}
if p.WALSizeRecommended != 128<<20 {
t.Errorf("wal_size_recommended = %d, want %d", p.WALSizeRecommended, 128<<20)
}
if p.WorkloadHint != "database" {
t.Errorf("workload_hint = %q, want database", p.WorkloadHint)
}
if p.StorageProfile != "single" {
t.Errorf("storage_profile = %q, want single", p.StorageProfile)
}
if len(r.Overrides) != 0 {
t.Errorf("overrides = %v, want empty", r.Overrides)
}
}
func TestResolvePolicy_GeneralDefaults(t *testing.T) {
r := ResolvePolicy(PresetGeneral, "", 0, "", EnvironmentInfo{
ServerCount: 2, WALSizeDefault: 64 << 20, BlockSizeDefault: 4096,
})
if len(r.Errors) > 0 {
t.Fatalf("unexpected errors: %v", r.Errors)
}
p := r.Policy
if p.DurabilityMode != "best_effort" {
t.Errorf("durability_mode = %q, want best_effort", p.DurabilityMode)
}
if p.ReplicaFactor != 2 {
t.Errorf("replica_factor = %d, want 2", p.ReplicaFactor)
}
if p.DiskType != "" {
t.Errorf("disk_type = %q, want empty", p.DiskType)
}
if p.TransportPref != "iscsi" {
t.Errorf("transport_preference = %q, want iscsi", p.TransportPref)
}
if p.WALSizeRecommended != 64<<20 {
t.Errorf("wal_size_recommended = %d, want %d", p.WALSizeRecommended, 64<<20)
}
}
func TestResolvePolicy_ThroughputDefaults(t *testing.T) {
r := ResolvePolicy(PresetThroughput, "", 0, "", EnvironmentInfo{
ServerCount: 2, WALSizeDefault: 64 << 20, BlockSizeDefault: 4096,
})
if len(r.Errors) > 0 {
t.Fatalf("unexpected errors: %v", r.Errors)
}
p := r.Policy
if p.DurabilityMode != "best_effort" {
t.Errorf("durability_mode = %q, want best_effort", p.DurabilityMode)
}
if p.WALSizeRecommended != 128<<20 {
t.Errorf("wal_size_recommended = %d, want %d", p.WALSizeRecommended, 128<<20)
}
if p.WorkloadHint != "throughput" {
t.Errorf("workload_hint = %q, want throughput", p.WorkloadHint)
}
}
func TestResolvePolicy_OverrideDurability(t *testing.T) {
r := ResolvePolicy(PresetDatabase, "best_effort", 0, "", EnvironmentInfo{
NVMeAvailable: true, ServerCount: 2, WALSizeDefault: 128 << 20, BlockSizeDefault: 4096,
})
if len(r.Errors) > 0 {
t.Fatalf("unexpected errors: %v", r.Errors)
}
if r.Policy.DurabilityMode != "best_effort" {
t.Errorf("durability_mode = %q, want best_effort (override)", r.Policy.DurabilityMode)
}
if !containsStr(r.Overrides, "durability_mode") {
t.Errorf("overrides = %v, want durability_mode present", r.Overrides)
}
}
func TestResolvePolicy_OverrideRF(t *testing.T) {
r := ResolvePolicy(PresetGeneral, "", 3, "", EnvironmentInfo{
ServerCount: 3, WALSizeDefault: 64 << 20, BlockSizeDefault: 4096,
})
if len(r.Errors) > 0 {
t.Fatalf("unexpected errors: %v", r.Errors)
}
if r.Policy.ReplicaFactor != 3 {
t.Errorf("replica_factor = %d, want 3 (override)", r.Policy.ReplicaFactor)
}
if !containsStr(r.Overrides, "replica_factor") {
t.Errorf("overrides = %v, want replica_factor present", r.Overrides)
}
}
func TestResolvePolicy_NoPreset_SystemDefaults(t *testing.T) {
r := ResolvePolicy("", "", 0, "", EnvironmentInfo{
ServerCount: 2, WALSizeDefault: 64 << 20, BlockSizeDefault: 4096,
})
if len(r.Errors) > 0 {
t.Fatalf("unexpected errors: %v", r.Errors)
}
p := r.Policy
if p.DurabilityMode != "best_effort" {
t.Errorf("durability_mode = %q, want best_effort", p.DurabilityMode)
}
if p.ReplicaFactor != 2 {
t.Errorf("replica_factor = %d, want 2", p.ReplicaFactor)
}
if p.TransportPref != "iscsi" {
t.Errorf("transport_preference = %q, want iscsi", p.TransportPref)
}
if p.Preset != "" {
t.Errorf("preset = %q, want empty", p.Preset)
}
}
func TestResolvePolicy_InvalidPreset(t *testing.T) {
r := ResolvePolicy("nosuch", "", 0, "", EnvironmentInfo{})
if len(r.Errors) == 0 {
t.Fatal("expected error for unknown preset")
}
if !strings.Contains(r.Errors[0], "unknown preset") {
t.Errorf("error = %q, want to contain 'unknown preset'", r.Errors[0])
}
}
func TestResolvePolicy_IncompatibleCombo(t *testing.T) {
r := ResolvePolicy("", "sync_quorum", 2, "", EnvironmentInfo{
ServerCount: 2, WALSizeDefault: 64 << 20, BlockSizeDefault: 4096,
})
if len(r.Errors) == 0 {
t.Fatal("expected error for sync_quorum + RF=2")
}
if !strings.Contains(r.Errors[0], "replica_factor >= 3") {
t.Errorf("error = %q, want sync_quorum RF constraint", r.Errors[0])
}
}
func TestResolvePolicy_WALWarning(t *testing.T) {
r := ResolvePolicy(PresetDatabase, "", 0, "", EnvironmentInfo{
NVMeAvailable: true, ServerCount: 2, WALSizeDefault: 64 << 20, BlockSizeDefault: 4096,
})
if len(r.Errors) > 0 {
t.Fatalf("unexpected errors: %v", r.Errors)
}
found := false
for _, w := range r.Warnings {
if strings.Contains(w, "128MB WAL") && strings.Contains(w, "64MB") {
found = true
}
}
if !found {
t.Errorf("expected WAL sizing warning, got warnings: %v", r.Warnings)
}
}
func containsStr(ss []string, target string) bool {
for _, s := range ss {
if s == target {
return true
}
}
return false
}