From 18458c4f792225e242e0d4966eae89bfad78c07e Mon Sep 17 00:00:00 2001 From: Craig Peterson Date: Mon, 7 Dec 2015 11:01:35 -0700 Subject: [PATCH] moving silence to redis. --- cmd/bosun/database/database.go | 1 + cmd/bosun/database/silence_data.go | 141 ++++++++++++++++++++ cmd/bosun/database/test/database_test.go | 9 +- cmd/bosun/database/test/silence_test.go | 39 ++++++ cmd/bosun/sched/bolt.go | 31 ++++- cmd/bosun/sched/check.go | 2 +- cmd/bosun/sched/host.go | 2 +- cmd/bosun/sched/notify.go | 2 +- cmd/bosun/sched/sched.go | 12 +- cmd/bosun/sched/sched_test.go | 19 +++ cmd/bosun/sched/silence.go | 108 ++++------------ cmd/bosun/web/static.go | 142 ++++++++++----------- cmd/bosun/web/static/js/bosun.js | 2 + cmd/bosun/web/static/js/silence.ts | 2 + cmd/bosun/web/static/partials/silence.html | 4 +- cmd/bosun/web/web.go | 6 +- models/silence.go | 57 +++++++++ {cmd/bosun/sched => util}/match.go | 2 +- {cmd/bosun/sched => util}/match_test.go | 2 +- 19 files changed, 407 insertions(+), 176 deletions(-) create mode 100644 cmd/bosun/database/silence_data.go create mode 100644 cmd/bosun/database/test/silence_test.go create mode 100644 models/silence.go rename {cmd/bosun/sched => util}/match.go (99%) rename {cmd/bosun/sched => util}/match_test.go (99%) diff --git a/cmd/bosun/database/database.go b/cmd/bosun/database/database.go index 0a35e3355d..43702bb70b 100644 --- a/cmd/bosun/database/database.go +++ b/cmd/bosun/database/database.go @@ -20,6 +20,7 @@ type DataAccess interface { Metadata() MetadataDataAccess Search() SearchDataAccess Errors() ErrorDataAccess + Silence() SilenceDataAccess Incidents() IncidentDataAccess } diff --git a/cmd/bosun/database/silence_data.go b/cmd/bosun/database/silence_data.go new file mode 100644 index 0000000000..108a3c63dd --- /dev/null +++ b/cmd/bosun/database/silence_data.go @@ -0,0 +1,141 @@ +package database + +import ( + "encoding/json" + "log" + "time" + + "bosun.org/_third_party/github.com/garyburd/redigo/redis" + "bosun.org/collect" + "bosun.org/models" + "bosun.org/opentsdb" +) + +/* + +Silences : hash of Id - json of silence. Id is sha of fields + +SilencesByEnd : zlist of end-time to id. + +Easy to find active. Find all with end time in future, and filter to those with start time in the past. + +*/ + +const ( + silenceHash = "Silences" + silenceIdx = "SilencesByEnd" +) + +type SilenceDataAccess interface { + GetActiveSilences() ([]*models.Silence, error) + AddSilence(*models.Silence) error + DeleteSilence(id string) error + + ListSilences(endingAfter int64) (map[string]*models.Silence, error) +} + +func (d *dataAccess) Silence() SilenceDataAccess { + return d +} + +func (d *dataAccess) GetActiveSilences() ([]*models.Silence, error) { + defer collect.StartTimer("redis", opentsdb.TagSet{"op": "GetActiveSilences"})() + conn := d.GetConnection() + defer conn.Close() + + now := time.Now().UTC() + vals, err := redis.Strings(conn.Do("ZRANGEBYSCORE", silenceIdx, now.Unix(), "+inf")) + if err != nil { + return nil, err + } + if len(vals) == 0 { + return nil, nil + } + silences, err := getSilences(vals, conn) + if err != nil { + return nil, err + } + filtered := make([]*models.Silence, 0, len(silences)) + for _, s := range silences { + if s.Start.After(now) { + continue + } + filtered = append(filtered, s) + } + return filtered, nil +} + +func getSilences(ids []string, conn redis.Conn) ([]*models.Silence, error) { + args := make([]interface{}, len(ids)+1) + args[0] = silenceHash + for i := range ids { + args[i+1] = ids[i] + } + jsons, err := redis.Strings(conn.Do("HMGET", args...)) + if err != nil { + log.Fatal(err, args) + return nil, err + } + silences := make([]*models.Silence, 0, len(jsons)) + for _, j := range jsons { + s := &models.Silence{} + if err := json.Unmarshal([]byte(j), s); err != nil { + return nil, err + } + silences = append(silences, s) + } + return silences, nil +} + +func (d *dataAccess) AddSilence(s *models.Silence) error { + defer collect.StartTimer("redis", opentsdb.TagSet{"op": "AddSilence"})() + conn := d.GetConnection() + defer conn.Close() + + if _, err := conn.Do("ZADD", silenceIdx, s.End.UTC().Unix(), s.ID()); err != nil { + return err + } + dat, err := json.Marshal(s) + if err != nil { + return err + } + _, err = conn.Do("HSET", silenceHash, s.ID(), dat) + return err +} + +func (d *dataAccess) DeleteSilence(id string) error { + defer collect.StartTimer("redis", opentsdb.TagSet{"op": "DeleteSilence"})() + conn := d.GetConnection() + defer conn.Close() + + if _, err := conn.Do("ZREM", silenceIdx, id); err != nil { + return err + } + if _, err := conn.Do("HDEL", silenceHash, id); err != nil { + return err + } + return nil +} + +func (d *dataAccess) ListSilences(endingAfter int64) (map[string]*models.Silence, error) { + defer collect.StartTimer("redis", opentsdb.TagSet{"op": "ListSilences"})() + conn := d.GetConnection() + defer conn.Close() + + ids, err := redis.Strings(conn.Do("ZRANGEBYSCORE", silenceIdx, endingAfter, "+inf")) + if err != nil { + return nil, err + } + if len(ids) == 0 { + return map[string]*models.Silence{}, nil + } + silences, err := getSilences(ids, conn) + if err != nil { + return nil, err + } + m := make(map[string]*models.Silence, len(silences)) + for _, s := range silences { + m[s.ID()] = s + } + return m, nil +} diff --git a/cmd/bosun/database/test/database_test.go b/cmd/bosun/database/test/database_test.go index e73c360c63..bf0f6764dc 100644 --- a/cmd/bosun/database/test/database_test.go +++ b/cmd/bosun/database/test/database_test.go @@ -1,8 +1,11 @@ package dbtest import ( + "fmt" "math/rand" "os" + "path/filepath" + "runtime" "testing" "time" @@ -34,6 +37,10 @@ func randString(l int) string { func check(t *testing.T, err error) { if err != nil { - t.Fatal(err) + s := err.Error() + if _, filename, line, ok := runtime.Caller(1); ok { + s = fmt.Sprintf("%s:%d: %v", filepath.Base(filename), line, s) + } + t.Fatal(s) } } diff --git a/cmd/bosun/database/test/silence_test.go b/cmd/bosun/database/test/silence_test.go new file mode 100644 index 0000000000..34f67f7da1 --- /dev/null +++ b/cmd/bosun/database/test/silence_test.go @@ -0,0 +1,39 @@ +package dbtest + +import ( + "testing" + "time" + + "bosun.org/models" +) + +func TestSilence(t *testing.T) { + sd := testData.Silence() + + silence := &models.Silence{ + Start: time.Now().Add(-48 * time.Hour), + End: time.Now().Add(5 * time.Hour), + Alert: "Foo", + } + future := &models.Silence{ + Start: time.Now().Add(1 * time.Hour), + End: time.Now().Add(2 * time.Hour), + Alert: "Foo", + } + past := &models.Silence{ + Start: time.Now().Add(-48 * time.Hour), + End: time.Now().Add(-5 * time.Hour), + Alert: "Foo", + } + + check(t, sd.AddSilence(silence)) + check(t, sd.AddSilence(past)) + check(t, sd.AddSilence(future)) + + active, err := sd.GetActiveSilences() + check(t, err) + if len(active) != 1 { + t.Fatalf("Expected only one active silence. Got %d.", len(active)) + } + +} diff --git a/cmd/bosun/sched/bolt.go b/cmd/bosun/sched/bolt.go index 5c1edc6783..860e7e516c 100644 --- a/cmd/bosun/sched/bolt.go +++ b/cmd/bosun/sched/bolt.go @@ -44,7 +44,6 @@ const ( dbBucket = "bindata" dbConfigTextBucket = "configText" dbNotifications = "notifications" - dbSilence = "silence" dbStatus = "status" ) @@ -55,7 +54,6 @@ func (s *Schedule) save() { s.Lock("Save") store := map[string]interface{}{ dbNotifications: s.Notifications, - dbSilence: s.Silence, dbStatus: s.status, } tostore := make(map[string][]byte) @@ -142,9 +140,6 @@ func (s *Schedule) RestoreState() error { if err := decode(db, dbNotifications, ¬ifications); err != nil { slog.Errorln(dbNotifications, err) } - if err := decode(db, dbSilence, &s.Silence); err != nil { - slog.Errorln(dbSilence, err) - } status := make(States) if err := decode(db, dbStatus, &status); err != nil { @@ -280,6 +275,9 @@ func migrateOldDataToRedis(db *bolt.DB, data database.DataAccess) error { if err := migrateIncidents(db, data); err != nil { return err } + if err := migrateSilence(db, data); err != nil { + return err + } return nil } @@ -441,6 +439,29 @@ func migrateIncidents(db *bolt.DB, data database.DataAccess) error { return nil } +func migrateSilence(db *bolt.DB, data database.DataAccess) error { + migrated, err := isMigrated(db, "silence") + if err != nil { + return err + } + if migrated { + return nil + } + slog.Info("migrating silence") + silence := map[string]*models.Silence{} + if err := decode(db, "silence", &silence); err != nil { + return err + } + for _, v := range silence { + v.TagString = v.Tags.Tags() + data.Silence().AddSilence(v) + } + if err = setMigrated(db, "silence"); err != nil { + return err + } + return nil +} + func isMigrated(db *bolt.DB, name string) (bool, error) { found := false err := db.View(func(tx *bolt.Tx) error { diff --git a/cmd/bosun/sched/check.go b/cmd/bosun/sched/check.go index 51a8246144..949ec23507 100644 --- a/cmd/bosun/sched/check.go +++ b/cmd/bosun/sched/check.go @@ -123,7 +123,7 @@ func (s *Schedule) RunHistory(r *RunHistory) { } // RunHistory for a single alert key. Returns true if notifications were altered. -func (s *Schedule) runHistory(r *RunHistory, ak models.AlertKey, event *Event, silenced map[models.AlertKey]Silence) bool { +func (s *Schedule) runHistory(r *RunHistory, ak models.AlertKey, event *Event, silenced map[models.AlertKey]models.Silence) bool { checkNotify := false // get existing state object for alert key. add to schedule status if doesn't already exist state := s.GetStatus(ak) diff --git a/cmd/bosun/sched/host.go b/cmd/bosun/sched/host.go index c7264418b8..ae39ed9d4c 100644 --- a/cmd/bosun/sched/host.go +++ b/cmd/bosun/sched/host.go @@ -610,7 +610,7 @@ func (s *Schedule) Host(filter string) (map[string]*HostData, error) { return hosts, nil } -func processHostIncidents(host *HostData, states States, silences map[models.AlertKey]Silence) { +func processHostIncidents(host *HostData, states States, silences map[models.AlertKey]models.Silence) { for ak, state := range states { if stateHost, ok := state.Group["host"]; !ok { continue diff --git a/cmd/bosun/sched/notify.go b/cmd/bosun/sched/notify.go index 1e026fc3b9..9a0b330fb7 100644 --- a/cmd/bosun/sched/notify.go +++ b/cmd/bosun/sched/notify.go @@ -91,7 +91,7 @@ func (s *Schedule) CheckNotifications() time.Duration { return timeout } -func (s *Schedule) sendNotifications(silenced map[models.AlertKey]Silence) { +func (s *Schedule) sendNotifications(silenced map[models.AlertKey]models.Silence) { if s.Conf.Quiet { slog.Infoln("quiet mode prevented", len(s.pendingNotifications), "notifications") return diff --git a/cmd/bosun/sched/sched.go b/cmd/bosun/sched/sched.go index 762ff50e01..c8d5252761 100644 --- a/cmd/bosun/sched/sched.go +++ b/cmd/bosun/sched/sched.go @@ -36,10 +36,9 @@ type Schedule struct { mutexAquired time.Time mutexWaitTime int64 - Conf *conf.Conf - status States - Silence map[string]*Silence - Group map[time.Time]models.AlertKeys + Conf *conf.Conf + status States + Group map[time.Time]models.AlertKeys Search *search.Search @@ -68,7 +67,6 @@ func (s *Schedule) Init(c *conf.Conf) error { //be avoided. var err error s.Conf = c - s.Silence = make(map[string]*Silence) s.Group = make(map[time.Time]models.AlertKeys) s.pendingUnknowns = make(map[*conf.Notification][]*State) s.status = make(States) @@ -225,7 +223,7 @@ type StateTuple struct { } // GroupStates groups by NeedAck, Active, Status, and Silenced. -func (states States) GroupStates(silenced map[models.AlertKey]Silence) map[StateTuple]States { +func (states States) GroupStates(silenced map[models.AlertKey]models.Silence) map[StateTuple]States { r := make(map[StateTuple]States) for ak, st := range states { _, sil := silenced[ak] @@ -362,7 +360,7 @@ type StateGroups struct { } func (s *Schedule) MarshalGroups(T miniprofiler.Timer, filter string) (*StateGroups, error) { - var silenced map[models.AlertKey]Silence + var silenced map[models.AlertKey]models.Silence T.Step("Silenced", func(miniprofiler.Timer) { silenced = s.Silenced() }) diff --git a/cmd/bosun/sched/sched_test.go b/cmd/bosun/sched/sched_test.go index 96ded5f034..00f25a92ed 100644 --- a/cmd/bosun/sched/sched_test.go +++ b/cmd/bosun/sched/sched_test.go @@ -58,15 +58,18 @@ type nopDataAccess struct { database.SearchDataAccess database.ErrorDataAccess database.IncidentDataAccess + database.SilenceDataAccess failingAlerts map[string]bool idCounter uint64 incidents map[uint64]*models.Incident + silences map[string]*models.Silence } func (n *nopDataAccess) Search() database.SearchDataAccess { return n } func (n *nopDataAccess) Metadata() database.MetadataDataAccess { return n } func (n *nopDataAccess) Errors() database.ErrorDataAccess { return n } func (n *nopDataAccess) Incidents() database.IncidentDataAccess { return n } +func (n *nopDataAccess) Silence() database.SilenceDataAccess { return n } func (n *nopDataAccess) BackupLastInfos(map[string]map[string]*database.LastInfo) error { return nil } func (n *nopDataAccess) LoadLastInfos() (map[string]map[string]*database.LastInfo, error) { @@ -95,6 +98,21 @@ func (n *nopDataAccess) UpdateIncident(id uint64, i *models.Incident) error { n.incidents[id] = i return nil } +func (n *nopDataAccess) GetActiveSilences() ([]*models.Silence, error) { + r := make([]*models.Silence, 0, len(n.silences)) + for _, s := range n.silences { + r = append(r, s) + } + return r, nil +} +func (n *nopDataAccess) DeleteSilence(id string) error { + delete(n.silences, id) + return nil +} +func (n *nopDataAccess) AddSilence(s *models.Silence) error { + n.silences[s.ID()] = s + return nil +} func initSched(c *conf.Conf) (*Schedule, error) { c.StateFile = "" @@ -102,6 +120,7 @@ func initSched(c *conf.Conf) (*Schedule, error) { s.DataAccess = &nopDataAccess{ failingAlerts: map[string]bool{}, incidents: map[uint64]*models.Incident{}, + silences: map[string]*models.Silence{}, } err := s.Init(c) return s, err diff --git a/cmd/bosun/sched/silence.go b/cmd/bosun/sched/silence.go index 157303c8cb..7639bb2edc 100644 --- a/cmd/bosun/sched/silence.go +++ b/cmd/bosun/sched/silence.go @@ -1,89 +1,26 @@ package sched import ( - "crypto/sha1" - "encoding/json" "fmt" - "sync" "time" "bosun.org/models" "bosun.org/opentsdb" + "bosun.org/slog" ) -type Silence struct { - Start, End time.Time - Alert string - Tags opentsdb.TagSet - Forget bool - User string - Message string -} - -func (s *Silence) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - Start, End time.Time - Alert string - Tags string - Forget bool - User string - Message string - }{ - Start: s.Start, - End: s.End, - Alert: s.Alert, - Tags: s.Tags.Tags(), - Forget: s.Forget, - User: s.User, - Message: s.Message, - }) -} - -func (s *Silence) Silenced(now time.Time, alert string, tags opentsdb.TagSet) bool { - if !s.ActiveAt(now) { - return false - } - return s.Matches(alert, tags) -} - -func (s *Silence) ActiveAt(now time.Time) bool { - if now.Before(s.Start) || now.After(s.End) { - return false - } - return true -} - -func (s *Silence) Matches(alert string, tags opentsdb.TagSet) bool { - if s.Alert != "" && s.Alert != alert { - return false - } - for k, pattern := range s.Tags { - tagv, ok := tags[k] - if !ok { - return false - } - matched, _ := Match(pattern, tagv) - if !matched { - return false - } - } - return true -} - -func (s Silence) ID() string { - h := sha1.New() - fmt.Fprintf(h, "%s|%s|%s%s", s.Start, s.End, s.Alert, s.Tags) - return fmt.Sprintf("%x", h.Sum(nil)) -} - // Silenced returns all currently silenced AlertKeys and the time they will be // unsilenced. -func (s *Schedule) Silenced() map[models.AlertKey]Silence { - aks := make(map[models.AlertKey]Silence) +func (s *Schedule) Silenced() map[models.AlertKey]models.Silence { + aks := make(map[models.AlertKey]models.Silence) + now := time.Now() - silenceLock.RLock() - defer silenceLock.RUnlock() - for _, si := range s.Silence { + silences, err := s.DataAccess.Silence().GetActiveSilences() + if err != nil { + slog.Error("Error fetching silences.", err) + return nil + } + for _, si := range silences { if !si.ActiveAt(now) { continue } @@ -100,8 +37,6 @@ func (s *Schedule) Silenced() map[models.AlertKey]Silence { return aks } -var silenceLock = sync.RWMutex{} - func (s *Schedule) AddSilence(start, end time.Time, alert, tagList string, forget, confirm bool, edit, user, message string) (map[models.AlertKey]bool, error) { if start.IsZero() || end.IsZero() { return nil, fmt.Errorf("both start and end must be specified") @@ -115,7 +50,7 @@ func (s *Schedule) AddSilence(start, end time.Time, alert, tagList string, forge if alert == "" && tagList == "" { return nil, fmt.Errorf("must specify either alert or tags") } - si := &Silence{ + si := &models.Silence{ Start: start, End: end, Alert: alert, @@ -130,12 +65,20 @@ func (s *Schedule) AddSilence(start, end time.Time, alert, tagList string, forge return nil, err } si.Tags = tags + si.TagString = tags.Tags() } - silenceLock.Lock() - defer silenceLock.Unlock() if confirm { - delete(s.Silence, edit) - s.Silence[si.ID()] = si + if edit != "" { + if err := s.DataAccess.Silence().DeleteSilence(edit); err != nil { + return nil, err + } + } + if err := s.DataAccess.Silence().DeleteSilence(si.ID()); err != nil { + return nil, err + } + if err := s.DataAccess.Silence().AddSilence(si); err != nil { + return nil, err + } return nil, nil } aks := make(map[models.AlertKey]bool) @@ -148,8 +91,5 @@ func (s *Schedule) AddSilence(start, end time.Time, alert, tagList string, forge } func (s *Schedule) ClearSilence(id string) error { - silenceLock.Lock() - defer silenceLock.Unlock() - delete(s.Silence, id) - return nil + return s.DataAccess.Silence().DeleteSilence(id) } diff --git a/cmd/bosun/web/static.go b/cmd/bosun/web/static.go index 8dac4aa62d..b5879e0cc7 100644 --- a/cmd/bosun/web/static.go +++ b/cmd/bosun/web/static.go @@ -9099,7 +9099,7 @@ xitlUKAxazlo8/8JAAD//4NB/DtLfAAA "/js/bosun.js": { local: "web/static/js/bosun.js", - size: 103314, + size: 103390, modtime: 0, compressed: ` H4sIAAAJbogA/+y9e3/bRpIo+vfmU8AcjwlFFCk5cSYjW/Z17Lx24iQbOdmdQ2u1IAGSsEiABkA9xtZ3 @@ -9416,31 +9416,31 @@ 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 -syH/bwAAAP//heYmOpKTAQA= +dynKFnpTmLPBrsU5a2vKC93SgaBDXjjBS2rW0MpMPEzAuoiccFwH75IjD0H7A1sfibNIzdcvqCJ8N2NO +d7quTfQiHwYQgH9qljAIysSY4RjEHS/SLM4vxFiE/RdUkG475R2m5CRm+zvcvnJj/k+tb0I1BsH7j85C +bz7U/Iij5+i7OLslLABW7V+cUrLdnKVbB820B9Oz7/FSEGYTVAyAzU2WkglQsvnx7ztKkhAtRwI+1Fk9 +RQg+0fSM0oSYQhbdU4IG0CO5haYQO10k8WaZOLBgA6Msjlm2ESkdCY2JdmbElcxhekaNYYkc1DLbZxep +h4Ny5k/Pjvn5ZJT9eCTPGSXz+zlJ4jIACFDDlkk8p4QnFh8VK7WOsmT5ArOo1Iju440HoJw3n1yFMcuU +UQxfugqwIFWjCHvtKlQu8ovMYryTRiRfLqN1qa4I5/l8CSu7IPGe+sZu6ScDlloSjVj0htEc5jJ8sUiX +MfRJuizAm27PnuzHCd40//6kgK1yCtp62MuzX9YJXoOaOtzg+Fyj0cD+2XK83znSmBNGGeb7FGrmjLWV +ivJJChHSPcYMHwxyOqv+kVw1gyxWLGgTyfTsx/gSldcmxa5n1MjKi4Wk1cKnzIH6ZMjh3eMpH+Blx55A +6YSuDVzN9cwNWWvJQSEOUN0Uk+ITY54Q9IsdIXqvQ0yeghTJ1jOf8oN+ojRruxDKn/NRX0TNXLXOwjap +G11kRH21V8HVk3VsyWlIFTl5FnFnZaHwvjgjpS3eTHlgung1m/q3HkzfkN2g/6yIV0hXC8Cm9mKZl4m0 +rbmvi6qLfEdayRZlouxKgia12w5+V9NBN5LrXMe1x+BDAvJ82EIt4mlffu6lZxsR/91u2Lb5kNnakTHP +0QuFMZx4te8mQ4kj6xsfSPT1XtTUtMZCBp5J8vePtdbSws5NMcjrRm3ZYntdbZYgO/Lzd3JqDE+OYCnB +ls3ngo+RPUsiRllEpzdtuYjvMaG2I4He/GIEmj6qipLltNzVgA+OAjOna+VuUr0+ZgylkO8GCmOHd93b +RuCd2uYS1xzvVZm6TBB92GPUtIf19+guPNdhlsaWsQYhRBTstUJzy0cPaRRqIFLdYqdoMi61bxWU+ZQy +ifZHvJwteyk+/z8hecqaiJlBYGJdxwA/vcXQpdUfbZlwQwO0oZVKjYg8jzUE95lEhEe5T6rYzqlYTR/e +FJHMlnhndgWyFV0xWZiAxr2q/L5Y9yz7Cd8cWXarY3GEQy7FznW4y2YspYRwDcnT7BSnuYtJLkMD7ipw +sz0U7XQmJTat9lNUf8RqfUZpVCnRN+ao35Xo/MzWRQtrpChY0O+T+Js8vnILHtwYmM/ny22EbbRgqHaP +LjYPivZqWtWuhDAq5T3oYck0mw+HQ0+ifqXTfmHUkfmSJ9G0UZdpb5WfLVx88kNZf1l7KVLUVvEJDYJf +RbYMGbKLIQxCWT0vf4C1HmI9Xk7o72DHGzNbWsVvc+HTWfMpGoRDusjFuGNTbeStjVD38yzsM6OXwl6T +1qScR0eMz7XTLltTt0yISZj+dVH6LfVyE+85v+IjGM1WFzGLWI86+dooHA/eX4c7JzujOW5TB282D/f3 +J7aLDNz2DaKI1/kGrSmNl8Hy0Rl1aJfQWFlLxlXflYfnao7XkP121uyeK1Y7P39gadDY8s59IgEfiYVS +IX7AVjJryq9bUpXqDf0JGvpc7HQWdOP9TilM5JK/bZYocpCITf65OeaZsLaEObZd8pvSHATccaF5MCvy +1Utxx0YLKjqKgbMrXFa95iqO3o6/jtfiAo6t66B7Onq2U2Zk3yGtwqF/+8icnFNdiZzbnUiHxBtye0fI +a/VsjDTDjhhfxXLVlcYEQbArTDizPyVUXoVDCsPqlN6ny/6nw7QdoPE7L291eYHH6edI3G40gQxdH6UR +U8TctRnfiUiPu28HCxoxG/L/BgAA///rX+gs3pMBAA== `, }, @@ -12375,31 +12375,31 @@ HloWq6Fue4GY2Etp8CZenfK/OObdO4Av9Cqe/ggAAP//hZ9khuQIAAA= "/js/silence.ts": { local: "web/static/js/silence.ts", - size: 4084, + size: 4140, modtime: 0, compressed: ` -H4sIAAAJbogA/7RXX2/bNhB/Tj4FUxSljBpKN2AvdtOgywYswB6GBX0KgoGRaJuDRBki5axI9N13fyiR -Ulr3z7AXm3fH4x3vfrw7Get1u1GFFtc3ptK20DdFs9dC/+O1LZ2w2/yaOY+nJ453uJVQ9uP69ES3bdOu -hPOtsVugnVetT2g4IaHKrlXeNDZhqUpPFHaN8y6hvdqmpC5Nuttr529mLiFvJbKFuHgnDo0pgVU0dmPa -es6ttALfMzO6OJHulN3qmUppnLqv9NVw3n3TwCEWrZoaNx/IC1IZndw07VanXndOpzGrtXMKTQ2c/vT0 -vnGdBTO+bSqIkMuLcZ3JcOMr31ZyKW7lS4fpgaV8ufN+T4uqKSjURLRN51G+6WyBzIw1VtOMLwWpryjj -v8HqRrcHUyB/OI1lvwcqyskACWmVX/+Jf0G+QOAcVCscxLvYiYt4Xs6sbAFhYJ9yQhDsYQmTUQp4ijIg -omTAVhQPnLiHwBY3EBmlBL0oJTJKEYhRiFTiFaAycQuoKOPsRynTUY5gACnwPsAqjUTARVQNDNhhNiI7 -S0Ly6pU4m8WBov6J4MgfdhJO6AGXAQ1iYyooAVmpvCL43t4tBcX9Zw3eauIFzvuNR+wSAwzPyMn+ytT4 -Vm1X3+uWvUEQtBSMx34d6KLpLHLeIOOvXCuAA3qSoPWwtKpmGJ3Q1elovDQrv7tgYwvacAIWutbisscf -Qh4YqJtaW58d8hu8yCLvfEHR5h063fGrLVM5mkzigYadeJeGKPgWTK9H26MmBYoU3yaBPKY2RBeVtHg7 -RvsLOtFFDS6OjM9pAX2Lwb2D6x+Q3dOVeRvmagYVgGkWkIW1Ikdanqu9OQ+d4Rw4coFH564roCy7jNJJ -JZF9GB56qNtg+faOPKJE2OZhTEWSg7lWvu/cLuMDT/AGKyHfg48HqHPMjK0qwfcSz4efrqomyzfkcrj9 -Vxn7sC+aGsr1l8zNLX2vvT+U819nK/1Fuz/+NDHWc3aoc2cZ/T1PDrEhD/Qf1BgKhID1FBK/gPksPvFQ -KrO0cj49CSkXudtXBhCzlOQLYjYtvgGlqMAhkMi+kOK1SLeFU85vxfLp7vX5Iv+7MTaTT3JBpyKwgwt0 -UohRduBrigDugzi7EC9erAXHBR0fKyBWKPQkjDNpa6IU0FgT6y/x4nAzK7okDXNO2oiWw11X7CffAkLD -JmjSSRoMcYdZYtpcLoX0baelWHHecScPGUmTIe44a0x7DMr65OFjIDjdGBYOSNqQMZ2xCSfU2Ggii5tu -pLnNRnqAx7w/Ut32ymvujoyx2B15PkOYjegd4j4Z0hAFEBu8TWicNKAl+wesY+jWY2Xbg5uz0uagtC3Z -p+MVjtoz88IjRgJBJTPbWL2Q8HJV5XTPz79Pn14604JKSMV/frfJ6ZOguTB4JEih9/BsRpOUeQjAZFCD -xGHcPqMC4IgKASlHtg/oiTopno4oEsai1gi5IyoEw6gyovKICiI1agy4PaLAWI4q4bkeVwpPMmoNc+BE -jefsVleNKulZ9MnTGJH/7G08w/onMTdICerJcVxlvu2NfDteSW1jrKqqjxnEa347/GzDu82+2x5DQzl7 -MLZsHga3M3lFCn5nnAheXkKnYD/iPNR/R0EgVy5NSR3KlEsYa8X/dGv8vsRLJx+YQ7utk9E1GVtDKa8R -dbXyA0j6O1j8GwAA//+4yx/k9A8AAA== +H4sIAAAJbogA/7RXX2/cNgx/Tj6FUhSVDzWcbsBe7poWXTdgAfYwLOhTEAyKrctpsOWDJV9WJP7u4x/Z +kp32+mfYy51IiiJF/kTSxnrdbVWpxeWVqbUt9VXZ7rXQ/3htKyfsXXHJnIfTE8c73Foo+3FzeqK7ru3W +wvnO2DugnVedT2g4IaGqvlPetDZhqVrPFHat8y6hvbpLSV2ZdLfXzl8tXELeWmQrcfFGHFpTAats7dZ0 +zZJbawW+Z2ZycSbdKXunFyqVceq21u/H827bFg6xaNU0uPlAXpDK5OS27e506nXvdBqzRjun0NTIGU5P +b1vXWzDju7aGCLminNaZDDd+77ta5uJaPneYHljK5zvv97So25JCTUTX9h7l296WyMxYYz3PeC5IfU0Z +/w1WV7o7mBL542ks+z1QUU4GSEir4vJP/AvyFQLnoDrhIN7lTlzE8wpmZSsIA/tUEIJgD0uYjFLAU5QB +ESUjtqJ45MQ9BLa4gcgoJehFKZFRikCMQqQSrwCViVtARRlnP0qZjnIEA0iB9wFWaSQCLqJqYMAOsxXZ +WRKSFy/E2SIOFPVPBEf+sJNwwgC4DGgQW1NDCcgq5RXB9/omFxT3nzV4q4kXOO+2HrFLDDC8IGf7a9Pg +W7V9c6s79gZB0FEwHoZNoMu2t8h5hYy/Cq0ADuhJgtZDblXDMDqhq9PReGlWfnPBxla04QQs9J3F5YA/ +hDww0LSNtj47FFd4kVXR+5KizTt0uuNXW6VyNJnEAw078SYNUfAtmN5MtidNChQpvk4CeUxtjC4qafF6 +ivYXdKKLGlycGJ/TAvoag3sD1z8ge6Ar8zbM1QIqANMsIAtrRYG0PFd7cx46wzlw5AqPLlxfQll2GaWT +SiL7MD70ULfB8vUNeUSJsO39lIokB0utYt+7XcYHnuAN1kK+Ax8PUOeYGVtVgu8cz4efvq5ny1fkcrj9 +Vxn7sC/bBsr1l8wtLX2vvT+U819nK/1Fuz/+NDM2cHaoc2cZ/T1NDrEhD/Qf1BgKhIDNHBK/gPksPvFQ +KrO0cj4+CilXhdvXBhCTS/IFMZsW34BSVOAQSGRfSPFSpNvCKefXIn+8eXm+Kv5ujc3ko1zRqQjs4AKd +FGKUHfiaIoD7IM4uxLNnG8FxQcenCogVCj0J40zamigFNNbE+ku8ONwsii5Jw5yTNqJ8vOua/eRbQGjY +BE06SYMh7jhLzJvLWyF912sp1px33MlDRtJkiDvNGvMeg7IhefgYCE43hoUDkjZkTGdswgk1NZrI4qYb +aW6zkR7hseyPVLe98pq7I2MsdkeezxBmE3rHuM+GNEQBxAZvExonDWjJ/hHrGLrNVNn24OaitDkobTn7 +dLzCUXtmXnjESCCoZGZbq1cSXq6qnR74+Q/p00tnWlAJqfjP7zY5fRY0FwaPBCn0Hp7MaJIyDwGYDWqQ +OIzbZ1QAHFEhIOXI9hE9USfF0xFFwljUmiB3RIVgGFUmVB5RQaRGjRG3RxQYy1ElPNfjSuFJRq1xDpyp +8Zzd6bpVFT2LIXkaE/KfvI0nWP8k5hbSMN5O3Kdphg0yj97RE0nc4Or0bW/r23FOaltjVV1/zCDOy6jg +5x7GZPG99xAa0dm9sVV7P7qdyfek4HfGieDlW+gw7Eeco4bvKCTkyltTUWczVQ7jsPifbo3fpXjp5MN0 +bNNNMvIm425oAQ2itVF+BNdwA4t/AwAA///HUuwLLBAAAA== `, }, @@ -13331,30 +13331,30 @@ woLNftwrBMhfehixJlnPEedx+La6+9qYv55sHCeafqeeb+2/AAAA//8CAgz6mQQAAA== "/partials/silence.html": { local: "web/static/partials/silence.html", - size: 4417, + size: 4427, modtime: 0, compressed: ` -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 `, }, diff --git a/cmd/bosun/web/static/js/bosun.js b/cmd/bosun/web/static/js/bosun.js index ed84ac956b..b314db0d97 100644 --- a/cmd/bosun/web/static/js/bosun.js +++ b/cmd/bosun/web/static/js/bosun.js @@ -2530,6 +2530,8 @@ bosunControllers.controller('SilenceCtrl', ['$scope', '$http', '$location', '$ro $scope.confirm = function () { $scope.error = null; $scope.testSilences = null; + $scope.edit = null; + $location.search('edit', null); state.confirm = 'true'; $http.post('/api/silence/set', state) .error(function (error) { diff --git a/cmd/bosun/web/static/js/silence.ts b/cmd/bosun/web/static/js/silence.ts index 14db373925..dbead77d8c 100644 --- a/cmd/bosun/web/static/js/silence.ts +++ b/cmd/bosun/web/static/js/silence.ts @@ -135,6 +135,8 @@ bosunControllers.controller('SilenceCtrl', ['$scope', '$http', '$location', '$ro $scope.confirm = () => { $scope.error = null; $scope.testSilences = null; + $scope.edit = null; + $location.search('edit', null); state.confirm = 'true'; $http.post('/api/silence/set', state) .error((error) => { diff --git a/cmd/bosun/web/static/partials/silence.html b/cmd/bosun/web/static/partials/silence.html index 6b41056e7e..6dea9b9c06 100644 --- a/cmd/bosun/web/static/partials/silence.html +++ b/cmd/bosun/web/static/partials/silence.html @@ -110,11 +110,11 @@

- + - edit + edit diff --git a/cmd/bosun/web/web.go b/cmd/bosun/web/web.go index bf3a5bd272..279e0a67c6 100644 --- a/cmd/bosun/web/web.go +++ b/cmd/bosun/web/web.go @@ -503,7 +503,11 @@ func (m MultiError) Error() string { } func SilenceGet(t miniprofiler.Timer, w http.ResponseWriter, r *http.Request) (interface{}, error) { - return schedule.Silence, nil + endingAfter := time.Now().UTC().Unix() + if t := r.FormValue("t"); t != "" { + endingAfter, _ = strconv.ParseInt(t, 10, 64) + } + return schedule.DataAccess.Silence().ListSilences(endingAfter) } var silenceLayouts = []string{ diff --git a/models/silence.go b/models/silence.go new file mode 100644 index 0000000000..bd38063bab --- /dev/null +++ b/models/silence.go @@ -0,0 +1,57 @@ +package models + +import ( + "crypto/sha1" + "fmt" + "time" + + "bosun.org/opentsdb" + "bosun.org/util" +) + +type Silence struct { + Start, End time.Time + Alert string + Tags opentsdb.TagSet + TagString string + Forget bool + User string + Message string +} + +func (s *Silence) Silenced(now time.Time, alert string, tags opentsdb.TagSet) bool { + if !s.ActiveAt(now) { + return false + } + return s.Matches(alert, tags) +} + +func (s *Silence) ActiveAt(now time.Time) bool { + if now.Before(s.Start) || now.After(s.End) { + return false + } + return true +} + +func (s *Silence) Matches(alert string, tags opentsdb.TagSet) bool { + if s.Alert != "" && s.Alert != alert { + return false + } + for k, pattern := range s.Tags { + tagv, ok := tags[k] + if !ok { + return false + } + matched, _ := util.Match(pattern, tagv) + if !matched { + return false + } + } + return true +} + +func (s Silence) ID() string { + h := sha1.New() + fmt.Fprintf(h, "%s|%s|%s%s", s.Start, s.End, s.Alert, s.Tags) + return fmt.Sprintf("%x", h.Sum(nil)) +} diff --git a/cmd/bosun/sched/match.go b/util/match.go similarity index 99% rename from cmd/bosun/sched/match.go rename to util/match.go index d8e872e358..13ac86b989 100644 --- a/cmd/bosun/sched/match.go +++ b/util/match.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package sched +package util import ( "errors" diff --git a/cmd/bosun/sched/match_test.go b/util/match_test.go similarity index 99% rename from cmd/bosun/sched/match_test.go rename to util/match_test.go index f5a6e88c84..4ac6277930 100644 --- a/cmd/bosun/sched/match_test.go +++ b/util/match_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package sched +package util import "testing"