Replay every unanimous upstream status, not just 4xx
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful

openvoxdb does not reserve 5xx for its own faults: the same malformed
query is a 400 on /nodes and a 500 on /facts, and /metrics answers a flat
403, so a 4xx-only replay rule made pdbmux's behaviour depend on the
route. The meta and metrics handlers held their own copy of the gateway
error and bypassed the replay entirely.

- Replay any status from 400 up that every backend agreed on, with the
  backend's own body and content type.
- Keep 502 for backends disagreeing on the status, or a backend that
  answered nothing at all.
- Route /pdb/meta, /metrics and the pass-through path through the same
  rule as the merged query handlers.
- Count a unanimous 5xx as a failed round and let it fall back to a stale
  cache entry; only a unanimous 4xx stays exempt from both.
- Answer successful queries with openvoxdb's application/json;charset=utf-8.
This commit is contained in:
2026-09-13 13:35:21 +10:00
parent 5207a79ad4
commit 121bfacc2f
11 changed files with 541 additions and 126 deletions
+13 -3
View File
@@ -61,6 +61,7 @@ type countingBackend struct {
hits map[string]int
bodies map[string]string
fail bool
dead bool
block chan struct{}
}
@@ -70,12 +71,15 @@ func newCountingBackend(t *testing.T, bodies map[string]string) *countingBackend
cb.srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cb.mu.Lock()
cb.hits[r.URL.Path]++
fail, block, body := cb.fail, cb.block, cb.bodies[r.URL.Path]
fail, dead, block, body := cb.fail, cb.dead, cb.block, cb.bodies[r.URL.Path]
cb.mu.Unlock()
if block != nil {
<-block
}
if dead {
panic(http.ErrAbortHandler)
}
if fail {
http.Error(w, "boom", http.StatusInternalServerError)
return
@@ -108,6 +112,12 @@ func (cb *countingBackend) totalHits() int {
return n
}
func (cb *countingBackend) setDead(v bool) {
cb.mu.Lock()
defer cb.mu.Unlock()
cb.dead = v
}
func (cb *countingBackend) setFail(v bool) {
cb.mu.Lock()
defer cb.mu.Unlock()
@@ -392,8 +402,8 @@ func TestHandler_StaleSourceFactDrilldownStaysFilteredByOwner(t *testing.T) {
func TestHandler_NoCacheEntryMeansBackendFailureIs502(t *testing.T) {
a := newCountingBackend(t, map[string]string{factsPath: `[]`})
b := newCountingBackend(t, map[string]string{factsPath: `[]`})
a.setFail(true)
b.setFail(true)
a.setDead(true)
b.setDead(true)
srv, _ := newCachedServer(t, cacheTestConfig(a.srv.URL, b.srv.URL))
rec := doGet(t, srv.Handler(), factsPath, "")