diff --git a/cmd/karma/script_test.go b/cmd/karma/script_test.go index e09653c18..55f0e4489 100644 --- a/cmd/karma/script_test.go +++ b/cmd/karma/script_test.go @@ -1,9 +1,16 @@ package main import ( + "context" + "errors" "fmt" + "net" + "net/http" "os" + "strconv" + "strings" "testing" + "time" "github.com/beme/abide" "github.com/rogpeppe/go-internal/testscript" @@ -49,5 +56,133 @@ func TestScripts(t *testing.T) { testscript.Run(t, testscript.Params{ Dir: "tests/testscript", UpdateScripts: os.Getenv("UPDATE_SNAPSHOTS") == "1", + Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){ + "http": httpServer, + }, + Setup: func(env *testscript.Env) error { + env.Values["mocks"] = &httpMocks{responses: map[string][]httpMock{}} + return nil + }, }) } + +func httpServer(ts *testscript.TestScript, neg bool, args []string) { + mocks := ts.Value("mocks").(*httpMocks) + + if len(args) == 0 { + ts.Fatalf("! http command requires arguments") + } + cmd := args[0] + + switch cmd { + // http response name /200 200 OK + case "response": + if len(args) < 5 { + ts.Fatalf("! http response command requires '$NAME $PATH $CODE $BODY' args, got [%s]", strings.Join(args, " ")) + } + name := args[1] + path := args[2] + code, err := strconv.Atoi(args[3]) + ts.Check(err) + body := strings.Join(args[4:], " ") + mocks.add(name, httpMock{pattern: path, handler: func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(code) + _, err := w.Write([]byte(body)) + ts.Check(err) + }}) + // http response name /200 200 OK + case "slow-response": + if len(args) < 6 { + ts.Fatalf("! http response command requires '$NAME $PATH $DELAY $CODE $BODY' args, got [%s]", strings.Join(args, " ")) + } + name := args[1] + path := args[2] + delay, err := time.ParseDuration(args[3]) + ts.Check(err) + code, err := strconv.Atoi(args[4]) + ts.Check(err) + body := strings.Join(args[5:], " ") + mocks.add(name, httpMock{pattern: path, handler: func(w http.ResponseWriter, r *http.Request) { + time.Sleep(delay) + w.WriteHeader(code) + _, err := w.Write([]byte(body)) + ts.Check(err) + }}) + // http redirect name /foo/src /dst + case "redirect": + if len(args) != 4 { + ts.Fatalf("! http redirect command requires '$NAME $SRCPATH $DSTPATH' args, got [%s]", strings.Join(args, " ")) + } + name := args[1] + srcpath := args[2] + dstpath := args[3] + mocks.add(name, httpMock{pattern: srcpath, handler: func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", dstpath) + w.WriteHeader(http.StatusFound) + }}) + // http start name 127.0.0.1:7088 [TLS cert] [TLS key] + case "start": + if len(args) < 3 { + ts.Fatalf("! http start command requires '$NAME $LISTEN [$TLS_CERT $TLS_KEY]' args, got [%s]", strings.Join(args, " ")) + } + name := args[1] + listen := args[2] + var isTLS bool + var tlsCert, tlsKey string + if len(args) == 5 { + isTLS = true + tlsCert = args[3] + tlsKey = args[4] + } + + mux := http.NewServeMux() + for n, mockList := range mocks.responses { + if n == name { + for _, mock := range mockList { + mock := mock + mux.HandleFunc(mock.pattern, mock.handler) + } + break + } + } + + listener, err := net.Listen("tcp", listen) + ts.Check(err) + server := &http.Server{Addr: listen, Handler: mux} + go func() { + if isTLS { + err = server.ServeTLS(listener, tlsCert, tlsKey) + } else { + err = server.Serve(listener) + } + if err != nil && !errors.Is(err, http.ErrServerClosed) { + ts.Fatalf("http server failed to start: %s", err) + } + }() + + ts.Defer(func() { + ts.Logf("http server %s shutting down", name) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + _ = server.Shutdown(ctx) + }) + default: + ts.Fatalf("! unknown http command: %v", args) + } +} + +type httpMock struct { + pattern string + handler func(http.ResponseWriter, *http.Request) +} + +type httpMocks struct { + responses map[string][]httpMock +} + +func (m *httpMocks) add(name string, mock httpMock) { + if _, ok := m.responses[name]; !ok { + m.responses[name] = []httpMock{} + } + m.responses[name] = append(m.responses[name], mock) +} diff --git a/cmd/karma/tests/testscript/103_alert_history_tls.txt b/cmd/karma/tests/testscript/103_alert_history_tls.txt index 810cda51e..b9df5476d 100644 --- a/cmd/karma/tests/testscript/103_alert_history_tls.txt +++ b/cmd/karma/tests/testscript/103_alert_history_tls.txt @@ -1,6 +1,9 @@ -# GET /history.json - exec bash -x ./tls.sh + +http response prometheus /api/v1/labels 200 {"status":"success","data":["alertname"]} +http response prometheus /api/v1/query_range 200 {"status":"success","data":{"resultType":"matrix","result":[{"metric":{},"values":[]}]}} +http start prometheus 127.0.0.1:9103 $WORK/prometheus.pem $WORK/prometheus.key + exec bash -x ./test.sh & karma.bin-should-work --pid-file=karma.pid --config.file=karma.yaml ! stdout . @@ -69,18 +72,12 @@ subjectAltName = @alt_names DNS.1 = localhost IP.1 = 127.0.0.1 -- test.sh -- -env GOCACHE=$TMPDIR go run prometheus.go & - -I=0 -while [ ! -f prometheus.pid ] && [ $I -lt 30 ]; do sleep 1; I=$((I+1)); done - I=0 while [ ! -f karma.pid ] && [ $I -lt 30 ]; do sleep 1; I=$((I+1)); done sleep 5 curl -s -f -o /dev/null -XPOST -d @query.json http://127.0.0.1:8103/history.json cat karma.pid | xargs kill -cat prometheus.pid | xargs kill -- tls.sh -- openssl ecparam -genkey -name secp256r1 | openssl ec -out ca.key @@ -90,77 +87,3 @@ openssl ecparam -genkey -name secp256r1 | openssl ec -out prometheus.key openssl req -new -sha256 -key prometheus.key -out prometheus.csr -subj "/C=CI/ST=CI/L=CI/O=CI/CN=127.0.0.1" -config prometheus.conf -extensions SAN openssl x509 -req -sha256 -days 7 -extfile prometheus.conf -extensions SAN -in prometheus.csr -CA ca.pem -CAkey ca.key -set_serial 01 -out prometheus.pem openssl x509 -in prometheus.pem -text - --- prometheus.go -- -package main - -import ( - "context" - "io" - "log" - "net" - "net/http" - "os" - "os/signal" - "strconv" - "syscall" - "time" -) - -func labelNames(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - io.WriteString(w, `{ - "status": "success", - "data": ["alertname"] -}`) -} - -func query(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - io.WriteString(w, `{ - "status": "success", - "data": { - "resultType": "matrix", - "result": [ - { - "metric": {}, - "values": [] - } - ] - } -}`) -} - -func main() { - pid := os.Getpid() - err := os.WriteFile("prometheus.pid", []byte(strconv.Itoa(pid)), 0644) - if err != nil { - log.Fatal(err) - } - - http.HandleFunc("/api/v1/labels", labelNames) - http.HandleFunc("/api/v1/query_range", query) - - listener, err := net.Listen("tcp", "127.0.0.1:9103") - if err != nil { - log.Fatal(err) - } - - server := &http.Server{ - Addr: "127.0.0.1:9103", - } - - go func() { - err := server.ServeTLS(listener, "prometheus.pem", "prometheus.key") - if err != nil { - log.Printf("Serve returned error: %v", err) - } - }() - - stop := make(chan os.Signal, 1) - signal.Notify(stop, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - <-stop - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - server.Shutdown(ctx) -} diff --git a/cmd/karma/tests/testscript/108_alert_history_proxy_url.txt b/cmd/karma/tests/testscript/108_alert_history_proxy_url.txt index 7e46cf4c3..0c76fe7f8 100644 --- a/cmd/karma/tests/testscript/108_alert_history_proxy_url.txt +++ b/cmd/karma/tests/testscript/108_alert_history_proxy_url.txt @@ -1,4 +1,6 @@ -# GET /history.json +http response prometheus /api/v1/labels 200 {"status":"success","data":["alertname"]} +http response prometheus /api/v1/query_range 200 {"status":"success","data":{"resultType":"matrix","result":[{"metric":{},"values":[]}]}} +http start prometheus 127.0.0.1:9108 exec bash -x ./test.sh & karma.bin-should-work --pid-file=karma.pid --config.file=karma.yaml @@ -51,84 +53,7 @@ history: uri: '$1' proxy_url: http://localhost:9108 -- test.sh -- -env GOCACHE=$TMPDIR go run prometheus.go & -while [ ! -f prometheus.pid ]; do sleep 1 ; done while [ ! -f karma.pid ]; do sleep 1 ; done sleep 5 curl -s -f -o /dev/null -XPOST -d @query.json http://127.0.0.1:8108/history.json cat karma.pid | xargs kill -cat prometheus.pid | xargs kill - --- prometheus.go -- -package main - -import ( - "context" - "io" - "log" - "net" - "net/http" - "os" - "os/signal" - "strconv" - "syscall" - "time" -) - -func labelNames(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - io.WriteString(w, `{ - "status": "success", - "data": ["alertname"] -}`) -} - -func query(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - io.WriteString(w, `{ - "status": "success", - "data": { - "resultType": "matrix", - "result": [ - { - "metric": {}, - "values": [] - } - ] - } -}`) -} - -func main() { - pid := os.Getpid() - err := os.WriteFile("prometheus.pid", []byte(strconv.Itoa(pid)), 0644) - if err != nil { - log.Fatal(err) - } - - http.HandleFunc("/api/v1/labels", labelNames) - http.HandleFunc("/api/v1/query_range", query) - - listener, err := net.Listen("tcp", "127.0.0.1:9108") - if err != nil { - log.Fatal(err) - } - - server := &http.Server{ - Addr: "127.0.0.1:9108", - } - - go func() { - err := server.Serve(listener) - if err != nil { - log.Printf("Serve returned error: %v", err) - } - }() - - stop := make(chan os.Signal, 1) - signal.Notify(stop, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - <-stop - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - server.Shutdown(ctx) -} diff --git a/cmd/karma/tests/testscript/109_alert_history_proxy_url_invalid.txt b/cmd/karma/tests/testscript/109_alert_history_proxy_url_invalid.txt index f219db1f1..c90f40924 100644 --- a/cmd/karma/tests/testscript/109_alert_history_proxy_url_invalid.txt +++ b/cmd/karma/tests/testscript/109_alert_history_proxy_url_invalid.txt @@ -1,4 +1,6 @@ -# GET /history.json +http response prometheus /api/v1/labels 200 {"status":"success","data":["alertname"]} +http response prometheus /api/v1/query_range 200 {"status":"success","data":{"resultType":"matrix","result":[{"metric":{},"values":[]}]}} +http start prometheus 127.0.0.1:9109 exec bash -x ./test.sh & karma.bin-should-work --pid-file=karma.pid --config.file=karma.yaml @@ -53,85 +55,7 @@ history: uri: '$1' proxy_url: '%gh&%ij' -- test.sh -- -env GOCACHE=$TMPDIR go run prometheus.go & -while [ ! -f prometheus.pid ]; do sleep 1 ; done while [ ! -f karma.pid ]; do sleep 1 ; done sleep 5 curl -s -f -o /dev/null -XPOST -d @query.json http://127.0.0.1:8109/history.json cat karma.pid | xargs kill -cat prometheus.pid | xargs kill - --- prometheus.go -- -package main - -import ( - "context" - "io" - "log" - "net" - "net/http" - "os" - "os/signal" - "strconv" - "syscall" - "time" -) - -func labelNames(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - io.WriteString(w, `{ - "status": "success", - "data": ["alertname"] -}`) -} - -func query(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - io.WriteString(w, `{ - "status": "success", - "data": { - "resultType": "matrix", - "result": [ - { - "metric": {}, - "values": [] - } - ] - } -}`) -} - -func main() { - pid := os.Getpid() - err := os.WriteFile("prometheus.pid", []byte(strconv.Itoa(pid)), 0644) - if err != nil { - log.Fatal(err) - } - - http.HandleFunc("/api/v1/labels", labelNames) - http.HandleFunc("/api/v1/query_range", query) - - listener, err := net.Listen("tcp", "127.0.0.1:9109") - if err != nil { - log.Fatal(err) - } - - server := &http.Server{ - Addr: "127.0.0.1:9109", - } - - go func() { - err := server.Serve(listener) - if err != nil { - log.Printf("Serve returned error: %v", err) - } - }() - - stop := make(chan os.Signal, 1) - signal.Notify(stop, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - <-stop - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - server.Shutdown(ctx) -} - diff --git a/cmd/karma/tests/testscript/110_alert_history.txt b/cmd/karma/tests/testscript/110_alert_history.txt index 776095c02..c7098eb7d 100644 --- a/cmd/karma/tests/testscript/110_alert_history.txt +++ b/cmd/karma/tests/testscript/110_alert_history.txt @@ -1,4 +1,6 @@ -# GET /history.json +http response prometheus /api/v1/labels 200 {"status":"success","data":["alertname"]} +http response prometheus /api/v1/query_range 200 {"status":"success","data":{"resultType":"matrix","result":[{"metric":{},"values":[]}]}} +http start prometheus 127.0.0.1:9110 exec bash -x ./test.sh & karma.bin-should-work --pid-file=karma.pid --alertmanager.uri=http://127.0.0.1 --listen.address=127.0.0.1 --listen.port=8110 --history.enabled=true --history.timeout=10s @@ -35,84 +37,7 @@ level=info msg="Removing PID file" path=karma.pid } } -- test.sh -- -env GOCACHE=$TMPDIR go run prometheus.go & -while [ ! -f prometheus.pid ]; do sleep 1 ; done while [ ! -f karma.pid ]; do sleep 1 ; done sleep 5 curl -s -f -o /dev/null -XPOST -d @query.json http://127.0.0.1:8110/history.json cat karma.pid | xargs kill -cat prometheus.pid | xargs kill - --- prometheus.go -- -package main - -import ( - "context" - "io" - "log" - "net" - "net/http" - "os" - "os/signal" - "strconv" - "syscall" - "time" -) - -func labelNames(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - io.WriteString(w, `{ - "status": "success", - "data": ["alertname"] -}`) -} - -func query(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - io.WriteString(w, `{ - "status": "success", - "data": { - "resultType": "matrix", - "result": [ - { - "metric": {}, - "values": [] - } - ] - } -}`) -} - -func main() { - pid := os.Getpid() - err := os.WriteFile("prometheus.pid", []byte(strconv.Itoa(pid)), 0644) - if err != nil { - log.Fatal(err) - } - - http.HandleFunc("/api/v1/labels", labelNames) - http.HandleFunc("/api/v1/query_range", query) - - listener, err := net.Listen("tcp", "127.0.0.1:9110") - if err != nil { - log.Fatal(err) - } - - server := &http.Server{ - Addr: "127.0.0.1:9110", - } - - go func() { - err := server.Serve(listener) - if err != nil { - log.Printf("Serve returned error: %v", err) - } - }() - - stop := make(chan os.Signal, 1) - signal.Notify(stop, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - <-stop - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - server.Shutdown(ctx) -} diff --git a/cmd/karma/tests/testscript/111_alert_history_tls_bad.txt b/cmd/karma/tests/testscript/111_alert_history_tls_bad.txt index 957ad2866..bd091b296 100644 --- a/cmd/karma/tests/testscript/111_alert_history_tls_bad.txt +++ b/cmd/karma/tests/testscript/111_alert_history_tls_bad.txt @@ -1,4 +1,6 @@ -# GET /history.json +http response prometheus /api/v1/labels 200 {"status":"success","data":["alertname"]} +http response prometheus /api/v1/query_range 200 {"status":"success","data":{"resultType":"matrix","result":[{"metric":{},"values":[]}]}} +http start prometheus 127.0.0.1:9111 exec bash -x ./test.sh & karma.bin-should-work --pid-file=karma.pid --config.file=karma.yaml @@ -55,89 +57,9 @@ history: } } -- test.sh -- -env GOCACHE=$TMPDIR go run prometheus.go & - -I=0 -while [ ! -f prometheus.pid ] && [ $I -lt 30 ]; do sleep 1; I=$((I+1)); done - I=0 while [ ! -f karma.pid ] && [ $I -lt 30 ]; do sleep 1; I=$((I+1)); done sleep 5 curl -s -f -o /dev/null -XPOST -d @query.json http://127.0.0.1:8111/history.json cat karma.pid | xargs kill -cat prometheus.pid | xargs kill - --- prometheus.go -- -package main - -import ( - "context" - "io" - "log" - "net" - "net/http" - "os" - "os/signal" - "strconv" - "syscall" - "time" -) - -func labelNames(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - io.WriteString(w, `{ - "status": "success", - "data": ["alertname"] -}`) -} - -func query(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - io.WriteString(w, `{ - "status": "success", - "data": { - "resultType": "matrix", - "result": [ - { - "metric": {}, - "values": [] - } - ] - } -}`) -} - -func main() { - pid := os.Getpid() - err := os.WriteFile("prometheus.pid", []byte(strconv.Itoa(pid)), 0644) - if err != nil { - log.Fatal(err) - } - - http.HandleFunc("/api/v1/labels", labelNames) - http.HandleFunc("/api/v1/query_range", query) - - listener, err := net.Listen("tcp", "127.0.0.1:9111") - if err != nil { - log.Fatal(err) - } - - server := &http.Server{ - Addr: "127.0.0.1:9111", - } - - go func() { - err := server.Serve(listener) - if err != nil { - log.Printf("Serve returned error: %v", err) - } - }() - - stop := make(chan os.Signal, 1) - signal.Notify(stop, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - <-stop - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - server.Shutdown(ctx) -}