test: use dynamic ports in reload tests (#8206)

Signed-off-by: immanuwell <pchpr.00@list.ru>
This commit is contained in:
Immanuel Tikhonov
2026-06-29 11:58:46 +04:00
committed by GitHub
parent 02f28345d1
commit 70f7922ec5

View File

@@ -66,16 +66,14 @@ func send(t *testing.T, server string) {
} }
func TestReloadHealth(t *testing.T) { func TestReloadHealth(t *testing.T) {
healthAddr := fmt.Sprintf("127.0.0.1:%d", pickPort(t))
corefile := `.:0 { corefile := `.:0 {
health 127.0.0.1:52182 health ` + healthAddr + `
whoami whoami
}` }`
c, err := CoreDNSServer(corefile) c, err := CoreDNSServer(corefile)
if err != nil { if err != nil {
if strings.Contains(err.Error(), inUse) {
return // meh, but don't error
}
t.Fatalf("Could not get service instance: %s", err) t.Fatalf("Could not get service instance: %s", err)
} }
@@ -87,17 +85,16 @@ func TestReloadHealth(t *testing.T) {
} }
func TestReloadMetricsHealth(t *testing.T) { func TestReloadMetricsHealth(t *testing.T) {
metricsAddr := fmt.Sprintf("127.0.0.1:%d", pickPort(t))
healthAddr := fmt.Sprintf("127.0.0.1:%d", pickPort(t))
corefile := `.:0 { corefile := `.:0 {
prometheus 127.0.0.1:53183 prometheus ` + metricsAddr + `
health 127.0.0.1:53184 health ` + healthAddr + `
whoami whoami
}` }`
c, err := CoreDNSServer(corefile) c, err := CoreDNSServer(corefile)
if err != nil { if err != nil {
if strings.Contains(err.Error(), inUse) {
return // meh, but don't error
}
t.Fatalf("Could not get service instance: %s", err) t.Fatalf("Could not get service instance: %s", err)
} }
@@ -108,24 +105,14 @@ func TestReloadMetricsHealth(t *testing.T) {
defer c1.Stop() defer c1.Stop()
// Health // Health
resp, err := http.Get("http://localhost:53184/health") ok := httpGetBodyEventually(t, "http://"+healthAddr+"/health")
if err != nil {
t.Fatal(err)
}
ok, _ := io.ReadAll(resp.Body)
resp.Body.Close()
if string(ok) != http.StatusText(http.StatusOK) { if string(ok) != http.StatusText(http.StatusOK) {
t.Errorf("Failed to receive OK, got %s", ok) t.Errorf("Failed to receive OK, got %s", ok)
} }
// Metrics // Metrics
resp, err = http.Get("http://localhost:53183/metrics")
if err != nil {
t.Fatal(err)
}
const proc = "coredns_build_info" const proc = "coredns_build_info"
metrics, _ := io.ReadAll(resp.Body) metrics := httpGetBodyEventually(t, "http://"+metricsAddr+"/metrics")
resp.Body.Close()
if !bytes.Contains(metrics, []byte(proc)) { if !bytes.Contains(metrics, []byte(proc)) {
t.Errorf("Failed to see %s in metric output", proc) t.Errorf("Failed to see %s in metric output", proc)
} }
@@ -147,6 +134,71 @@ func collectMetricsInfo(addr string, procs ...string) error {
return nil return nil
} }
func waitForMetricsInfo(t *testing.T, addr string, procs ...string) {
t.Helper()
var lastErr error
for range 50 {
lastErr = collectMetricsInfo(addr, procs...)
if lastErr == nil {
return
}
time.Sleep(20 * time.Millisecond)
}
t.Fatalf("metrics endpoint did not become ready: %v", lastErr)
}
func httpGetBodyEventually(t *testing.T, url string) []byte {
t.Helper()
var lastErr error
for range 50 {
resp, err := http.Get(url)
if err == nil {
body, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr == nil && resp.StatusCode == http.StatusOK {
return body
}
if readErr != nil {
lastErr = readErr
} else {
lastErr = fmt.Errorf("unexpected status %d with body %q", resp.StatusCode, body)
}
} else {
lastErr = err
}
time.Sleep(20 * time.Millisecond)
}
t.Fatalf("GET %s did not become ready: %v", url, lastErr)
return nil
}
func waitForHTTPBody(t *testing.T, url string, want string) {
t.Helper()
var lastErr error
for range 50 {
resp, err := http.Get(url)
if err == nil {
body, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr == nil && string(body) == want {
return
}
if readErr != nil {
lastErr = readErr
} else {
lastErr = fmt.Errorf("unexpected body %q", body)
}
} else {
lastErr = err
}
time.Sleep(20 * time.Millisecond)
}
t.Fatalf("GET %s did not return expected body %q: %v", url, want, lastErr)
}
// TestReloadSeveralTimeMetrics ensures that metrics are not pushed to // TestReloadSeveralTimeMetrics ensures that metrics are not pushed to
// prometheus once the metrics plugin is removed and a coredns // prometheus once the metrics plugin is removed and a coredns
// reload is triggered // reload is triggered
@@ -156,9 +208,7 @@ func collectMetricsInfo(addr string, procs ...string) error {
// 4. remove the metrics plugin and trigger a final reload // 4. remove the metrics plugin and trigger a final reload
// 5. ensure the original prometheus exporter has not received more metrics // 5. ensure the original prometheus exporter has not received more metrics
func TestReloadSeveralTimeMetrics(t *testing.T) { func TestReloadSeveralTimeMetrics(t *testing.T) {
//TODO: add a tool that find an available port because this needs to be a port promAddress := fmt.Sprintf("127.0.0.1:%d", pickPort(t))
// that is not used in another test
promAddress := "127.0.0.1:53185"
proc := "coredns_build_info" proc := "coredns_build_info"
corefileWithMetrics := `.:0 { corefileWithMetrics := `.:0 {
prometheus ` + promAddress + ` prometheus ` + promAddress + `
@@ -173,15 +223,10 @@ func TestReloadSeveralTimeMetrics(t *testing.T) {
} }
serverWithMetrics, err := CoreDNSServer(corefileWithMetrics) serverWithMetrics, err := CoreDNSServer(corefileWithMetrics)
if err != nil { if err != nil {
if strings.Contains(err.Error(), inUse) {
return
}
t.Errorf("Could not get service instance: %s", err) t.Errorf("Could not get service instance: %s", err)
} }
// verify prometheus is running // verify prometheus is running
if err := collectMetricsInfo(promAddress, proc); err != nil { waitForMetricsInfo(t, promAddress, proc)
t.Errorf("Prometheus is not listening : %s", err)
}
reloadCount := 2 reloadCount := 2
for i := range reloadCount { for i := range reloadCount {
serverReload, err := serverWithMetrics.Restart( serverReload, err := serverWithMetrics.Restart(
@@ -190,9 +235,7 @@ func TestReloadSeveralTimeMetrics(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("Could not restart CoreDNS : %s, at loop %v", err, i) t.Errorf("Could not restart CoreDNS : %s, at loop %v", err, i)
} }
if err := collectMetricsInfo(promAddress, proc); err != nil { waitForMetricsInfo(t, promAddress, proc)
t.Errorf("Prometheus is not listening : %s", err)
}
serverWithMetrics = serverReload serverWithMetrics = serverReload
} }
// reload without prometheus // reload without prometheus
@@ -210,9 +253,7 @@ func TestReloadSeveralTimeMetrics(t *testing.T) {
} }
func TestMetricsAvailableAfterReload(t *testing.T) { func TestMetricsAvailableAfterReload(t *testing.T) {
//TODO: add a tool that find an available port because this needs to be a port promAddress := fmt.Sprintf("127.0.0.1:%d", pickPort(t))
// that is not used in another test
promAddress := "127.0.0.1:53186"
procMetric := "coredns_build_info" procMetric := "coredns_build_info"
procCache := "coredns_cache_entries" procCache := "coredns_cache_entries"
procForward := "coredns_dns_request_duration_seconds" procForward := "coredns_dns_request_duration_seconds"
@@ -226,9 +267,6 @@ func TestMetricsAvailableAfterReload(t *testing.T) {
inst, _, tcp, err := CoreDNSServerAndPorts(corefileWithMetrics) inst, _, tcp, err := CoreDNSServerAndPorts(corefileWithMetrics)
if err != nil { if err != nil {
if strings.Contains(err.Error(), inUse) {
return
}
t.Errorf("Could not get service instance: %s", err) t.Errorf("Could not get service instance: %s", err)
} }
// send a query and check we can scrap corresponding metrics // send a query and check we can scrap corresponding metrics
@@ -241,9 +279,7 @@ func TestMetricsAvailableAfterReload(t *testing.T) {
} }
// we should have metrics from forward, cache, and metrics itself // we should have metrics from forward, cache, and metrics itself
if err := collectMetricsInfo(promAddress, procMetric, procCache, procForward); err != nil { waitForMetricsInfo(t, promAddress, procMetric, procCache, procForward)
t.Errorf("Could not scrap one of expected stats : %s", err)
}
// now reload // now reload
instReload, err := inst.Restart( instReload, err := inst.Restart(
@@ -255,18 +291,14 @@ func TestMetricsAvailableAfterReload(t *testing.T) {
} }
// check the metrics are available still // check the metrics are available still
if err := collectMetricsInfo(promAddress, procMetric, procCache, procForward); err != nil { waitForMetricsInfo(t, promAddress, procMetric, procCache, procForward)
t.Errorf("Could not scrap one of expected stats : %s", err)
}
instReload.Stop() instReload.Stop()
// verify that metrics have not been pushed // verify that metrics have not been pushed
} }
func TestMetricsAvailableAfterReloadAndFailedReload(t *testing.T) { func TestMetricsAvailableAfterReloadAndFailedReload(t *testing.T) {
//TODO: add a tool that find an available port because this needs to be a port promAddress := fmt.Sprintf("127.0.0.1:%d", pickPort(t))
// that is not used in another test
promAddress := "127.0.0.1:53187"
procMetric := "coredns_build_info" procMetric := "coredns_build_info"
procCache := "coredns_cache_entries" procCache := "coredns_cache_entries"
procForward := "coredns_dns_request_duration_seconds" procForward := "coredns_dns_request_duration_seconds"
@@ -288,9 +320,6 @@ func TestMetricsAvailableAfterReloadAndFailedReload(t *testing.T) {
inst, _, tcp, err := CoreDNSServerAndPorts(corefileWithMetrics) inst, _, tcp, err := CoreDNSServerAndPorts(corefileWithMetrics)
if err != nil { if err != nil {
if strings.Contains(err.Error(), inUse) {
return
}
t.Errorf("Could not get service instance: %s", err) t.Errorf("Could not get service instance: %s", err)
} }
// send a query and check we can scrap corresponding metrics // send a query and check we can scrap corresponding metrics
@@ -303,9 +332,7 @@ func TestMetricsAvailableAfterReloadAndFailedReload(t *testing.T) {
} }
// we should have metrics from forward, cache, and metrics itself // we should have metrics from forward, cache, and metrics itself
if err := collectMetricsInfo(promAddress, procMetric, procCache, procForward); err != nil { waitForMetricsInfo(t, promAddress, procMetric, procCache, procForward)
t.Errorf("Could not scrap one of expected stats : %s", err)
}
for range 2 { for range 2 {
// now provide a failed reload // now provide a failed reload
@@ -328,9 +355,7 @@ func TestMetricsAvailableAfterReloadAndFailedReload(t *testing.T) {
} }
// check the metrics are available still // check the metrics are available still
if err := collectMetricsInfo(promAddress, procMetric, procCache, procForward); err != nil { waitForMetricsInfo(t, promAddress, procMetric, procCache, procForward)
t.Errorf("Could not scrap one of expected stats : %s", err)
}
instReload.Stop() instReload.Stop()
// verify that metrics have not been pushed // verify that metrics have not been pushed
@@ -339,6 +364,7 @@ func TestMetricsAvailableAfterReloadAndFailedReload(t *testing.T) {
// TestReloadUnreadyPlugin tests that the ready plugin properly resets the list of readiness implementors during a reload. // 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"). // 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) { func TestReloadUnreadyPlugin(t *testing.T) {
readyAddr := fmt.Sprintf("127.0.0.1:%d", pickPort(t))
// Add/Register a perpetually unready plugin // Add/Register a perpetually unready plugin
dnsserver.Directives = append([]string{"unready"}, dnsserver.Directives...) dnsserver.Directives = append([]string{"unready"}, dnsserver.Directives...)
u := new(unready) u := new(unready)
@@ -354,7 +380,7 @@ func TestReloadUnreadyPlugin(t *testing.T) {
corefile := `.:0 { corefile := `.:0 {
unready unready
whoami whoami
ready 127.0.0.1:53185 ready ` + readyAddr + `
}` }`
coreInput := NewInput(corefile) coreInput := NewInput(corefile)
@@ -369,15 +395,7 @@ func TestReloadUnreadyPlugin(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
resp, err := http.Get("http://127.0.0.1:53185/ready") waitForHTTPBody(t, "http://"+readyAddr+"/ready", u.Name())
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() c1.Stop()
} }
@@ -385,6 +403,7 @@ func TestReloadUnreadyPlugin(t *testing.T) {
// TestReloadTwoServerBlocksUnreadyPlugin tests that the ready plugin properly resets the list of readiness implementors during a reload // TestReloadTwoServerBlocksUnreadyPlugin tests that the ready plugin properly resets the list of readiness implementors during a reload
// when there are multiple server blocks. // when there are multiple server blocks.
func TestReloadTwoServerBlocksUnreadyPlugin(t *testing.T) { func TestReloadTwoServerBlocksUnreadyPlugin(t *testing.T) {
readyAddr := fmt.Sprintf("127.0.0.1:%d", pickPort(t))
// Add/Register a perpetually unready plugin // Add/Register a perpetually unready plugin
dnsserver.Directives = append([]string{"unready1", "unready2"}, dnsserver.Directives...) dnsserver.Directives = append([]string{"unready1", "unready2"}, dnsserver.Directives...)
u1 := new(unready) u1 := new(unready)
@@ -408,12 +427,12 @@ func TestReloadTwoServerBlocksUnreadyPlugin(t *testing.T) {
corefile := `cluster.local.:0 { corefile := `cluster.local.:0 {
unready1 unready1
whoami whoami
ready 127.0.0.1:53185 ready ` + readyAddr + `
} }
.:0 { .:0 {
unready2 unready2
whoami whoami
ready 127.0.0.1:53185 ready ` + readyAddr + `
}` }`
coreInput := NewInput(corefile) coreInput := NewInput(corefile)
@@ -428,16 +447,8 @@ func TestReloadTwoServerBlocksUnreadyPlugin(t *testing.T) {
t.Fatal(err) 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()
uName := u1.Name() + "," + u2.Name() uName := u1.Name() + "," + u2.Name()
if string(bod) != uName { waitForHTTPBody(t, "http://"+readyAddr+"/ready", uName)
t.Errorf("Expected /ready endpoint response body %q, got %q", uName, bod)
}
c1.Stop() c1.Stop()
} }