mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-27 10:24:16 +00:00
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>
148 lines
4.2 KiB
Go
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)")
|
|
}
|