Fix several kapacitor validation failures
parent
f4fa08e36b
commit
b1ad6443e5
|
@ -49,7 +49,7 @@
|
|||
|
||||
[[projects]]
|
||||
name = "github.com/google/go-cmp"
|
||||
packages = ["cmp"]
|
||||
packages = ["cmp","cmp/cmpopts"]
|
||||
revision = "79b2d888f100ec053545168aa94bcfb322e8bfc8"
|
||||
|
||||
[[projects]]
|
||||
|
@ -140,6 +140,6 @@
|
|||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "f34fb88755292baba8b52c14bf5b9a028daff96a763368a7cf1de90004d33695"
|
||||
inputs-digest = "85a5451fc9e0596e486a676204eb2de0b12900522341ee0804cf9ec86fb2765e"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/influxdata/chronograf"
|
||||
client "github.com/influxdata/kapacitor/client/v1"
|
||||
)
|
||||
|
@ -946,9 +947,21 @@ func TestClient_Update(t *testing.T) {
|
|||
href: "/kapacitor/v1/tasks/howdy",
|
||||
rule: chronograf.AlertRule{
|
||||
ID: "howdy",
|
||||
Name: "myname",
|
||||
Query: &chronograf.QueryConfig{
|
||||
Database: "db",
|
||||
RetentionPolicy: "rp",
|
||||
Measurement: "meas",
|
||||
Fields: []chronograf.Field{
|
||||
{
|
||||
Type: "field",
|
||||
Value: "usage_user",
|
||||
},
|
||||
},
|
||||
},
|
||||
Trigger: "threshold",
|
||||
TriggerValues: chronograf.TriggerValues{
|
||||
Operator: greaterThan,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1010,9 +1023,21 @@ func TestClient_Update(t *testing.T) {
|
|||
href: "/kapacitor/v1/tasks/howdy",
|
||||
rule: chronograf.AlertRule{
|
||||
ID: "howdy",
|
||||
Name: "myname",
|
||||
Query: &chronograf.QueryConfig{
|
||||
Database: "db",
|
||||
RetentionPolicy: "rp",
|
||||
Measurement: "meas",
|
||||
Fields: []chronograf.Field{
|
||||
{
|
||||
Type: "field",
|
||||
Value: "usage_user",
|
||||
},
|
||||
},
|
||||
},
|
||||
Trigger: "threshold",
|
||||
TriggerValues: chronograf.TriggerValues{
|
||||
Operator: greaterThan,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1061,6 +1086,135 @@ func TestClient_Update(t *testing.T) {
|
|||
},
|
||||
wantStatus: client.Disabled,
|
||||
},
|
||||
{
|
||||
name: "error because relative cannot have inside range",
|
||||
wantErr: true,
|
||||
fields: fields{
|
||||
kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) {
|
||||
return kapa, nil
|
||||
},
|
||||
Ticker: &Alert{},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
href: "/kapacitor/v1/tasks/error",
|
||||
rule: chronograf.AlertRule{
|
||||
ID: "error",
|
||||
Query: &chronograf.QueryConfig{
|
||||
Database: "db",
|
||||
RetentionPolicy: "rp",
|
||||
Fields: []chronograf.Field{
|
||||
{
|
||||
Value: "usage_user",
|
||||
Type: "field",
|
||||
},
|
||||
},
|
||||
},
|
||||
Trigger: Relative,
|
||||
TriggerValues: chronograf.TriggerValues{
|
||||
Operator: InsideRange,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "error because rule has an unknown trigger mechanism",
|
||||
wantErr: true,
|
||||
fields: fields{
|
||||
kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) {
|
||||
return kapa, nil
|
||||
},
|
||||
Ticker: &Alert{},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
href: "/kapacitor/v1/tasks/error",
|
||||
rule: chronograf.AlertRule{
|
||||
ID: "error",
|
||||
Query: &chronograf.QueryConfig{
|
||||
Database: "db",
|
||||
RetentionPolicy: "rp",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "error because query has no fields",
|
||||
wantErr: true,
|
||||
fields: fields{
|
||||
kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) {
|
||||
return kapa, nil
|
||||
},
|
||||
Ticker: &Alert{},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
href: "/kapacitor/v1/tasks/error",
|
||||
rule: chronograf.AlertRule{
|
||||
ID: "error",
|
||||
Trigger: Threshold,
|
||||
TriggerValues: chronograf.TriggerValues{
|
||||
Period: "1d",
|
||||
},
|
||||
Name: "myname",
|
||||
Query: &chronograf.QueryConfig{
|
||||
Database: "db",
|
||||
RetentionPolicy: "rp",
|
||||
Measurement: "meas",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "error because alert has no name",
|
||||
wantErr: true,
|
||||
fields: fields{
|
||||
kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) {
|
||||
return kapa, nil
|
||||
},
|
||||
Ticker: &Alert{},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
href: "/kapacitor/v1/tasks/error",
|
||||
rule: chronograf.AlertRule{
|
||||
ID: "error",
|
||||
Trigger: Deadman,
|
||||
TriggerValues: chronograf.TriggerValues{
|
||||
Period: "1d",
|
||||
},
|
||||
Query: &chronograf.QueryConfig{
|
||||
Database: "db",
|
||||
RetentionPolicy: "rp",
|
||||
Measurement: "meas",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "error because alert period cannot be an empty string in deadman alert",
|
||||
wantErr: true,
|
||||
fields: fields{
|
||||
kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) {
|
||||
return kapa, nil
|
||||
},
|
||||
Ticker: &Alert{},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
href: "/kapacitor/v1/tasks/error",
|
||||
rule: chronograf.AlertRule{
|
||||
ID: "error",
|
||||
Name: "myname",
|
||||
Trigger: Deadman,
|
||||
Query: &chronograf.QueryConfig{
|
||||
Database: "db",
|
||||
RetentionPolicy: "rp",
|
||||
Measurement: "meas",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
kapa.ResTask = tt.resTask
|
||||
|
@ -1079,11 +1233,17 @@ func TestClient_Update(t *testing.T) {
|
|||
t.Errorf("Client.Update() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if tt.wantErr {
|
||||
return
|
||||
}
|
||||
if !cmp.Equal(got, tt.want) {
|
||||
t.Errorf("%q. Client.Update() = -got/+want %s", tt.name, cmp.Diff(got, tt.want))
|
||||
}
|
||||
if !reflect.DeepEqual(kapa.UpdateTaskOptions, tt.updateTaskOptions) {
|
||||
t.Errorf("Client.Update() = %v, want %v", kapa.UpdateTaskOptions, tt.updateTaskOptions)
|
||||
var cmpOptions = cmp.Options{
|
||||
cmpopts.IgnoreFields(client.UpdateTaskOptions{}, "TICKscript"),
|
||||
}
|
||||
if !cmp.Equal(kapa.UpdateTaskOptions, tt.updateTaskOptions, cmpOptions...) {
|
||||
t.Errorf("Client.Update() = %s", cmp.Diff(got, tt.updateTaskOptions, cmpOptions...))
|
||||
}
|
||||
if tt.wantStatus != kapa.LastStatus {
|
||||
t.Errorf("Client.Update() = %v, want %v", kapa.LastStatus, tt.wantStatus)
|
||||
|
@ -1131,9 +1291,15 @@ func TestClient_Create(t *testing.T) {
|
|||
ctx: context.Background(),
|
||||
rule: chronograf.AlertRule{
|
||||
ID: "howdy",
|
||||
Name: "mynames",
|
||||
Query: &chronograf.QueryConfig{
|
||||
Database: "db",
|
||||
RetentionPolicy: "rp",
|
||||
Measurement: "meas",
|
||||
},
|
||||
Trigger: Deadman,
|
||||
TriggerValues: chronograf.TriggerValues{
|
||||
Period: "1d",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1152,7 +1318,76 @@ func TestClient_Create(t *testing.T) {
|
|||
},
|
||||
},
|
||||
createTaskOptions: &client.CreateTaskOptions{
|
||||
TICKscript: "",
|
||||
TICKscript: `var db = 'db'
|
||||
|
||||
var rp = 'rp'
|
||||
|
||||
var measurement = 'meas'
|
||||
|
||||
var groupBy = []
|
||||
|
||||
var whereFilter = lambda: TRUE
|
||||
|
||||
var period = 1d
|
||||
|
||||
var name = 'mynames'
|
||||
|
||||
var idVar = name + ':{{.Group}}'
|
||||
|
||||
var message = ''
|
||||
|
||||
var idTag = 'alertID'
|
||||
|
||||
var levelTag = 'level'
|
||||
|
||||
var messageField = 'message'
|
||||
|
||||
var durationField = 'duration'
|
||||
|
||||
var outputDB = 'chronograf'
|
||||
|
||||
var outputRP = 'autogen'
|
||||
|
||||
var outputMeasurement = 'alerts'
|
||||
|
||||
var triggerType = 'deadman'
|
||||
|
||||
var threshold = 0.0
|
||||
|
||||
var data = stream
|
||||
|from()
|
||||
.database(db)
|
||||
.retentionPolicy(rp)
|
||||
.measurement(measurement)
|
||||
.groupBy(groupBy)
|
||||
.where(whereFilter)
|
||||
|
||||
var trigger = data
|
||||
|deadman(threshold, period)
|
||||
.stateChangesOnly()
|
||||
.message(message)
|
||||
.id(idVar)
|
||||
.idTag(idTag)
|
||||
.levelTag(levelTag)
|
||||
.messageField(messageField)
|
||||
.durationField(durationField)
|
||||
|
||||
trigger
|
||||
|eval(lambda: "emitted")
|
||||
.as('value')
|
||||
.keep('value', messageField, durationField)
|
||||
|influxDBOut()
|
||||
.create()
|
||||
.database(outputDB)
|
||||
.retentionPolicy(outputRP)
|
||||
.measurement(outputMeasurement)
|
||||
.tag('alertName', name)
|
||||
.tag('triggerType', triggerType)
|
||||
|
||||
trigger
|
||||
|httpOut('output')
|
||||
`,
|
||||
|
||||
ID: "chronograf-v1-howdy",
|
||||
Type: client.StreamTask,
|
||||
Status: client.Enabled,
|
||||
|
@ -1205,7 +1440,6 @@ func TestClient_Create(t *testing.T) {
|
|||
},
|
||||
resError: fmt.Errorf("error"),
|
||||
createTaskOptions: &client.CreateTaskOptions{
|
||||
TICKscript: "",
|
||||
ID: "chronograf-v1-howdy",
|
||||
Type: client.StreamTask,
|
||||
Status: client.Enabled,
|
||||
|
@ -1236,6 +1470,9 @@ func TestClient_Create(t *testing.T) {
|
|||
t.Errorf("Client.Create() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if tt.wantErr {
|
||||
return
|
||||
}
|
||||
if !cmp.Equal(got, tt.want) {
|
||||
t.Errorf("%q. Client.Create() = -got/+want %s", tt.name, cmp.Diff(got, tt.want))
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package kapacitor
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
greaterThan = "greater than"
|
||||
|
|
|
@ -15,11 +15,11 @@ type Alert struct{}
|
|||
func (a *Alert) Generate(rule chronograf.AlertRule) (chronograf.TICKScript, error) {
|
||||
vars, err := Vars(rule)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
return "", err
|
||||
}
|
||||
data, err := Data(rule)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
return "", err
|
||||
}
|
||||
trigger, err := Trigger(rule)
|
||||
if err != nil {
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
package kapacitor
|
||||
|
||||
import "github.com/influxdata/chronograf"
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/influxdata/chronograf"
|
||||
)
|
||||
|
||||
const (
|
||||
// Deadman triggers when data is missing for a period of time
|
||||
|
|
|
@ -76,7 +76,37 @@ func Vars(rule chronograf.AlertRule) (string, error) {
|
|||
}
|
||||
}
|
||||
|
||||
type NotEmpty struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (n *NotEmpty) Valid(name, s string) error {
|
||||
if n.Err != nil {
|
||||
return n.Err
|
||||
|
||||
}
|
||||
if s == "" {
|
||||
n.Err = fmt.Errorf("%s cannot be an empty string", name)
|
||||
}
|
||||
return n.Err
|
||||
}
|
||||
|
||||
func commonVars(rule chronograf.AlertRule) (string, error) {
|
||||
n := new(NotEmpty)
|
||||
n.Valid("database", rule.Query.Database)
|
||||
n.Valid("retention policy", rule.Query.RetentionPolicy)
|
||||
n.Valid("measurement", rule.Query.Measurement)
|
||||
n.Valid("alert name", rule.Name)
|
||||
n.Valid("trigger type", rule.Trigger)
|
||||
if n.Err != nil {
|
||||
return "", n.Err
|
||||
}
|
||||
|
||||
wind, err := window(rule)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
common := `
|
||||
var db = '%s'
|
||||
var rp = '%s'
|
||||
|
@ -104,7 +134,7 @@ func commonVars(rule chronograf.AlertRule) (string, error) {
|
|||
rule.Query.Measurement,
|
||||
groupBy(rule.Query),
|
||||
whereFilter(rule.Query),
|
||||
window(rule),
|
||||
wind,
|
||||
rule.Name,
|
||||
rule.Message,
|
||||
IDTag,
|
||||
|
@ -127,17 +157,27 @@ func commonVars(rule chronograf.AlertRule) (string, error) {
|
|||
|
||||
// window is only used if deadman or threshold/relative with aggregate. Will return empty
|
||||
// if no period.
|
||||
func window(rule chronograf.AlertRule) string {
|
||||
func window(rule chronograf.AlertRule) (string, error) {
|
||||
if rule.Trigger == Deadman {
|
||||
return fmt.Sprintf("var period = %s", rule.TriggerValues.Period)
|
||||
if rule.TriggerValues.Period == "" {
|
||||
return "", fmt.Errorf("period cannot be an empty string in deadman alert")
|
||||
}
|
||||
return fmt.Sprintf("var period = %s", rule.TriggerValues.Period), nil
|
||||
|
||||
}
|
||||
// Period only makes sense if the field has a been grouped via a time duration.
|
||||
for _, field := range rule.Query.Fields {
|
||||
if field.Type == "func" {
|
||||
return fmt.Sprintf("var period = %s\nvar every = %s", rule.Query.GroupBy.Time, rule.Every)
|
||||
n := new(NotEmpty)
|
||||
n.Valid("group by time", rule.Query.GroupBy.Time)
|
||||
n.Valid("every", rule.Every)
|
||||
if n.Err != nil {
|
||||
return "", n.Err
|
||||
}
|
||||
return fmt.Sprintf("var period = %s\nvar every = %s", rule.Query.GroupBy.Time, rule.Every), nil
|
||||
}
|
||||
}
|
||||
return ""
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func groupBy(q *chronograf.QueryConfig) string {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import {CHANGES, OPERATORS, SHIFTS} from 'src/kapacitor/constants'
|
||||
import {CHANGES, RELATIVE_OPERATORS, SHIFTS} from 'src/kapacitor/constants'
|
||||
import Dropdown from 'shared/components/Dropdown'
|
||||
|
||||
const mapToItems = (arr, type) => arr.map(text => ({text, type}))
|
||||
const changes = mapToItems(CHANGES, 'change')
|
||||
const shifts = mapToItems(SHIFTS, 'shift')
|
||||
const operators = mapToItems(OPERATORS, 'operator')
|
||||
const operators = mapToItems(RELATIVE_OPERATORS, 'operator')
|
||||
|
||||
const Relative = ({
|
||||
onRuleTypeInputChange,
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import {OPERATORS} from 'src/kapacitor/constants'
|
||||
import {THRESHOLD_OPERATORS} from 'src/kapacitor/constants'
|
||||
import Dropdown from 'shared/components/Dropdown'
|
||||
import _ from 'lodash'
|
||||
|
||||
const mapToItems = (arr, type) => arr.map(text => ({text, type}))
|
||||
const operators = mapToItems(OPERATORS, 'operator')
|
||||
const operators = mapToItems(THRESHOLD_OPERATORS, 'operator')
|
||||
const noopSubmit = e => e.preventDefault()
|
||||
const getField = ({fields}) => {
|
||||
const alias = _.get(fields, ['0', 'alias'], false)
|
||||
|
|
|
@ -31,7 +31,7 @@ export const OUTSIDE_RANGE = 'outside range'
|
|||
export const EQUAL_TO_OR_GREATER_THAN = 'equal to or greater'
|
||||
export const EQUAL_TO_OR_LESS_THAN = 'equal to or less than'
|
||||
|
||||
export const OPERATORS = [
|
||||
export const THRESHOLD_OPERATORS = [
|
||||
GREATER_THAN,
|
||||
EQUAL_TO_OR_GREATER_THAN,
|
||||
EQUAL_TO_OR_LESS_THAN,
|
||||
|
@ -42,6 +42,15 @@ export const OPERATORS = [
|
|||
OUTSIDE_RANGE,
|
||||
]
|
||||
|
||||
export const RELATIVE_OPERATORS = [
|
||||
GREATER_THAN,
|
||||
EQUAL_TO_OR_GREATER_THAN,
|
||||
EQUAL_TO_OR_LESS_THAN,
|
||||
LESS_THAN,
|
||||
EQUAL_TO,
|
||||
NOT_EQUAL_TO,
|
||||
]
|
||||
|
||||
// export const RELATIONS = ['once', 'more than ', 'less than'];
|
||||
export const PERIODS = ['1m', '5m', '10m', '30m', '1h', '2h', '24h']
|
||||
export const CHANGES = ['change', '% change']
|
||||
|
|
Loading…
Reference in New Issue