// Copyright (c) 2026, Pim van Pelt package health import ( "git.ipng.ch/ipng/vpp-maglev/internal/config" ) // ActivePoolIndex returns the priority-failover pool index for fe given // the current backend states. The active pool is the first pool that // contains at least one backend which is both in StateUp AND has a // non-zero configured weight: a pool whose up backends are all // weight=0 contributes no serving capacity, so failover falls through // to the next tier. Returns 0 when no pool can serve, in which case // every backend maps to weight 0 and the return value is unobservable. // // pool[0] is the primary, pool[1] the first fallback, and so on. func ActivePoolIndex(fe config.Frontend, states map[string]State) int { for i, pool := range fe.Pools { for bName, pb := range pool.Backends { if states[bName] == StateUp && pb.Weight > 0 { return i } } } return 0 } // BackendEffectiveWeight is the pure mapping from (pool index, active pool, // backend state, config weight) to the desired VPP AS weight and flush hint. // This is the single source of truth for the state → dataplane rule. // // A backend gets its configured weight iff it is up AND belongs to the // currently-active pool. Every other case yields weight 0. Only StateDisabled // produces flush=true (immediate session teardown). // // state in active pool not in active pool flush // -------- -------------- ------------------- ----- // unknown 0 0 no // up configured 0 (standby) no // down 0 0 no // paused 0 0 no // disabled 0 0 yes func BackendEffectiveWeight(poolIdx, activePool int, state State, cfgWeight int) (weight uint8, flush bool) { switch state { case StateUp: if poolIdx == activePool { return clampWeight(cfgWeight), false } return 0, false case StateDisabled: return 0, true default: return 0, false } } // EffectiveWeights computes per-pool per-backend effective weights for fe, // given a snapshot of backend states. Result layout: weights[poolIdx][backendName]. func EffectiveWeights(fe config.Frontend, states map[string]State) map[int]map[string]uint8 { activePool := ActivePoolIndex(fe, states) out := make(map[int]map[string]uint8, len(fe.Pools)) for poolIdx, pool := range fe.Pools { out[poolIdx] = make(map[string]uint8, len(pool.Backends)) for bName, pb := range pool.Backends { w, _ := BackendEffectiveWeight(poolIdx, activePool, states[bName], pb.Weight) out[poolIdx][bName] = w } } return out } // ComputeFrontendState derives the FrontendState for fe from a snapshot of // backend states. Rules: // // - no backends → unknown // - every referenced backend is in StateUnknown → unknown // - any backend has effective weight > 0 → up // - otherwise → down func ComputeFrontendState(fe config.Frontend, states map[string]State) FrontendState { // Unique set of backends referenced by this frontend (a single backend // may appear in multiple pools; we count it once). seen := make(map[string]struct{}) for _, pool := range fe.Pools { for bName := range pool.Backends { seen[bName] = struct{}{} } } if len(seen) == 0 { return FrontendStateUnknown } allUnknown := true for bName := range seen { if states[bName] != StateUnknown { allUnknown = false break } } if allUnknown { return FrontendStateUnknown } ew := EffectiveWeights(fe, states) for _, poolMap := range ew { for _, w := range poolMap { if w > 0 { return FrontendStateUp } } } return FrontendStateDown } func clampWeight(w int) uint8 { if w < 0 { return 0 } if w > 100 { return 100 } return uint8(w) }