package render import ( "net/netip" "strings" "testing" "jarl/internal/model" ) func statsHooks() *StatsHooks { return &StatsHooks{Channel: netip.MustParseAddrPort("127.0.0.1:8058"), LogFile: "/var/log/named/jarl-responses.log", LogVersions: 2, LogSize: "20m"} } func TestStatsHooksAdded(t *testing.T) { s := sampleState(t) // Without a logging statement of its own, Jarl adds one. for id, o := range s.Options { if o.Name == "logging" { delete(s.Options, id) } } p := Paths{ConfigDir: "/etc/bind/jarl", ZoneDir: "/var/lib/bind/jarl", CacheDir: "/var/cache/bind/jarl"} conf := string(Render(s, p, Extras{Stats: statsHooks()}).File(p.MainConfig()).Content) for _, want := range []string{ "statistics-channels {\n\tinet 127.0.0.1 port 8058 allow { 127.0.0.1; };\n};", `file "/var/log/named/jarl-responses.log" versions 2 size 20m;`, "\tcategory responses { jarl_responses; };", "\tresponselog yes;\n", } { if !strings.Contains(conf, want) { t.Errorf("named.conf lacks %q:\n%s", want, conf) } } // Counters only: no logging, no responselog. h := statsHooks() h.LogFile = "" conf = string(Render(s, p, Extras{Stats: h}).File(p.MainConfig()).Content) if !strings.Contains(conf, "statistics-channels") || strings.Contains(conf, "responselog") || strings.Contains(conf, "jarl_responses") { t.Errorf("counters-only config:\n%s", conf) } // Disabled: nothing. conf = string(Render(s, p, Extras{}).File(p.MainConfig()).Content) if strings.Contains(conf, "statistics-channels") || strings.Contains(conf, "responselog") { t.Errorf("stats disabled but present:\n%s", conf) } } func TestStatsHooksMerged(t *testing.T) { s := sampleState(t) add := func(scope, name, value string) { id := int64(10000 + len(s.Options)) s.Options[id] = &model.Option{ID: id, Scope: scope, Name: name, Value: value, Position: int(id)} } for _, o := range s.Options { if o.Name == "logging" { o.Value = `{ channel default_log { stderr; severity info; }; category default { default_log; }; category responses { default_log; }; }` } } add(model.ScopeTopLevel, "statistics-channels", `{ inet 127.0.0.1 port 8080 allow { 127.0.0.1; }; }`) add(model.ScopeOptions, "responselog", "no") out := checkWithNamed(t, s, Extras{Stats: statsHooks()}) var conf string for _, f := range out.Files { if f.Kind == KindConfig { conf = string(f.Content) } } if strings.Count(conf, "logging {") != 1 || strings.Count(conf, "statistics-channels {") != 1 { t.Errorf("statements duplicated instead of merged:\n%s", conf) } for _, want := range []string{"port 8080", "port 8058", "category responses { default_log; jarl_responses; };", "responselog no;"} { if !strings.Contains(conf, want) { t.Errorf("named.conf lacks %q:\n%s", want, conf) } } if strings.Contains(conf, "responselog yes") { t.Errorf("Jarl overrode the user's responselog:\n%s", conf) } } func TestStatsHooksWithNamed(t *testing.T) { checkWithNamed(t, sampleState(t), Extras{Stats: statsHooks()}) } // Rate limiting's log lines go to the response log unless // stats.rate_limit_log is off; the list of what Jarl adds says so. func TestRateLimitLogAndGenerated(t *testing.T) { s := sampleState(t) for id, o := range s.Options { if o.Name == "logging" { delete(s.Options, id) } } s.Options[20000] = &model.Option{ID: 20000, Scope: model.ScopeOptions, Name: "rate-limit", Value: "{ responses-per-second 10; log-only yes; }", Position: 20000} p := Paths{ConfigDir: "/etc/bind/jarl", ZoneDir: "/var/lib/bind/jarl", CacheDir: "/var/cache/bind/jarl"} conf := string(Render(s, p, Extras{Stats: statsHooks()}).File(p.MainConfig()).Content) if !strings.Contains(conf, "category query-errors { jarl_responses; default_syslog; };") { t.Errorf("query-errors not logged:\n%s", conf) } gen := GeneratedStatements(s, p, Extras{Stats: statsHooks()}) var logging *Generated for i := range gen { if strings.HasPrefix(gen[i].Text, "logging") { logging = &gen[i] } } if logging == nil || !strings.Contains(logging.Text, "query-errors") || !strings.Contains(logging.Setting, "stats.rate_limit_log") { t.Errorf("generated logging = %+v (all %+v)", logging, gen) } if !strings.HasPrefix(gen[0].Text, "include ") || gen[0].Where != "top level" { t.Errorf("first generated = %+v", gen[0]) } h := statsHooks() h.NoRateLimitLog = true conf = string(Render(s, p, Extras{Stats: h}).File(p.MainConfig()).Content) if strings.Contains(conf, "query-errors") || !strings.Contains(conf, "category responses") { t.Errorf("rate_limit_log off but query-errors logged:\n%s", conf) } for _, g := range GeneratedStatements(s, p, Extras{Stats: h}) { if strings.Contains(g.Text, "query-errors") { t.Errorf("listed while off: %+v", g) } } }