Update to store alert information in boltdb

pull/10616/head
Chris Goller 2016-11-03 19:44:28 -05:00
parent cd98eb2433
commit a95c998300
15 changed files with 427 additions and 158 deletions

115
bolt/alerts.go Normal file
View File

@ -0,0 +1,115 @@
package bolt
import (
"context"
"github.com/boltdb/bolt"
"github.com/influxdata/chronograf"
"github.com/influxdata/chronograf/bolt/internal"
)
// Ensure AlertsStore implements chronograf.AlertsStore.
var _ chronograf.AlertRulesStore = &AlertsStore{}
var AlertsBucket = []byte("Alerts")
type AlertsStore struct {
client *Client
}
// All returns all known alerts
func (s *AlertsStore) All(ctx context.Context) ([]chronograf.AlertRule, error) {
var srcs []chronograf.AlertRule
if err := s.client.db.View(func(tx *bolt.Tx) error {
if err := tx.Bucket(AlertsBucket).ForEach(func(k, v []byte) error {
var src chronograf.AlertRule
if err := internal.UnmarshalAlertRule(v, &src); err != nil {
return err
}
srcs = append(srcs, src)
return nil
}); err != nil {
return err
}
return nil
}); err != nil {
return nil, err
}
return srcs, nil
}
// Add creates a new Alerts in the AlertsStore.
func (s *AlertsStore) Add(ctx context.Context, src chronograf.AlertRule) (chronograf.AlertRule, error) {
if err := s.client.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket(AlertsBucket)
if v, err := internal.MarshalAlertRule(&src); err != nil {
return err
} else if err := b.Put([]byte(src.ID), v); err != nil {
return err
}
return nil
}); err != nil {
return chronograf.AlertRule{}, err
}
return src, nil
}
// Delete removes the Alerts from the AlertsStore
func (s *AlertsStore) Delete(ctx context.Context, src chronograf.AlertRule) error {
_, err := s.Get(ctx, src.ID)
if err != nil {
return err
}
if err := s.client.db.Update(func(tx *bolt.Tx) error {
if err := tx.Bucket(AlertsBucket).Delete([]byte(src.ID)); err != nil {
return err
}
return nil
}); err != nil {
return err
}
return nil
}
// Get returns a Alerts if the id exists.
func (s *AlertsStore) Get(ctx context.Context, id string) (chronograf.AlertRule, error) {
var src chronograf.AlertRule
if err := s.client.db.View(func(tx *bolt.Tx) error {
if v := tx.Bucket(AlertsBucket).Get([]byte(id)); v == nil {
return chronograf.ErrAlertNotFound
} else if err := internal.UnmarshalAlertRule(v, &src); err != nil {
return err
}
return nil
}); err != nil {
return chronograf.AlertRule{}, err
}
return src, nil
}
// Update a Alerts
func (s *AlertsStore) Update(ctx context.Context, src chronograf.AlertRule) error {
if err := s.client.db.Update(func(tx *bolt.Tx) error {
// Get an existing alerts with the same ID.
b := tx.Bucket(AlertsBucket)
if v := b.Get([]byte(src.ID)); v == nil {
return chronograf.ErrAlertNotFound
}
if v, err := internal.MarshalAlertRule(&src); err != nil {
return err
} else if err := b.Put([]byte(src.ID), v); err != nil {
return err
}
return nil
}); err != nil {
return err
}
return nil
}

View File

