Files
vpp-maglev/internal/health/weights.go

134 lines
4.4 KiB
Go

// SPDX-License-Identifier: Apache-2.0
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, flush-on-down policy) 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.
//
// The flush hint controls whether VPP tears down existing flows pinned to
// the AS on the weight update (is_flush=true on lb_as_set_weight) or merely
// stops accepting new flows (drain, keep existing). StateDisabled always
// flushes — it's an operator-driven "this AS is going away" signal. StateDown
// flushes iff the frontend has flush-on-down enabled; the default is true,
// because rise/fall debouncing in the health checker already absorbs flaps
// and a fall-counted down is almost always a real outage the operator wants
// cleared from the session table fast. Unknown / paused never flush —
// unknown is pre-probe, and paused is an explicit drain-don't-kill signal.
//
// state in active pool not in active pool flush
// -------- -------------- ------------------- ----------------
// unknown 0 0 no
// up configured 0 (standby) no
// down 0 0 flushOnDown
// paused 0 0 no
// disabled 0 0 yes
func BackendEffectiveWeight(poolIdx, activePool int, state State, cfgWeight int, flushOnDown bool) (weight uint8, flush bool) {
switch state {
case StateUp:
if poolIdx == activePool {
return clampWeight(cfgWeight), false
}
return 0, false
case StateDown:
return 0, flushOnDown
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, fe.FlushOnDown)
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)
}