394 lines
9.9 KiB
Go
394 lines
9.9 KiB
Go
package test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/coredns/caddy"
|
|
"github.com/coredns/coredns/core/dnsserver"
|
|
"github.com/coredns/coredns/plugin"
|
|
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
func TestReload(t *testing.T) {
|
|
corefile := `.:0 {
|
|
whoami
|
|
}`
|
|
|
|
coreInput := NewInput(corefile)
|
|
|
|
c, err := CoreDNSServer(corefile)
|
|
if err != nil {
|
|
t.Fatalf("Could not get CoreDNS serving instance: %s", err)
|
|
}
|
|
|
|
udp, _ := CoreDNSServerPorts(c, 0)
|
|
|
|
send(t, udp)
|
|
|
|
c1, err := c.Restart(coreInput)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
udp, _ = CoreDNSServerPorts(c1, 0)
|
|
|
|
send(t, udp)
|
|
|
|
c1.Stop()
|
|
}
|
|
|
|
func send(t *testing.T, server string) {
|
|
m := new(dns.Msg)
|
|
m.SetQuestion("whoami.example.org.", dns.TypeSRV)
|
|
|
|
r, err := dns.Exchange(m, server)
|
|
if err != nil {
|
|
// This seems to fail a lot on travis, quick'n dirty: redo
|
|
r, err = dns.Exchange(m, server)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
if r.Rcode != dns.RcodeSuccess {
|
|
t.Fatalf("Expected successful reply, got %s", dns.RcodeToString[r.Rcode])
|
|
}
|
|
if len(r.Extra) != 2 {
|
|
t.Fatalf("Expected 2 RRs in additional, got %d", len(r.Extra))
|
|
}
|
|
}
|
|
|
|
func TestReloadHealth(t *testing.T) {
|
|
corefile := `.:0 {
|
|
health 127.0.0.1:52182
|
|
whoami
|
|
}`
|
|
|
|
c, err := CoreDNSServer(corefile)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), inUse) {
|
|
return // meh, but don't error
|
|
}
|
|
t.Fatalf("Could not get service instance: %s", err)
|
|
}
|
|
|
|
if c1, err := c.Restart(NewInput(corefile)); err != nil {
|
|
t.Fatal(err)
|
|
} else {
|
|
c1.Stop()
|
|
}
|
|
}
|
|
|
|
func TestReloadMetricsHealth(t *testing.T) {
|
|
corefile := `.:0 {
|
|
prometheus 127.0.0.1:53183
|
|
health 127.0.0.1:53184
|
|
whoami
|
|
}`
|
|
|
|
c, err := CoreDNSServer(corefile)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), inUse) {
|
|
return // meh, but don't error
|
|
}
|
|
t.Fatalf("Could not get service instance: %s", err)
|
|
}
|
|
|
|
c1, err := c.Restart(NewInput(corefile))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer c1.Stop()
|
|
|
|
// Health
|
|
resp, err := http.Get("http://localhost:53184/health")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ok, _ := io.ReadAll(resp.Body)
|
|
resp.Body.Close()
|
|
if string(ok) != http.StatusText(http.StatusOK) {
|
|
t.Errorf("Failed to receive OK, got %s", ok)
|
|
}
|
|
|
|
// Metrics
|
|
resp, err = http.Get("http://localhost:53183/metrics")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
const proc = "coredns_build_info"
|
|
metrics, _ := io.ReadAll(resp.Body)
|
|
resp.Body.Close()
|
|
if !bytes.Contains(metrics, []byte(proc)) {
|
|
t.Errorf("Failed to see %s in metric output", proc)
|
|
}
|
|
}
|
|
|
|
func collectMetricsInfo(addr string, procs ...string) error {
|
|
cl := &http.Client{}
|
|
resp, err := cl.Get(fmt.Sprintf("http://%s/metrics", addr))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
metrics, _ := io.ReadAll(resp.Body)
|
|
resp.Body.Close()
|
|
for _, p := range procs {
|
|
if !bytes.Contains(metrics, []byte(p)) {
|
|
return fmt.Errorf("failed to see %s in metric output \n%s", p, metrics)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// TestReloadSeveralTimeMetrics ensures that metrics are not pushed to
|
|
// prometheus once the metrics plugin is removed and a coredns
|
|
// reload is triggered
|
|
// 1. check that metrics have not been exported to prometheus before coredns starts
|
|
// 2. ensure that build-related metrics have been exported once coredns starts
|
|
// 3. trigger multiple reloads without changing the corefile
|
|
// 4. remove the metrics plugin and trigger a final reload
|
|
// 5. ensure the original prometheus exporter has not received more metrics
|
|
func TestReloadSeveralTimeMetrics(t *testing.T) {
|
|
//TODO: add a tool that find an available port because this needs to be a port
|
|
// that is not used in another test
|
|
promAddress := "127.0.0.1:53185"
|
|
proc := "coredns_build_info"
|
|
corefileWithMetrics := `.:0 {
|
|
prometheus ` + promAddress + `
|
|
whoami
|
|
}`
|
|
corefileWithoutMetrics := `.:0 {
|
|
whoami
|
|
}`
|
|
|
|
if err := collectMetricsInfo(promAddress, proc); err == nil {
|
|
t.Errorf("Prometheus is listening before the test started")
|
|
}
|
|
serverWithMetrics, err := CoreDNSServer(corefileWithMetrics)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), inUse) {
|
|
return
|
|
}
|
|
t.Errorf("Could not get service instance: %s", err)
|
|
}
|
|
// verify prometheus is running
|
|
if err := collectMetricsInfo(promAddress, proc); err != nil {
|
|
t.Errorf("Prometheus is not listening : %s", err)
|
|
}
|
|
reloadCount := 2
|
|
for i := 0; i < reloadCount; i++ {
|
|
serverReload, err := serverWithMetrics.Restart(
|
|
NewInput(corefileWithMetrics),
|
|
)
|
|
if err != nil {
|
|
t.Errorf("Could not restart CoreDNS : %s, at loop %v", err, i)
|
|
}
|
|
if err := collectMetricsInfo(promAddress, proc); err != nil {
|
|
t.Errorf("Prometheus is not listening : %s", err)
|
|
}
|
|
serverWithMetrics = serverReload
|
|
}
|
|
// reload without prometheus
|
|
serverWithoutMetrics, err := serverWithMetrics.Restart(
|
|
NewInput(corefileWithoutMetrics),
|
|
)
|
|
if err != nil {
|
|
t.Errorf("Could not restart a second time CoreDNS : %s", err)
|
|
}
|
|
serverWithoutMetrics.Stop()
|
|
// verify that metrics have not been pushed
|
|
if err := collectMetricsInfo(promAddress, proc); err == nil {
|
|
t.Errorf("Prometheus is still listening")
|
|
}
|
|
}
|
|
|
|
func TestMetricsAvailableAfterReload(t *testing.T) {
|
|
//TODO: add a tool that find an available port because this needs to be a port
|
|
// that is not used in another test
|
|
promAddress := "127.0.0.1:53186"
|
|
procMetric := "coredns_build_info"
|
|
procCache := "coredns_cache_entries"
|
|
procForward := "coredns_dns_request_duration_seconds"
|
|
corefileWithMetrics := `.:0 {
|
|
prometheus ` + promAddress + `
|
|
cache
|
|
forward . 8.8.8.8 {
|
|
force_tcp
|
|
}
|
|
}`
|
|
|
|
inst, _, tcp, err := CoreDNSServerAndPorts(corefileWithMetrics)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), inUse) {
|
|
return
|
|
}
|
|
t.Errorf("Could not get service instance: %s", err)
|
|
}
|
|
// send a query and check we can scrap corresponding metrics
|
|
cl := dns.Client{Net: "tcp"}
|
|
m := new(dns.Msg)
|
|
m.SetQuestion("www.example.org.", dns.TypeA)
|
|
|
|
if _, _, err := cl.Exchange(m, tcp); err != nil {
|
|
t.Fatalf("Could not send message: %s", err)
|
|
}
|
|
|
|
// we should have metrics from forward, cache, and metrics itself
|
|
if err := collectMetricsInfo(promAddress, procMetric, procCache, procForward); err != nil {
|
|
t.Errorf("Could not scrap one of expected stats : %s", err)
|
|
}
|
|
|
|
// now reload
|
|
instReload, err := inst.Restart(
|
|
NewInput(corefileWithMetrics),
|
|
)
|
|
if err != nil {
|
|
t.Errorf("Could not restart CoreDNS : %s", err)
|
|
instReload = inst
|
|
}
|
|
|
|
// check the metrics are available still
|
|
if err := collectMetricsInfo(promAddress, procMetric, procCache, procForward); err != nil {
|
|
t.Errorf("Could not scrap one of expected stats : %s", err)
|
|
}
|
|
|
|
instReload.Stop()
|
|
// verify that metrics have not been pushed
|
|
}
|
|
|
|
func TestMetricsAvailableAfterReloadAndFailedReload(t *testing.T) {
|
|
//TODO: add a tool that find an available port because this needs to be a port
|
|
// that is not used in another test
|
|
promAddress := "127.0.0.1:53187"
|
|
procMetric := "coredns_build_info"
|
|
procCache := "coredns_cache_entries"
|
|
procForward := "coredns_dns_request_duration_seconds"
|
|
corefileWithMetrics := `.:0 {
|
|
prometheus ` + promAddress + `
|
|
cache
|
|
forward . 8.8.8.8 {
|
|
force_tcp
|
|
}
|
|
}`
|
|
invalidCorefileWithMetrics := `.:0 {
|
|
prometheus ` + promAddress + `
|
|
cache
|
|
forward . 8.8.8.8 {
|
|
force_tcp
|
|
}
|
|
invalid
|
|
}`
|
|
|
|
inst, _, tcp, err := CoreDNSServerAndPorts(corefileWithMetrics)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), inUse) {
|
|
return
|
|
}
|
|
t.Errorf("Could not get service instance: %s", err)
|
|
}
|
|
// send a query and check we can scrap corresponding metrics
|
|
cl := dns.Client{Net: "tcp"}
|
|
m := new(dns.Msg)
|
|
m.SetQuestion("www.example.org.", dns.TypeA)
|
|
|
|
if _, _, err := cl.Exchange(m, tcp); err != nil {
|
|
t.Fatalf("Could not send message: %s", err)
|
|
}
|
|
|
|
// we should have metrics from forward, cache, and metrics itself
|
|
if err := collectMetricsInfo(promAddress, procMetric, procCache, procForward); err != nil {
|
|
t.Errorf("Could not scrap one of expected stats : %s", err)
|
|
}
|
|
|
|
for i := 0; i < 2; i++ {
|
|
// now provide a failed reload
|
|
invInst, err := inst.Restart(
|
|
NewInput(invalidCorefileWithMetrics),
|
|
)
|
|
if err == nil {
|
|
t.Errorf("Invalid test - this reload should fail")
|
|
inst = invInst
|
|
}
|
|
}
|
|
|
|
// now reload with correct corefile
|
|
instReload, err := inst.Restart(
|
|
NewInput(corefileWithMetrics),
|
|
)
|
|
if err != nil {
|
|
t.Errorf("Could not restart CoreDNS : %s", err)
|
|
instReload = inst
|
|
}
|
|
|
|
// check the metrics are available still
|
|
if err := collectMetricsInfo(promAddress, procMetric, procCache, procForward); err != nil {
|
|
t.Errorf("Could not scrap one of expected stats : %s", err)
|
|
}
|
|
|
|
instReload.Stop()
|
|
// verify that metrics have not been pushed
|
|
}
|
|
|
|
// TestReloadUnreadyPlugin tests that the ready plugin properly resets the list of readiness implementors during a reload.
|
|
// If it fails to do so, ready will respond with duplicate plugin names after a reload (e.g. in this test "unready,unready").
|
|
func TestReloadUnreadyPlugin(t *testing.T) {
|
|
// Add/Register a perpetually unready plugin
|
|
dnsserver.Directives = append([]string{"unready"}, dnsserver.Directives...)
|
|
u := new(unready)
|
|
plugin.Register("unready", func(c *caddy.Controller) error {
|
|
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
|
|
u.next = next
|
|
return u
|
|
})
|
|
return nil
|
|
})
|
|
|
|
corefile := `.:0 {
|
|
unready
|
|
whoami
|
|
ready 127.0.0.1:53185
|
|
}`
|
|
|
|
coreInput := NewInput(corefile)
|
|
|
|
c, err := CoreDNSServer(corefile)
|
|
if err != nil {
|
|
t.Fatalf("Could not get CoreDNS serving instance: %s", err)
|
|
}
|
|
|
|
c1, err := c.Restart(coreInput)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
resp, err := http.Get("http://127.0.0.1:53185/ready")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
bod, _ := io.ReadAll(resp.Body)
|
|
resp.Body.Close()
|
|
if string(bod) != u.Name() {
|
|
t.Errorf("Expected /ready endpoint response body %q, got %q", u.Name(), bod)
|
|
}
|
|
|
|
c1.Stop()
|
|
}
|
|
|
|
type unready struct {
|
|
next plugin.Handler
|
|
}
|
|
|
|
func (u *unready) Ready() bool { return false }
|
|
|
|
func (u *unready) Name() string { return "unready" }
|
|
|
|
func (u *unready) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
|
return u.next.ServeDNS(ctx, w, r)
|
|
}
|
|
|
|
const inUse = "address already in use"
|