Add handling of string values for kapacitor threshold alerts

pull/867/head
Chris Goller 2017-02-10 08:47:08 -06:00
parent 740fae3528
commit dac665b944
3 changed files with 341 additions and 4 deletions

View File

@ -199,6 +199,280 @@ trigger
}
}
func TestThresholdStringCrit(t *testing.T) {
alert := chronograf.AlertRule{
Name: "haproxy",
Trigger: "threshold",
Alerts: []string{"email"},
TriggerValues: chronograf.TriggerValues{
Operator: "equal to",
Value: "DOWN",
},
Every: "10s",
Message: `Haproxy monitor : {{.ID}} : {{ index .Tags "server" }} : {{ index .Tags "pxname" }} is {{ .Level }} `,
Details: "Email template",
Query: chronograf.QueryConfig{
Database: "influxdb",
RetentionPolicy: "autogen",
Measurement: "haproxy",
Fields: []chronograf.Field{
{
Field: "status",
Funcs: []string{"last"},
},
},
GroupBy: chronograf.GroupBy{
Time: "10s",
Tags: []string{"pxname"},
},
AreTagsAccepted: true,
},
}
tests := []struct {
name string
alert chronograf.AlertRule
want chronograf.TICKScript
wantErr bool
}{
{
name: "Test valid template alert",
alert: alert,
want: `var db = 'influxdb'
var rp = 'autogen'
var measurement = 'haproxy'
var groupBy = ['pxname']
var whereFilter = lambda: TRUE
var period = 10s
var every = 10s
var name = 'haproxy'
var idVar = name + ':{{.Group}}'
var message = 'Haproxy monitor : {{.ID}} : {{ index .Tags "server" }} : {{ index .Tags "pxname" }} is {{ .Level }} '
var idTag = 'alertID'
var levelTag = 'level'
var messageField = 'message'
var durationField = 'duration'
var outputDB = 'chronograf'
var outputRP = 'autogen'
var outputMeasurement = 'alerts'
var triggerType = 'threshold'
var details = 'Email template'
var crit = 'DOWN'
var data = stream
|from()
.database(db)
.retentionPolicy(rp)
.measurement(measurement)
.groupBy(groupBy)
.where(whereFilter)
|window()
.period(period)
.every(every)
.align()
|last('status')
.as('value')
var trigger = data
|alert()
.crit(lambda: "value" == crit)
.stateChangesOnly()
.message(message)
.id(idVar)
.idTag(idTag)
.levelTag(levelTag)
.messageField(messageField)
.durationField(durationField)
.details(details)
.email()
trigger
|influxDBOut()
.create()
.database(outputDB)
.retentionPolicy(outputRP)
.measurement(outputMeasurement)
.tag('alertName', name)
.tag('triggerType', triggerType)
trigger
|httpOut('output')
`,
wantErr: false,
},
}
for _, tt := range tests {
gen := Alert{}
got, err := gen.Generate(tt.alert)
if (err != nil) != tt.wantErr {
t.Errorf("%q. Threshold() error = %v, wantErr %v", tt.name, err, tt.wantErr)
continue
}
if got != tt.want {
diff := diffmatchpatch.New()
delta := diff.DiffMain(string(tt.want), string(got), true)
t.Errorf("%q\n%s", tt.name, diff.DiffPrettyText(delta))
}
}
}
// TODO: Check with Nathaniel if kapacitor can do inequalities on strings
// If it cannot, I think we should add operator checks.
func TestThresholdStringCritGreater(t *testing.T) {
alert := chronograf.AlertRule{
Name: "haproxy",
Trigger: "threshold",
Alerts: []string{"email"},
TriggerValues: chronograf.TriggerValues{
Operator: "greater than",
Value: "DOWN",
},
Every: "10s",
Message: `Haproxy monitor : {{.ID}} : {{ index .Tags "server" }} : {{ index .Tags "pxname" }} is {{ .Level }} `,
Details: "Email template",
Query: chronograf.QueryConfig{
Database: "influxdb",
RetentionPolicy: "autogen",
Measurement: "haproxy",
Fields: []chronograf.Field{
{
Field: "status",
Funcs: []string{"last"},
},
},
GroupBy: chronograf.GroupBy{
Time: "10s",
Tags: []string{"pxname"},
},
AreTagsAccepted: true,
},
}
tests := []struct {
name string
alert chronograf.AlertRule
want chronograf.TICKScript
wantErr bool
}{
{
name: "Test valid template alert",
alert: alert,
want: `var db = 'influxdb'
var rp = 'autogen'
var measurement = 'haproxy'
var groupBy = ['pxname']
var whereFilter = lambda: TRUE
var period = 10s
var every = 10s
var name = 'haproxy'
var idVar = name + ':{{.Group}}'
var message = 'Haproxy monitor : {{.ID}} : {{ index .Tags "server" }} : {{ index .Tags "pxname" }} is {{ .Level }} '
var idTag = 'alertID'
var levelTag = 'level'
var messageField = 'message'
var durationField = 'duration'
var outputDB = 'chronograf'
var outputRP = 'autogen'
var outputMeasurement = 'alerts'
var triggerType = 'threshold'
var details = 'Email template'
var crit = 'DOWN'
var data = stream
|from()
.database(db)
.retentionPolicy(rp)
.measurement(measurement)
.groupBy(groupBy)
.where(whereFilter)
|window()
.period(period)
.every(every)
.align()
|last('status')
.as('value')
var trigger = data
|alert()
.crit(lambda: "value" > crit)
.stateChangesOnly()
.message(message)
.id(idVar)
.idTag(idTag)
.levelTag(levelTag)
.messageField(messageField)
.durationField(durationField)
.details(details)
.email()
trigger
|influxDBOut()
.create()
.database(outputDB)
.retentionPolicy(outputRP)
.measurement(outputMeasurement)
.tag('alertName', name)
.tag('triggerType', triggerType)
trigger
|httpOut('output')
`,
wantErr: false,
},
}
for _, tt := range tests {
gen := Alert{}
got, err := gen.Generate(tt.alert)
if (err != nil) != tt.wantErr {
t.Errorf("%q. Threshold() error = %v, wantErr %v", tt.name, err, tt.wantErr)
continue
}
if got != tt.want {
diff := diffmatchpatch.New()
delta := diff.DiffMain(string(tt.want), string(got), true)
t.Errorf("%q\n%s", tt.name, diff.DiffPrettyText(delta))
}
}
}
func TestThresholdDetail(t *testing.T) {
alert := chronograf.AlertRule{
Name: "name",

View File

@ -3,6 +3,7 @@ package kapacitor
import (
"fmt"
"sort"
"strconv"
"strings"
"github.com/influxdata/chronograf"
@ -39,15 +40,16 @@ func Vars(rule chronograf.AlertRule) (string, error) {
%s
var crit = %s
`
return fmt.Sprintf(vars,
common,
rule.TriggerValues.Value), nil
// If critical value is a string, we'll
// need to single-quote it.
crit := critVar(rule.TriggerValues.Value)
return fmt.Sprintf(vars, common, crit), nil
} else {
vars := `
%s
var lower = %s
var upper = %s
`
`
return fmt.Sprintf(vars,
common,
rule.TriggerValues.Value,
@ -178,3 +180,14 @@ func whereFilter(q chronograf.QueryConfig) string {
return "lambda: TRUE"
}
// critVar return the same string if a numeric type
// or if it is a string will return it as a kapacitor
// formatted single-quoted string
func critVar(value string) string {
// Test if numeric if it can be converted to a float
if _, err := strconv.ParseFloat(value, 64); err == nil {
return value
}
return "'" + value + "'"
}

50
kapacitor/vars_test.go Normal file
View File

@ -0,0 +1,50 @@
package kapacitor
import (
"fmt"
"testing"
"github.com/influxdata/chronograf"
)
func TestVarsCritStringEqual(t *testing.T) {
alert := chronograf.AlertRule{
Name: "name",
Trigger: "threshold",
TriggerValues: chronograf.TriggerValues{
Operator: "equal to",
Value: "DOWN",
},
Every: "30s",
Query: chronograf.QueryConfig{
Database: "telegraf",
Measurement: "haproxy",
RetentionPolicy: "autogen",
Fields: []chronograf.Field{
{
Field: "status",
},
},
GroupBy: chronograf.GroupBy{
Time: "10m",
Tags: []string{"pxname"},
},
AreTagsAccepted: true,
},
}
raw, err := Vars(alert)
if err != nil {
fmt.Printf("%s", raw)
t.Fatalf("Error generating alert: %v %s", err, raw)
}
tick, err := formatTick(raw)
if err != nil {
t.Errorf("Error formatting alert: %v %s", err, raw)
}
if err := validateTick(tick); err != nil {
t.Errorf("Error validating alert: %v %s", err, tick)
}
}