@ -19,6 +19,7 @@ type Client struct {
SourcesStore *SourcesStore
ServersStore *ServersStore
LayoutStore *LayoutStore
AlertsStore *AlertsStore
}
func NewClient() *Client {
@ -26,6 +27,7 @@ func NewClient() *Client {
c.ExplorationStore = &ExplorationStore{client: c}
c.SourcesStore = &SourcesStore{client: c}
c.ServersStore = &ServersStore{client: c}
c.AlertsStore = &AlertsStore{client: c}
c.LayoutStore = &LayoutStore{
client: c,
IDs: &uuid.V4{},
@ -59,20 +61,15 @@ func (c *Client) Open() error {
if _, err := tx.CreateBucketIfNotExists(LayoutBucket); err != nil {
return err
}
// Always create Alerts bucket.
if _, err := tx.CreateBucketIfNotExists(AlertsBucket); err != nil {
return err
}
return nil
}); err != nil {
return err
}
// TODO: Ask @gunnar about these
/*
c.ExplorationStore = &ExplorationStore{client: c}
c.SourcesStore = &SourcesStore{client: c}
c.ServersStore = &ServersStore{client: c}
c.LayoutStore = &LayoutStore{client: c}
*/
return nil
}

View File

@ -1,6 +1,7 @@
package internal
import (
"encoding/json"
"time"
"github.com/gogo/protobuf/proto"
@ -161,3 +162,29 @@ func UnmarshalLayout(data []byte, l *chronograf.Layout) error {
l.Cells = cells
return nil
}
// MarshalAlertRule encodes an alert rule to binary protobuf format.
func MarshalAlertRule(r *chronograf.AlertRule) ([]byte, error) {
j, err := json.Marshal(r)
if err != nil {
return nil, err
}
return proto.Marshal(&AlertRule{
ID: r.ID,
JSON: string(j),
})
}
// UnmarshalAlertRule decodes an alert rule from binary protobuf data.
func UnmarshalAlertRule(data []byte, r *chronograf.AlertRule) error {
var pb AlertRule
if err := proto.Unmarshal(data, &pb); err != nil {
return err
}
err := json.Unmarshal([]byte(pb.JSON), r)
if err != nil {
return err
}
return nil
}

View File

@ -15,6 +15,7 @@ It has these top-level messages:
Layout
Cell
Query
AlertRule
*/
package internal
@ -34,13 +35,13 @@ var _ = math.Inf
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
type Exploration struct {
ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"`
UserID int64 `protobuf:"varint,3,opt,name=UserID,proto3" json:"UserID,omitempty"`
Data string `protobuf:"bytes,4,opt,name=Data,proto3" json:"Data,omitempty"`
CreatedAt int64 `protobuf:"varint,5,opt,name=CreatedAt,proto3" json:"CreatedAt,omitempty"`
UpdatedAt int64 `protobuf:"varint,6,opt,name=UpdatedAt,proto3" json:"UpdatedAt,omitempty"`
Default bool `protobuf:"varint,7,opt,name=Default,proto3" json:"Default,omitempty"`
ID int64 `protobuf:"varint,1,opt,name=ID,json=iD,proto3" json:"ID,omitempty"`
Name string `protobuf:"bytes,2,opt,name=Name,json=name,proto3" json:"Name,omitempty"`
UserID int64 `protobuf:"varint,3,opt,name=UserID,json=userID,proto3" json:"UserID,omitempty"`
Data string `protobuf:"bytes,4,opt,name=Data,json=data,proto3" json:"Data,omitempty"`
CreatedAt int64 `protobuf:"varint,5,opt,name=CreatedAt,json=createdAt,proto3" json:"CreatedAt,omitempty"`
UpdatedAt int64 `protobuf:"varint,6,opt,name=UpdatedAt,json=updatedAt,proto3" json:"UpdatedAt,omitempty"`
Default bool `protobuf:"varint,7,opt,name=Default,json=default,proto3" json:"Default,omitempty"`
}
func (m *Exploration) Reset() { *m = Exploration{} }
@ -49,13 +50,13 @@ func (*Exploration) ProtoMessage() {}
func (*Exploration) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{0} }
type Source struct {
ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"`
Type string `protobuf:"bytes,3,opt,name=Type,proto3" json:"Type,omitempty"`
Username string `protobuf:"bytes,4,opt,name=Username,proto3" json:"Username,omitempty"`
Password string `protobuf:"bytes,5,opt,name=Password,proto3" json:"Password,omitempty"`
URL string `protobuf:"bytes,6,opt,name=URL,proto3" json:"URL,omitempty"`
Default bool `protobuf:"varint,7,opt,name=Default,proto3" json:"Default,omitempty"`
ID int64 `protobuf:"varint,1,opt,name=ID,json=iD,proto3" json:"ID,omitempty"`
Name string `protobuf:"bytes,2,opt,name=Name,json=name,proto3" json:"Name,omitempty"`
Type string `protobuf:"bytes,3,opt,name=Type,json=type,proto3" json:"Type,omitempty"`
Username string `protobuf:"bytes,4,opt,name=Username,json=username,proto3" json:"Username,omitempty"`
Password string `protobuf:"bytes,5,opt,name=Password,json=password,proto3" json:"Password,omitempty"`
URL string `protobuf:"bytes,6,opt,name=URL,json=uRL,proto3" json:"URL,omitempty"`
Default bool `protobuf:"varint,7,opt,name=Default,json=default,proto3" json:"Default,omitempty"`
}
func (m *Source) Reset() { *m = Source{} }
@ -64,12 +65,12 @@ func (*Source) ProtoMessage() {}
func (*Source) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{1} }
type Server struct {
ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"`
Username string `protobuf:"bytes,3,opt,name=Username,proto3" json:"Username,omitempty"`
Password string `protobuf:"bytes,4,opt,name=Password,proto3" json:"Password,omitempty"`
URL string `protobuf:"bytes,5,opt,name=URL,proto3" json:"URL,omitempty"`
SrcID int64 `protobuf:"varint,6,opt,name=SrcID,proto3" json:"SrcID,omitempty"`
ID int64 `protobuf:"varint,1,opt,name=ID,json=iD,proto3" json:"ID,omitempty"`
Name string `protobuf:"bytes,2,opt,name=Name,json=name,proto3" json:"Name,omitempty"`
Username string `protobuf:"bytes,3,opt,name=Username,json=username,proto3" json:"Username,omitempty"`
Password string `protobuf:"bytes,4,opt,name=Password,json=password,proto3" json:"Password,omitempty"`
URL string `protobuf:"bytes,5,opt,name=URL,json=uRL,proto3" json:"URL,omitempty"`
SrcID int64 `protobuf:"varint,6,opt,name=SrcID,json=srcID,proto3" json:"SrcID,omitempty"`
}
func (m *Server) Reset() { *m = Server{} }
@ -78,10 +79,10 @@ func (*Server) ProtoMessage() {}
func (*Server) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{2} }
type Layout struct {
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
Application string `protobuf:"bytes,2,opt,name=Application,proto3" json:"Application,omitempty"`
Measurement string `protobuf:"bytes,3,opt,name=Measurement,proto3" json:"Measurement,omitempty"`
Cells []*Cell `protobuf:"bytes,4,rep,name=Cells" json:"Cells,omitempty"`
ID string `protobuf:"bytes,1,opt,name=ID,json=iD,proto3" json:"ID,omitempty"`
Application string `protobuf:"bytes,2,opt,name=Application,json=application,proto3" json:"Application,omitempty"`
Measurement string `protobuf:"bytes,3,opt,name=Measurement,json=measurement,proto3" json:"Measurement,omitempty"`
Cells []*Cell `protobuf:"bytes,4,rep,name=Cells,json=cells" json:"Cells,omitempty"`
}
func (m *Layout) Reset() { *m = Layout{} }
@ -119,9 +120,9 @@ func (m *Cell) GetQueries() []*Query {
}
type Query struct {
Command string `protobuf:"bytes,1,opt,name=Command,proto3" json:"Command,omitempty"`
DB string `protobuf:"bytes,2,opt,name=DB,proto3" json:"DB,omitempty"`
RP string `protobuf:"bytes,3,opt,name=RP,proto3" json:"RP,omitempty"`
Command string `protobuf:"bytes,1,opt,name=Command,json=command,proto3" json:"Command,omitempty"`
DB string `protobuf:"bytes,2,opt,name=DB,json=dB,proto3" json:"DB,omitempty"`
RP string `protobuf:"bytes,3,opt,name=RP,json=rP,proto3" json:"RP,omitempty"`
}
func (m *Query) Reset() { *m = Query{} }
@ -129,6 +130,16 @@ func (m *Query) String() string { return proto.CompactTextString(m) }
func (*Query) ProtoMessage() {}
func (*Query) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{5} }
type AlertRule struct {
ID string `protobuf:"bytes,1,opt,name=ID,json=iD,proto3" json:"ID,omitempty"`
JSON string `protobuf:"bytes,2,opt,name=JSON,json=jSON,proto3" json:"JSON,omitempty"`
}
func (m *AlertRule) Reset() { *m = AlertRule{} }
func (m *AlertRule) String() string { return proto.CompactTextString(m) }
func (*AlertRule) ProtoMessage() {}
func (*AlertRule) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{6} }
func init() {
proto.RegisterType((*Exploration)(nil), "internal.Exploration")
proto.RegisterType((*Source)(nil), "internal.Source")
@ -136,38 +147,42 @@ func init() {
proto.RegisterType((*Layout)(nil), "internal.Layout")
proto.RegisterType((*Cell)(nil), "internal.Cell")
proto.RegisterType((*Query)(nil), "internal.Query")
proto.RegisterType((*AlertRule)(nil), "internal.AlertRule")
}
func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) }
var fileDescriptorInternal = []byte{
// 442 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x93, 0xcf, 0x8e, 0xd3, 0x30,
0x10, 0xc6, 0xe5, 0x24, 0x4e, 0x9b, 0x29, 0x2a, 0xc8, 0x42, 0xc8, 0x42, 0x1c, 0xa2, 0x88, 0x43,
0xb9, 0xec, 0x01, 0x9e, 0xa0, 0xdb, 0x70, 0xa8, 0xb4, 0xa0, 0xe2, 0xa5, 0x0f, 0x60, 0x5a, 0xa3,
0x8d, 0x94, 0x26, 0xc1, 0x71, 0x68, 0x73, 0xe5, 0x0a, 0x8f, 0xc1, 0x1b, 0xf0, 0x82, 0x68, 0x26,
0xee, 0x9f, 0xc3, 0x6a, 0xd5, 0xdb, 0x7c, 0x33, 0x5f, 0x34, 0x3f, 0x7f, 0x76, 0x60, 0x5a, 0x54,
0xce, 0xd8, 0x4a, 0x97, 0x37, 0x8d, 0xad, 0x5d, 0x2d, 0xc6, 0x47, 0x9d, 0xfd, 0x63, 0x30, 0xf9,
0x78, 0x68, 0xca, 0xda, 0x6a, 0x57, 0xd4, 0x95, 0x98, 0x42, 0xb0, 0xcc, 0x25, 0x4b, 0xd9, 0x2c,
0x54, 0xc1, 0x32, 0x17, 0x02, 0xa2, 0xcf, 0x7a, 0x67, 0x64, 0x90, 0xb2, 0x59, 0xa2, 0xa8, 0x16,
0xaf, 0x20, 0x5e, 0xb7, 0xc6, 0x2e, 0x73, 0x19, 0x92, 0xcf, 0x2b, 0xf4, 0xe6, 0xda, 0x69, 0x19,
0x0d, 0x5e, 0xac, 0xc5, 0x1b, 0x48, 0x16, 0xd6, 0x68, 0x67, 0xb6, 0x73, 0x27, 0x39, 0xd9, 0xcf,
0x0d, 0x9c, 0xae, 0x9b, 0xad, 0x9f, 0xc6, 0xc3, 0xf4, 0xd4, 0x10, 0x12, 0x46, 0xb9, 0xf9, 0xae,
0xbb, 0xd2, 0xc9, 0x51, 0xca, 0x66, 0x63, 0x75, 0x94, 0xd9, 0x5f, 0x06, 0xf1, 0x7d, 0xdd, 0xd9,
0x8d, 0xb9, 0x0a, 0x58, 0x40, 0xf4, 0xb5, 0x6f, 0x0c, 0xe1, 0x26, 0x8a, 0x6a, 0xf1, 0x1a, 0xc6,
0x88, 0x5d, 0xa1, 0x77, 0x00, 0x3e, 0x69, 0x9c, 0xad, 0x74, 0xdb, 0xee, 0x6b, 0xbb, 0x25, 0xe6,
0x44, 0x9d, 0xb4, 0x78, 0x01, 0xe1, 0x5a, 0xdd, 0x11, 0x6c, 0xa2, 0xb0, 0x7c, 0x02, 0xf3, 0x0f,
0x62, 0x1a, 0xfb, 0xd3, 0xd8, 0xab, 0x30, 0x2f, 0x91, 0xc2, 0x27, 0x90, 0xa2, 0xc7, 0x91, 0xf8,
0x19, 0xe9, 0x25, 0xf0, 0x7b, 0xbb, 0x59, 0xe6, 0x3e, 0xd3, 0x41, 0x64, 0xbf, 0x18, 0xc4, 0x77,
0xba, 0xaf, 0x3b, 0x77, 0x81, 0x93, 0x10, 0x4e, 0x0a, 0x93, 0x79, 0xd3, 0x94, 0xc5, 0x86, 0x5e,
0x81, 0xa7, 0xba, 0x6c, 0xa1, 0xe3, 0x93, 0xd1, 0x6d, 0x67, 0xcd, 0xce, 0x54, 0xce, 0xf3, 0x5d,
0xb6, 0xc4, 0x5b, 0xe0, 0x0b, 0x53, 0x96, 0xad, 0x8c, 0xd2, 0x70, 0x36, 0x79, 0x3f, 0xbd, 0x39,
0x3d, 0x3a, 0x6c, 0xab, 0x61, 0x98, 0xfd, 0x66, 0x10, 0x61, 0x25, 0x9e, 0x01, 0x3b, 0x10, 0x01,
0x57, 0xec, 0x80, 0xaa, 0xa7, 0xb5, 0x5c, 0xb1, 0x1e, 0xd5, 0x9e, 0x56, 0x70, 0xc5, 0xf6, 0xa8,
0x1e, 0xe8, 0xd0, 0x5c, 0xb1, 0x07, 0xf1, 0x0e, 0x46, 0x3f, 0x3a, 0x63, 0x0b, 0xd3, 0x4a, 0x4e,
0x8b, 0x9e, 0x9f, 0x17, 0x7d, 0xe9, 0x8c, 0xed, 0xd5, 0x71, 0x8e, 0x1f, 0x16, 0xfe, 0xa6, 0x58,
0x81, 0x91, 0x53, 0xb4, 0xa3, 0x21, 0x72, 0xac, 0xb3, 0x39, 0x70, 0xfa, 0x06, 0x2f, 0x71, 0x51,
0xef, 0x76, 0xba, 0xda, 0xfa, 0x54, 0x8e, 0x12, 0xa3, 0xca, 0x6f, 0x7d, 0x22, 0x41, 0x7e, 0x8b,
0x5a, 0xad, 0xfc, 0xf9, 0x03, 0xb5, 0xfa, 0x16, 0xd3, 0x2f, 0xf5, 0xe1, 0x7f, 0x00, 0x00, 0x00,
0xff, 0xff, 0x85, 0xa7, 0xa7, 0xb1, 0x64, 0x03, 0x00, 0x00,
// 486 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x93, 0xcf, 0x8e, 0xd3, 0x3c,
0x14, 0xc5, 0xe5, 0x26, 0x4e, 0x9a, 0xdb, 0x4f, 0xfd, 0x90, 0x85, 0x50, 0x84, 0x58, 0x54, 0x11,
0x8b, 0xb2, 0x19, 0x24, 0x78, 0x82, 0x4e, 0xc3, 0xa2, 0xa8, 0x74, 0x8a, 0x4b, 0x1f, 0xc0, 0x24,
0x17, 0x4d, 0x50, 0xfe, 0xe1, 0xd8, 0xb4, 0xd9, 0xb2, 0x85, 0xc7, 0xe0, 0x0d, 0x78, 0x41, 0x64,
0xd7, 0x21, 0x23, 0x81, 0x46, 0xb3, 0x3c, 0xf7, 0xdc, 0xe8, 0xfe, 0xee, 0xb9, 0x0e, 0xcc, 0x8b,
0x5a, 0xa1, 0xac, 0x45, 0x79, 0xd5, 0xca, 0x46, 0x35, 0x6c, 0x3a, 0xe8, 0xe4, 0x17, 0x81, 0xd9,
0x9b, 0x73, 0x5b, 0x36, 0x52, 0xa8, 0xa2, 0xa9, 0xd9, 0x1c, 0x26, 0x9b, 0x34, 0x26, 0x0b, 0xb2,
0xf4, 0xf8, 0xa4, 0x48, 0x19, 0x03, 0x7f, 0x27, 0x2a, 0x8c, 0x27, 0x0b, 0xb2, 0x8c, 0xb8, 0x5f,
0x8b, 0x0a, 0xd9, 0x13, 0x08, 0x8e, 0x1d, 0xca, 0x4d, 0x1a, 0x7b, 0xb6, 0x2f, 0xd0, 0x56, 0x99,
0xde, 0x54, 0x28, 0x11, 0xfb, 0x97, 0xde, 0x5c, 0x28, 0xc1, 0x9e, 0x41, 0xb4, 0x96, 0x28, 0x14,
0xe6, 0x2b, 0x15, 0x53, 0xdb, 0x1e, 0x65, 0x43, 0xc1, 0xb8, 0xc7, 0x36, 0x77, 0x6e, 0x70, 0x71,
0xf5, 0x50, 0x60, 0x31, 0x84, 0x29, 0x7e, 0x12, 0xba, 0x54, 0x71, 0xb8, 0x20, 0xcb, 0x29, 0x0f,
0xf3, 0x8b, 0x4c, 0x7e, 0x12, 0x08, 0x0e, 0x8d, 0x96, 0x19, 0x3e, 0x08, 0x98, 0x81, 0xff, 0xa1,
0x6f, 0xd1, 0xe2, 0x46, 0xdc, 0x57, 0x7d, 0x8b, 0xec, 0x29, 0x4c, 0xcd, 0x12, 0xc6, 0x77, 0xc0,
0x53, 0xed, 0xb4, 0xf1, 0xf6, 0xa2, 0xeb, 0x4e, 0x8d, 0xcc, 0x2d, 0x73, 0xc4, 0xa7, 0xad, 0xd3,
0xec, 0x11, 0x78, 0x47, 0xbe, 0xb5, 0xb0, 0x11, 0xf7, 0x34, 0xdf, 0xde, 0x83, 0xf9, 0xc3, 0x60,
0xa2, 0xfc, 0x8a, 0xf2, 0x41, 0x98, 0x77, 0x91, 0xbc, 0x7b, 0x90, 0xfc, 0x7f, 0x23, 0xd1, 0x11,
0xe9, 0x31, 0xd0, 0x83, 0xcc, 0x36, 0xa9, 0xcb, 0x94, 0x76, 0x46, 0x24, 0xdf, 0x08, 0x04, 0x5b,
0xd1, 0x37, 0x5a, 0xdd, 0xc1, 0x89, 0x2c, 0xce, 0x02, 0x66, 0xab, 0xb6, 0x2d, 0x8b, 0xcc, 0xbe,
0x02, 0x47, 0x35, 0x13, 0x63, 0xc9, 0x74, 0xbc, 0x43, 0xd1, 0x69, 0x89, 0x15, 0xd6, 0xca, 0xf1,
0xcd, 0xaa, 0xb1, 0xc4, 0x9e, 0x03, 0x5d, 0x63, 0x59, 0x76, 0xb1, 0xbf, 0xf0, 0x96, 0xb3, 0x57,
0xf3, 0xab, 0x3f, 0x8f, 0xce, 0x94, 0x39, 0xcd, 0x8c, 0x99, 0x7c, 0x27, 0xe0, 0x1b, 0xcd, 0xfe,
0x03, 0x72, 0xb6, 0x04, 0x94, 0x93, 0xb3, 0x51, 0xbd, 0x1d, 0x4b, 0x39, 0xe9, 0x8d, 0x3a, 0xd9,
0x11, 0x94, 0x93, 0x93, 0x51, 0xb7, 0x76, 0x69, 0xca, 0xc9, 0x2d, 0x7b, 0x01, 0xe1, 0x17, 0x8d,
0xb2, 0xc0, 0x2e, 0xa6, 0x76, 0xd0, 0xff, 0xe3, 0xa0, 0xf7, 0x1a, 0x65, 0xcf, 0x07, 0xdf, 0x7c,
0x58, 0xb8, 0x4b, 0x91, 0xc2, 0x44, 0x6e, 0xa3, 0x0d, 0xc7, 0xc8, 0x93, 0x15, 0x50, 0xfb, 0x8d,
0x39, 0xe2, 0xba, 0xa9, 0x2a, 0x51, 0xe7, 0x2e, 0x95, 0x30, 0xbb, 0x48, 0x13, 0x55, 0x7a, 0xed,
0x12, 0x99, 0xe4, 0xd7, 0x46, 0xf3, 0xbd, 0xdb, 0x7f, 0x22, 0xf7, 0xc9, 0x4b, 0x88, 0x56, 0x25,
0x4a, 0xc5, 0x75, 0x89, 0x7f, 0xe5, 0xca, 0xc0, 0x7f, 0x7b, 0xb8, 0xd9, 0x0d, 0x67, 0xfe, 0x7c,
0xb8, 0xd9, 0x7d, 0x0c, 0xec, 0x3f, 0xf8, 0xfa, 0x77, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe0, 0xb6,
0x3a, 0xf1, 0x95, 0x03, 0x00, 0x00,
}

