mirror of
https://github.com/prometheus/alertmanager
synced 2024-12-26 16:12:20 +00:00
*: refactor Silence type, use UUID
This commit removes the dependency on model.Silence for the internal Silence type, uses UUIDs instead of uint64s and clarifies invariants around timestamp handling. The created_at timestamp is removed for the time being.
This commit is contained in:
parent
2d0444cac1
commit
81cbf3cda7
21
api.go
21
api.go
@ -17,7 +17,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -26,6 +25,7 @@ import (
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/prometheus/common/route"
|
||||
"github.com/prometheus/common/version"
|
||||
"github.com/satori/go.uuid"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/prometheus/alertmanager/provider"
|
||||
@ -293,9 +293,9 @@ func (api *API) addSilence(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
sil := types.NewSilence(&msil)
|
||||
|
||||
if sil.CreatedAt.IsZero() {
|
||||
sil.CreatedAt = time.Now()
|
||||
}
|
||||
// if sil.CreatedAt.IsZero() {
|
||||
// sil.CreatedAt = time.Now()
|
||||
// }
|
||||
|
||||
if err := sil.Validate(); err != nil {
|
||||
respondError(w, apiError{
|
||||
@ -304,6 +304,13 @@ func (api *API) addSilence(w http.ResponseWriter, r *http.Request) {
|
||||
}, nil)
|
||||
return
|
||||
}
|
||||
if err := sil.Init(); err != nil {
|
||||
respondError(w, apiError{
|
||||
typ: errorBadData,
|
||||
err: err,
|
||||
}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
sid, err := api.silences.Set(sil)
|
||||
if err != nil {
|
||||
@ -315,7 +322,7 @@ func (api *API) addSilence(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
respond(w, struct {
|
||||
SilenceID uint64 `json:"silenceId"`
|
||||
SilenceID uuid.UUID `json:"silenceId"`
|
||||
}{
|
||||
SilenceID: sid,
|
||||
})
|
||||
@ -323,7 +330,7 @@ func (api *API) addSilence(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (api *API) getSilence(w http.ResponseWriter, r *http.Request) {
|
||||
sids := route.Param(api.context(r), "sid")
|
||||
sid, err := strconv.ParseUint(sids, 10, 64)
|
||||
sid, err := uuid.FromString(sids)
|
||||
if err != nil {
|
||||
respondError(w, apiError{
|
||||
typ: errorBadData,
|
||||
@ -343,7 +350,7 @@ func (api *API) getSilence(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (api *API) delSilence(w http.ResponseWriter, r *http.Request) {
|
||||
sids := route.Param(api.context(r), "sid")
|
||||
sid, err := strconv.ParseUint(sids, 10, 64)
|
||||
sid, err := uuid.FromString(sids)
|
||||
if err != nil {
|
||||
respondError(w, apiError{
|
||||
typ: errorBadData,
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/satori/go.uuid"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
@ -72,8 +73,8 @@ type AlertBlock struct {
|
||||
type APIAlert struct {
|
||||
*model.Alert
|
||||
|
||||
Inhibited bool `json:"inhibited"`
|
||||
Silenced uint64 `json:"silenced,omitempty"`
|
||||
Inhibited bool `json:"inhibited"`
|
||||
Silenced uuid.UUID `json:"silenced,omitempty"`
|
||||
}
|
||||
|
||||
// AlertGroup is a list of alert blocks grouped by the same label set.
|
||||
|
@ -22,8 +22,8 @@ import (
|
||||
"github.com/prometheus/common/model"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/prometheus/alertmanager/provider"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
"github.com/satori/go.uuid"
|
||||
)
|
||||
|
||||
type recordNotifier struct {
|
||||
@ -169,7 +169,7 @@ func TestDedupingNotifierHasUpdate(t *testing.T) {
|
||||
func TestDedupingNotifier(t *testing.T) {
|
||||
var (
|
||||
record = &recordNotifier{}
|
||||
notifies = provider.NewMemNotifies(provider.NewMemData())
|
||||
notifies = newTestInfos()
|
||||
deduper = Dedup(notifies, record)
|
||||
ctx = context.Background()
|
||||
)
|
||||
@ -331,7 +331,7 @@ func TestSilenceNotifier(t *testing.T) {
|
||||
|
||||
// Set the second alert als previously silenced. It is expected to have
|
||||
// the WasSilenced flag set to true afterwards.
|
||||
marker.SetSilenced(inAlerts[1].Fingerprint(), 123)
|
||||
marker.SetSilenced(inAlerts[1].Fingerprint(), uuid.NewV4())
|
||||
|
||||
if err := silenceNotifer.Notify(nil, inAlerts...); err != nil {
|
||||
t.Fatalf("Notifying failed: %s", err)
|
||||
|
@ -25,6 +25,7 @@ import (
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/satori/go.uuid"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -277,47 +278,37 @@ func (s *Silences) initCache() error {
|
||||
c := b.Cursor()
|
||||
|
||||
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||
var ms model.Silence
|
||||
if err := json.Unmarshal(v, &ms); err != nil {
|
||||
var s types.Silence
|
||||
if err := json.Unmarshal(v, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
// The ID is duplicated in the value and always equal
|
||||
// to the stored key.
|
||||
s.cache[ms.ID] = types.NewSilence(&ms)
|
||||
if err := s.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
res = append(res, &s)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Set a new silence.
|
||||
func (s *Silences) Set(sil *types.Silence) (uint64, error) {
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
|
||||
func (s *Silences) Set(sil *types.Silence) (uuid.UUID, error) {
|
||||
var (
|
||||
uid uint64
|
||||
uid uuid.UUID
|
||||
err error
|
||||
)
|
||||
err = s.db.Update(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(bktSilences)
|
||||
|
||||
// Silences are immutable and we always create a new one.
|
||||
uid, err = b.NextSequence()
|
||||
sil.ID = uuid.NewV4()
|
||||
|
||||
msb, err := json.Marshal(sil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sil.ID = uid
|
||||
|
||||
k := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(k, uid)
|
||||
|
||||
msb, err := json.Marshal(sil.Silence)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.Put(k, msb)
|
||||
uid = sil.ID
|
||||
return b.Put(sil.ID[:], msb)
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@ -327,17 +318,13 @@ func (s *Silences) Set(sil *types.Silence) (uint64, error) {
|
||||
}
|
||||
|
||||
// Del removes a silence.
|
||||
func (s *Silences) Del(uid uint64) error {
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
|
||||
func (s *Silences) Del(uid uuid.UUID) error {
|
||||
err := s.db.Update(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(bktSilences)
|
||||
|
||||
k := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(k, uid)
|
||||
|
||||
return b.Delete(k)
|
||||
// TODO(fabxc): this does not yet comply with the semantics of
|
||||
// deleting just setting EndsAt to now if it is in the future.
|
||||
// As long as we are don't have strong semantics we might still
|
||||
// keep supporting this.
|
||||
return tx.Bucket(bktSilences).Delete(uid[:])
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@ -347,15 +334,22 @@ func (s *Silences) Del(uid uint64) error {
|
||||
}
|
||||
|
||||
// Get a silence associated with a fingerprint.
|
||||
func (s *Silences) Get(uid uint64) (*types.Silence, error) {
|
||||
s.mtx.RLock()
|
||||
defer s.mtx.RUnlock()
|
||||
func (s *Silences) Get(uid uuid.UUID) (*types.Silence, error) {
|
||||
var sil types.Silence
|
||||
|
||||
sil, ok := s.cache[uid]
|
||||
if !ok {
|
||||
return nil, provider.ErrNotFound
|
||||
err := s.db.View(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(bktSilences)
|
||||
|
||||
v := b.Get(uid[:])
|
||||
if v == nil {
|
||||
return provider.ErrNotFound
|
||||
}
|
||||
return json.Unmarshal(v, &sil)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sil, nil
|
||||
return &sil, sil.Init()
|
||||
}
|
||||
|
||||
// NotificationInfo provides information about pending and successful
|
||||
|
@ -123,7 +123,7 @@ func TestNotifiesSet(t *testing.T) {
|
||||
func TestSilencesSet(t *testing.T) {
|
||||
var (
|
||||
t0 = time.Now()
|
||||
t1 = t0.Add(10 * time.Minute)
|
||||
// t1 = t0.Add(10 * time.Minute)
|
||||
t2 = t0.Add(20 * time.Minute)
|
||||
// t3 = t0.Add(30 * time.Minute)
|
||||
)
|
||||
@ -132,16 +132,15 @@ func TestSilencesSet(t *testing.T) {
|
||||
insert *types.Silence
|
||||
}{
|
||||
{
|
||||
insert: types.NewSilence(&model.Silence{
|
||||
Matchers: []*model.Matcher{
|
||||
insert: &types.Silence{
|
||||
Matchers: types.Matchers{
|
||||
{Name: "key", Value: "val"},
|
||||
},
|
||||
StartsAt: t0,
|
||||
EndsAt: t2,
|
||||
CreatedAt: t1,
|
||||
CreatedBy: "user",
|
||||
Comment: "test comment",
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -186,13 +185,11 @@ func TestSilencesDelete(t *testing.T) {
|
||||
}
|
||||
|
||||
uid, err := silences.Set(&types.Silence{
|
||||
Matchers: []*types.Matcher{
|
||||
Matchers: types.Matchers{
|
||||
{Name: "key", Value: "val"},
|
||||
},
|
||||
Silence: model.Silence{
|
||||
CreatedBy: "user",
|
||||
Comment: "test comment",
|
||||
},
|
||||
CreatedBy: "user",
|
||||
Comment: "test comment",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@ -216,37 +213,34 @@ func TestSilencesAll(t *testing.T) {
|
||||
)
|
||||
|
||||
insert := []*types.Silence{
|
||||
types.NewSilence(&model.Silence{
|
||||
Matchers: []*model.Matcher{
|
||||
&types.Silence{
|
||||
Matchers: types.Matchers{
|
||||
{Name: "key", Value: "val"},
|
||||
},
|
||||
StartsAt: t0,
|
||||
EndsAt: t2,
|
||||
CreatedAt: t1,
|
||||
CreatedBy: "user",
|
||||
Comment: "test comment",
|
||||
}),
|
||||
types.NewSilence(&model.Silence{
|
||||
Matchers: []*model.Matcher{
|
||||
},
|
||||
&types.Silence{
|
||||
Matchers: types.Matchers{
|
||||
{Name: "key", Value: "val"},
|
||||
{Name: "key2", Value: "val2.*", IsRegex: true},
|
||||
},
|
||||
StartsAt: t1,
|
||||
EndsAt: t2,
|
||||
CreatedAt: t1,
|
||||
CreatedBy: "user2",
|
||||
Comment: "test comment",
|
||||
}),
|
||||
types.NewSilence(&model.Silence{
|
||||
Matchers: []*model.Matcher{
|
||||
},
|
||||
&types.Silence{
|
||||
Matchers: types.Matchers{
|
||||
{Name: "key", Value: "val"},
|
||||
},
|
||||
StartsAt: t2,
|
||||
EndsAt: t3,
|
||||
CreatedAt: t3,
|
||||
CreatedBy: "user",
|
||||
Comment: "another test comment",
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
dir, err := ioutil.TempDir("", "silences_test")
|
||||
@ -281,7 +275,7 @@ func TestSilencesAll(t *testing.T) {
|
||||
func TestSilencesMutes(t *testing.T) {
|
||||
var (
|
||||
t0 = time.Now()
|
||||
t1 = t0.Add(10 * time.Minute)
|
||||
// t1 = t0.Add(10 * time.Minute)
|
||||
t2 = t0.Add(20 * time.Minute)
|
||||
t3 = t0.Add(30 * time.Minute)
|
||||
)
|
||||
@ -289,36 +283,33 @@ func TestSilencesMutes(t *testing.T) {
|
||||
// All silences are active for the time of the test. Time restriction
|
||||
// testing is covered for the Mutes() method of the Silence type.
|
||||
insert := []*types.Silence{
|
||||
types.NewSilence(&model.Silence{
|
||||
Matchers: []*model.Matcher{
|
||||
&types.Silence{
|
||||
Matchers: types.Matchers{
|
||||
{Name: "key", Value: "val"},
|
||||
},
|
||||
StartsAt: t0,
|
||||
EndsAt: t2,
|
||||
CreatedAt: t1,
|
||||
CreatedBy: "user",
|
||||
Comment: "test comment",
|
||||
}),
|
||||
types.NewSilence(&model.Silence{
|
||||
Matchers: []*model.Matcher{
|
||||
},
|
||||
&types.Silence{
|
||||
Matchers: types.Matchers{
|
||||
{Name: "key2", Value: "val2.*", IsRegex: true},
|
||||
},
|
||||
StartsAt: t0,
|
||||
EndsAt: t2,
|
||||
CreatedAt: t1,
|
||||
CreatedBy: "user2",
|
||||
Comment: "test comment",
|
||||
}),
|
||||
types.NewSilence(&model.Silence{
|
||||
Matchers: []*model.Matcher{
|
||||
},
|
||||
&types.Silence{
|
||||
Matchers: types.Matchers{
|
||||
{Name: "key", Value: "val2"},
|
||||
},
|
||||
StartsAt: t0,
|
||||
EndsAt: t3,
|
||||
CreatedAt: t3,
|
||||
CreatedBy: "user",
|
||||
Comment: "another test comment",
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
dir, err := ioutil.TempDir("", "silences_test")
|
||||
@ -503,7 +494,9 @@ func silencesEqual(s1, s2 *types.Silence) bool {
|
||||
if !reflect.DeepEqual(s1.Matchers, s2.Matchers) {
|
||||
return false
|
||||
}
|
||||
if !reflect.DeepEqual(s1.Silence.Matchers, s2.Silence.Matchers) {
|
||||
// XXX: As the matchers contain regexes DeepEqual is not generally
|
||||
// reliable. May be false negative.
|
||||
if !reflect.DeepEqual(s1.Matchers, s2.Matchers) {
|
||||
return false
|
||||
}
|
||||
if !s1.StartsAt.Equal(s2.EndsAt) {
|
||||
@ -512,9 +505,6 @@ func silencesEqual(s1, s2 *types.Silence) bool {
|
||||
if !s1.EndsAt.Equal(s2.EndsAt) {
|
||||
return false
|
||||
}
|
||||
if !s1.CreatedAt.Equal(s2.CreatedAt) {
|
||||
return false
|
||||
}
|
||||
return s1.Comment == s2.Comment && s1.CreatedBy == s2.CreatedBy
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/satori/go.uuid"
|
||||
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
)
|
||||
@ -97,11 +98,11 @@ type Silences interface {
|
||||
// All returns all existing silences.
|
||||
All() ([]*types.Silence, error)
|
||||
// Set a new silence.
|
||||
Set(*types.Silence) (uint64, error)
|
||||
Set(*types.Silence) (uuid.UUID, error)
|
||||
// Del removes a silence.
|
||||
Del(uint64) error
|
||||
Del(uuid.UUID) error
|
||||
// Get a silence associated with a fingerprint.
|
||||
Get(uint64) (*types.Silence, error)
|
||||
Get(uuid.UUID) (*types.Silence, error)
|
||||
}
|
||||
|
||||
// Notifies provides information about pending and successful
|
||||
|
@ -15,9 +15,11 @@ package test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@ -28,6 +30,7 @@ import (
|
||||
|
||||
"github.com/prometheus/client_golang/api/alertmanager"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/satori/go.uuid"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
@ -297,25 +300,52 @@ func (am *Alertmanager) Push(at float64, alerts ...*TestAlert) {
|
||||
|
||||
// SetSilence updates or creates the given Silence.
|
||||
func (am *Alertmanager) SetSilence(at float64, sil *TestSilence) {
|
||||
silences := alertmanager.NewSilenceAPI(am.client)
|
||||
|
||||
am.t.Do(at, func() {
|
||||
sid, err := silences.Set(context.Background(), sil.nativeSilence(am.opts))
|
||||
var buf bytes.Buffer
|
||||
if err := json.NewEncoder(&buf).Encode(sil.nativeSilence(am.opts)); err != nil {
|
||||
am.t.Errorf("Error setting silence %v: %s", sil, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := http.Post(fmt.Sprintf("http://%s/api/v1/silences", am.addr), "application/json", &buf)
|
||||
if err != nil {
|
||||
am.t.Errorf("Error setting silence %v: %s", sil, err)
|
||||
return
|
||||
}
|
||||
sil.ID = sid
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var v struct {
|
||||
Status string `json:"status"`
|
||||
Data struct {
|
||||
SilenceID uuid.UUID `json:"silenceId"`
|
||||
} `json:"data"`
|
||||
}
|
||||
if err := json.Unmarshal(b, &v); err != nil {
|
||||
am.t.Errorf("error setting silence %v: %s", sil, err)
|
||||
return
|
||||
}
|
||||
sil.ID = v.Data.SilenceID
|
||||
})
|
||||
}
|
||||
|
||||
// DelSilence deletes the silence with the sid at the given time.
|
||||
func (am *Alertmanager) DelSilence(at float64, sil *TestSilence) {
|
||||
silences := alertmanager.NewSilenceAPI(am.client)
|
||||
|
||||
am.t.Do(at, func() {
|
||||
if err := silences.Del(context.Background(), sil.ID); err != nil {
|
||||
req, err := http.NewRequest("DELETE", fmt.Sprintf("http://%s/api/v1/silence/%s", am.addr, sil.ID), nil)
|
||||
if err != nil {
|
||||
am.t.Errorf("Error deleting silence %v: %s", sil, err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
am.t.Errorf("Error deleting silence %v: %s", sil, err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
16
test/mock.go
16
test/mock.go
@ -24,6 +24,8 @@ import (
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
"github.com/satori/go.uuid"
|
||||
)
|
||||
|
||||
// At is a convenience method to allow for declarative syntax of Acceptance
|
||||
@ -52,7 +54,7 @@ func Between(start, end float64) Interval {
|
||||
|
||||
// TestSilence models a model.Silence with relative times.
|
||||
type TestSilence struct {
|
||||
ID uint64
|
||||
ID uuid.UUID
|
||||
match []string
|
||||
matchRE []string
|
||||
startsAt, endsAt float64
|
||||
@ -84,18 +86,18 @@ func (s *TestSilence) MatchRE(v ...string) *TestSilence {
|
||||
|
||||
// nativeSilence converts the declared test silence into a regular
|
||||
// silence with resolved times.
|
||||
func (s *TestSilence) nativeSilence(opts *AcceptanceOpts) *model.Silence {
|
||||
nsil := &model.Silence{}
|
||||
func (s *TestSilence) nativeSilence(opts *AcceptanceOpts) *types.Silence {
|
||||
nsil := &types.Silence{}
|
||||
|
||||
for i := 0; i < len(s.match); i += 2 {
|
||||
nsil.Matchers = append(nsil.Matchers, &model.Matcher{
|
||||
Name: model.LabelName(s.match[i]),
|
||||
nsil.Matchers = append(nsil.Matchers, &types.Matcher{
|
||||
Name: s.match[i],
|
||||
Value: s.match[i+1],
|
||||
})
|
||||
}
|
||||
for i := 0; i < len(s.matchRE); i += 2 {
|
||||
nsil.Matchers = append(nsil.Matchers, &model.Matcher{
|
||||
Name: model.LabelName(s.matchRE[i]),
|
||||
nsil.Matchers = append(nsil.Matchers, &types.Matcher{
|
||||
Name: s.matchRE[i],
|
||||
Value: s.matchRE[i+1],
|
||||
IsRegex: true,
|
||||
})
|
||||
|
@ -14,58 +14,49 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
// Matcher defines a matching rule for the value of a given label.
|
||||
type Matcher struct {
|
||||
Name model.LabelName
|
||||
Value string
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
IsRegex bool `json:"isRegex"`
|
||||
|
||||
isRegex bool
|
||||
regex *regexp.Regexp
|
||||
regex *regexp.Regexp
|
||||
}
|
||||
|
||||
// Init internals of the Matcher. Must be called before using Match.
|
||||
func (m *Matcher) Init() error {
|
||||
if !m.IsRegex {
|
||||
return nil
|
||||
}
|
||||
re, err := regexp.Compile(m.Value)
|
||||
if err == nil {
|
||||
m.regex = re
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Matcher) String() string {
|
||||
if m.isRegex {
|
||||
if m.IsRegex {
|
||||
return fmt.Sprintf("<RegexMatcher %s:%q>", m.Name, m.Value)
|
||||
}
|
||||
return fmt.Sprintf("<Matcher %s:%q>", m.Name, m.Value)
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (m *Matcher) MarshalJSON() ([]byte, error) {
|
||||
v := struct {
|
||||
Name model.LabelName `json:"name"`
|
||||
Value string `json:"value"`
|
||||
IsRegex bool `json:"isRegex"`
|
||||
}{
|
||||
Name: m.Name,
|
||||
Value: m.Value,
|
||||
IsRegex: m.isRegex,
|
||||
}
|
||||
return json.Marshal(&v)
|
||||
}
|
||||
|
||||
// IsRegex returns true of the matcher compares against a regular expression.
|
||||
func (m *Matcher) IsRegex() bool {
|
||||
return m.isRegex
|
||||
}
|
||||
|
||||
// Match checks whether the label of the matcher has the specified
|
||||
// matching value.
|
||||
func (m *Matcher) Match(lset model.LabelSet) bool {
|
||||
// Unset labels are treated as unset labels globally. Thus, if a
|
||||
// label is not set we retrieve the empty label which is correct
|
||||
// for the comparison below.
|
||||
v := lset[m.Name]
|
||||
v := lset[model.LabelName(m.Name)]
|
||||
|
||||
if m.isRegex {
|
||||
if m.IsRegex {
|
||||
return m.regex.MatchString(string(v))
|
||||
}
|
||||
return string(v) == m.Value
|
||||
@ -75,23 +66,21 @@ func (m *Matcher) Match(lset model.LabelSet) bool {
|
||||
// the given value.
|
||||
func NewMatcher(name model.LabelName, value string) *Matcher {
|
||||
return &Matcher{
|
||||
Name: name,
|
||||
Value: value,
|
||||
Name: string(name),
|
||||
Value: value,
|
||||
IsRegex: false,
|
||||
}
|
||||
}
|
||||
|
||||
// NewRegexMatcher returns a new matcher that treats value as a regular
|
||||
// expression which is used for matching.
|
||||
// NewRegexMatcher returns a new matcher that compares values against
|
||||
// a regular expression. The matcher is already initialized.
|
||||
//
|
||||
// TODO(fabxc): refactor usage.
|
||||
func NewRegexMatcher(name model.LabelName, re *regexp.Regexp) *Matcher {
|
||||
value := strings.TrimSuffix(strings.TrimPrefix(re.String(), "^(?:"), ")$")
|
||||
if len(re.String())-len(value) != 6 {
|
||||
// Any non-anchored regexp is a bug.
|
||||
panic(fmt.Errorf("regexp %q not properly anchored", re))
|
||||
}
|
||||
return &Matcher{
|
||||
Name: name,
|
||||
Value: value,
|
||||
isRegex: true,
|
||||
Name: string(name),
|
||||
Value: re.String(),
|
||||
IsRegex: true,
|
||||
regex: re,
|
||||
}
|
||||
}
|
||||
@ -109,12 +98,27 @@ func (ms Matchers) Match(lset model.LabelSet) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Validate returns true iff all fields of the matcher have valid values.
|
||||
func (m *Matcher) Validate() error {
|
||||
if !model.LabelName(m.Name).IsValid() {
|
||||
return fmt.Errorf("invalid name %q", m.Name)
|
||||
}
|
||||
if m.IsRegex {
|
||||
if _, err := regexp.Compile(m.Value); err != nil {
|
||||
return fmt.Errorf("invalid regular expression %q", m.Value)
|
||||
}
|
||||
} else if !model.LabelValue(m.Value).IsValid() || len(m.Value) == 0 {
|
||||
return fmt.Errorf("invalid value %q", m.Value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fingerprint returns a quasi-unique fingerprint for the matchers.
|
||||
func (ms Matchers) Fingerprint() model.Fingerprint {
|
||||
lset := make(model.LabelSet, 3*len(ms))
|
||||
|
||||
for _, m := range ms {
|
||||
lset[model.LabelName(fmt.Sprintf("%s-%s-%v", m.Name, m.Value, m.isRegex))] = ""
|
||||
lset[model.LabelName(fmt.Sprintf("%s-%s-%v", m.Name, m.Value, m.IsRegex))] = ""
|
||||
}
|
||||
|
||||
return lset.Fingerprint()
|
||||
|
111
types/types.go
111
types/types.go
@ -16,21 +16,21 @@ package types
|
||||
import (
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/satori/go.uuid"
|
||||
)
|
||||
|
||||
// Marker helps to mark alerts as silenced and/or inhibited.
|
||||
// All methods are goroutine-safe.
|
||||
type Marker interface {
|
||||
SetInhibited(alert model.Fingerprint, b bool)
|
||||
SetSilenced(alert model.Fingerprint, sil ...uint64)
|
||||
SetSilenced(alert model.Fingerprint, sil ...uuid.UUID)
|
||||
|
||||
Silenced(alert model.Fingerprint) (uint64, bool)
|
||||
Silenced(alert model.Fingerprint) (uuid.UUID, bool)
|
||||
Inhibited(alert model.Fingerprint) bool
|
||||
}
|
||||
|
||||
@ -38,13 +38,13 @@ type Marker interface {
|
||||
func NewMarker() Marker {
|
||||
return &memMarker{
|
||||
inhibited: map[model.Fingerprint]struct{}{},
|
||||
silenced: map[model.Fingerprint]uint64{},
|
||||
silenced: map[model.Fingerprint]uuid.UUID{},
|
||||
}
|
||||
}
|
||||
|
||||
type memMarker struct {
|
||||
inhibited map[model.Fingerprint]struct{}
|
||||
silenced map[model.Fingerprint]uint64
|
||||
silenced map[model.Fingerprint]uuid.UUID
|
||||
|
||||
mtx sync.RWMutex
|
||||
}
|
||||
@ -57,7 +57,7 @@ func (m *memMarker) Inhibited(alert model.Fingerprint) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
func (m *memMarker) Silenced(alert model.Fingerprint) (uint64, bool) {
|
||||
func (m *memMarker) Silenced(alert model.Fingerprint) (uuid.UUID, bool) {
|
||||
m.mtx.RLock()
|
||||
defer m.mtx.RUnlock()
|
||||
|
||||
@ -76,7 +76,7 @@ func (m *memMarker) SetInhibited(alert model.Fingerprint, b bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *memMarker) SetSilenced(alert model.Fingerprint, sil ...uint64) {
|
||||
func (m *memMarker) SetSilenced(alert model.Fingerprint, sil ...uuid.UUID) {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
|
||||
@ -203,48 +203,91 @@ type MuteFunc func(model.LabelSet) bool
|
||||
// Mutes implements the Muter interface.
|
||||
func (f MuteFunc) Mutes(lset model.LabelSet) bool { return f(lset) }
|
||||
|
||||
// A Silence determines whether a given label set is muted
|
||||
// at the current time.
|
||||
// A Silence determines whether a given label set is muted.
|
||||
type Silence struct {
|
||||
model.Silence
|
||||
|
||||
// A set of matchers determining if an alert is affected
|
||||
// A unique identifier across all connected instances.
|
||||
ID uuid.UUID `json:"id"`
|
||||
// A set of matchers determining if a label set is affect
|
||||
// by the silence.
|
||||
Matchers Matchers `json:"-"`
|
||||
Matchers Matchers `json:"matchers"`
|
||||
|
||||
// Time range of the silence.
|
||||
//
|
||||
// * StartsAt must not be before creation time
|
||||
// * EndsAt must be after StartsAt
|
||||
// * Deleting a silence means to set EndsAt to now
|
||||
// * Time range must not be modified in different ways
|
||||
//
|
||||
// TODO(fabxc): this may potentially be extended by
|
||||
// creation and update timestamps.
|
||||
StartsAt time.Time `json:"startsAt"`
|
||||
EndsAt time.Time `json:"endsAt"`
|
||||
|
||||
// Information about who created the silence for which reason.
|
||||
CreatedBy string `json:"createdBy"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
|
||||
// timeFunc provides the time against which to evaluate
|
||||
// the silence.
|
||||
timeFunc func() time.Time
|
||||
// the silence. Used for test injection.
|
||||
now func() time.Time
|
||||
}
|
||||
|
||||
// NewSilence creates a new internal Silence from a public silence object.
|
||||
func NewSilence(s *model.Silence) *Silence {
|
||||
sil := &Silence{
|
||||
Silence: *s,
|
||||
timeFunc: time.Now,
|
||||
// Validate returns true iff all fields of the silence have valid values.
|
||||
func (s *Silence) Validate() error {
|
||||
if len(s.Matchers) == 0 {
|
||||
return fmt.Errorf("at least one matcher required")
|
||||
}
|
||||
for _, m := range s.Matchers {
|
||||
if !m.IsRegex {
|
||||
sil.Matchers = append(sil.Matchers, NewMatcher(m.Name, m.Value))
|
||||
continue
|
||||
if err := m.Validate(); err != nil {
|
||||
return fmt.Errorf("invalid matcher: %s", err)
|
||||
}
|
||||
rem := NewRegexMatcher(m.Name, regexp.MustCompile("^(?:"+m.Value+")$"))
|
||||
sil.Matchers = append(sil.Matchers, rem)
|
||||
}
|
||||
return sil
|
||||
if s.StartsAt.IsZero() {
|
||||
return fmt.Errorf("start time missing")
|
||||
}
|
||||
if s.EndsAt.IsZero() {
|
||||
return fmt.Errorf("end time missing")
|
||||
}
|
||||
if s.EndsAt.Before(s.StartsAt) {
|
||||
return fmt.Errorf("start time must be before end time")
|
||||
}
|
||||
if s.CreatedBy == "" {
|
||||
return fmt.Errorf("creator information missing")
|
||||
}
|
||||
if s.Comment == "" {
|
||||
return fmt.Errorf("comment missing")
|
||||
}
|
||||
// if s.CreatedAt.IsZero() {
|
||||
// return fmt.Errorf("creation timestamp missing")
|
||||
// }
|
||||
return nil
|
||||
}
|
||||
|
||||
// Init initializes a silence. Must be called before using Mutes.
|
||||
func (s *Silence) Init() error {
|
||||
for _, m := range s.Matchers {
|
||||
if err := m.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Mutes implements the Muter interface.
|
||||
func (sil *Silence) Mutes(lset model.LabelSet) bool {
|
||||
t := sil.timeFunc()
|
||||
|
||||
if t.Before(sil.StartsAt) || t.After(sil.EndsAt) {
|
||||
//
|
||||
// TODO(fabxc): consider making this a function accepting a
|
||||
// timestamp and returning a Muter, i.e. s.Muter(ts).Mutes(lset).
|
||||
func (s *Silence) Mutes(lset model.LabelSet) bool {
|
||||
var now time.Time
|
||||
if s.now != nil {
|
||||
now = s.now()
|
||||
} else {
|
||||
now = time.Now()
|
||||
}
|
||||
if now.Before(s.StartsAt) || now.After(s.EndsAt) {
|
||||
return false
|
||||
}
|
||||
|
||||
b := sil.Matchers.Match(lset)
|
||||
|
||||
return b
|
||||
return s.Matchers.Match(lset)
|
||||
}
|
||||
|
||||
// NotifyInfo holds information about the last successful notification
|
||||
|
Loading…
Reference in New Issue
Block a user