*: 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:
Fabian Reinartz 2016-05-29 16:12:05 -07:00
parent 2d0444cac1
commit 81cbf3cda7
10 changed files with 255 additions and 183 deletions

21
api.go
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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