// Copyright (c) 2026, Pim van Pelt package health import ( "testing" "git.ipng.ch/ipng/vpp-maglev/internal/config" ) // TestBackendEffectiveWeight locks down the state → (weight, flush) truth // table. This is the single source of truth for how maglevd decides what // to program into VPP for each backend state. If this test needs updating // the behavior has deliberately changed. func TestBackendEffectiveWeight(t *testing.T) { cases := []struct { name string poolIdx int activePool int state State cfgWeight int wantWeight uint8 wantFlush bool }{ {"up active w100", 0, 0, StateUp, 100, 100, false}, {"up active w50", 0, 0, StateUp, 50, 50, false}, {"up active w0", 0, 0, StateUp, 0, 0, false}, {"up active clamp-high", 0, 0, StateUp, 150, 100, false}, {"up active clamp-low", 0, 0, StateUp, -5, 0, false}, {"up standby pool0 active=1", 0, 1, StateUp, 100, 0, false}, {"up standby pool1 active=0", 1, 0, StateUp, 100, 0, false}, {"up standby pool2 active=0", 2, 0, StateUp, 100, 0, false}, {"up failover pool1 active=1", 1, 1, StateUp, 100, 100, false}, {"unknown pool0 active=0", 0, 0, StateUnknown, 100, 0, false}, {"unknown pool1 active=0", 1, 0, StateUnknown, 100, 0, false}, {"down pool0 active=0", 0, 0, StateDown, 100, 0, false}, {"down pool1 active=1", 1, 1, StateDown, 100, 0, false}, {"paused pool0 active=0", 0, 0, StatePaused, 100, 0, false}, {"disabled pool0 active=0", 0, 0, StateDisabled, 100, 0, true}, {"disabled pool1 active=1", 1, 1, StateDisabled, 100, 0, true}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { w, f := BackendEffectiveWeight(tc.poolIdx, tc.activePool, tc.state, tc.cfgWeight) if w != tc.wantWeight { t.Errorf("weight: got %d, want %d", w, tc.wantWeight) } if f != tc.wantFlush { t.Errorf("flush: got %v, want %v", f, tc.wantFlush) } }) } } // TestActivePoolIndex locks down the priority-failover selector: the first // pool containing at least one up backend is the active pool. Default 0. func TestActivePoolIndex(t *testing.T) { mkFE := func(pools ...[]string) config.Frontend { out := make([]config.Pool, len(pools)) for i, p := range pools { out[i] = config.Pool{Name: "p", Backends: map[string]config.PoolBackend{}} for _, name := range p { out[i].Backends[name] = config.PoolBackend{Weight: 100} } } return config.Frontend{Pools: out} } cases := []struct { name string fe config.Frontend states map[string]State want int }{ { name: "pool0 has up, pool1 standby", fe: mkFE([]string{"a", "b"}, []string{"c", "d"}), states: map[string]State{"a": StateUp, "b": StateDown, "c": StateUp, "d": StateUp}, want: 0, }, { name: "pool0 all down, pool1 has up → failover", fe: mkFE([]string{"a", "b"}, []string{"c", "d"}), states: map[string]State{"a": StateDown, "b": StateDown, "c": StateUp, "d": StateUp}, want: 1, }, { name: "pool0 all disabled, pool1 has up → failover", fe: mkFE([]string{"a", "b"}, []string{"c"}), states: map[string]State{"a": StateDisabled, "b": StateDisabled, "c": StateUp}, want: 1, }, { name: "pool0 all paused, pool1 has up → failover", fe: mkFE([]string{"a"}, []string{"c"}), states: map[string]State{"a": StatePaused, "c": StateUp}, want: 1, }, { name: "pool0 all unknown (startup), pool1 up → pool1", fe: mkFE([]string{"a"}, []string{"c"}), states: map[string]State{"a": StateUnknown, "c": StateUp}, want: 1, }, { name: "nothing up anywhere → default 0", fe: mkFE([]string{"a"}, []string{"c"}), states: map[string]State{"a": StateDown, "c": StateDown}, want: 0, }, { name: "1 up in pool0 is enough", fe: mkFE([]string{"a", "b", "c"}, []string{"d"}), states: map[string]State{"a": StateDown, "b": StateDown, "c": StateUp, "d": StateUp}, want: 0, }, { name: "three tiers, pool0 and pool1 both empty → pool2", fe: mkFE([]string{"a"}, []string{"b"}, []string{"c"}), states: map[string]State{"a": StateDown, "b": StateDown, "c": StateUp}, want: 2, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { got := ActivePoolIndex(tc.fe, tc.states) if got != tc.want { t.Errorf("got pool %d, want pool %d", got, tc.want) } }) } } // TestComputeFrontendState locks down the reduction rule: frontends are // up iff any backend has effective weight > 0, unknown iff all backends // are still in StateUnknown (or there are no backends), and down otherwise. func TestComputeFrontendState(t *testing.T) { mkFE := func(pools ...[]string) config.Frontend { out := make([]config.Pool, len(pools)) for i, p := range pools { out[i] = config.Pool{Name: "p", Backends: map[string]config.PoolBackend{}} for _, name := range p { out[i].Backends[name] = config.PoolBackend{Weight: 100} } } return config.Frontend{Pools: out} } cases := []struct { name string fe config.Frontend states map[string]State want FrontendState }{ { name: "no backends → unknown", fe: config.Frontend{Pools: []config.Pool{{Name: "primary", Backends: map[string]config.PoolBackend{}}}}, want: FrontendStateUnknown, }, { name: "all unknown (startup) → unknown", fe: mkFE([]string{"a", "b"}), states: map[string]State{"a": StateUnknown, "b": StateUnknown}, want: FrontendStateUnknown, }, { name: "one up in primary → up", fe: mkFE([]string{"a", "b"}), states: map[string]State{"a": StateUp, "b": StateDown}, want: FrontendStateUp, }, { name: "all down → down", fe: mkFE([]string{"a", "b"}), states: map[string]State{"a": StateDown, "b": StateDown}, want: FrontendStateDown, }, { name: "all disabled → down", fe: mkFE([]string{"a", "b"}), states: map[string]State{"a": StateDisabled, "b": StateDisabled}, want: FrontendStateDown, }, { name: "all paused → down", fe: mkFE([]string{"a"}), states: map[string]State{"a": StatePaused}, want: FrontendStateDown, }, { name: "primary down, secondary up → up (failover)", fe: mkFE([]string{"a"}, []string{"b"}), states: map[string]State{"a": StateDown, "b": StateUp}, want: FrontendStateUp, }, { name: "primary up, secondary down → up (secondary standby ignored)", fe: mkFE([]string{"a"}, []string{"b"}), states: map[string]State{"a": StateUp, "b": StateDown}, want: FrontendStateUp, }, { name: "primary unknown, secondary unknown → unknown", fe: mkFE([]string{"a"}, []string{"b"}), states: map[string]State{"a": StateUnknown, "b": StateUnknown}, want: FrontendStateUnknown, }, { name: "primary down, secondary unknown → down", fe: mkFE([]string{"a"}, []string{"b"}), states: map[string]State{"a": StateDown, "b": StateUnknown}, want: FrontendStateDown, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { got := ComputeFrontendState(tc.fe, tc.states) if got != tc.want { t.Errorf("got %s, want %s", got, tc.want) } }) } }