mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
130e0ea5f0
1. split package rule under /app/vmalert, expose needed objects 2. add vmalert-tool with unittest subcmd https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2945
194 lines
5.1 KiB
Go
194 lines
5.1 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/datasource"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/notifier"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/remotewrite"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/rule"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/procutil"
|
|
)
|
|
|
|
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
|
|
}
|
|
|
|
func TestGetExternalURL(t *testing.T) {
|
|
expURL := "https://vicotriametrics.com/path"
|
|
u, err := getExternalURL(expURL, "", false)
|
|
if err != nil {
|
|
t.Errorf("unexpected error %s", err)
|
|
}
|
|
if u.String() != expURL {
|
|
t.Errorf("unexpected url want %s, got %s", expURL, u.String())
|
|
}
|
|
h, _ := os.Hostname()
|
|
expURL = fmt.Sprintf("https://%s:4242", h)
|
|
u, err = getExternalURL("", "0.0.0.0:4242", true)
|
|
if err != nil {
|
|
t.Errorf("unexpected error %s", err)
|
|
}
|
|
if u.String() != expURL {
|
|
t.Errorf("unexpected url want %s, got %s", expURL, u.String())
|
|
}
|
|
}
|
|
|
|
func TestGetAlertURLGenerator(t *testing.T) {
|
|
testAlert := notifier.Alert{GroupID: 42, ID: 2, Value: 4, Labels: map[string]string{"tenant": "baz"}}
|
|
u, _ := url.Parse("https://victoriametrics.com/path")
|
|
fn, err := getAlertURLGenerator(u, "", false)
|
|
if err != nil {
|
|
t.Errorf("unexpected error %s", err)
|
|
}
|
|
exp := fmt.Sprintf("https://victoriametrics.com/path/vmalert/alert?%s=42&%s=2", paramGroupID, paramAlertID)
|
|
if exp != fn(testAlert) {
|
|
t.Errorf("unexpected url want %s, got %s", exp, fn(testAlert))
|
|
}
|
|
_, err = getAlertURLGenerator(nil, "foo?{{invalid}}", true)
|
|
if err == nil {
|
|
t.Errorf("expected template validation error got nil")
|
|
}
|
|
fn, err = getAlertURLGenerator(u, "foo?query={{$value}}&ds={{ $labels.tenant }}", true)
|
|
if err != nil {
|
|
t.Errorf("unexpected error %s", err)
|
|
}
|
|
if exp := "https://victoriametrics.com/path/foo?query=4&ds=baz"; exp != fn(testAlert) {
|
|
t.Errorf("unexpected url want %s, got %s", exp, fn(testAlert))
|
|
}
|
|
}
|
|
|
|
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)
|
|
`
|
|
)
|
|
|
|
f, err := os.CreateTemp("", "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() { _ = os.Remove(f.Name()) }()
|
|
writeToFile(t, f.Name(), rules1)
|
|
|
|
*configCheckInterval = 200 * time.Millisecond
|
|
*rulePath = []string{f.Name()}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
m := &manager{
|
|
querierBuilder: &datasource.FakeQuerier{},
|
|
groups: make(map[uint64]*rule.Group),
|
|
labels: map[string]string{},
|
|
notifiers: func() []notifier.Notifier { return []notifier.Notifier{¬ifier.FakeNotifier{}} },
|
|
rw: &remotewrite.Client{},
|
|
}
|
|
|
|
syncCh := make(chan struct{})
|
|
sighupCh := procutil.NewSighupChan()
|
|
go func() {
|
|
configReload(ctx, m, nil, sighupCh)
|
|
close(syncCh)
|
|
}()
|
|
|
|
lenLocked := func(m *manager) int {
|
|
m.groupsMu.RLock()
|
|
defer m.groupsMu.RUnlock()
|
|
return len(m.groups)
|
|
}
|
|
|
|
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 {
|
|
t.Fatalf("expected to have metric configSuccess to be set to 0; got %d instead", cfgSuc)
|
|
}
|
|
return
|
|
}
|
|
|
|
if cErr != nil {
|
|
t.Fatalf("unexpected config error: %s", cErr)
|
|
}
|
|
if cfgSuc != 1 {
|
|
t.Fatalf("expected to have metric configSuccess to be set to 1; got %d instead", cfgSuc)
|
|
}
|
|
}
|
|
|
|
time.Sleep(*configCheckInterval * 2)
|
|
checkCfg(nil)
|
|
groupsLen := lenLocked(m)
|
|
if groupsLen != 1 {
|
|
t.Fatalf("expected to have exactly 1 group loaded; got %d", groupsLen)
|
|
}
|
|
|
|
writeToFile(t, f.Name(), rules2)
|
|
time.Sleep(*configCheckInterval * 2)
|
|
checkCfg(nil)
|
|
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()
|
|
time.Sleep(*configCheckInterval / 2)
|
|
checkCfg(nil)
|
|
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()
|
|
time.Sleep(*configCheckInterval / 2)
|
|
checkCfg(fmt.Errorf("config error"))
|
|
groupsLen = lenLocked(m)
|
|
if groupsLen != 1 { // should remain unchanged
|
|
t.Fatalf("expected to have exactly 1 group loaded; got %d", groupsLen)
|
|
}
|
|
|
|
cancel()
|
|
<-syncCh
|
|
}
|
|
|
|
func writeToFile(t *testing.T, file, b string) {
|
|
t.Helper()
|
|
err := os.WriteFile(file, []byte(b), 0644)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|