New metrics: section (enabled, listen, allow, token). /metrics is served on the main listeners, the local socket (root and jarld's user) and optional metrics-only addresses; a scrape needs an allowed address or the bearer token. Exports health (named up, problems, drift, backups, blocklists, DNSSEC phases, token expiry) and, with stats on, named's and Jarl's query counters since jarld started plus named's process and cache gauges. docs/metrics.md has example queries and alerts checked with promtool. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
118 lines
4.0 KiB
Go
118 lines
4.0 KiB
Go
package config
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestExampleParses(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "jarl.yaml")
|
|
os.WriteFile(path, []byte(Example), 0o644)
|
|
c, err := Load(path, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
d := Default()
|
|
if c.BIND.ZoneDir != d.BIND.ZoneDir || time.Duration(c.Session.IdleTimeout) != 2*time.Hour || c.Listen[0] != "127.0.0.1:8053" {
|
|
t.Errorf("example differs from defaults: %+v", c)
|
|
}
|
|
if c.ACME != d.ACME || c.ACME.HTTPReq.Enabled || !c.ACME.HTTPReq.RequireHTTPS {
|
|
t.Errorf("acme = %+v, want the defaults %+v", c.ACME, d.ACME)
|
|
}
|
|
}
|
|
|
|
func TestHTTPReqDefaults(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "jarl.yaml")
|
|
// Enabling the endpoint keeps require_https on unless it is turned off.
|
|
os.WriteFile(path, []byte("acme: {httpreq: {enabled: true}}\n"), 0o644)
|
|
c, err := Load(path, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if h := c.ACME.HTTPReq; !h.Enabled || !h.RequireHTTPS || h.AllowLoopbackHTTP {
|
|
t.Errorf("httpreq = %+v", h)
|
|
}
|
|
}
|
|
|
|
func TestLoad(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "jarl.yaml")
|
|
os.WriteFile(path, []byte("listen: [\"0.0.0.0:8443\"]\ntls: {cert_file: /a, key_file: /b}\ntrusted_proxies: [10.0.0.1, 192.168.0.0/16]\nsession: {lifetime: 1d}\n"), 0o644)
|
|
c, err := Load(path, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if time.Duration(c.Session.Lifetime) != 24*time.Hour || len(c.Proxies()) != 2 || c.BIND.FileGroup != "bind" {
|
|
t.Errorf("config = %+v", c)
|
|
}
|
|
os.WriteFile(path, []byte("lisen: []\n"), 0o644)
|
|
if _, err := Load(path, false); err == nil {
|
|
t.Error("unknown field accepted")
|
|
}
|
|
os.WriteFile(path, []byte("tls: {cert_file: /a}\n"), 0o644)
|
|
if _, err := Load(path, false); err == nil {
|
|
t.Error("half TLS config accepted")
|
|
}
|
|
if _, err := Load(filepath.Join(dir, "missing.yaml"), true); err != nil {
|
|
t.Errorf("missing file with allowMissing: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestStatsConfig(t *testing.T) {
|
|
d := Default()
|
|
if d.Stats.Enabled || !d.Stats.QueryLog || d.Stats.TopN != 100 || time.Duration(d.Stats.Retention.Hour) != 90*24*time.Hour {
|
|
t.Errorf("defaults = %+v", d.Stats)
|
|
}
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "jarl.yaml")
|
|
for _, tc := range []struct {
|
|
yaml string
|
|
ok bool
|
|
}{
|
|
{"stats: {enabled: true}", true},
|
|
{"stats: {enabled: true, query_log: false, retention: {minute: 2d}}", true},
|
|
{"stats: {enabled: true, statistics_channel: 0.0.0.0:8058}", false}, // must be loopback
|
|
{"stats: {enabled: true, statistics_channel: localhost}", false},
|
|
{"stats: {enabled: true, log_size: lots}", false},
|
|
{"stats: {enabled: true, retention: {minute: 10m}}", false},
|
|
{"stats: {enabled: true, exclude_clients: [not-an-ip]}", false},
|
|
{"stats: {enabled: false, statistics_channel: nonsense}", true}, // only checked when enabled
|
|
} {
|
|
os.WriteFile(path, []byte(tc.yaml+"\n"), 0o644)
|
|
_, err := Load(path, false)
|
|
if (err == nil) != tc.ok {
|
|
t.Errorf("%s: err = %v, want ok=%v", tc.yaml, err, tc.ok)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMetricsConfig(t *testing.T) {
|
|
d := Default()
|
|
if d.Metrics.Enabled || len(d.Metrics.AllowPrefixes()) != 2 || d.Metrics.Token != "" {
|
|
t.Errorf("defaults = %+v", d.Metrics)
|
|
}
|
|
path := filepath.Join(t.TempDir(), "jarl.yaml")
|
|
for _, tc := range []struct {
|
|
yaml string
|
|
ok bool
|
|
}{
|
|
{"metrics: {enabled: true}", true},
|
|
{"metrics: {enabled: true, listen: [\"0.0.0.0:9450\", \"[::]:9450\"], allow: [10.0.0.5, 192.168.1.0/24]}", true},
|
|
{"metrics: {enabled: true, allow: [], token: 0123456789abcdef}", true},
|
|
{"metrics: {enabled: true, allow: []}", false}, // nobody could scrape
|
|
{"metrics: {enabled: true, token: short}", false},
|
|
{"metrics: {enabled: true, token: \"has space 0123456789\"}", false},
|
|
{"metrics: {enabled: true, listen: [\"9450\"]}", false},
|
|
{"metrics: {enabled: true, allow: [prometheus.lan]}", false},
|
|
{"metrics: {enabled: false, listen: [nonsense]}", true}, // only checked when enabled
|
|
} {
|
|
os.WriteFile(path, []byte(tc.yaml+"\n"), 0o644)
|
|
_, err := Load(path, false)
|
|
if (err == nil) != tc.ok {
|
|
t.Errorf("%s: err = %v, want ok=%v", tc.yaml, err, tc.ok)
|
|
}
|
|
}
|
|
}
|