* TimeMuter returns the names of time intervals This commit updates the TimeMuter interface to also return the names of the time intervals that muted the alerts. Signed-off-by: George Robinson <george.robinson@grafana.com> --------- Signed-off-by: George Robinson <george.robinson@grafana.com>
This commit is contained in:
parent
a9b5cb4351
commit
dacbf0050b
|
@ -949,7 +949,7 @@ func (tms TimeMuteStage) Exec(ctx context.Context, l log.Logger, alerts ...*type
|
||||||
return ctx, alerts, nil
|
return ctx, alerts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
muted, err := tms.muter.Mutes(muteTimeIntervalNames, now)
|
muted, _, err := tms.muter.Mutes(muteTimeIntervalNames, now)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ctx, alerts, err
|
return ctx, alerts, err
|
||||||
}
|
}
|
||||||
|
@ -987,7 +987,7 @@ func (tas TimeActiveStage) Exec(ctx context.Context, l log.Logger, alerts ...*ty
|
||||||
return ctx, alerts, errors.New("missing now timestamp")
|
return ctx, alerts, errors.New("missing now timestamp")
|
||||||
}
|
}
|
||||||
|
|
||||||
muted, err := tas.muter.Mutes(activeTimeIntervalNames, now)
|
muted, _, err := tas.muter.Mutes(activeTimeIntervalNames, now)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ctx, alerts, err
|
return ctx, alerts, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,21 +33,23 @@ type Intervener struct {
|
||||||
intervals map[string][]TimeInterval
|
intervals map[string][]TimeInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Intervener) Mutes(names []string, now time.Time) (bool, error) {
|
// Mutes implements the TimeMuter interface.
|
||||||
|
func (i *Intervener) Mutes(names []string, now time.Time) (bool, []string, error) {
|
||||||
|
var in []string
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
interval, ok := i.intervals[name]
|
interval, ok := i.intervals[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, fmt.Errorf("time interval %s doesn't exist in config", name)
|
return false, nil, fmt.Errorf("time interval %s doesn't exist in config", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ti := range interval {
|
for _, ti := range interval {
|
||||||
if ti.ContainsTime(now.UTC()) {
|
if ti.ContainsTime(now.UTC()) {
|
||||||
return true, nil
|
in = append(in, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, nil
|
return len(in) > 0, in, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIntervener(ti map[string][]TimeInterval) *Intervener {
|
func NewIntervener(ti map[string][]TimeInterval) *Intervener {
|
||||||
|
|
|
@ -16,6 +16,7 @@ package timeinterval
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -662,95 +663,90 @@ func mustLoadLocation(name string) *time.Location {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntervener_Mutes(t *testing.T) {
|
func TestIntervener_Mutes(t *testing.T) {
|
||||||
// muteIn mutes alerts outside business hours in November, using the +1100 timezone.
|
sydney, err := time.LoadLocation("Australia/Sydney")
|
||||||
muteIn := `
|
if err != nil {
|
||||||
---
|
t.Fatalf("Failed to load location Australia/Sydney: %s", err)
|
||||||
- weekdays:
|
}
|
||||||
- monday:friday
|
eveningsAndWeekends := map[string][]TimeInterval{
|
||||||
location: Australia/Sydney
|
"evenings": {{
|
||||||
months:
|
Times: []TimeRange{{
|
||||||
- November
|
StartMinute: 0, // 00:00
|
||||||
times:
|
EndMinute: 540, // 09:00
|
||||||
- start_time: 00:00
|
}, {
|
||||||
end_time: 09:00
|
StartMinute: 1020, // 17:00
|
||||||
- start_time: 17:00
|
EndMinute: 1440, // 24:00
|
||||||
end_time: 24:00
|
}},
|
||||||
- weekdays:
|
Location: &Location{Location: sydney},
|
||||||
- saturday
|
}},
|
||||||
- sunday
|
"weekends": {{
|
||||||
months:
|
Weekdays: []WeekdayRange{{
|
||||||
- November
|
InclusiveRange: InclusiveRange{Begin: 6, End: 6}, // Saturday
|
||||||
location: 'Australia/Sydney'
|
}, {
|
||||||
`
|
InclusiveRange: InclusiveRange{Begin: 0, End: 0}, // Sunday
|
||||||
intervalName := "test"
|
}},
|
||||||
var intervals []TimeInterval
|
Location: &Location{Location: sydney},
|
||||||
err := yaml.Unmarshal([]byte(muteIn), &intervals)
|
}},
|
||||||
require.NoError(t, err)
|
|
||||||
m := map[string][]TimeInterval{intervalName: intervals}
|
|
||||||
|
|
||||||
tc := []struct {
|
|
||||||
name string
|
|
||||||
firedAt string
|
|
||||||
expected bool
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Should not mute on Friday during business hours",
|
|
||||||
firedAt: "19 Nov 21 13:00 +1100",
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Should not mute on a Tuesday before 5pm",
|
|
||||||
firedAt: "16 Nov 21 16:59 +1100",
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Should mute on a Saturday",
|
|
||||||
firedAt: "20 Nov 21 10:00 +1100",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Should mute before 9am on a Wednesday",
|
|
||||||
firedAt: "17 Nov 21 05:00 +1100",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Should mute even if we are in a different timezone (KST)",
|
|
||||||
firedAt: "14 Nov 21 20:00 +0900",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Should mute even if the timezone is UTC",
|
|
||||||
firedAt: "14 Nov 21 21:30 +0000",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Should not mute different timezone (KST)",
|
|
||||||
firedAt: "15 Nov 22 14:30 +0900",
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Should mute in a different timezone (PET)",
|
|
||||||
firedAt: "15 Nov 21 02:00 -0500",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tc {
|
tests := []struct {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
name string
|
||||||
now, err := time.Parse(time.RFC822Z, tt.firedAt)
|
intervals map[string][]TimeInterval
|
||||||
require.NoError(t, err)
|
now time.Time
|
||||||
|
mutedBy []string
|
||||||
|
}{{
|
||||||
|
name: "Should be muted outside working hours",
|
||||||
|
intervals: eveningsAndWeekends,
|
||||||
|
now: time.Date(2024, 1, 1, 0, 0, 0, 0, sydney),
|
||||||
|
mutedBy: []string{"evenings"},
|
||||||
|
}, {
|
||||||
|
name: "Should not be muted during working hours",
|
||||||
|
intervals: eveningsAndWeekends,
|
||||||
|
now: time.Date(2024, 1, 1, 9, 0, 0, 0, sydney),
|
||||||
|
mutedBy: nil,
|
||||||
|
}, {
|
||||||
|
name: "Should be muted during weekends",
|
||||||
|
intervals: eveningsAndWeekends,
|
||||||
|
now: time.Date(2024, 1, 6, 10, 0, 0, 0, sydney),
|
||||||
|
mutedBy: []string{"weekends"},
|
||||||
|
}, {
|
||||||
|
name: "Should be muted during weekend evenings",
|
||||||
|
intervals: eveningsAndWeekends,
|
||||||
|
now: time.Date(2024, 1, 6, 17, 0, 0, 0, sydney),
|
||||||
|
mutedBy: []string{"evenings", "weekends"},
|
||||||
|
}, {
|
||||||
|
name: "Should be muted at 12pm UTC on a weekday",
|
||||||
|
intervals: eveningsAndWeekends,
|
||||||
|
now: time.Date(2024, 1, 1, 10, 0, 0, 0, time.UTC),
|
||||||
|
mutedBy: []string{"evenings"},
|
||||||
|
}, {
|
||||||
|
name: "Should be muted at 12pm UTC on a weekend",
|
||||||
|
intervals: eveningsAndWeekends,
|
||||||
|
now: time.Date(2024, 1, 6, 10, 0, 0, 0, time.UTC),
|
||||||
|
mutedBy: []string{"evenings", "weekends"},
|
||||||
|
}}
|
||||||
|
|
||||||
intervener := NewIntervener(m)
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
intervener := NewIntervener(test.intervals)
|
||||||
|
|
||||||
expected, err := intervener.Mutes([]string{intervalName}, now)
|
// Get the names of all time intervals for the context.
|
||||||
if err != nil {
|
timeIntervalNames := make([]string, 0, len(test.intervals))
|
||||||
require.Error(t, tt.err)
|
for name := range test.intervals {
|
||||||
require.False(t, tt.expected)
|
timeIntervalNames = append(timeIntervalNames, name)
|
||||||
}
|
}
|
||||||
|
// Sort the names so we can compare mutedBy with test.mutedBy.
|
||||||
|
sort.Strings(timeIntervalNames)
|
||||||
|
|
||||||
|
isMuted, mutedBy, err := intervener.Mutes(timeIntervalNames, test.now)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, expected, tt.expected)
|
|
||||||
|
if len(test.mutedBy) == 0 {
|
||||||
|
require.False(t, isMuted)
|
||||||
|
require.Empty(t, mutedBy)
|
||||||
|
} else {
|
||||||
|
require.True(t, isMuted)
|
||||||
|
require.Equal(t, test.mutedBy, mutedBy)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -416,9 +416,11 @@ type Muter interface {
|
||||||
Mutes(model.LabelSet) bool
|
Mutes(model.LabelSet) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// TimeMuter determines if alerts should be muted based on the specified current time and active time interval on the route.
|
// A TimeMuter determines if the time is muted by one or more active or mute
|
||||||
|
// time intervals. If the time is muted, it returns true and the names of the
|
||||||
|
// time intervals that muted it. Otherwise, it returns false and a nil slice.
|
||||||
type TimeMuter interface {
|
type TimeMuter interface {
|
||||||
Mutes(timeIntervalName []string, now time.Time) (bool, error)
|
Mutes(timeIntervalNames []string, now time.Time) (bool, []string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A MuteFunc is a function that implements the Muter interface.
|
// A MuteFunc is a function that implements the Muter interface.
|
||||||
|
|
Loading…
Reference in New Issue