mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-11-05 00:50:21 +00:00
fix(alert): prevent persisted rule crash loops
This commit is contained in:
1 parent
8f1147c800
commit
19daf4ad3c
8 files changed
+479
-71
No files matched your search
@@ -0,0 +1,123 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestRuleSnapshotIgnoresEmptyMetricSamples(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ruleType string
|
||||
hostState *HostState
|
||||
}{
|
||||
{
|
||||
name: "GPU list is empty",
|
||||
ruleType: "gpu_max",
|
||||
hostState: &HostState{},
|
||||
},
|
||||
{
|
||||
name: "all temperature samples are filtered",
|
||||
ruleType: "temperature_max",
|
||||
hostState: &HostState{Temperatures: []SensorTemperature{
|
||||
{Name: "coretemp", Temperature: 0},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
server := &Server{Common: Common{ID: 1}, State: test.hostState, Host: &Host{}}
|
||||
rule := &Rule{Type: test.ruleType, Max: 1, Duration: 3, Cover: RuleCoverAll}
|
||||
|
||||
defer func() {
|
||||
if recovered := recover(); recovered != nil {
|
||||
t.Fatalf("Snapshot panicked for missing metric data: %v", recovered)
|
||||
}
|
||||
}()
|
||||
if passed := rule.Snapshot(nil, server, nil); !passed {
|
||||
t.Fatal("missing metric data must be ignored instead of treated as a failed threshold")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlertRuleCheckRejectsOverflowingPersistedDuration(t *testing.T) {
|
||||
rule := &AlertRule{Rules: []*Rule{{
|
||||
Type: "offline",
|
||||
Duration: math.MaxUint64,
|
||||
Cover: RuleCoverAll,
|
||||
}}}
|
||||
|
||||
defer func() {
|
||||
if recovered := recover(); recovered != nil {
|
||||
t.Fatalf("Check panicked after uint64-to-int overflow: %v", recovered)
|
||||
}
|
||||
}()
|
||||
duration, passed := rule.Check([][]bool{{false}})
|
||||
if duration != 0 || !passed {
|
||||
t.Fatalf("invalid persisted duration must be ignored safely, got duration=%d passed=%v", duration, passed)
|
||||
}
|
||||
if window := rule.RetentionWindow(); window != 0 {
|
||||
t.Fatalf("invalid persisted duration must not create a retention window, got %d", window)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlertRulePersistedDataSafety(t *testing.T) {
|
||||
cycleStart := time.Now().Add(-time.Hour)
|
||||
tests := []struct {
|
||||
name string
|
||||
rule *AlertRule
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "normal rule",
|
||||
rule: &AlertRule{Rules: []*Rule{{
|
||||
Type: "cpu", Duration: 3, Cover: RuleCoverAll,
|
||||
}}},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "normal cycle rule",
|
||||
rule: &AlertRule{Rules: []*Rule{{
|
||||
Type: "transfer_in_cycle", CycleStart: &cycleStart, CycleInterval: 1, Cover: RuleCoverAll,
|
||||
}}},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "unknown type",
|
||||
rule: &AlertRule{Rules: []*Rule{{
|
||||
Type: "attacker_controlled", Duration: 3, Cover: RuleCoverAll,
|
||||
}}},
|
||||
},
|
||||
{
|
||||
name: "overflowing duration",
|
||||
rule: &AlertRule{Rules: []*Rule{{
|
||||
Type: "offline", Duration: math.MaxUint64, Cover: RuleCoverAll,
|
||||
}}},
|
||||
},
|
||||
{
|
||||
name: "cycle without start",
|
||||
rule: &AlertRule{Rules: []*Rule{{
|
||||
Type: "transfer_in_cycle", CycleInterval: 1, Cover: RuleCoverAll,
|
||||
}}},
|
||||
},
|
||||
{
|
||||
name: "nil rule",
|
||||
rule: &AlertRule{Rules: []*Rule{nil}},
|
||||
},
|
||||
{
|
||||
name: "empty rule list",
|
||||
rule: &AlertRule{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if got := test.rule.IsSafeToEvaluate(); got != test.want {
|
||||
t.Fatalf("IsSafeToEvaluate()=%v, want %v", got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user