View File

@ -52,3 +52,8 @@ message Query {
string DB = 2; // DB the database for the query (optional)
string RP = 3; // RP is a retention policy and optional;
}
message AlertRule {
string ID = 1; // ID is the unique ID of this alert rule
string JSON = 2; // JSON byte representation of the alert
}

View File

@ -13,6 +13,7 @@ const (
ErrSourceNotFound = Error("source not found")
ErrServerNotFound = Error("server not found")
ErrLayoutNotFound = Error("layout not found")
ErrAlertNotFound = Error("alert not found")
ErrAuthentication = Error("user not authenticated")
)
@ -122,34 +123,15 @@ type Ticker interface {
Generate(AlertRule) (TICKScript, error)
}
// DeadmanValue specifies the timeout duration of a deadman alert.
type DeadmanValue struct {
Period string `json:"period, omitempty"` // Period is the max time data can be missed before an alert
}
// RelativeValue specifies the trigger logic for a relative value change alert.
type RelativeValue struct {
Change string `json:"change,omitempty"` // Change specifies if the change is a percent or absolute
Period string `json:"period,omitempty"` // Period is the window to search for alerting criteria
Shift string `json:"shift,omitempty"` // Shift is the amount of time to look into the past for the alert to compare to the present
Operator string `json:"operator,omitempty"` // Operator for alert comparison
Value string `json:"value,omitempty"` // Value is the boundary value when alert goes critical
}
// ThresholdValue specifies the trigger logic for a threshold change alert.
type ThresholdValue struct {
Period string `json:"period,omitempty"` // Period is the window to search for the alerting criteria
// TriggerValues specifies the alerting logic for a specific trigger type
type TriggerValues struct {
Change string `json:"change,omitempty"` // Change specifies if the change is a percent or absolute
Period string `json:"period,omitempty"` // Period is the window to search for alerting criteria
Shift string `json:"shift,omitempty"` // Shift is the amount of time to look into the past for the alert to compare to the present
Operator string `json:"operator,omitempty"` // Operator for alert comparison
Value string `json:"value,omitempty"` // Value is the boundary value when alert goes critical
Percentile string `json:"percentile,omitempty"` // Percentile is defined only when Relation is not "Once"
Relation string `json:"relation,omitempty"` // Relation defines the logic about how often the threshold is met to be an alert.
Value string `json:"value,omitempty"` // Value is the boundary value when alert goes critical
}
// TriggerValues specifies which of the trigger types defines the alerting logic. One of these whould not be nil.
type TriggerValues struct {
Deadman *DeadmanValue `json:"deadman,omitempty"`
Relative *RelativeValue `json:"relative,omitempty"`
Threshold *ThresholdValue `json:"threshold,omitempty"`
}
// QueryConfig represents UI query from the data explorer

View File

@ -6,14 +6,37 @@ import (
"github.com/influxdata/chronograf"
)
func kapaService(alert string) (string, error) {
switch alert {
case "hipchat":
return "hipChat", nil
case "opsgenie":
return "opsGenie", nil
case "pagerduty":
return "pagerDuty", nil
case "victorops":
return "victorOps", nil
case "smtp":
return "email", nil
case "sensu", "slack", "email", "talk", "telegram":
return alert, nil
default:
return "", fmt.Errorf("Unsupport alert %s", alert)
}
}
// AlertServices generates alert chaining methods to be attached to an alert from all rule Services
func AlertServices(rule chronograf.AlertRule) (string, error) {
alert := ""
for _, service := range rule.Alerts {
if err := ValidateAlert(service); err != nil {
srv, err := kapaService(service)
if err != nil {
return "", err
}
alert = alert + fmt.Sprintf(".%s()", service)
if err := ValidateAlert(srv); err != nil {
return "", err
}
alert = alert + fmt.Sprintf(".%s()", srv)
}
return alert, nil
}

View File

@ -16,7 +16,7 @@ func TestAlertServices(t *testing.T) {
{
name: "Test several valid services",
rule: chronograf.AlertRule{
Alerts: []string{"slack", "victorOps", "email"},
Alerts: []string{"slack", "victorops", "email"},
},
want: `alert()
.slack()

View File

@ -1,6 +1,7 @@
package kapacitor
import (
"encoding/json"
"fmt"
"testing"
@ -11,15 +12,13 @@ func TestGenerate(t *testing.T) {
alert := chronograf.AlertRule{
Name: "name",
Trigger: "relative",
Alerts: []string{"slack", "victorOps", "email"},
Alerts: []string{"slack", "victorops", "email"},
TriggerValues: chronograf.TriggerValues{
Relative: &chronograf.RelativeValue{
Change: "change",
Period: "10m",
Shift: "1m",
Operator: "greater than",
Value: "90",
},
Change: "change",
Period: "10m",
Shift: "1m",
Operator: "greater than",
Value: "90",
},
Every: "30s",
Query: chronograf.QueryConfig{
@ -67,15 +66,13 @@ func TestThreshold(t *testing.T) {
alert := chronograf.AlertRule{
Name: "name",
Trigger: "threshold",
Alerts: []string{"slack", "victorOps", "email"},
Alerts: []string{"slack", "victorops", "email"},
TriggerValues: chronograf.TriggerValues{
Threshold: &chronograf.ThresholdValue{
Relation: "once",
Period: "10m",
Percentile: "", // TODO: if relation is not once then this will have a number
Operator: "greater than",
Value: "90",
},
Relation: "once",
Period: "10m",
Percentile: "", // TODO: if relation is not once then this will have a number
Operator: "greater than",
Value: "90",
},
Every: "30s",
Message: "message",
@ -222,15 +219,13 @@ func TestRelative(t *testing.T) {
alert := chronograf.AlertRule{
Name: "name",
Trigger: "relative",
Alerts: []string{"slack", "victorOps", "email"},
Alerts: []string{"slack", "victorops", "email"},
TriggerValues: chronograf.TriggerValues{
Relative: &chronograf.RelativeValue{
Change: "change",
Period: "10m",
Shift: "1m",
Operator: "greater than",
Value: "90",
},
Change: "change",
Period: "10m",
Shift: "1m",
Operator: "greater than",
Value: "90",
},
Every: "30s",
Message: "message",
@ -382,6 +377,8 @@ trigger
fmt.Printf("%s", got)
t.Errorf("%q. Relative() = %v, want %v", tt.name, got, tt.want)
}
b, _ := json.Marshal(tt.alert)
fmt.Printf("%s", string(b))
}
}
@ -389,11 +386,9 @@ func TestDeadman(t *testing.T) {
alert := chronograf.AlertRule{
Name: "name",
Trigger: "deadman",
Alerts: []string{"slack", "victorOps", "email"},
Alerts: []string{"slack", "victorops", "email"},
TriggerValues: chronograf.TriggerValues{
Deadman: &chronograf.DeadmanValue{
Period: "10m",
},
Period: "10m",
},
Every: "30s",
Message: "message",

View File

@ -59,13 +59,13 @@ func Trigger(rule chronograf.AlertRule) (string, error) {
case "deadman":
return DeadmanTrigger, nil
case "relative":
op, err := kapaOperator(rule.TriggerValues.Relative.Operator)
op, err := kapaOperator(rule.TriggerValues.Operator)
if err != nil {
return "", err
}
return fmt.Sprintf(RelativeTrigger, op), nil
case "threshold":
op, err := kapaOperator(rule.TriggerValues.Threshold.Operator)
op, err := kapaOperator(rule.TriggerValues.Operator)
if err != nil {
return "", err
}

View File

@ -35,9 +35,7 @@ func TestTrigger(t *testing.T) {
rule: chronograf.AlertRule{
Trigger: "relative",
TriggerValues: chronograf.TriggerValues{
Relative: &chronograf.RelativeValue{
Operator: "greater than",
},
Operator: "greater than",
},
},
want: `var past = data
@ -68,9 +66,7 @@ var trigger = past
rule: chronograf.AlertRule{
Trigger: "threshold",
TriggerValues: chronograf.TriggerValues{
Threshold: &chronograf.ThresholdValue{
Operator: "greater than",
},
Operator: "greater than",
},
},
want: `var trigger = data

View File

@ -41,8 +41,8 @@ func Vars(rule chronograf.AlertRule) (string, error) {
`
return fmt.Sprintf(vars,
common,
rule.TriggerValues.Threshold.Period,
rule.TriggerValues.Threshold.Value), nil
rule.TriggerValues.Period,
rule.TriggerValues.Value), nil
case "relative":
vars := `
%s
@ -52,9 +52,9 @@ func Vars(rule chronograf.AlertRule) (string, error) {
`
return fmt.Sprintf(vars,
common,
rule.TriggerValues.Relative.Period,
rule.TriggerValues.Relative.Shift,
rule.TriggerValues.Relative.Value,
rule.TriggerValues.Period,
rule.TriggerValues.Shift,
rule.TriggerValues.Value,
), nil
case "deadman":
vars := `
@ -65,7 +65,7 @@ func Vars(rule chronograf.AlertRule) (string, error) {
return fmt.Sprintf(vars,
common,
"0.0", // deadman threshold hardcoded to zero
rule.TriggerValues.Deadman.Period,
rule.TriggerValues.Period,
), nil
default:
return "", fmt.Errorf("Unknown trigger mechanism")

View File

@ -293,21 +293,53 @@ func (h *Service) KapacitorTasksPost(w http.ResponseWriter, r *http.Request) {
ID: &uuid.V4{},
}
var rule chronograf.AlertRule
task, err := c.Create(ctx, rule)
var req chronograf.AlertRule
if err = json.NewDecoder(r.Body).Decode(&req); err != nil {
invalidJSON(w)
return
}
// TODO: validate this data
/*
if err := req.Valid(); err != nil {
invalidData(w, err)
return
}
*/
task, err := c.Create(ctx, req)
if err != nil {
Error(w, http.StatusInternalServerError, err.Error())
return
}
// TODO: Set the tickscript the store
// TODO: possibly use the Href in update to the store
_ = task.TICKScript
_ = task.ID
_ = task.Href
// TODO: Add the task from the store
// TODO: Return POST response
w.WriteHeader(http.StatusNoContent)
req.ID = task.ID
rule, err := h.AlertRulesStore.Add(ctx, req)
if err != nil {
Error(w, http.StatusInternalServerError, err.Error())
return
}
res := alertResponse{
AlertRule: rule,
Links: alertLinks{
Self: fmt.Sprintf("/chronograf/v1/sources/%d/kapacitors/%d/tasks/%s", srv.SrcID, srv.ID, req.ID),
Kapacitor: fmt.Sprintf("/chronograf/v1/sources/%d/kapacitors/%d/proxy?path=%s", srv.SrcID, srv.ID, url.QueryEscape(task.Href)),
},
TICKScript: string(task.TICKScript),
}
w.Header().Add("Location", res.Links.Self)
encodeJSON(w, http.StatusCreated, res, h.Logger)
}
type alertLinks struct {
Self string `json:"self"`
Kapacitor string `json:"kapacitor"`
}
type alertResponse struct {
chronograf.AlertRule
TICKScript string `json:"tickscript"`
Links alertLinks `json:"links"`
}
// KapacitorTasksPut proxies PATCH to kapacitor
@ -338,19 +370,40 @@ func (h *Service) KapacitorTasksPut(w http.ResponseWriter, r *http.Request) {
Password: srv.Password,
Ticker: &kapa.Alert{},
}
// TODO: Pull rule from PUT parameters
var rule chronograf.AlertRule
task, err := c.Update(ctx, c.Href(tid), rule)
var req chronograf.AlertRule
if err = json.NewDecoder(r.Body).Decode(&req); err != nil {
invalidJSON(w)
return
}
// TODO: validate this data
/*
if err := req.Valid(); err != nil {
invalidData(w, err)
return
}
*/
req.ID = tid
task, err := c.Update(ctx, c.Href(tid), req)
if err != nil {
Error(w, http.StatusInternalServerError, err.Error())
return
}
// TODO: Set the tickscript in the update to the store
// TODO: possibly use the Href in update to the store
_ = task.TICKScript
// TODO: Update the task from the store
// TODO: Return Patch response
w.WriteHeader(http.StatusNoContent)
if err := h.AlertRulesStore.Update(ctx, req); err != nil {
Error(w, http.StatusInternalServerError, err.Error())
return
}
res := alertResponse{
AlertRule: req,
Links: alertLinks{
Self: fmt.Sprintf("/chronograf/v1/sources/%d/kapacitors/%d/tasks/%s", srv.SrcID, srv.ID, req.ID),
Kapacitor: fmt.Sprintf("/chronograf/v1/sources/%d/kapacitors/%d/proxy?path=%s", srv.SrcID, srv.ID, url.QueryEscape(task.Href)),
},
TICKScript: string(task.TICKScript),
}
encodeJSON(w, http.StatusOK, res, h.Logger)
}
// KapacitorTasksGet retrieves all tasks
@ -373,7 +426,40 @@ func (h *Service) KapacitorTasksGet(w http.ResponseWriter, r *http.Request) {
notFound(w, id)
return
}
// TODO: GET tasks from store
rules, err := h.AlertRulesStore.All(ctx)
if err != nil {
Error(w, http.StatusInternalServerError, err.Error())
return
}
ticker := &kapa.Alert{}
c := kapa.Client{}
res := allAlertsResponse{
Tasks: []alertResponse{},
}
for _, rule := range rules {
tickscript, err := ticker.Generate(rule)
if err != nil {
Error(w, http.StatusInternalServerError, err.Error())
return
}
ar := alertResponse{
AlertRule: rule,
Links: alertLinks{
Self: fmt.Sprintf("/chronograf/v1/sources/%d/kapacitors/%d/tasks/%s", srv.SrcID, srv.ID, rule.ID),
Kapacitor: fmt.Sprintf("/chronograf/v1/sources/%d/kapacitors/%d/proxy?path=%s", srv.SrcID, srv.ID, url.QueryEscape(c.Href(rule.ID))),
},
TICKScript: string(tickscript),
}
res.Tasks = append(res.Tasks, ar)
}
encodeJSON(w, http.StatusOK, res, h.Logger)
}
type allAlertsResponse struct {
Tasks []alertResponse `json:"tasks"`
}
// KapacitorTasksGet retrieves specific task
@ -397,8 +483,29 @@ func (h *Service) KapacitorTasksID(w http.ResponseWriter, r *http.Request) {
return
}
tid := httprouter.GetParamFromContext(ctx, "tid")
// TODO: GET task from store
_ = tid
rule, err := h.AlertRulesStore.Get(ctx, tid)
if err != nil {
Error(w, http.StatusInternalServerError, err.Error())
return
}
ticker := &kapa.Alert{}
c := kapa.Client{}
tickscript, err := ticker.Generate(rule)
if err != nil {
Error(w, http.StatusInternalServerError, err.Error())
return
}
res := alertResponse{
AlertRule: rule,
Links: alertLinks{
Self: fmt.Sprintf("/chronograf/v1/sources/%d/kapacitors/%d/tasks/%s", srv.SrcID, srv.ID, rule.ID),
Kapacitor: fmt.Sprintf("/chronograf/v1/sources/%d/kapacitors/%d/proxy?path=%s", srv.SrcID, srv.ID, url.QueryEscape(c.Href(rule.ID))),
},
TICKScript: string(tickscript),
}
encodeJSON(w, http.StatusOK, res, h.Logger)
}
// KapacitorTasksDelete proxies DELETE to kapacitor
@ -422,7 +529,6 @@ func (h *Service) KapacitorTasksDelete(w http.ResponseWriter, r *http.Request) {
return
}
// TODO: Delete the task from the store
tid := httprouter.GetParamFromContext(ctx, "tid")
c := kapa.Client{
URL: srv.URL,
@ -433,5 +539,11 @@ func (h *Service) KapacitorTasksDelete(w http.ResponseWriter, r *http.Request) {
Error(w, http.StatusInternalServerError, err.Error())
return
}
if err := h.AlertRulesStore.Delete(ctx, chronograf.AlertRule{ID: tid}); err != nil {
Error(w, http.StatusInternalServerError, err.Error())
return
}
w.WriteHeader(http.StatusNoContent)
}

View File

@ -128,6 +128,7 @@ func openService(boltPath, cannedPath string, logger chronograf.Logger) Service
ServersStore: db.ServersStore,
TimeSeries: &influx.Client{},
LayoutStore: layouts,
AlertRulesStore: db.AlertsStore,
}
}

View File

@ -8,6 +8,7 @@ type Service struct {
SourcesStore chronograf.SourcesStore
ServersStore chronograf.ServersStore
LayoutStore chronograf.LayoutStore
AlertRulesStore chronograf.AlertRulesStore
TimeSeries chronograf.TimeSeries
Logger chronograf.Logger
}