Rename suppressions to silences everywhere.

This makes internal code consistent with the API and user interface.
This commit is contained in:
Julius Volz 2013-07-31 14:39:01 +02:00
parent 854f5ef47e
commit 65f83e973e
8 changed files with 271 additions and 271 deletions

12
main.go
View File

@ -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)
}

212
manager/silencer.go Normal file
View File

@ -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()
}
}
}

View File

@ -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),
},

View File

@ -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()
}
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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)
}