From 0019d4f8586d114b04c96e3f41aa87fc0e26161b Mon Sep 17 00:00:00 2001 From: Fabian Reinartz Date: Fri, 29 Apr 2016 13:46:47 +0200 Subject: [PATCH] provider/boltmem: Implement Notify, add dummies This commit creates dummy implementations of the provider interfaces for the boltmem provider. It implements the NotifyInfo provider in BoltDB. --- provider/boltmem/boltmem.go | 174 +++++++++++++++++++++++++++++++ provider/boltmem/boltmem_test.go | 107 +++++++++++++++++++ 2 files changed, 281 insertions(+) create mode 100644 provider/boltmem/boltmem.go create mode 100644 provider/boltmem/boltmem_test.go diff --git a/provider/boltmem/boltmem.go b/provider/boltmem/boltmem.go new file mode 100644 index 00000000..f1fa065f --- /dev/null +++ b/provider/boltmem/boltmem.go @@ -0,0 +1,174 @@ +package boltmem + +import ( + "encoding/binary" + "path/filepath" + "sync" + + "github.com/boltdb/bolt" + "github.com/prometheus/alertmanager/provider" + "github.com/prometheus/alertmanager/types" + "github.com/prometheus/common/model" +) + +// Alerts gives access to a set of alerts. All methods are goroutine-safe. +type Alerts struct { + mtx sync.RWMutex + alerts map[model.Fingerprint]*types.Alert + + listeners map[int]chan *types.Alert +} + +func NewAlerts() (*Alerts, error) { + return nil, nil +} + +// Subscribe returns an iterator over active alerts that have not been +// resolved and successfully notified about. +// They are not guaranteed to be in chronological order. +func (a *Alerts) Subscribe() provider.AlertIterator { + return nil +} + +// GetPending returns an iterator over all alerts that have +// pending notifications. +func (a *Alerts) GetPending() provider.AlertIterator { + return nil +} + +// Get returns the alert for a given fingerprint. +func (a *Alerts) Get(model.Fingerprint) (*types.Alert, error) { + return nil, nil +} + +// Put adds the given alert to the set. +func (a *Alerts) Put(...*types.Alert) error { + return nil +} + +// Silences gives access to silences. All methods are goroutine-safe. +type Silences struct { + db *bolt.DB +} + +func NewSilences(path string) (*Silences, error) { + db, err := bolt.Open(filepath.Join(path, "silences.db"), 0666, nil) + if err != nil { + return nil, err + } + return &Silences{db: db}, nil +} + +// The Silences provider must implement the Muter interface +// for all its silences. The data provider may have access to an +// optimized view of the data to perform this evaluation. +func (s *Silences) Mutes(lset model.LabelSet) bool { + return false +} + +// All returns all existing silences. +func (s *Silences) All() ([]*types.Silence, error) { + return nil, nil +} + +// Set a new silence. +func (s *Silences) Set(*types.Silence) (uint64, error) { + return 0, nil +} + +// Del removes a silence. +func (s *Silences) Del(uint64) error { + return nil +} + +// Get a silence associated with a fingerprint. +func (s *Silences) Get(uint64) (*types.Silence, error) { + return nil, nil +} + +// Notifies provides information about pending and successful +// notifications. All methods are goroutine-safe. +type Notifies struct { + db *bolt.DB +} + +func NewNotifies(path string) (*Notifies, error) { + db, err := bolt.Open(filepath.Join(path, "notifies.db"), 0666, nil) + if err != nil { + return nil, err + } + err = db.Update(func(tx *bolt.Tx) error { + _, err := tx.CreateBucketIfNotExists(bktNotifies) + return err + }) + return &Notifies{db: db}, err +} + +var ( + bktNotifies = []byte("notifies") +) + +func (n *Notifies) Get(recv string, fps ...model.Fingerprint) ([]*types.NotifyInfo, error) { + var res []*types.NotifyInfo + + err := n.db.View(func(tx *bolt.Tx) error { + b := tx.Bucket(bktNotifies) + + for _, fp := range fps { + k := make([]byte, 16+len([]byte(recv))) + binary.BigEndian.PutUint64(k, uint64(fp)) + copy(k[16:], []byte(recv)) + + v := b.Get(k) + if v == nil { + res = append(res, nil) + continue + } + + ni := &types.NotifyInfo{ + Alert: fp, + Receiver: recv, + Resolved: v[0] == 1, + } + if err := ni.Timestamp.UnmarshalBinary(v[1:]); err != nil { + return err + } + res = append(res, ni) + } + return nil + }) + + return res, err +} + +// Set several notifies at once. All or none must succeed. +func (n *Notifies) Set(ns ...*types.NotifyInfo) error { + err := n.db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket(bktNotifies) + + for _, n := range ns { + k := make([]byte, 16+len([]byte(n.Receiver))) + binary.BigEndian.PutUint64(k, uint64(n.Alert)) + copy(k[16:], []byte(n.Receiver)) + + var v []byte + if n.Resolved { + v = []byte{1} + } else { + v = []byte{0} + } + tsb, err := n.Timestamp.MarshalBinary() + if err != nil { + return err + } + v = append(v, tsb...) + + if err := b.Put(k, v); err != nil { + return err + } + } + + return nil + }) + return err +} diff --git a/provider/boltmem/boltmem_test.go b/provider/boltmem/boltmem_test.go new file mode 100644 index 00000000..b34d5b6b --- /dev/null +++ b/provider/boltmem/boltmem_test.go @@ -0,0 +1,107 @@ +package boltmem + +import ( + "io/ioutil" + "reflect" + "testing" + "time" + + "github.com/kylelemons/godebug/pretty" + "github.com/prometheus/alertmanager/types" + "github.com/prometheus/common/model" +) + +func init() { + pretty.CompareConfig.IncludeUnexported = false +} + +func TestNotifiesSet(t *testing.T) { + var ( + t0 = time.Now() + // t1 = t0.Add(10 * time.Minute) + // t2 = t0.Add(20 * time.Minute) + // t3 = t0.Add(30 * time.Minute) + ) + type query struct { + recv string + fps []model.Fingerprint + expected []*types.NotifyInfo + } + var steps = []struct { + insert []*types.NotifyInfo + queries []query + }{ + { + insert: []*types.NotifyInfo{ + { + Alert: 30000, + Receiver: "receiver", + Resolved: false, + Timestamp: t0, + }, { + Alert: 20000, + Receiver: "receiver", + Resolved: true, + Timestamp: t0, + }, { + Alert: 10000, + Receiver: "receiver", + Resolved: true, + Timestamp: t0, + }, + }, + queries: []query{ + { + recv: "receiver", + fps: []model.Fingerprint{30000, 30001, 20000, 10000}, + expected: []*types.NotifyInfo{ + { + Alert: 30000, + Receiver: "receiver", + Resolved: false, + Timestamp: t0, + }, + nil, { + Alert: 20000, + Receiver: "receiver", + Resolved: true, + Timestamp: t0, + }, { + Alert: 10000, + Receiver: "receiver", + Resolved: true, + Timestamp: t0, + }, + }, + }, + }, + }, + } + + dir, err := ioutil.TempDir("", "notify_set_set") + if err != nil { + t.Fatal(err) + } + + n, err := NewNotifies(dir) + if err != nil { + t.Fatal(err) + } + + for _, step := range steps { + if err := n.Set(step.insert...); err != nil { + t.Fatalf("Insert failed: %s", err) + } + + for _, q := range step.queries { + res, err := n.Get(q.recv, q.fps...) + if err != nil { + t.Fatalf("Query failed: %s", err) + } + if !reflect.DeepEqual(res, q.expected) { + t.Errorf("Unexpected query result") + t.Fatalf(pretty.Compare(res, q.expected)) + } + } + } +}