From 65f83e973e51f161883adda4db82295c200db1d5 Mon Sep 17 00:00:00 2001 From: Julius Volz Date: Wed, 31 Jul 2013 14:39:01 +0200 Subject: [PATCH] Rename suppressions to silences everywhere. This makes internal code consistent with the API and user interface. --- main.go | 12 +- manager/silencer.go | 212 ++++++++++++++++++ .../{suppressor_test.go => silencer_test.go} | 74 +++--- manager/suppressor.go | 212 ------------------ web/alerts.go | 4 +- web/api/api.go | 6 +- web/api/silence.go | 16 +- web/silences.go | 6 +- 8 files changed, 271 insertions(+), 271 deletions(-) create mode 100644 manager/silencer.go rename manager/{suppressor_test.go => silencer_test.go} (56%) delete mode 100644 manager/suppressor.go diff --git a/main.go b/main.go index c37a603c5c..ceeb2859b3 100644 --- a/main.go +++ b/main.go @@ -35,8 +35,8 @@ func main() { log.Fatalf("Error loading configuration from %s: %s", *configFile, err) } - suppressor := manager.NewSuppressor() - defer suppressor.Close() + silencer := manager.NewSilencer() + defer silencer.Close() notifier := manager.NewNotifier(conf.NotificationConfig) defer notifier.Close() @@ -48,16 +48,16 @@ func main() { // REST API Service. AlertManagerService: &api.AlertManagerService{ Aggregator: aggregator, - Suppressor: suppressor, + Silencer: silencer, }, // Template-based page handlers. AlertsHandler: &web.AlertsHandler{ Aggregator: aggregator, - IsInhibitedInterrogator: suppressor, + IsInhibitedInterrogator: silencer, }, SilencesHandler: &web.SilencesHandler{ - Suppressor: suppressor, + Silencer: silencer, }, } go webService.ServeForever() @@ -65,5 +65,5 @@ func main() { aggregator.SetRules(conf.AggregationRules()) log.Println("Running summary dispatcher...") - notifier.Dispatch(suppressor) + notifier.Dispatch(silencer) } diff --git a/manager/silencer.go b/manager/silencer.go new file mode 100644 index 0000000000..94c588858c --- /dev/null +++ b/manager/silencer.go @@ -0,0 +1,212 @@ +// Copyright 2013 Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package manager + +import ( + "encoding/json" + "fmt" + "log" + "sync" + "time" +) + +type SilenceId uint +type Silences []*Silence + +type Silence struct { + // The numeric ID of the silence. + Id SilenceId + // Name/email of the silence creator. + CreatedBy string + // When the silence was first created (Unix timestamp). + CreatedAt time.Time + // When the silence expires (Unix timestamp). + EndsAt time.Time + // Additional comment about the silence. + Comment string + // Filters that determine which events are silenced. + Filters Filters + // Timer used to trigger the deletion of the Silence after its expiry + // time. + expiryTimer *time.Timer +} + +type ApiSilence struct { + CreatedBy string + CreatedAtSeconds int64 + EndsAtSeconds int64 + Comment string + Filters map[string]string +} + +func (s *Silence) MarshalJSON() ([]byte, error) { + filters := map[string]string{} + for _, f := range s.Filters { + name := f.Name.String()[1 : len(f.Name.String())-1] + value := f.Value.String()[1 : len(f.Value.String())-1] + filters[name] = value + } + + return json.Marshal(&ApiSilence{ + CreatedBy: s.CreatedBy, + CreatedAtSeconds: s.CreatedAt.Unix(), + EndsAtSeconds: s.EndsAt.Unix(), + Comment: s.Comment, + Filters: filters, + }) +} + +func (s *Silence) UnmarshalJSON(data []byte) error { + sc := &ApiSilence{} + json.Unmarshal(data, sc) + + filters := make(Filters, 0, len(sc.Filters)) + for label, value := range sc.Filters { + filters = append(filters, NewFilter(label, value)) + } + + if sc.EndsAtSeconds == 0 { + sc.EndsAtSeconds = time.Now().Add(time.Hour).Unix() + } + + *s = Silence{ + CreatedBy: sc.CreatedBy, + CreatedAt: time.Now().UTC(), + EndsAt: time.Unix(sc.EndsAtSeconds, 0).UTC(), + Comment: sc.Comment, + Filters: filters, + } + return nil +} + +type Silencer struct { + // Silences managed by this Silencer. + Silences map[SilenceId]*Silence + // Used to track the next Silence Id to allocate. + nextId SilenceId + + // Mutex to protect the above. + mu sync.Mutex +} + +type IsInhibitedInterrogator interface { + IsInhibited(*Event) (bool, *Silence) +} + +func NewSilencer() *Silencer { + return &Silencer{ + Silences: make(map[SilenceId]*Silence), + } +} + +func (s *Silencer) nextSilenceId() SilenceId { + // BUG: Build proper ID management. For now, as we are only keeping + // data in memory anyways, this is enough. + s.nextId++ + return s.nextId +} + +func (s *Silencer) setupExpiryTimer(sc *Silence) { + if sc.expiryTimer != nil { + sc.expiryTimer.Stop() + } + expDuration := sc.EndsAt.Sub(time.Now()) + sc.expiryTimer = time.AfterFunc(expDuration, func() { + if err := s.DelSilence(sc.Id); err != nil { + log.Printf("Failed to delete silence %d: %s", sc.Id, err) + } + }) +} + +func (s *Silencer) AddSilence(sc *Silence) SilenceId { + s.mu.Lock() + defer s.mu.Unlock() + + sc.Id = s.nextSilenceId() + s.setupExpiryTimer(sc) + s.Silences[sc.Id] = sc + return sc.Id +} + +func (s *Silencer) UpdateSilence(sc *Silence) error { + s.mu.Lock() + defer s.mu.Unlock() + + origSilence, ok := s.Silences[sc.Id] + if !ok { + return fmt.Errorf("Silence with ID %d doesn't exist", sc.Id) + } + if sc.EndsAt != origSilence.EndsAt { + origSilence.expiryTimer.Stop() + } + *origSilence = *sc + s.setupExpiryTimer(origSilence) + return nil +} + +func (s *Silencer) GetSilence(id SilenceId) (*Silence, error) { + s.mu.Lock() + defer s.mu.Unlock() + + sc, ok := s.Silences[id] + if !ok { + return nil, fmt.Errorf("Silence with ID %d doesn't exist", id) + } + return sc, nil +} + +func (s *Silencer) DelSilence(id SilenceId) error { + s.mu.Lock() + defer s.mu.Unlock() + + if _, ok := s.Silences[id]; !ok { + return fmt.Errorf("Silence with ID %d doesn't exist", id) + } + delete(s.Silences, id) + return nil +} + +func (s *Silencer) SilenceSummary() Silences { + s.mu.Lock() + defer s.mu.Unlock() + + silences := make(Silences, 0, len(s.Silences)) + for _, sc := range s.Silences { + silences = append(silences, sc) + } + return silences +} + +func (s *Silencer) IsInhibited(e *Event) (bool, *Silence) { + s.mu.Lock() + defer s.mu.Unlock() + + for _, s := range s.Silences { + if s.Filters.Handles(e) { + return true, s + } + } + return false, nil +} + +func (s *Silencer) Close() { + s.mu.Lock() + defer s.mu.Unlock() + + for _, sc := range s.Silences { + if sc.expiryTimer != nil { + sc.expiryTimer.Stop() + } + } +} diff --git a/manager/suppressor_test.go b/manager/silencer_test.go similarity index 56% rename from manager/suppressor_test.go rename to manager/silencer_test.go index 045399114e..9283a2a6d7 100644 --- a/manager/suppressor_test.go +++ b/manager/silencer_test.go @@ -18,70 +18,70 @@ import ( "time" ) -type testSuppressorScenario struct { - suppressions Suppressions - inhibited Events - uninhibited Events +type testSilencerScenario struct { + silences Silences + inhibited Events + uninhibited Events } -func (sc *testSuppressorScenario) test(i int, t *testing.T) { - s := NewSuppressor() +func (scenario *testSilencerScenario) test(i int, t *testing.T) { + s := NewSilencer() - for j, sup := range sc.suppressions { - id := s.AddSuppression(sup) - retrievedSup, err := s.GetSuppression(id) + for j, sc := range scenario.silences { + id := s.AddSilence(sc) + retrievedSilence, err := s.GetSilence(id) if err != nil { - t.Fatalf("%d.%d. Error getting suppression: %s", i, j, err) + t.Fatalf("%d.%d. Error getting silence: %s", i, j, err) } - if retrievedSup.Id != id { - t.Fatalf("%d.%d. Expected ID %d, got %d", i, j, id, retrievedSup.Id) + if retrievedSilence.Id != id { + t.Fatalf("%d.%d. Expected ID %d, got %d", i, j, id, retrievedSilence.Id) } - sup.Id = id - if sup != retrievedSup { - t.Fatalf("%d.%d. Expected suppression %v, got %v", i, j, sup, retrievedSup) + sc.Id = id + if sc != retrievedSilence { + t.Fatalf("%d.%d. Expected silence %v, got %v", i, j, sc, retrievedSilence) } } - for j, ev := range sc.inhibited { - inhibited, sup := s.IsInhibited(ev) + for j, ev := range scenario.inhibited { + inhibited, sc := s.IsInhibited(ev) if !inhibited { t.Fatalf("%d.%d. Expected %v to be inhibited", i, j, ev) } - if sup == nil { - t.Fatalf("%d.%d. Expected non-nil Suppression for inhibited event %v", i, j, ev) + if sc == nil { + t.Fatalf("%d.%d. Expected non-nil Silence for inhibited event %v", i, j, ev) } } - for j, ev := range sc.uninhibited { - inhibited, sup := s.IsInhibited(ev) + for j, ev := range scenario.uninhibited { + inhibited, sc := s.IsInhibited(ev) if inhibited { - t.Fatalf("%d.%d. Expected %v to not be inhibited, was inhibited by %v", i, j, ev, sup) + t.Fatalf("%d.%d. Expected %v to not be inhibited, was inhibited by %v", i, j, ev, sc) } } - suppressions := s.SuppressionSummary() - if len(suppressions) != len(sc.suppressions) { - t.Fatalf("%d. Expected %d suppressions, got %d", i, len(sc.suppressions), len(suppressions)) + silences := s.SilenceSummary() + if len(silences) != len(scenario.silences) { + t.Fatalf("%d. Expected %d silences, got %d", i, len(scenario.silences), len(silences)) } - for j, sup := range suppressions { - if err := s.DelSuppression(sup.Id); err != nil { - t.Fatalf("%d.%d. Got error while deleting suppression: %s", i, j, err) + for j, sc := range silences { + if err := s.DelSilence(sc.Id); err != nil { + t.Fatalf("%d.%d. Got error while deleting silence: %s", i, j, err) } - newSuppressions := s.SuppressionSummary() - if len(newSuppressions) != len(suppressions)-j-1 { - t.Fatalf("%d. Expected %d suppressions, got %d", i, len(suppressions), len(newSuppressions)) + newSilences := s.SilenceSummary() + if len(newSilences) != len(silences)-j-1 { + t.Fatalf("%d. Expected %d silences, got %d", i, len(silences), len(newSilences)) } } s.Close() } -func TestSuppressor(t *testing.T) { - scenarios := []testSuppressorScenario{ +func TestSilencer(t *testing.T) { + scenarios := []testSilencerScenario{ { - // No suppressions, one event. + // No silences, one event. uninhibited: Events{ &Event{ Labels: map[string]string{ @@ -92,12 +92,12 @@ func TestSuppressor(t *testing.T) { }, { // One rule, two matching events, one non-matching. - suppressions: Suppressions{ - &Suppression{ + silences: Silences{ + &Silence{ Filters: Filters{NewFilter("service", "test(-)?service")}, EndsAt: time.Now().Add(time.Hour), }, - &Suppression{ + &Silence{ Filters: Filters{NewFilter("testlabel", ".*")}, EndsAt: time.Now().Add(time.Hour), }, diff --git a/manager/suppressor.go b/manager/suppressor.go deleted file mode 100644 index aba3bef338..0000000000 --- a/manager/suppressor.go +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright 2013 Prometheus Team -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package manager - -import ( - "encoding/json" - "fmt" - "log" - "sync" - "time" -) - -type SuppressionId uint -type Suppressions []*Suppression - -type Suppression struct { - // The numeric ID of the suppression. - Id SuppressionId - // Name/email of the suppression creator. - CreatedBy string - // When the suppression was first created (Unix timestamp). - CreatedAt time.Time - // When the suppression expires (Unix timestamp). - EndsAt time.Time - // Additional comment about the suppression. - Comment string - // Filters that determine which events are suppressed. - Filters Filters - // Timer used to trigger the deletion of the Suppression after its expiry - // time. - expiryTimer *time.Timer -} - -type ApiSilence struct { - CreatedBy string - CreatedAtSeconds int64 - EndsAtSeconds int64 - Comment string - Filters map[string]string -} - -func (s *Suppression) MarshalJSON() ([]byte, error) { - filters := map[string]string{} - for _, f := range s.Filters { - name := f.Name.String()[1 : len(f.Name.String())-1] - value := f.Value.String()[1 : len(f.Value.String())-1] - filters[name] = value - } - - return json.Marshal(&ApiSilence{ - CreatedBy: s.CreatedBy, - CreatedAtSeconds: s.CreatedAt.Unix(), - EndsAtSeconds: s.EndsAt.Unix(), - Comment: s.Comment, - Filters: filters, - }) -} - -func (s *Suppression) UnmarshalJSON(data []byte) error { - sc := &ApiSilence{} - json.Unmarshal(data, sc) - - filters := make(Filters, 0, len(sc.Filters)) - for label, value := range sc.Filters { - filters = append(filters, NewFilter(label, value)) - } - - if sc.EndsAtSeconds == 0 { - sc.EndsAtSeconds = time.Now().Add(time.Hour).Unix() - } - - *s = Suppression{ - CreatedBy: sc.CreatedBy, - CreatedAt: time.Now().UTC(), - EndsAt: time.Unix(sc.EndsAtSeconds, 0).UTC(), - Comment: sc.Comment, - Filters: filters, - } - return nil -} - -type Suppressor struct { - // Suppressions managed by this Suppressor. - Suppressions map[SuppressionId]*Suppression - // Used to track the next Suppression Id to allocate. - nextId SuppressionId - - // Mutex to protect the above. - mu sync.Mutex -} - -type IsInhibitedInterrogator interface { - IsInhibited(*Event) (bool, *Suppression) -} - -func NewSuppressor() *Suppressor { - return &Suppressor{ - Suppressions: make(map[SuppressionId]*Suppression), - } -} - -func (s *Suppressor) nextSuppressionId() SuppressionId { - // BUG: Build proper ID management. For now, as we are only keeping - // data in memory anyways, this is enough. - s.nextId++ - return s.nextId -} - -func (s *Suppressor) setupExpiryTimer(sup *Suppression) { - if sup.expiryTimer != nil { - sup.expiryTimer.Stop() - } - expDuration := sup.EndsAt.Sub(time.Now()) - sup.expiryTimer = time.AfterFunc(expDuration, func() { - if err := s.DelSuppression(sup.Id); err != nil { - log.Printf("Failed to delete suppression %d: %s", sup.Id, err) - } - }) -} - -func (s *Suppressor) AddSuppression(sup *Suppression) SuppressionId { - s.mu.Lock() - defer s.mu.Unlock() - - sup.Id = s.nextSuppressionId() - s.setupExpiryTimer(sup) - s.Suppressions[sup.Id] = sup - return sup.Id -} - -func (s *Suppressor) UpdateSuppression(sup *Suppression) error { - s.mu.Lock() - defer s.mu.Unlock() - - origSup, ok := s.Suppressions[sup.Id] - if !ok { - return fmt.Errorf("Suppression with ID %d doesn't exist", sup.Id) - } - if sup.EndsAt != origSup.EndsAt { - origSup.expiryTimer.Stop() - } - *origSup = *sup - s.setupExpiryTimer(origSup) - return nil -} - -func (s *Suppressor) GetSuppression(id SuppressionId) (*Suppression, error) { - s.mu.Lock() - defer s.mu.Unlock() - - sup, ok := s.Suppressions[id] - if !ok { - return nil, fmt.Errorf("Suppression with ID %d doesn't exist", id) - } - return sup, nil -} - -func (s *Suppressor) DelSuppression(id SuppressionId) error { - s.mu.Lock() - defer s.mu.Unlock() - - if _, ok := s.Suppressions[id]; !ok { - return fmt.Errorf("Suppression with ID %d doesn't exist", id) - } - delete(s.Suppressions, id) - return nil -} - -func (s *Suppressor) SuppressionSummary() Suppressions { - s.mu.Lock() - defer s.mu.Unlock() - - suppressions := make(Suppressions, 0, len(s.Suppressions)) - for _, sup := range s.Suppressions { - suppressions = append(suppressions, sup) - } - return suppressions -} - -func (s *Suppressor) IsInhibited(e *Event) (bool, *Suppression) { - s.mu.Lock() - defer s.mu.Unlock() - - for _, s := range s.Suppressions { - if s.Filters.Handles(e) { - return true, s - } - } - return false, nil -} - -func (s *Suppressor) Close() { - s.mu.Lock() - defer s.mu.Unlock() - - for _, sup := range s.Suppressions { - if sup.expiryTimer != nil { - sup.expiryTimer.Stop() - } - } -} diff --git a/web/alerts.go b/web/alerts.go index 6e9c173258..b143e6ae85 100644 --- a/web/alerts.go +++ b/web/alerts.go @@ -21,7 +21,7 @@ import ( type AlertStatus struct { AlertAggregates []*manager.AggregationInstance - SilenceForEvent func(*manager.Event) *manager.Suppression + SilenceForEvent func(*manager.Event) *manager.Silence } type AlertsHandler struct { @@ -29,7 +29,7 @@ type AlertsHandler struct { IsInhibitedInterrogator manager.IsInhibitedInterrogator } -func (h *AlertsHandler) silenceForEvent(e *manager.Event) *manager.Suppression { +func (h *AlertsHandler) silenceForEvent(e *manager.Event) *manager.Silence { _, silence := h.IsInhibitedInterrogator.IsInhibited(e) return silence } diff --git a/web/api/api.go b/web/api/api.go index 9280fb3d17..201e62ff2f 100644 --- a/web/api/api.go +++ b/web/api/api.go @@ -23,12 +23,12 @@ type AlertManagerService struct { gorest.RestService `root:"/api/" consumes:"application/json" produces:"application/json"` addEvents gorest.EndPoint `method:"POST" path:"/events" postdata:"Events"` - addSilence gorest.EndPoint `method:"POST" path:"/silences" postdata:"Suppression"` + addSilence gorest.EndPoint `method:"POST" path:"/silences" postdata:"Silence"` getSilence gorest.EndPoint `method:"GET" path:"/silences/{id:int}" output:"string"` - updateSilence gorest.EndPoint `method:"POST" path:"/silences/{id:int}" postdata:"Suppression"` + updateSilence gorest.EndPoint `method:"POST" path:"/silences/{id:int}" postdata:"Silence"` delSilence gorest.EndPoint `method:"DELETE" path:"/silences/{id:int}"` silenceSummary gorest.EndPoint `method:"GET" path:"/silences" output:"string"` Aggregator *manager.Aggregator - Suppressor *manager.Suppressor + Silencer *manager.Silencer } diff --git a/web/api/silence.go b/web/api/silence.go index 654542d9dd..4132e3714f 100644 --- a/web/api/silence.go +++ b/web/api/silence.go @@ -32,9 +32,9 @@ type Silence struct { Filters map[string]string } -func (s AlertManagerService) AddSilence(sc manager.Suppression) { +func (s AlertManagerService) AddSilence(sc manager.Silence) { // BUG: add server-side form validation. - id := s.Suppressor.AddSuppression(&sc) + id := s.Silencer.AddSilence(&sc) rb := s.ResponseBuilder() rb.SetResponseCode(http.StatusCreated) @@ -44,7 +44,7 @@ func (s AlertManagerService) AddSilence(sc manager.Suppression) { func (s AlertManagerService) GetSilence(id int) string { rb := s.ResponseBuilder() rb.SetContentType(gorest.Application_Json) - silence, err := s.Suppressor.GetSuppression(manager.SuppressionId(id)) + silence, err := s.Silencer.GetSilence(manager.SilenceId(id)) if err != nil { log.Printf("Error getting silence: %s", err) rb.SetResponseCode(http.StatusNotFound) @@ -60,10 +60,10 @@ func (s AlertManagerService) GetSilence(id int) string { return string(resultBytes) } -func (s AlertManagerService) UpdateSilence(sc manager.Suppression, id int) { +func (s AlertManagerService) UpdateSilence(sc manager.Silence, id int) { // BUG: add server-side form validation. - sc.Id = manager.SuppressionId(id) - if err := s.Suppressor.UpdateSuppression(&sc); err != nil { + sc.Id = manager.SilenceId(id) + if err := s.Silencer.UpdateSilence(&sc); err != nil { log.Printf("Error updating silence: %s", err) rb := s.ResponseBuilder() rb.SetResponseCode(http.StatusNotFound) @@ -71,7 +71,7 @@ func (s AlertManagerService) UpdateSilence(sc manager.Suppression, id int) { } func (s AlertManagerService) DelSilence(id int) { - if err := s.Suppressor.DelSuppression(manager.SuppressionId(id)); err != nil { + if err := s.Silencer.DelSilence(manager.SilenceId(id)); err != nil { log.Printf("Error deleting silence: %s", err) rb := s.ResponseBuilder() rb.SetResponseCode(http.StatusNotFound) @@ -81,7 +81,7 @@ func (s AlertManagerService) DelSilence(id int) { func (s AlertManagerService) SilenceSummary() string { rb := s.ResponseBuilder() rb.SetContentType(gorest.Application_Json) - silenceSummary := s.Suppressor.SuppressionSummary() + silenceSummary := s.Silencer.SilenceSummary() resultBytes, err := json.Marshal(silenceSummary) if err != nil { diff --git a/web/silences.go b/web/silences.go index 40a5dcd821..816346308b 100644 --- a/web/silences.go +++ b/web/silences.go @@ -20,16 +20,16 @@ import ( ) type SilenceStatus struct { - Silences manager.Suppressions + Silences manager.Silences } type SilencesHandler struct { - Suppressor *manager.Suppressor + Silencer *manager.Silencer } func (h *SilencesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { silenceStatus := &SilenceStatus{ - Silences: h.Suppressor.SuppressionSummary(), + Silences: h.Silencer.SilenceSummary(), } executeTemplate(w, "silences", silenceStatus) }