mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
2a3a62dc41
vmalert: support configuration file for notifiers * vmalert notifiers now can be configured via file see https://docs.victoriametrics.com/vmalert.html#notifier-configuration-file * add support of Consul service discovery for notifiers config see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1947 * add UI section for currently loaded/discovered notifiers * deprecate `-rule.configCheckInterval` in favour of `-configCheckInterval` * add ability to suppress logs for duplicated targets for notifiers discovery * change behaviour of `vmalert_alerts_send_errors_total` - it now accounts for failed alerts, not HTTP calls.
160 lines
4 KiB
Go
160 lines
4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/url"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/notifier"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/remotewrite"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/procutil"
|
|
)
|
|
|
|
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}
|
|
u, _ := url.Parse("https://victoriametrics.com/path")
|
|
fn, err := getAlertURLGenerator(u, "", false)
|
|
if err != nil {
|
|
t.Errorf("unexpected error %s", err)
|
|
}
|
|
if exp := "https://victoriametrics.com/path/api/v1/42/2/status"; 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 tempalte validation error got nil")
|
|
}
|
|
fn, err = getAlertURLGenerator(u, "foo?query={{$value}}", true)
|
|
if err != nil {
|
|
t.Errorf("unexpected error %s", err)
|
|
}
|
|
if exp := "https://victoriametrics.com/path/foo?query=4"; 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 := ioutil.TempFile("", "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
writeToFile(t, f.Name(), rules1)
|
|
|
|
*rulesCheckInterval = 200 * time.Millisecond
|
|
*rulePath = []string{f.Name()}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
m := &manager{
|
|
querierBuilder: &fakeQuerier{},
|
|
groups: make(map[uint64]*Group),
|
|
labels: map[string]string{},
|
|
notifiers: func() []notifier.Notifier { return []notifier.Notifier{&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)
|
|
}
|
|
|
|
time.Sleep(*rulesCheckInterval * 2)
|
|
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(*rulesCheckInterval * 2)
|
|
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(*rulesCheckInterval / 2)
|
|
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(*rulesCheckInterval / 2)
|
|
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 := ioutil.WriteFile(file, []byte(b), 0644)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|