2020-06-21 12:32:46 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2021-05-25 15:27:22 +02:00
|
|
|
"context"
|
2020-06-21 12:32:46 +02:00
|
|
|
"fmt"
|
|
|
|
"net/url"
|
|
|
|
"os"
|
|
|
|
"testing"
|
2021-05-25 15:27:22 +02:00
|
|
|
"time"
|
2020-06-21 12:32:46 +02:00
|
|
|
|
2023-10-13 13:54:33 +02:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/datasource"
|
2020-06-21 12:32:46 +02:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/notifier"
|
2021-11-30 00:18:48 +01:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/remotewrite"
|
2023-10-13 13:54:33 +02:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/rule"
|
2021-05-25 15:27:22 +02:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/procutil"
|
2020-06-21 12:32:46 +02:00
|
|
|
)
|
|
|
|
|
2023-10-13 13:54:33 +02:00
|
|
|
func init() {
|
|
|
|
// Disable rand sleep on group start during tests in order to speed up test execution.
|
|
|
|
// Rand sleep is needed only in prod code.
|
|
|
|
rule.SkipRandSleepOnGroupStart = true
|
|
|
|
}
|
|
|
|
|
2020-06-21 12:32:46 +02:00
|
|
|
func TestGetExternalURL(t *testing.T) {
|
2023-12-15 11:13:56 +01:00
|
|
|
invalidURL := "victoriametrics.com/path"
|
|
|
|
_, err := getExternalURL(invalidURL)
|
|
|
|
if err == nil {
|
2024-07-12 21:57:56 +02:00
|
|
|
t.Fatalf("expected error, got nil")
|
2023-12-15 11:13:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
expURL := "https://victoriametrics.com/path"
|
|
|
|
u, err := getExternalURL(expURL)
|
2020-06-21 12:32:46 +02:00
|
|
|
if err != nil {
|
2024-07-12 21:57:56 +02:00
|
|
|
t.Fatalf("unexpected error %s", err)
|
2020-06-21 12:32:46 +02:00
|
|
|
}
|
|
|
|
if u.String() != expURL {
|
2024-07-12 21:57:56 +02:00
|
|
|
t.Fatalf("unexpected url: want %q, got %s", expURL, u.String())
|
2020-06-21 12:32:46 +02:00
|
|
|
}
|
2023-12-15 11:13:56 +01:00
|
|
|
|
2020-06-21 12:32:46 +02:00
|
|
|
h, _ := os.Hostname()
|
2023-12-15 11:13:56 +01:00
|
|
|
expURL = fmt.Sprintf("http://%s:8880", h)
|
|
|
|
u, err = getExternalURL("")
|
2020-06-21 12:32:46 +02:00
|
|
|
if err != nil {
|
2024-07-12 21:57:56 +02:00
|
|
|
t.Fatalf("unexpected error %s", err)
|
2020-06-21 12:32:46 +02:00
|
|
|
}
|
|
|
|
if u.String() != expURL {
|
2024-07-12 21:57:56 +02:00
|
|
|
t.Fatalf("unexpected url: want %s, got %s", expURL, u.String())
|
2020-06-21 12:32:46 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestGetAlertURLGenerator(t *testing.T) {
|
2022-10-05 19:25:03 +02:00
|
|
|
testAlert := notifier.Alert{GroupID: 42, ID: 2, Value: 4, Labels: map[string]string{"tenant": "baz"}}
|
2020-06-21 12:32:46 +02:00
|
|
|
u, _ := url.Parse("https://victoriametrics.com/path")
|
|
|
|
fn, err := getAlertURLGenerator(u, "", false)
|
|
|
|
if err != nil {
|
2024-07-12 21:57:56 +02:00
|
|
|
t.Fatalf("unexpected error %s", err)
|
2020-06-21 12:32:46 +02:00
|
|
|
}
|
2022-08-17 14:46:28 +02:00
|
|
|
exp := fmt.Sprintf("https://victoriametrics.com/path/vmalert/alert?%s=42&%s=2", paramGroupID, paramAlertID)
|
2022-07-08 10:26:13 +02:00
|
|
|
if exp != fn(testAlert) {
|
2024-07-12 21:57:56 +02:00
|
|
|
t.Fatalf("unexpected url want %s, got %s", exp, fn(testAlert))
|
2020-06-21 12:32:46 +02:00
|
|
|
}
|
|
|
|
_, err = getAlertURLGenerator(nil, "foo?{{invalid}}", true)
|
|
|
|
if err == nil {
|
2024-07-12 21:57:56 +02:00
|
|
|
t.Fatalf("expected template validation error got nil")
|
2020-06-21 12:32:46 +02:00
|
|
|
}
|
2022-10-05 19:25:03 +02:00
|
|
|
fn, err = getAlertURLGenerator(u, "foo?query={{$value}}&ds={{ $labels.tenant }}", true)
|
2020-06-21 12:32:46 +02:00
|
|
|
if err != nil {
|
2024-07-12 21:57:56 +02:00
|
|
|
t.Fatalf("unexpected error %s", err)
|
2020-06-21 12:32:46 +02:00
|
|
|
}
|
2022-10-27 21:30:27 +02:00
|
|
|
if exp := "https://victoriametrics.com/path/foo?query=4&ds=baz"; exp != fn(testAlert) {
|
2024-07-12 21:57:56 +02:00
|
|
|
t.Fatalf("unexpected url want %s, got %s", exp, fn(testAlert))
|
2020-06-21 12:32:46 +02:00
|
|
|
}
|
|
|
|
}
|
2021-05-25 15:27:22 +02:00
|
|
|
|
|
|
|
func TestConfigReload(t *testing.T) {
|
|
|
|
originalRulePath := *rulePath
|
|
|
|
defer func() {
|
|
|
|
*rulePath = originalRulePath
|
|
|
|
}()
|
|
|
|
|
|
|
|
const (
|
|
|
|
rules1 = `
|
|
|
|
groups:
|
|
|
|
- name: group-1
|
|
|
|
rules:
|
|
|
|
- alert: ExampleAlertAlwaysFiring
|
|
|
|
expr: sum by(job) (up == 1)
|
|
|
|
- record: handler:requests:rate5m
|
|
|
|
expr: sum(rate(prometheus_http_requests_total[5m])) by (handler)
|
|
|
|
`
|
|
|
|
rules2 = `
|
|
|
|
groups:
|
|
|
|
- name: group-1
|
|
|
|
rules:
|
|
|
|
- alert: ExampleAlertAlwaysFiring
|
|
|
|
expr: sum by(job) (up == 1)
|
|
|
|
- name: group-2
|
|
|
|
rules:
|
|
|
|
- record: handler:requests:rate5m
|
|
|
|
expr: sum(rate(prometheus_http_requests_total[5m])) by (handler)
|
|
|
|
`
|
|
|
|
)
|
|
|
|
|
2022-08-21 23:20:55 +02:00
|
|
|
f, err := os.CreateTemp("", "")
|
2021-05-25 15:27:22 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2023-08-09 21:16:00 +02:00
|
|
|
defer func() { _ = os.Remove(f.Name()) }()
|
2021-05-25 15:27:22 +02:00
|
|
|
writeToFile(t, f.Name(), rules1)
|
|
|
|
|
2023-07-31 16:39:57 +02:00
|
|
|
*configCheckInterval = 200 * time.Millisecond
|
2021-05-25 15:27:22 +02:00
|
|
|
*rulePath = []string{f.Name()}
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
|
|
|
|
m := &manager{
|
2023-10-13 13:54:33 +02:00
|
|
|
querierBuilder: &datasource.FakeQuerier{},
|
|
|
|
groups: make(map[uint64]*rule.Group),
|
2021-05-25 15:27:22 +02:00
|
|
|
labels: map[string]string{},
|
2023-10-13 13:54:33 +02:00
|
|
|
notifiers: func() []notifier.Notifier { return []notifier.Notifier{¬ifier.FakeNotifier{}} },
|
2021-11-30 00:18:48 +01:00
|
|
|
rw: &remotewrite.Client{},
|
2021-05-25 15:27:22 +02:00
|
|
|
}
|
2021-09-13 14:48:18 +02:00
|
|
|
|
|
|
|
syncCh := make(chan struct{})
|
2021-10-19 15:35:27 +02:00
|
|
|
sighupCh := procutil.NewSighupChan()
|
2021-09-13 14:48:18 +02:00
|
|
|
go func() {
|
2021-10-19 15:35:27 +02:00
|
|
|
configReload(ctx, m, nil, sighupCh)
|
2021-09-13 14:48:18 +02:00
|
|
|
close(syncCh)
|
|
|
|
}()
|
2021-05-25 15:27:22 +02:00
|
|
|
|
|
|
|
lenLocked := func(m *manager) int {
|
|
|
|
m.groupsMu.RLock()
|
|
|
|
defer m.groupsMu.RUnlock()
|
|
|
|
return len(m.groups)
|
|
|
|
}
|
|
|
|
|
2023-08-07 21:58:40 +02:00
|
|
|
checkCfg := func(err error) {
|
|
|
|
cErr := getLastConfigError()
|
|
|
|
cfgSuc := configSuccess.Get()
|
|
|
|
if err != nil {
|
|
|
|
if cErr == nil {
|
|
|
|
t.Fatalf("expected to have config error %s; got nil instead", cErr)
|
|
|
|
}
|
|
|
|
if cfgSuc != 0 {
|
2023-12-20 13:23:38 +01:00
|
|
|
t.Fatalf("expected to have metric configSuccess to be set to 0; got %v instead", cfgSuc)
|
2023-08-07 21:58:40 +02:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if cErr != nil {
|
|
|
|
t.Fatalf("unexpected config error: %s", cErr)
|
|
|
|
}
|
|
|
|
if cfgSuc != 1 {
|
2023-12-20 13:23:38 +01:00
|
|
|
t.Fatalf("expected to have metric configSuccess to be set to 1; got %v instead", cfgSuc)
|
2023-08-07 21:58:40 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-31 16:39:57 +02:00
|
|
|
time.Sleep(*configCheckInterval * 2)
|
2023-08-07 21:58:40 +02:00
|
|
|
checkCfg(nil)
|
2021-05-25 15:27:22 +02:00
|
|
|
groupsLen := lenLocked(m)
|
|
|
|
if groupsLen != 1 {
|
|
|
|
t.Fatalf("expected to have exactly 1 group loaded; got %d", groupsLen)
|
|
|
|
}
|
|
|
|
|
|
|
|
writeToFile(t, f.Name(), rules2)
|
2023-07-31 16:39:57 +02:00
|
|
|
time.Sleep(*configCheckInterval * 2)
|
2023-08-07 21:58:40 +02:00
|
|
|
checkCfg(nil)
|
2021-05-25 15:27:22 +02:00
|
|
|
groupsLen = lenLocked(m)
|
|
|
|
if groupsLen != 2 {
|
|
|
|
fmt.Println(m.groups)
|
|
|
|
t.Fatalf("expected to have exactly 2 groups loaded; got %d", groupsLen)
|
|
|
|
}
|
|
|
|
|
|
|
|
writeToFile(t, f.Name(), rules1)
|
|
|
|
procutil.SelfSIGHUP()
|
2023-07-31 16:39:57 +02:00
|
|
|
time.Sleep(*configCheckInterval / 2)
|
2023-08-07 21:58:40 +02:00
|
|
|
checkCfg(nil)
|
2021-05-25 15:27:22 +02:00
|
|
|
groupsLen = lenLocked(m)
|
|
|
|
if groupsLen != 1 {
|
|
|
|
t.Fatalf("expected to have exactly 1 group loaded; got %d", groupsLen)
|
|
|
|
}
|
|
|
|
|
|
|
|
writeToFile(t, f.Name(), `corrupted`)
|
|
|
|
procutil.SelfSIGHUP()
|
2023-07-31 16:39:57 +02:00
|
|
|
time.Sleep(*configCheckInterval / 2)
|
2023-08-07 21:58:40 +02:00
|
|
|
checkCfg(fmt.Errorf("config error"))
|
2021-05-25 15:27:22 +02:00
|
|
|
groupsLen = lenLocked(m)
|
|
|
|
if groupsLen != 1 { // should remain unchanged
|
|
|
|
t.Fatalf("expected to have exactly 1 group loaded; got %d", groupsLen)
|
|
|
|
}
|
2021-09-13 14:48:18 +02:00
|
|
|
|
|
|
|
cancel()
|
|
|
|
<-syncCh
|
2021-05-25 15:27:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func writeToFile(t *testing.T, file, b string) {
|
|
|
|
t.Helper()
|
2022-08-21 22:51:13 +02:00
|
|
|
err := os.WriteFile(file, []byte(b), 0644)
|
2021-05-25 15:27:22 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|