b6d59af7ef
## Why Puppetboard 7.0.1 cannot run against pdbmux: it exits at import when /pdb/meta/v1/version 404s, and its landing page, metrics and radiator views 404 on the Jolokia surface. ## How - Serve /pdb/meta/v1/version, reporting the lowest version any backend runs, and /pdb/meta/v1/server-time from the first reachable backend. - Merge the Jolokia surface (/metrics/v2/read, /metrics/v2/list, /metrics/v1/mbeans): objects union, numeric attributes sum by default, and Min/Max/Uptime/StartTime plus the distribution stats take a bound or a mean. - Route /nodes extract-count queries to the summing path ahead of the certname merge, and give /resources aggregates the same path. - Document the endpoints and merge semantics in the README. - Cover version disagreement, metric rules, escaped MBean names, count summing and the non-aggregate /nodes merge with httptest backends.
171 lines
5.7 KiB
Go
171 lines
5.7 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
)
|
|
|
|
func metaGet(t *testing.T, h http.Handler, path string) *httptest.ResponseRecorder {
|
|
t.Helper()
|
|
rec := httptest.NewRecorder()
|
|
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, path, nil))
|
|
return rec
|
|
}
|
|
|
|
func metaString(t *testing.T, body []byte, field string) string {
|
|
t.Helper()
|
|
var obj map[string]string
|
|
if err := json.Unmarshal(body, &obj); err != nil {
|
|
t.Fatalf("unmarshal %s: %v", body, err)
|
|
}
|
|
return obj[field]
|
|
}
|
|
|
|
func TestMetaVersion_BackendsAgree(t *testing.T) {
|
|
// Puppetboard's check_db_version() calls this at import and exits 2 on any
|
|
// non-200, so a 404 here is the difference between a running dashboard and
|
|
// CrashLoopBackOff.
|
|
a := newFakeBackend(t, `[]`, `[]`)
|
|
a.bodies[metaVersionPath] = `{"version":"7.12.1"}`
|
|
b := newFakeBackend(t, `[]`, `[]`)
|
|
b.bodies[metaVersionPath] = `{"version":"7.12.1"}`
|
|
srv := newTestServer(testConfig(a.srv.URL, b.srv.URL, mergeStatic))
|
|
|
|
rec := metaGet(t, srv.Handler(), metaVersionPath)
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status %d: %s", rec.Code, rec.Body.String())
|
|
}
|
|
if got := metaString(t, rec.Body.Bytes(), "version"); got != "7.12.1" {
|
|
t.Errorf("version = %q, want 7.12.1", got)
|
|
}
|
|
}
|
|
|
|
func TestMetaVersion_DisagreementReportsLowest(t *testing.T) {
|
|
// The estate can only be relied on for what its oldest PuppetDB implements.
|
|
a := newFakeBackend(t, `[]`, `[]`)
|
|
a.bodies[metaVersionPath] = `{"version":"8.4.0"}`
|
|
b := newFakeBackend(t, `[]`, `[]`)
|
|
b.bodies[metaVersionPath] = `{"version":"7.12.1"}`
|
|
srv := newTestServer(testConfig(a.srv.URL, b.srv.URL, mergeStatic))
|
|
|
|
if got := metaString(t, metaGet(t, srv.Handler(), metaVersionPath).Body.Bytes(), "version"); got != "7.12.1" {
|
|
t.Errorf("version = %q, want the lower 7.12.1", got)
|
|
}
|
|
}
|
|
|
|
func TestMetaVersion_LowestIsIndependentOfBackendOrder(t *testing.T) {
|
|
a := newFakeBackend(t, `[]`, `[]`)
|
|
a.bodies[metaVersionPath] = `{"version":"7.12.1"}`
|
|
b := newFakeBackend(t, `[]`, `[]`)
|
|
b.bodies[metaVersionPath] = `{"version":"8.4.0"}`
|
|
srv := newTestServer(testConfig(a.srv.URL, b.srv.URL, mergeStatic))
|
|
|
|
if got := metaString(t, metaGet(t, srv.Handler(), metaVersionPath).Body.Bytes(), "version"); got != "7.12.1" {
|
|
t.Errorf("version = %q, want the lower 7.12.1", got)
|
|
}
|
|
}
|
|
|
|
func TestMetaVersion_OneBackendDown(t *testing.T) {
|
|
a := newFakeBackend(t, `[]`, `[]`)
|
|
a.fail = true
|
|
b := newFakeBackend(t, `[]`, `[]`)
|
|
b.bodies[metaVersionPath] = `{"version":"8.4.0"}`
|
|
srv := newTestServer(testConfig(a.srv.URL, b.srv.URL, mergeStatic))
|
|
|
|
rec := metaGet(t, srv.Handler(), metaVersionPath)
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("expected 200 serving the survivor, got %d", rec.Code)
|
|
}
|
|
if got := metaString(t, rec.Body.Bytes(), "version"); got != "8.4.0" {
|
|
t.Errorf("version = %q, want 8.4.0", got)
|
|
}
|
|
}
|
|
|
|
func TestMetaVersion_AllBackendsDown(t *testing.T) {
|
|
a := newFakeBackend(t, `[]`, `[]`)
|
|
a.fail = true
|
|
b := newFakeBackend(t, `[]`, `[]`)
|
|
b.fail = true
|
|
srv := newTestServer(testConfig(a.srv.URL, b.srv.URL, mergeStatic))
|
|
|
|
if rec := metaGet(t, srv.Handler(), metaVersionPath); rec.Code != http.StatusBadGateway {
|
|
t.Errorf("status %d, want 502", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestMetaServerTime_FirstReachableBackend(t *testing.T) {
|
|
a := newFakeBackend(t, `[]`, `[]`)
|
|
a.bodies[metaServerTimePath] = `{"server_time":"2026-08-29T01:00:00.000Z"}`
|
|
b := newFakeBackend(t, `[]`, `[]`)
|
|
b.bodies[metaServerTimePath] = `{"server_time":"2026-08-29T02:00:00.000Z"}`
|
|
srv := newTestServer(testConfig(a.srv.URL, b.srv.URL, mergeStatic))
|
|
|
|
got := metaString(t, metaGet(t, srv.Handler(), metaServerTimePath).Body.Bytes(), "server_time")
|
|
if got != "2026-08-29T01:00:00.000Z" {
|
|
t.Errorf("server_time = %q, want the first backend's", got)
|
|
}
|
|
}
|
|
|
|
func TestMetaServerTime_SkipsDeadBackend(t *testing.T) {
|
|
a := newFakeBackend(t, `[]`, `[]`)
|
|
a.fail = true
|
|
b := newFakeBackend(t, `[]`, `[]`)
|
|
b.bodies[metaServerTimePath] = `{"server_time":"2026-08-29T02:00:00.000Z"}`
|
|
srv := newTestServer(testConfig(a.srv.URL, b.srv.URL, mergeStatic))
|
|
|
|
rec := metaGet(t, srv.Handler(), metaServerTimePath)
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("expected 200 serving the survivor, got %d", rec.Code)
|
|
}
|
|
if got := metaString(t, rec.Body.Bytes(), "server_time"); got != "2026-08-29T02:00:00.000Z" {
|
|
t.Errorf("server_time = %q, want the survivor's", got)
|
|
}
|
|
}
|
|
|
|
func TestMetaVersion_RejectsNonGET(t *testing.T) {
|
|
a := newFakeBackend(t, `[]`, `[]`)
|
|
b := newFakeBackend(t, `[]`, `[]`)
|
|
srv := newTestServer(testConfig(a.srv.URL, b.srv.URL, mergeStatic))
|
|
|
|
rec := httptest.NewRecorder()
|
|
srv.Handler().ServeHTTP(rec, httptest.NewRequest(http.MethodPost, metaVersionPath, nil))
|
|
if rec.Code != http.StatusMethodNotAllowed {
|
|
t.Errorf("status %d, want 405", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestCompareVersions(t *testing.T) {
|
|
cases := []struct {
|
|
a, b string
|
|
want int
|
|
}{
|
|
{"7.12.1", "7.12.1", 0},
|
|
{"7.12.1", "8.4.0", -1},
|
|
{"8.4.0", "7.12.1", 1},
|
|
{"7.9.0", "7.12.0", -1}, // numeric, not lexical: 9 < 12
|
|
{"7.12", "7.12.1", -1},
|
|
{"8.0.0", "8.0.0-SNAPSHOT", -1},
|
|
{"8.0.0-SNAPSHOT", "8.0.0", 1},
|
|
}
|
|
for _, c := range cases {
|
|
if got := compareVersions(c.a, c.b); got != c.want {
|
|
t.Errorf("compareVersions(%q, %q) = %d, want %d", c.a, c.b, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMetaField_MalformedBodyIgnored(t *testing.T) {
|
|
// A backend serving junk must not become the "lowest" version.
|
|
a := newFakeBackend(t, `[]`, `[]`)
|
|
a.bodies[metaVersionPath] = `not json`
|
|
b := newFakeBackend(t, `[]`, `[]`)
|
|
b.bodies[metaVersionPath] = `{"version":"8.4.0"}`
|
|
srv := newTestServer(testConfig(a.srv.URL, b.srv.URL, mergeStatic))
|
|
|
|
if got := metaString(t, metaGet(t, srv.Handler(), metaVersionPath).Body.Bytes(), "version"); got != "8.4.0" {
|
|
t.Errorf("version = %q, want 8.4.0 from the only parseable backend", got)
|
|
}
|
|
}
|