Files
seaweedfs/weed/storage/blockvol/preset.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

175 lines
5.1 KiB
Go

package blockvol
import "fmt"
// PresetName identifies a named provisioning preset.
type PresetName string
const (
PresetDatabase PresetName = "database"
PresetGeneral PresetName = "general"
PresetThroughput PresetName = "throughput"
)
// VolumePolicy is the fully resolved configuration for a block volume.
type VolumePolicy struct {
Preset PresetName `json:"preset,omitempty"`
DurabilityMode string `json:"durability_mode"`
ReplicaFactor int `json:"replica_factor"`
DiskType string `json:"disk_type,omitempty"`
TransportPref string `json:"transport_preference"`
WorkloadHint string `json:"workload_hint"`
WALSizeRecommended uint64 `json:"wal_size_recommended"`
StorageProfile string `json:"storage_profile"`
}
// ResolvedPolicy is the result of resolving a preset + user overrides.
type ResolvedPolicy struct {
Policy VolumePolicy `json:"policy"`
Overrides []string `json:"overrides,omitempty"`
Warnings []string `json:"warnings,omitempty"`
Errors []string `json:"errors,omitempty"`
}
// EnvironmentInfo provides cluster state to the resolver.
type EnvironmentInfo struct {
NVMeAvailable bool
ServerCount int
WALSizeDefault uint64
BlockSizeDefault uint32
}
// presetDefaults holds the default policy for each named preset.
type presetDefaults struct {
DurabilityMode string
ReplicaFactor int
DiskType string
TransportPref string
WorkloadHint string
WALSizeRec uint64
}
var presets = map[PresetName]presetDefaults{
PresetDatabase: {
DurabilityMode: "sync_all",
ReplicaFactor: 2,
DiskType: "ssd",
TransportPref: "nvme",
WorkloadHint: WorkloadDatabase,
WALSizeRec: 128 << 20,
},
PresetGeneral: {
DurabilityMode: "best_effort",
ReplicaFactor: 2,
DiskType: "",
TransportPref: "iscsi",
WorkloadHint: WorkloadGeneral,
WALSizeRec: 64 << 20,
},
PresetThroughput: {
DurabilityMode: "best_effort",
ReplicaFactor: 2,
DiskType: "",
TransportPref: "iscsi",
WorkloadHint: WorkloadThroughput,
WALSizeRec: 128 << 20,
},
}
// system defaults when no preset is specified
var systemDefaults = presetDefaults{
DurabilityMode: "best_effort",
ReplicaFactor: 2,
DiskType: "",
TransportPref: "iscsi",
WorkloadHint: WorkloadGeneral,
WALSizeRec: 64 << 20,
}
// ResolvePolicy resolves a preset + explicit request fields into a final policy.
// Pure function — no side effects, no server dependencies.
func ResolvePolicy(preset PresetName, durabilityMode string, replicaFactor int,
diskType string, env EnvironmentInfo) ResolvedPolicy {
var result ResolvedPolicy
// Step 1: Look up preset defaults.
defaults := systemDefaults
if preset != "" {
pd, ok := presets[preset]
if !ok {
result.Errors = append(result.Errors, fmt.Sprintf("unknown preset %q", preset))
return result
}
defaults = pd
}
// Start with defaults.
policy := VolumePolicy{
Preset: preset,
DurabilityMode: defaults.DurabilityMode,
ReplicaFactor: defaults.ReplicaFactor,
DiskType: defaults.DiskType,
TransportPref: defaults.TransportPref,
WorkloadHint: defaults.WorkloadHint,
WALSizeRecommended: defaults.WALSizeRec,
StorageProfile: "single",
}
// Step 2: Apply overrides.
if durabilityMode != "" {
policy.DurabilityMode = durabilityMode
result.Overrides = append(result.Overrides, "durability_mode")
}
if replicaFactor != 0 {
policy.ReplicaFactor = replicaFactor
result.Overrides = append(result.Overrides, "replica_factor")
}
if diskType != "" {
policy.DiskType = diskType
result.Overrides = append(result.Overrides, "disk_type")
}
// Step 3: Normalize + validate durability_mode.
durMode, err := ParseDurabilityMode(policy.DurabilityMode)
if err != nil {
result.Errors = append(result.Errors, fmt.Sprintf("invalid durability_mode: %s", err))
result.Policy = policy
return result
}
// Step 4: Cross-validate durability vs RF.
if err := durMode.Validate(policy.ReplicaFactor); err != nil {
result.Errors = append(result.Errors, err.Error())
result.Policy = policy
return result
}
// Step 5: Advisory warnings.
if policy.ReplicaFactor == 1 && durMode == DurabilitySyncAll {
result.Warnings = append(result.Warnings,
"sync_all with replica_factor=1 provides no replication benefit")
}
if env.ServerCount > 0 && policy.ReplicaFactor > env.ServerCount {
result.Warnings = append(result.Warnings,
fmt.Sprintf("replica_factor=%d exceeds available servers (%d)", policy.ReplicaFactor, env.ServerCount))
}
// Step 6: Transport advisory.
if policy.TransportPref == "nvme" && !env.NVMeAvailable {
result.Warnings = append(result.Warnings,
"preset recommends NVMe transport but no NVMe-capable servers are available")
}
// Step 7: WAL sizing advisory.
// Check engine default against preset recommendation.
if env.WALSizeDefault > 0 && env.WALSizeDefault < policy.WALSizeRecommended {
result.Warnings = append(result.Warnings,
fmt.Sprintf("preset recommends %dMB WAL but engine default is %dMB",
policy.WALSizeRecommended>>20, env.WALSizeDefault>>20))
}
result.Policy = policy
return result
}