Files
seaweedfs/weed/server/qa_block_ec6_fix_test.go
T
pingqiuandClaude Opus 4.6 bdf20fde71 feat: Phase 12 — production hardening (disturbance, soak, testrunner scenarios)
P1 Disturbance: restart/reconnect correctness tests — assignment delivery
  through real proto → ProcessAssignments, epoch validation on promoted
  volume, mandatory reconnect assertions

P2 Soak: repeated create/failover/recover cycles with end-of-cycle truth
  checks, runtime hygiene (no stale tasks/entries), steady-state idempotence

Testrunner recovery actions + scenarios:
- recovery.go: wait_recovery_complete, assert_recovery_state, trigger_rebuild
- 8 new YAML scenarios: baseline (failover/crash/partition), stability
  (replication-tax, netem-sweep, packet-loss, degraded), robust shipper

HA edge case and EC6 fix tests for regression coverage.

(P3 diagnosability + P4 perf floor committed separately in 643a5a107)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 16:26:17 -07:00

148 lines
4.2 KiB
Go

package weed_server
import (
"testing"
"time"
"github.com/seaweedfs/seaweedfs/weed/storage/blockvol"
)
// ============================================================
// EC-6 Fix Verification
// ============================================================
// Gate still applies when primary is alive (prevents split-brain).
func TestEC6Fix_GateStillAppliesWhenPrimaryAlive(t *testing.T) {
r := NewBlockVolumeRegistry()
r.MarkBlockCapable("primary:9333")
r.MarkBlockCapable("replica:9333")
r.Register(&BlockVolumeEntry{
Name: "ec6-alive",
VolumeServer: "primary:9333",
Path: "/data/ec6-alive.blk",
SizeBytes: 1 << 30,
Epoch: 1,
Role: blockvol.RoleToWire(blockvol.RolePrimary),
Status: StatusActive,
LeaseTTL: 30 * time.Second,
LastLeaseGrant: time.Now(),
WALHeadLSN: 500,
Replicas: []ReplicaInfo{
{
Server: "replica:9333",
Path: "/data/ec6-alive.blk",
Role: blockvol.RoleToWire(blockvol.RoleReplica),
WALHeadLSN: 350, // 150 behind
HealthScore: 1.0,
LastHeartbeat: time.Now(),
},
},
})
// Primary still alive — DON'T unmark.
// WAL lag gate should still apply → reject.
pf, _ := r.EvaluatePromotion("ec6-alive")
if pf.Promotable {
t.Fatal("WAL lag gate should still reject when primary is ALIVE (prevents split-brain)")
}
hasWALLag := false
for _, rej := range pf.Rejections {
if rej.Reason == "wal_lag" {
hasWALLag = true
}
}
if !hasWALLag {
t.Fatalf("expected wal_lag rejection when primary alive, got: %s", pf.Reason)
}
t.Log("EC-6 fix safe: WAL lag gate still applies when primary is alive")
}
// Gate skipped when primary is dead → promotion succeeds.
func TestEC6Fix_GateSkippedWhenPrimaryDead(t *testing.T) {
r := NewBlockVolumeRegistry()
r.MarkBlockCapable("primary:9333")
r.MarkBlockCapable("replica:9333")
r.Register(&BlockVolumeEntry{
Name: "ec6-dead",
VolumeServer: "primary:9333",
Path: "/data/ec6-dead.blk",
SizeBytes: 1 << 30,
Epoch: 1,
Role: blockvol.RoleToWire(blockvol.RolePrimary),
Status: StatusActive,
LeaseTTL: 30 * time.Second,
LastLeaseGrant: time.Now(),
WALHeadLSN: 500,
Replicas: []ReplicaInfo{
{
Server: "replica:9333",
Path: "/data/ec6-dead.blk",
Role: blockvol.RoleToWire(blockvol.RoleReplica),
WALHeadLSN: 350, // 150 behind — would fail old gate
HealthScore: 1.0,
LastHeartbeat: time.Now(),
},
},
})
// Primary dies.
r.UnmarkBlockCapable("primary:9333")
// Should succeed now (EC-6 fix: gate skipped for dead primary).
newEpoch, err := r.PromoteBestReplica("ec6-dead")
if err != nil {
t.Fatalf("promotion should succeed when primary is dead: %v", err)
}
entry, _ := r.Lookup("ec6-dead")
if entry.VolumeServer != "replica:9333" {
t.Fatalf("primary=%s, want replica:9333", entry.VolumeServer)
}
t.Logf("EC-6 fix works: dead primary → WAL lag gate skipped → promoted to epoch %d", newEpoch)
}
// Large lag (1000+ entries behind) still promotes when primary dead.
func TestEC6Fix_LargeLag_StillPromotes(t *testing.T) {
r := NewBlockVolumeRegistry()
r.MarkBlockCapable("primary:9333")
r.MarkBlockCapable("replica:9333")
r.Register(&BlockVolumeEntry{
Name: "ec6-large",
VolumeServer: "primary:9333",
Path: "/data/ec6-large.blk",
SizeBytes: 1 << 30,
Epoch: 1,
Role: blockvol.RoleToWire(blockvol.RolePrimary),
Status: StatusActive,
LeaseTTL: 30 * time.Second,
LastLeaseGrant: time.Now(),
WALHeadLSN: 10000, // very active primary
Replicas: []ReplicaInfo{
{
Server: "replica:9333",
Path: "/data/ec6-large.blk",
Role: blockvol.RoleToWire(blockvol.RoleReplica),
WALHeadLSN: 5000, // 5000 entries behind
HealthScore: 1.0,
LastHeartbeat: time.Now(),
},
},
})
r.UnmarkBlockCapable("primary:9333")
_, err := r.PromoteBestReplica("ec6-large")
if err != nil {
t.Fatalf("even 5000-entry lag should promote when primary dead: %v", err)
}
t.Log("EC-6 fix: 5000-entry lag + dead primary → still promoted (best available)")
}