Alerting: Add tags to alert rules (#10989)

Ref #6552
This commit is contained in:
Thibault Chataigner
2019-06-06 13:29:30 +02:00
committed by Carl Bergquist
parent 34f314552d
commit e06abb30aa
13 changed files with 201 additions and 70 deletions
+15
View File
@@ -117,6 +117,21 @@ func (this *Alert) ContainsUpdates(other *Alert) bool {
return result
}
func (alert *Alert) GetTagsFromSettings() []*Tag {
tags := []*Tag{}
if alert.Settings != nil {
if data, ok := alert.Settings.CheckGet("alertRuleTags"); ok {
for tagNameString, tagValue := range data.MustMap() {
// MustMap() already guarantees the return of a `map[string]interface{}`.
// Therefore we only need to verify that tagValue is a String.
tagValueString := simplejson.NewFromAny(tagValue).MustString()
tags = append(tags, &Tag{Key: tagNameString, Value: tagValueString})
}
}
}
return tags
}
type AlertingClusterInfo struct {
ServerId string
ClusterSize int
+23
View File
@@ -35,5 +35,28 @@ func TestAlertingModelTest(t *testing.T) {
rule1.Settings = json2
So(rule1.ContainsUpdates(rule2), ShouldBeTrue)
})
Convey("Should parse alertRule tags correctly", func() {
json2, _ := simplejson.NewJson([]byte(`{
"field": "value",
"alertRuleTags": {
"foo": "bar",
"waldo": "fred",
"tagMap": { "mapValue": "value" }
}
}`))
rule1.Settings = json2
expectedTags := []*Tag{
{Id: 0, Key: "foo", Value: "bar"},
{Id: 0, Key: "waldo", Value: "fred"},
{Id: 0, Key: "tagMap", Value: ""},
}
actualTags := rule1.GetTagsFromSettings()
So(len(actualTags), ShouldEqual, len(expectedTags))
for _, tag := range expectedTags {
So(ContainsTag(actualTags, tag), ShouldBeTrue)
}
})
})
}
@@ -93,7 +93,7 @@ func (am *AlertmanagerNotifier) createAlert(evalContext *alerting.EvalContext, m
alertJSON.SetPath([]string{"annotations", "image"}, evalContext.ImagePublicURL)
}
// Labels (from metrics tags + mandatory alertname).
// Labels (from metrics tags + AlertRuleTags + mandatory alertname).
tags := make(map[string]string)
if match != nil {
if len(match.Tags) == 0 {
@@ -104,6 +104,9 @@ func (am *AlertmanagerNotifier) createAlert(evalContext *alerting.EvalContext, m
}
}
}
for _, tag := range evalContext.Rule.AlertRuleTags {
tags[tag.Key] = tag.Value
}
tags["alertname"] = evalContext.Rule.Name
alertJSON.Set("labels", tags)
return alertJSON
+2
View File
@@ -35,6 +35,7 @@ type Rule struct {
State models.AlertStateType
Conditions []Condition
Notifications []string
AlertRuleTags []*models.Tag
StateChanges int64
}
@@ -145,6 +146,7 @@ func NewRuleFromDBAlert(ruleDef *models.Alert) (*Rule, error) {
model.Notifications = append(model.Notifications, uid)
}
}
model.AlertRuleTags = ruleDef.GetTagsFromSettings()
for index, condition := range ruleDef.Settings.Get("conditions").MustArray() {
conditionModel := simplejson.NewFromAny(condition)
+19
View File
@@ -64,6 +64,10 @@ func deleteAlertByIdInternal(alertId int64, reason string, sess *DBSession) erro
return err
}
if _, err := sess.Exec("DELETE FROM alert_rule_tag WHERE alert_id = ?", alertId); err != nil {
return err
}
return nil
}
@@ -215,6 +219,21 @@ func updateAlerts(existingAlerts []*m.Alert, cmd *m.SaveAlertsCommand, sess *DBS
sqlog.Debug("Alert inserted", "name", alert.Name, "id", alert.Id)
}
tags := alert.GetTagsFromSettings()
if _, err := sess.Exec("DELETE FROM alert_rule_tag WHERE alert_id = ?", alert.Id); err != nil {
return err
}
if tags != nil {
tags, err := EnsureTagsExist(sess, tags)
if err != nil {
return err
}
for _, tag := range tags {
if _, err := sess.Exec("INSERT INTO alert_rule_tag (alert_id, tag_id) VALUES(?,?)", alert.Id, tag.Id); err != nil {
return err
}
}
}
}
return nil
+2 -22
View File
@@ -29,7 +29,7 @@ func (r *SqlAnnotationRepo) Save(item *annotations.Item) error {
}
if item.Tags != nil {
tags, err := r.ensureTagsExist(sess, tags)
tags, err := EnsureTagsExist(sess, tags)
if err != nil {
return err
}
@@ -44,26 +44,6 @@ func (r *SqlAnnotationRepo) Save(item *annotations.Item) error {
})
}
// Will insert if needed any new key/value pars and return ids
func (r *SqlAnnotationRepo) ensureTagsExist(sess *DBSession, tags []*models.Tag) ([]*models.Tag, error) {
for _, tag := range tags {
var existingTag models.Tag
// check if it exists
if exists, err := sess.Table("tag").Where(dialect.Quote("key")+"=? AND "+dialect.Quote("value")+"=?", tag.Key, tag.Value).Get(&existingTag); err != nil {
return nil, err
} else if exists {
tag.Id = existingTag.Id
} else {
if _, err := sess.Table("tag").Insert(tag); err != nil {
return nil, err
}
}
}
return tags, nil
}
func (r *SqlAnnotationRepo) Update(item *annotations.Item) error {
return inTransaction(func(sess *DBSession) error {
var (
@@ -94,7 +74,7 @@ func (r *SqlAnnotationRepo) Update(item *annotations.Item) error {
}
if item.Tags != nil {
tags, err := r.ensureTagsExist(sess, models.ParseTagPairs(item.Tags))
tags, err := EnsureTagsExist(sess, models.ParseTagPairs(item.Tags))
if err != nil {
return err
}
-28
View File
@@ -5,37 +5,9 @@ import (
. "github.com/smartystreets/goconvey/convey"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/annotations"
)
func TestSavingTags(t *testing.T) {
InitTestDB(t)
Convey("Testing annotation saving/loading", t, func() {
repo := SqlAnnotationRepo{}
Convey("Can save tags", func() {
Reset(func() {
_, err := x.Exec("DELETE FROM annotation_tag WHERE 1=1")
So(err, ShouldBeNil)
})
tagPairs := []*models.Tag{
{Key: "outage"},
{Key: "type", Value: "outage"},
{Key: "server", Value: "server-1"},
{Key: "error"},
}
tags, err := repo.ensureTagsExist(newSession(), tagPairs)
So(err, ShouldBeNil)
So(len(tags), ShouldEqual, 4)
})
})
}
func TestAnnotations(t *testing.T) {
InitTestDB(t)
@@ -45,6 +45,20 @@ func addAlertMigrations(mg *Migrator) {
mg.AddMigration("add index alert state", NewAddIndexMigration(alertV1, alertV1.Indices[1]))
mg.AddMigration("add index alert dashboard_id", NewAddIndexMigration(alertV1, alertV1.Indices[2]))
alertRuleTagTable := Table{
Name: "alert_rule_tag",
Columns: []*Column{
{Name: "alert_id", Type: DB_BigInt, Nullable: false},
{Name: "tag_id", Type: DB_BigInt, Nullable: false},
},
Indices: []*Index{
{Cols: []string{"alert_id", "tag_id"}, Type: UniqueIndex},
},
}
mg.AddMigration("Create alert_rule_tag table v1", NewAddTableMigration(alertRuleTagTable))
mg.AddMigration("Add unique index alert_rule_tag.alert_id_tag_id", NewAddIndexMigration(alertRuleTagTable, alertRuleTagTable.Indices[0]))
alert_notification := Table{
Name: "alert_notification",
Columns: []*Column{
+23
View File
@@ -0,0 +1,23 @@
package sqlstore
import "github.com/grafana/grafana/pkg/models"
// Will insert if needed any new key/value pars and return ids
func EnsureTagsExist(sess *DBSession, tags []*models.Tag) ([]*models.Tag, error) {
for _, tag := range tags {
var existingTag models.Tag
// check if it exists
if exists, err := sess.Table("tag").Where("`key`=? AND `value`=?", tag.Key, tag.Value).Get(&existingTag); err != nil {
return nil, err
} else if exists {
tag.Id = existingTag.Id
} else {
if _, err := sess.Table("tag").Insert(tag); err != nil {
return nil, err
}
}
}
return tags, nil
}
+26
View File
@@ -0,0 +1,26 @@
package sqlstore
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
"github.com/grafana/grafana/pkg/models"
)
func TestSavingTags(t *testing.T) {
Convey("Testing tags saving", t, func() {
InitTestDB(t)
tagPairs := []*models.Tag{
{Key: "outage"},
{Key: "type", Value: "outage"},
{Key: "server", Value: "server-1"},
{Key: "error"},
}
tags, err := EnsureTagsExist(newSession(), tagPairs)
So(err, ShouldBeNil)
So(len(tags), ShouldEqual, 4)
})
}