2020-04-12 13:51:03 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2023-10-13 13:54:33 +02:00
|
|
|
"context"
|
2020-04-12 13:51:03 +02:00
|
|
|
"encoding/json"
|
2022-07-08 10:26:13 +02:00
|
|
|
"fmt"
|
2020-04-12 13:51:03 +02:00
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
|
|
|
"reflect"
|
|
|
|
"testing"
|
2023-03-17 15:57:24 +01:00
|
|
|
"time"
|
2020-05-10 18:58:17 +02:00
|
|
|
|
2023-10-13 13:54:33 +02:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/config"
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/datasource"
|
2020-05-10 18:58:17 +02:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/notifier"
|
2023-10-13 13:54:33 +02:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/rule"
|
2020-04-12 13:51:03 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestHandler(t *testing.T) {
|
2023-10-13 13:54:33 +02:00
|
|
|
fq := &datasource.FakeQuerier{}
|
|
|
|
fq.Add(datasource.Metric{
|
|
|
|
Values: []float64{1}, Timestamps: []int64{0},
|
2023-03-17 15:57:24 +01:00
|
|
|
})
|
2023-10-13 13:54:33 +02:00
|
|
|
g := &rule.Group{
|
|
|
|
Name: "group",
|
2024-02-09 14:35:31 +01:00
|
|
|
File: "rules.yaml",
|
2023-10-13 13:54:33 +02:00
|
|
|
Concurrency: 1,
|
2020-04-12 13:51:03 +02:00
|
|
|
}
|
2023-10-13 13:54:33 +02:00
|
|
|
ar := rule.NewAlertingRule(fq, g, config.Rule{ID: 0, Alert: "alert"})
|
|
|
|
rr := rule.NewRecordingRule(fq, g, config.Rule{ID: 1, Record: "record"})
|
|
|
|
g.Rules = []rule.Rule{ar, rr}
|
|
|
|
g.ExecOnce(context.Background(), func() []notifier.Notifier { return nil }, nil, time.Time{})
|
|
|
|
|
|
|
|
m := &manager{groups: map[uint64]*rule.Group{
|
|
|
|
g.ID(): g,
|
|
|
|
}}
|
2020-05-10 18:58:17 +02:00
|
|
|
rh := &requestHandler{m: m}
|
|
|
|
|
2024-04-02 22:16:24 +02:00
|
|
|
getResp := func(t *testing.T, url string, to interface{}, code int) {
|
2020-04-12 13:51:03 +02:00
|
|
|
t.Helper()
|
|
|
|
resp, err := http.Get(url)
|
|
|
|
if err != nil {
|
2022-07-08 10:26:13 +02:00
|
|
|
t.Fatalf("unexpected err %s", err)
|
2020-04-12 13:51:03 +02:00
|
|
|
}
|
|
|
|
if code != resp.StatusCode {
|
|
|
|
t.Errorf("unexpected status code %d want %d", resp.StatusCode, code)
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
if err := resp.Body.Close(); err != nil {
|
|
|
|
t.Errorf("err closing body %s", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
if to != nil {
|
|
|
|
if err = json.NewDecoder(resp.Body).Decode(to); err != nil {
|
|
|
|
t.Errorf("unexpected err %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { rh.handler(w, r) }))
|
|
|
|
defer ts.Close()
|
2022-07-08 10:26:13 +02:00
|
|
|
|
|
|
|
t.Run("/", func(t *testing.T) {
|
2024-04-02 22:16:24 +02:00
|
|
|
getResp(t, ts.URL, nil, 200)
|
|
|
|
getResp(t, ts.URL+"/vmalert", nil, 200)
|
|
|
|
getResp(t, ts.URL+"/vmalert/alerts", nil, 200)
|
|
|
|
getResp(t, ts.URL+"/vmalert/groups", nil, 200)
|
|
|
|
getResp(t, ts.URL+"/vmalert/notifiers", nil, 200)
|
|
|
|
getResp(t, ts.URL+"/rules", nil, 200)
|
2022-09-14 14:04:24 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("/vmalert/rule", func(t *testing.T) {
|
2023-10-13 13:54:33 +02:00
|
|
|
a := ruleToAPI(ar)
|
2024-04-02 22:16:24 +02:00
|
|
|
getResp(t, ts.URL+"/vmalert/"+a.WebLink(), nil, 200)
|
2023-10-13 13:54:33 +02:00
|
|
|
r := ruleToAPI(rr)
|
2024-04-02 22:16:24 +02:00
|
|
|
getResp(t, ts.URL+"/vmalert/"+r.WebLink(), nil, 200)
|
2023-03-17 15:57:24 +01:00
|
|
|
})
|
|
|
|
t.Run("/vmalert/alert", func(t *testing.T) {
|
2023-10-13 13:54:33 +02:00
|
|
|
alerts := ruleToAPIAlert(ar)
|
2023-03-17 15:57:24 +01:00
|
|
|
for _, a := range alerts {
|
2024-04-02 22:16:24 +02:00
|
|
|
getResp(t, ts.URL+"/vmalert/"+a.WebLink(), nil, 200)
|
2023-03-17 15:57:24 +01:00
|
|
|
}
|
2022-09-14 14:04:24 +02:00
|
|
|
})
|
|
|
|
t.Run("/vmalert/rule?badParam", func(t *testing.T) {
|
|
|
|
params := fmt.Sprintf("?%s=0&%s=1", paramGroupID, paramRuleID)
|
2024-04-02 22:16:24 +02:00
|
|
|
getResp(t, ts.URL+"/vmalert/rule"+params, nil, 404)
|
2022-09-14 14:04:24 +02:00
|
|
|
|
|
|
|
params = fmt.Sprintf("?%s=1&%s=0", paramGroupID, paramRuleID)
|
2024-04-02 22:16:24 +02:00
|
|
|
getResp(t, ts.URL+"/vmalert/rule"+params, nil, 404)
|
2022-07-08 10:26:13 +02:00
|
|
|
})
|
|
|
|
|
2020-04-12 13:51:03 +02:00
|
|
|
t.Run("/api/v1/alerts", func(t *testing.T) {
|
|
|
|
lr := listAlertsResponse{}
|
2024-04-02 22:16:24 +02:00
|
|
|
getResp(t, ts.URL+"/api/v1/alerts", &lr, 200)
|
2020-04-12 13:51:03 +02:00
|
|
|
if length := len(lr.Data.Alerts); length != 1 {
|
|
|
|
t.Errorf("expected 1 alert got %d", length)
|
|
|
|
}
|
2022-07-08 10:26:13 +02:00
|
|
|
|
|
|
|
lr = listAlertsResponse{}
|
2024-04-02 22:16:24 +02:00
|
|
|
getResp(t, ts.URL+"/vmalert/api/v1/alerts", &lr, 200)
|
2022-07-08 10:26:13 +02:00
|
|
|
if length := len(lr.Data.Alerts); length != 1 {
|
|
|
|
t.Errorf("expected 1 alert got %d", length)
|
|
|
|
}
|
2020-04-12 13:51:03 +02:00
|
|
|
})
|
2022-07-08 10:26:13 +02:00
|
|
|
t.Run("/api/v1/alert?alertID&groupID", func(t *testing.T) {
|
2023-10-13 13:54:33 +02:00
|
|
|
expAlert := newAlertAPI(ar, ar.GetAlerts()[0])
|
|
|
|
alert := &apiAlert{}
|
2024-04-02 22:16:24 +02:00
|
|
|
getResp(t, ts.URL+"/"+expAlert.APILink(), alert, 200)
|
2022-07-08 10:26:13 +02:00
|
|
|
if !reflect.DeepEqual(alert, expAlert) {
|
|
|
|
t.Errorf("expected %v is equal to %v", alert, expAlert)
|
|
|
|
}
|
|
|
|
|
2023-10-13 13:54:33 +02:00
|
|
|
alert = &apiAlert{}
|
2024-04-02 22:16:24 +02:00
|
|
|
getResp(t, ts.URL+"/vmalert/"+expAlert.APILink(), alert, 200)
|
2022-07-08 10:26:13 +02:00
|
|
|
if !reflect.DeepEqual(alert, expAlert) {
|
|
|
|
t.Errorf("expected %v is equal to %v", alert, expAlert)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("/api/v1/alert?badParams", func(t *testing.T) {
|
|
|
|
params := fmt.Sprintf("?%s=0&%s=1", paramGroupID, paramAlertID)
|
2024-04-02 22:16:24 +02:00
|
|
|
getResp(t, ts.URL+"/api/v1/alert"+params, nil, 404)
|
|
|
|
getResp(t, ts.URL+"/vmalert/api/v1/alert"+params, nil, 404)
|
2022-07-08 10:26:13 +02:00
|
|
|
|
|
|
|
params = fmt.Sprintf("?%s=1&%s=0", paramGroupID, paramAlertID)
|
2024-04-02 22:16:24 +02:00
|
|
|
getResp(t, ts.URL+"/api/v1/alert"+params, nil, 404)
|
|
|
|
getResp(t, ts.URL+"/vmalert/api/v1/alert"+params, nil, 404)
|
2022-07-08 10:26:13 +02:00
|
|
|
|
|
|
|
// bad request, alertID is missing
|
|
|
|
params = fmt.Sprintf("?%s=1", paramGroupID)
|
2024-04-02 22:16:24 +02:00
|
|
|
getResp(t, ts.URL+"/api/v1/alert"+params, nil, 400)
|
|
|
|
getResp(t, ts.URL+"/vmalert/api/v1/alert"+params, nil, 400)
|
2022-07-08 10:26:13 +02:00
|
|
|
})
|
|
|
|
|
2022-03-15 12:54:53 +01:00
|
|
|
t.Run("/api/v1/rules", func(t *testing.T) {
|
2020-06-01 12:46:37 +02:00
|
|
|
lr := listGroupsResponse{}
|
2024-04-02 22:16:24 +02:00
|
|
|
getResp(t, ts.URL+"/api/v1/rules", &lr, 200)
|
2020-06-01 12:46:37 +02:00
|
|
|
if length := len(lr.Data.Groups); length != 1 {
|
|
|
|
t.Errorf("expected 1 group got %d", length)
|
|
|
|
}
|
2022-07-08 10:26:13 +02:00
|
|
|
|
|
|
|
lr = listGroupsResponse{}
|
2024-04-02 22:16:24 +02:00
|
|
|
getResp(t, ts.URL+"/vmalert/api/v1/rules", &lr, 200)
|
2022-07-08 10:26:13 +02:00
|
|
|
if length := len(lr.Data.Groups); length != 1 {
|
|
|
|
t.Errorf("expected 1 group got %d", length)
|
|
|
|
}
|
2020-06-01 12:46:37 +02:00
|
|
|
})
|
2024-02-09 14:35:31 +01:00
|
|
|
t.Run("/api/v1/rule?ruleID&groupID", func(t *testing.T) {
|
|
|
|
expRule := ruleToAPI(ar)
|
|
|
|
gotRule := apiRule{}
|
2024-04-02 22:16:24 +02:00
|
|
|
getResp(t, ts.URL+"/"+expRule.APILink(), &gotRule, 200)
|
2024-02-09 09:02:35 +01:00
|
|
|
|
2024-02-09 14:35:31 +01:00
|
|
|
if expRule.ID != gotRule.ID {
|
|
|
|
t.Errorf("expected to get Rule %q; got %q instead", expRule.ID, gotRule.ID)
|
2024-02-09 09:02:35 +01:00
|
|
|
}
|
|
|
|
|
2024-02-09 14:35:31 +01:00
|
|
|
gotRule = apiRule{}
|
2024-04-02 22:16:24 +02:00
|
|
|
getResp(t, ts.URL+"/vmalert/"+expRule.APILink(), &gotRule, 200)
|
2024-02-09 09:02:35 +01:00
|
|
|
|
2024-02-09 14:35:31 +01:00
|
|
|
if expRule.ID != gotRule.ID {
|
|
|
|
t.Errorf("expected to get Rule %q; got %q instead", expRule.ID, gotRule.ID)
|
2024-02-09 09:02:35 +01:00
|
|
|
}
|
|
|
|
|
2024-02-09 14:35:31 +01:00
|
|
|
gotRuleWithUpdates := apiRuleWithUpdates{}
|
2024-04-02 22:16:24 +02:00
|
|
|
getResp(t, ts.URL+"/"+expRule.APILink(), &gotRuleWithUpdates, 200)
|
2024-02-09 14:35:31 +01:00
|
|
|
if gotRuleWithUpdates.StateUpdates == nil || len(gotRuleWithUpdates.StateUpdates) < 1 {
|
|
|
|
t.Fatalf("expected %+v to have state updates field not empty", gotRuleWithUpdates.StateUpdates)
|
2024-02-09 09:02:35 +01:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2024-02-09 14:35:31 +01:00
|
|
|
t.Run("/api/v1/rules&filters", func(t *testing.T) {
|
|
|
|
check := func(url string, expGroups, expRules int) {
|
|
|
|
t.Helper()
|
|
|
|
lr := listGroupsResponse{}
|
2024-04-02 22:16:24 +02:00
|
|
|
getResp(t, ts.URL+url, &lr, 200)
|
2024-02-09 14:35:31 +01:00
|
|
|
if length := len(lr.Data.Groups); length != expGroups {
|
|
|
|
t.Errorf("expected %d groups got %d", expGroups, length)
|
2024-02-09 09:02:35 +01:00
|
|
|
}
|
2024-02-09 14:35:31 +01:00
|
|
|
if len(lr.Data.Groups) < 1 {
|
|
|
|
return
|
2024-02-09 09:02:35 +01:00
|
|
|
}
|
2024-02-09 14:35:31 +01:00
|
|
|
var rulesN int
|
|
|
|
for _, gr := range lr.Data.Groups {
|
|
|
|
rulesN += len(gr.Rules)
|
2024-02-09 09:02:35 +01:00
|
|
|
}
|
2024-02-09 14:35:31 +01:00
|
|
|
if rulesN != expRules {
|
|
|
|
t.Errorf("expected %d rules got %d", expRules, rulesN)
|
2024-02-09 09:02:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-09 14:35:31 +01:00
|
|
|
check("/api/v1/rules?type=alert", 1, 1)
|
|
|
|
check("/api/v1/rules?type=record", 1, 1)
|
2024-02-09 09:02:35 +01:00
|
|
|
|
2024-02-09 14:35:31 +01:00
|
|
|
check("/vmalert/api/v1/rules?type=alert", 1, 1)
|
|
|
|
check("/vmalert/api/v1/rules?type=record", 1, 1)
|
2023-12-04 16:40:33 +01:00
|
|
|
|
2024-02-09 14:35:31 +01:00
|
|
|
// no filtering expected due to bad params
|
|
|
|
check("/api/v1/rules?type=badParam", 1, 2)
|
|
|
|
check("/api/v1/rules?foo=bar", 1, 2)
|
2023-12-04 16:40:33 +01:00
|
|
|
|
2024-02-09 14:35:31 +01:00
|
|
|
check("/api/v1/rules?rule_group[]=foo&rule_group[]=bar", 0, 0)
|
|
|
|
check("/api/v1/rules?rule_group[]=foo&rule_group[]=group&rule_group[]=bar", 1, 2)
|
2023-12-04 16:40:33 +01:00
|
|
|
|
2024-02-09 14:35:31 +01:00
|
|
|
check("/api/v1/rules?rule_group[]=group&file[]=foo", 0, 0)
|
|
|
|
check("/api/v1/rules?rule_group[]=group&file[]=rules.yaml", 1, 2)
|
|
|
|
|
|
|
|
check("/api/v1/rules?rule_group[]=group&file[]=rules.yaml&rule_name[]=foo", 1, 0)
|
|
|
|
check("/api/v1/rules?rule_group[]=group&file[]=rules.yaml&rule_name[]=alert", 1, 1)
|
|
|
|
check("/api/v1/rules?rule_group[]=group&file[]=rules.yaml&rule_name[]=alert&rule_name[]=record", 1, 2)
|
|
|
|
})
|
|
|
|
t.Run("/api/v1/rules&exclude_alerts=true", func(t *testing.T) {
|
|
|
|
// check if response returns active alerts by default
|
|
|
|
lr := listGroupsResponse{}
|
2024-04-02 22:16:24 +02:00
|
|
|
getResp(t, ts.URL+"/api/v1/rules?rule_group[]=group&file[]=rules.yaml", &lr, 200)
|
2024-02-09 14:35:31 +01:00
|
|
|
activeAlerts := 0
|
|
|
|
for _, gr := range lr.Data.Groups {
|
|
|
|
for _, r := range gr.Rules {
|
|
|
|
activeAlerts += len(r.Alerts)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if activeAlerts == 0 {
|
|
|
|
t.Fatalf("expected at least 1 active alert in response; got 0")
|
2023-12-04 16:40:33 +01:00
|
|
|
}
|
|
|
|
|
2024-02-09 14:35:31 +01:00
|
|
|
// disable returning alerts via param
|
|
|
|
lr = listGroupsResponse{}
|
2024-04-02 22:16:24 +02:00
|
|
|
getResp(t, ts.URL+"/api/v1/rules?rule_group[]=group&file[]=rules.yaml&exclude_alerts=true", &lr, 200)
|
2024-02-09 14:35:31 +01:00
|
|
|
activeAlerts = 0
|
|
|
|
for _, gr := range lr.Data.Groups {
|
|
|
|
for _, r := range gr.Rules {
|
|
|
|
activeAlerts += len(r.Alerts)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if activeAlerts != 0 {
|
|
|
|
t.Fatalf("expected to get 0 active alert in response; got %d", activeAlerts)
|
2023-12-04 16:40:33 +01:00
|
|
|
}
|
|
|
|
})
|
2023-04-28 10:08:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestEmptyResponse(t *testing.T) {
|
2023-10-13 13:54:33 +02:00
|
|
|
rhWithNoGroups := &requestHandler{m: &manager{groups: make(map[uint64]*rule.Group)}}
|
2023-05-25 16:56:54 +02:00
|
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { rhWithNoGroups.handler(w, r) }))
|
2023-04-28 10:08:29 +02:00
|
|
|
defer ts.Close()
|
|
|
|
|
2024-04-02 22:16:24 +02:00
|
|
|
getResp := func(t *testing.T, url string, to interface{}, code int) {
|
2023-04-28 10:08:29 +02:00
|
|
|
t.Helper()
|
|
|
|
resp, err := http.Get(url)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected err %s", err)
|
|
|
|
}
|
|
|
|
if code != resp.StatusCode {
|
|
|
|
t.Errorf("unexpected status code %d want %d", resp.StatusCode, code)
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
if err := resp.Body.Close(); err != nil {
|
|
|
|
t.Errorf("err closing body %s", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
if to != nil {
|
|
|
|
if err = json.NewDecoder(resp.Body).Decode(to); err != nil {
|
|
|
|
t.Errorf("unexpected err %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-25 16:56:54 +02:00
|
|
|
t.Run("no groups /api/v1/alerts", func(t *testing.T) {
|
2023-04-28 10:08:29 +02:00
|
|
|
lr := listAlertsResponse{}
|
2024-04-02 22:16:24 +02:00
|
|
|
getResp(t, ts.URL+"/api/v1/alerts", &lr, 200)
|
2023-04-28 10:08:29 +02:00
|
|
|
if lr.Data.Alerts == nil {
|
|
|
|
t.Errorf("expected /api/v1/alerts response to have non-nil data")
|
|
|
|
}
|
|
|
|
|
|
|
|
lr = listAlertsResponse{}
|
2024-04-02 22:16:24 +02:00
|
|
|
getResp(t, ts.URL+"/vmalert/api/v1/alerts", &lr, 200)
|
2023-04-28 10:08:29 +02:00
|
|
|
if lr.Data.Alerts == nil {
|
|
|
|
t.Errorf("expected /api/v1/alerts response to have non-nil data")
|
|
|
|
}
|
|
|
|
})
|
2022-07-08 10:26:13 +02:00
|
|
|
|
2023-05-25 16:56:54 +02:00
|
|
|
t.Run("no groups /api/v1/rules", func(t *testing.T) {
|
2023-04-28 10:08:29 +02:00
|
|
|
lr := listGroupsResponse{}
|
2024-04-02 22:16:24 +02:00
|
|
|
getResp(t, ts.URL+"/api/v1/rules", &lr, 200)
|
2023-04-28 10:08:29 +02:00
|
|
|
if lr.Data.Groups == nil {
|
|
|
|
t.Errorf("expected /api/v1/rules response to have non-nil data")
|
|
|
|
}
|
|
|
|
|
|
|
|
lr = listGroupsResponse{}
|
2024-04-02 22:16:24 +02:00
|
|
|
getResp(t, ts.URL+"/vmalert/api/v1/rules", &lr, 200)
|
2023-04-28 10:08:29 +02:00
|
|
|
if lr.Data.Groups == nil {
|
|
|
|
t.Errorf("expected /api/v1/rules response to have non-nil data")
|
|
|
|
}
|
|
|
|
})
|
2023-05-25 16:56:54 +02:00
|
|
|
|
2023-10-13 13:54:33 +02:00
|
|
|
rhWithEmptyGroup := &requestHandler{m: &manager{groups: map[uint64]*rule.Group{0: {Name: "test"}}}}
|
2023-05-25 16:56:54 +02:00
|
|
|
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { rhWithEmptyGroup.handler(w, r) })
|
|
|
|
|
|
|
|
t.Run("empty group /api/v1/rules", func(t *testing.T) {
|
|
|
|
lr := listGroupsResponse{}
|
2024-04-02 22:16:24 +02:00
|
|
|
getResp(t, ts.URL+"/api/v1/rules", &lr, 200)
|
2023-05-25 16:56:54 +02:00
|
|
|
if lr.Data.Groups == nil {
|
|
|
|
t.Fatalf("expected /api/v1/rules response to have non-nil data")
|
|
|
|
}
|
|
|
|
|
|
|
|
lr = listGroupsResponse{}
|
2024-04-02 22:16:24 +02:00
|
|
|
getResp(t, ts.URL+"/vmalert/api/v1/rules", &lr, 200)
|
2023-05-25 16:56:54 +02:00
|
|
|
if lr.Data.Groups == nil {
|
|
|
|
t.Fatalf("expected /api/v1/rules response to have non-nil data")
|
|
|
|
}
|
|
|
|
|
|
|
|
group := lr.Data.Groups[0]
|
|
|
|
if group.Rules == nil {
|
|
|
|
t.Fatalf("expected /api/v1/rules response to have non-nil rules for group")
|
|
|
|
}
|
|
|
|
})
|
2020-04-12 13:51:03 +02:00
|
|
|
}
|