Files
vpp-maglev/internal/prober/http_test.go

194 lines
4.5 KiB
Go

package prober
import (
"context"
"fmt"
"io"
"net"
"net/http"
"net/http/httptest"
"regexp"
"testing"
"time"
"git.ipng.ch/ipng/vpp-maglev/internal/config"
)
// dialAndProbe dials addr directly (bypassing netns/interface binding) and
// exercises the HTTP probe response-checking logic.
func dialAndProbe(ctx context.Context, addr string, cfg ProbeConfig) (bool, error) {
if cfg.HTTP == nil {
return false, fmt.Errorf("dialAndProbe requires HTTP params")
}
p := cfg.HTTP
path := p.Path
if path == "" {
path = "/"
}
conn, err := net.DialTimeout("tcp", addr, cfg.Timeout)
if err != nil {
return false, err
}
transport := &http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return conn, nil
},
DisableKeepAlives: true,
}
client := &http.Client{
Transport: transport,
Timeout: cfg.Timeout,
CheckRedirect: func(_ *http.Request, _ []*http.Request) error {
return http.ErrUseLastResponse
},
}
target := "http://" + addr + path
req, err := http.NewRequestWithContext(ctx, http.MethodGet, target, nil)
if err != nil {
return false, err
}
if p.Host != "" {
req.Host = p.Host
}
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer resp.Body.Close()
if resp.StatusCode < p.ResponseCodeMin || resp.StatusCode > p.ResponseCodeMax {
return false, nil
}
if p.ResponseRegexp != nil {
body, _ := io.ReadAll(resp.Body)
if !p.ResponseRegexp.Match(body) {
return false, nil
}
}
return true, nil
}
func TestHTTPProbeStatusCode(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, "healthy")
}))
defer srv.Close()
cfg := ProbeConfig{
Timeout: 2 * time.Second,
HTTP: &config.HTTPParams{
Path: "/healthz",
ResponseCodeMin: 200,
ResponseCodeMax: 200,
},
}
ok, err := dialAndProbe(context.Background(), srv.Listener.Addr().String(), cfg)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !ok {
t.Error("expected probe success")
}
}
func TestHTTPProbeWrongStatusCode(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusServiceUnavailable)
}))
defer srv.Close()
cfg := ProbeConfig{
Timeout: 2 * time.Second,
HTTP: &config.HTTPParams{
Path: "/",
ResponseCodeMin: 200,
ResponseCodeMax: 200,
},
}
ok, err := dialAndProbe(context.Background(), srv.Listener.Addr().String(), cfg)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if ok {
t.Error("expected probe failure on wrong status code")
}
}
func TestHTTPProbeRegexpMatch(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprint(w, `{"status":"ok"}`)
}))
defer srv.Close()
cfg := ProbeConfig{
Timeout: 2 * time.Second,
HTTP: &config.HTTPParams{
Path: "/",
ResponseCodeMin: 200,
ResponseCodeMax: 200,
ResponseRegexp: regexp.MustCompile(`"status":"ok"`),
},
}
ok, err := dialAndProbe(context.Background(), srv.Listener.Addr().String(), cfg)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !ok {
t.Error("expected probe success with matching regexp")
}
}
func TestHTTPProbeRegexpNoMatch(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprint(w, `{"status":"degraded"}`)
}))
defer srv.Close()
cfg := ProbeConfig{
Timeout: 2 * time.Second,
HTTP: &config.HTTPParams{
Path: "/",
ResponseCodeMin: 200,
ResponseCodeMax: 200,
ResponseRegexp: regexp.MustCompile(`"status":"ok"`),
},
}
ok, err := dialAndProbe(context.Background(), srv.Listener.Addr().String(), cfg)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if ok {
t.Error("expected probe failure when regexp does not match")
}
}
func TestHTTPProbeNoRedirect(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/other", http.StatusFound)
}))
defer srv.Close()
// Probe expects 302 — redirect is not followed.
cfg := ProbeConfig{
Timeout: 2 * time.Second,
HTTP: &config.HTTPParams{
Path: "/",
ResponseCodeMin: 302,
ResponseCodeMax: 302,
},
}
ok, err := dialAndProbe(context.Background(), srv.Listener.Addr().String(), cfg)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !ok {
t.Error("expected success when probe expects 302 and server returns 302")
}
}