mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-23 06:20:48 +02:00
Durability mode implementation (sync_all, sync_quorum, best_effort): - DurabilityMode type with superblock persistence, parse/validate/string - MakeDistributedSync mode-aware barrier enforcement in dist_group_commit - blockerr sentinel package (ErrDurabilityBarrierFailed, ErrDurabilityQuorumLost) - gRPC create path: mode validation, idempotent create consistency, partial cleanup - F1: strict mode rejects partial replica provisioning with cleanup - F3: empty heartbeat does not overwrite persisted strict mode - F4: SCSI error mapping uses errors.Is sentinels (not string matching) - Proto/wire/blockapi/CLI/UI plumbing for durability_mode field - Observability dashboard: cluster health cards + per-volume columns Testrunner platform (YAML-driven integration test framework): - Engine, parser, registry, reporter (JUnit XML + HTML), metrics scraping - 52 registered actions: block, iSCSI, I/O, fault injection, assertions - Baseline regression framework with 7 hard-fail conditions - 15 YAML scenarios (smoke, crash, HA, fault, consistency, snapshot) - 49 unit tests for testrunner internals QA adversarial suite (21 tests, all PASS): - Idempotent create mode/RF mismatch detection - Heartbeat mode downgrade prevention (F3) - sync_all/sync_quorum partial replica enforcement (F1) - Concurrent create race safety - Failover/expand mode preservation - Cleanup resilience when delete fails - Master restart auto-register mode handling - Superblock roundtrip all 3 modes - Validate edge cases (mode×RF matrix) - RequiredReplicas quorum math verification - Sentinel error categorization Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
60 lines
1.9 KiB
Go
60 lines
1.9 KiB
Go
package iscsi
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/blockvol/blockerr"
|
|
)
|
|
|
|
func TestMapBlockVolError_Durability_HardwareError(t *testing.T) {
|
|
durErrs := []error{
|
|
blockerr.ErrDurabilityBarrierFailed,
|
|
blockerr.ErrDurabilityQuorumLost,
|
|
fmt.Errorf("wrapped: %w", blockerr.ErrDurabilityBarrierFailed),
|
|
fmt.Errorf("%w: 1 of 2 barriers failed", blockerr.ErrDurabilityBarrierFailed),
|
|
fmt.Errorf("%w: 1 durable of 2 needed", blockerr.ErrDurabilityQuorumLost),
|
|
}
|
|
for _, err := range durErrs {
|
|
result := mapBlockVolError(err)
|
|
if result.SenseKey != SenseHardwareError {
|
|
t.Errorf("mapBlockVolError(%v) SenseKey = %d, want %d (HARDWARE_ERROR)",
|
|
err, result.SenseKey, SenseHardwareError)
|
|
}
|
|
if result.SenseASC != 0x08 {
|
|
t.Errorf("mapBlockVolError(%v) ASC = 0x%02x, want 0x08",
|
|
err, result.SenseASC)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMapBlockVolError_NonDurability_MediumError(t *testing.T) {
|
|
normalErrs := []error{
|
|
errors.New("disk I/O error"),
|
|
fmt.Errorf("write failed: %w", errors.New("EIO")),
|
|
}
|
|
for _, err := range normalErrs {
|
|
result := mapBlockVolError(err)
|
|
if result.SenseKey != SenseMediumError {
|
|
t.Errorf("mapBlockVolError(%v) SenseKey = %d, want %d (MEDIUM_ERROR)",
|
|
err, result.SenseKey, SenseMediumError)
|
|
}
|
|
if result.SenseASC != 0x0C {
|
|
t.Errorf("mapBlockVolError(%v) ASC = 0x%02x, want 0x0C",
|
|
err, result.SenseASC)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMapBlockVolError_StringMatchNoLongerWorks(t *testing.T) {
|
|
// Verify that string-matching is NOT used — a plain error with the
|
|
// same text should NOT be detected as a durability error.
|
|
fakeErr := errors.New("blockvol: sync_all durability barrier failed")
|
|
result := mapBlockVolError(fakeErr)
|
|
// This should be MEDIUM_ERROR because it's not the real sentinel.
|
|
if result.SenseKey != SenseMediumError {
|
|
t.Errorf("string-based error should NOT match as durability error: SenseKey=%d", result.SenseKey)
|
|
}
|
|
}
|