| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- --[[
- test_state.lua — Watchdog state machine unit tests
- Run: lua5.1 tests/test_state.lua
- ]]
- package.path = package.path .. ";./src/?.lua"
- local M = require("balancerlite.state")
- local state = M.state
- local function assert_eq(a, b, msg)
- if a ~= b then
- error(((msg or "") .. ": expected " .. tostring(b) .. ", got " .. tostring(a)), 2)
- end
- end
- local TBASE = 1000000000
- local function feed_ok(s, wan_id, n, base)
- base = base or TBASE
- for i = 1, (n or 1) do
- M.feed(s, wan_id, {ok=true, rtt_ms=5, ts=base+i})
- end
- end
- local function feed_fail(s, wan_id, n, base)
- base = base or TBASE
- for i = 1, (n or 1) do
- M.feed(s, wan_id, {ok=false, ts=base+i})
- end
- end
- print("=== State machine tests ===")
- -- TEST 1: INIT → WAN_A_PRIMARY
- local S = M.new({ down_threshold=3, up_threshold=3, flap_threshold=99,
- flap_window_s=300, flap_recovery=300 })
- assert_eq(S.cur_state, state.INIT, "initial state")
- feed_ok(S, "wan-a", 3)
- local ev = M.advance(S)
- assert_eq(S.cur_state, state.WAN_A_PRIMARY, "wan-a up → WAN_A_PRIMARY")
- assert_eq(ev.type, "state.init", "event type")
- print(" TEST 1: INIT→WAN_A_PRIMARY: OK")
- -- TEST 2: Failover wan-a → wan-b
- S = M.new({ down_threshold=3, up_threshold=3, flap_threshold=99,
- flap_window_s=300, flap_recovery=300 })
- feed_ok(S, "wan-a", 3); M.advance(S) -- WAN_A_PRIMARY
- assert_eq(S.cur_state, state.WAN_A_PRIMARY, "setup: wan-a primary")
- feed_ok(S, "wan-b", 3); M.advance(S) -- wan-b is also up
- feed_fail(S, "wan-a", 3)
- ev = M.advance(S)
- assert_eq(S.cur_state, state.SWITCHING_TO_B, "wan-a down + wan-b up → SWITCHING_TO_B")
- assert_eq(ev.type, "failover.switch", "event type")
- ev = M.advance(S)
- assert_eq(S.cur_state, state.WAN_B_PRIMARY, "confirmed → WAN_B_PRIMARY")
- print(" TEST 2: wan-a→wan-b failover: OK")
- -- TEST 3: BOTH_DOWN when both fail
- S = M.new({ down_threshold=2, up_threshold=2, flap_threshold=99,
- flap_window_s=300, flap_recovery=300 })
- feed_ok(S, "wan-a", 2); M.advance(S)
- feed_fail(S, "wan-a", 2); feed_fail(S, "wan-b", 2)
- ev = M.advance(S)
- assert_eq(S.cur_state, state.BOTH_DOWN, "both fail → BOTH_DOWN")
- assert_eq(ev.type, "state.both_down", "event type")
- print(" TEST 3: BOTH_DOWN: OK")
- -- TEST 4: Recovery from BOTH_DOWN → WAN_B_PRIMARY (direct, no SWITCHING)
- S = M.new({ down_threshold=2, up_threshold=2, flap_threshold=99,
- flap_window_s=300, flap_recovery=300 })
- feed_ok(S, "wan-a", 2); M.advance(S)
- feed_fail(S, "wan-a", 2); feed_fail(S, "wan-b", 2)
- M.advance(S) -- BOTH_DOWN
- assert_eq(S.cur_state, state.BOTH_DOWN, "setup: BOTH_DOWN")
- feed_ok(S, "wan-b", 2)
- ev = M.advance(S)
- assert_eq(S.cur_state, state.WAN_B_PRIMARY, "wan-b recovers → WAN_B_PRIMARY")
- assert_eq(ev.type, "state.recovered", "event is state.recovered")
- print(" TEST 4: BOTH_DOWN recovery: OK")
- -- TEST 5: Hysteresis (up_thr > down_thr)
- S = M.new({ down_threshold=2, up_threshold=5, flap_threshold=99,
- flap_window_s=300, flap_recovery=300 })
- feed_ok(S, "wan-a", 5); M.advance(S)
- assert_eq(S.cur_state, state.WAN_A_PRIMARY, "setup: wan-a primary")
- feed_fail(S, "wan-a", 2); feed_fail(S, "wan-b", 2)
- ev = M.advance(S)
- assert_eq(S.cur_state, state.BOTH_DOWN, "both fail (2 fails each) → BOTH_DOWN")
- print(" TEST 5: hysteresis (up=5, down=2): OK")
- -- TEST 6: DEGRADED on flap (3 switches needed)
- -- Each switch: stable → failure+ok → SWITCHING → advance → stable (records switch).
- -- 3 switches = 3 advance() calls from stable states that changed = DEGRADED.
- S = M.new({ down_threshold=2, up_threshold=2, flap_threshold=3,
- flap_window_s=300, flap_recovery=300 })
- feed_ok(S, "wan-a", 2); M.advance(S) -- → WAN_A_PRIMARY [switch 1]
- assert_eq(S.cur_state, state.WAN_A_PRIMARY)
- -- Switch 1: A→B
- feed_fail(S, "wan-a", 2); feed_ok(S, "wan-b", 2)
- M.advance(S) -- → SWITCHING_TO_B
- M.advance(S) -- → WAN_B_PRIMARY [switch 2]
- -- Switch 2: B→A
- feed_fail(S, "wan-b", 2); feed_ok(S, "wan-a", 2)
- M.advance(S) -- → SWITCHING_TO_A
- M.advance(S) -- → WAN_A_PRIMARY [switch 3]
- -- Switch 3: A→B — 3rd switch → DEGRADED
- feed_fail(S, "wan-a", 2); feed_ok(S, "wan-b", 2)
- M.advance(S) -- → SWITCHING_TO_B
- ev = M.advance(S) -- → DEGRADED (nswitches=3 >= flap_threshold=3)
- assert_eq(S.cur_state, state.DEGRADED, "3 prior switches → DEGRADED")
- assert_eq(ev.type, "flap.alert", "event is flap.alert")
- print(" TEST 6: flap → DEGRADED: OK")
- -- TEST 7: DEGRADED persists until flap_recovery (use flap_threshold=2 so BOTH_DOWN
- -- doesn't keep accumulating; BOTH_DOWN clears switch_times, so only count stable switches)
- S = M.new({ down_threshold=2, up_threshold=2, flap_threshold=2,
- flap_window_s=300, flap_recovery=300 })
- feed_ok(S, "wan-a", 2); M.advance(S) -- → WAN_A_PRIMARY
- feed_fail(S, "wan-a", 2); feed_ok(S, "wan-b", 2)
- M.advance(S); M.advance(S) -- → WAN_B_PRIMARY [switch 1]
- assert_eq(S.cur_state, state.WAN_B_PRIMARY)
- -- Switch 2: B→A → DEGRADED (flap_threshold=2, 2nd switch triggers DEGRADED)
- feed_fail(S, "wan-b", 2); feed_ok(S, "wan-a", 2)
- M.advance(S); -- → SWITCHING_TO_A
- ev = M.advance(S) -- → WAN_A_PRIMARY [switch 2] → DEGRADED
- assert_eq(S.cur_state, state.DEGRADED, "2 switches → DEGRADED")
- assert_eq(ev.type, "flap.alert", "event is flap.alert")
- ev = M.advance(S) -- no new switches, no calm period → stays DEGRADED
- assert_eq(S.cur_state, state.DEGRADED, "still DEGRADED (no calm period)")
- print(" TEST 7: DEGRADED persists until calm: OK")
- -- TEST 8: Switch-back to preferred (A recovers while on B)
- S = M.new({ down_threshold=2, up_threshold=2, flap_threshold=99,
- flap_window_s=300, flap_recovery=300 })
- feed_ok(S, "wan-a", 2); M.advance(S) -- WAN_A_PRIMARY
- feed_fail(S, "wan-a", 2); feed_ok(S, "wan-b", 2)
- M.advance(S) -- → SWITCHING_TO_B
- M.advance(S) -- → WAN_B_PRIMARY (now on wan-b)
- assert_eq(S.cur_state, state.WAN_B_PRIMARY, "on wan-b")
- feed_ok(S, "wan-a", 2) -- wan-a recovers
- ev = M.advance(S)
- assert_eq(S.cur_state, state.SWITCHING_TO_A, "wan-a recovered → SWITCHING_TO_A")
- ev = M.advance(S)
- assert_eq(S.cur_state, state.WAN_A_PRIMARY, "confirmed → WAN_A_PRIMARY")
- print(" TEST 8: switch-back to preferred: OK")
- print("PASS: state")
|