Add kapa alert arguments and properties

pull/857/head
Chris Goller 2017-02-09 00:10:23 -06:00
parent aa4b6fb689
commit bd6f3e1a6b
5 changed files with 183 additions and 32 deletions

View File

@ -108,7 +108,7 @@ type AlertRule struct {
Query QueryConfig `json:"query"` // Query is the filter of data for the alert. Query QueryConfig `json:"query"` // Query is the filter of data for the alert.
Every string `json:"every"` // Every how often to check for the alerting criteria Every string `json:"every"` // Every how often to check for the alerting criteria
Alerts []string `json:"alerts"` // Alerts name all the services to notify (e.g. pagerduty) Alerts []string `json:"alerts"` // Alerts name all the services to notify (e.g. pagerduty)
AlertNodes []KapacitorNode `json:"alertNodes"` // AlertNodes define additional arguments to alerts AlertNodes []KapacitorNode `json:"alertNodes,omitempty"` // AlertNodes define additional arguments to alerts
Message string `json:"message"` // Message included with alert Message string `json:"message"` // Message included with alert
Details string `json:"details"` // Details is generally used for the Email alert. If empty will not be added. Details string `json:"details"` // Details is generally used for the Email alert. If empty will not be added.
Trigger string `json:"trigger"` // Trigger is a type that defines when to trigger the alert Trigger string `json:"trigger"` // Trigger is a type that defines when to trigger the alert
@ -178,7 +178,7 @@ type QueryConfig struct {
type KapacitorNode struct { type KapacitorNode struct {
Name string `json:"name"` Name string `json:"name"`
Args []string `json:"args"` Args []string `json:"args"`
Properties []*KapacitorProperty `json:"properties"` Properties []KapacitorProperty `json:"properties"`
// In the future we could add chaining methods here. // In the future we could add chaining methods here.
} }

View File

@ -2,12 +2,13 @@ package kapacitor
import ( import (
"fmt" "fmt"
"strings"
"github.com/influxdata/chronograf" "github.com/influxdata/chronograf"
) )
func kapaService(alert string) (string, error) { func kapaHandler(handler string) (string, error) {
switch alert { switch handler {
case "hipchat": case "hipchat":
return "hipChat", nil return "hipChat", nil
case "opsgenie": case "opsgenie":
@ -18,25 +19,78 @@ func kapaService(alert string) (string, error) {
return "victorOps", nil return "victorOps", nil
case "smtp": case "smtp":
return "email", nil return "email", nil
case "sensu", "slack", "email", "talk", "telegram": case "http":
return alert, nil return "post", nil
case "sensu", "slack", "email", "talk", "telegram", "post", "tcp":
return handler, nil
default: default:
return "", fmt.Errorf("Unsupport alert %s", alert) return "", fmt.Errorf("Unsupported alert handler %s", handler)
} }
} }
func toKapaFunc(method string, args []string) (string, error) {
if len(args) == 0 {
return fmt.Sprintf(".%s()", method), nil
}
params := make([]string, len(args))
copy(params, args)
// Kapacitor strings are quoted
for i, p := range params {
params[i] = fmt.Sprintf("'%s'", p)
}
return fmt.Sprintf(".%s(%s)", method, strings.Join(params, ",")), nil
}
func addAlertNodes(rule chronograf.AlertRule) (string, error) {
alert := ""
// Using a map to try to combine older API in .Alerts with .AlertNodes
nodes := map[string]chronograf.KapacitorNode{}
for _, node := range rule.AlertNodes {
handler, err := kapaHandler(node.Name)
if err != nil {
return "", err
}
nodes[handler] = node
}
for _, a := range rule.Alerts {
handler, err := kapaHandler(a)
if err != nil {
return "", err
}
// If the this handler is not in nodes, then there are
// there are no arguments or properties
if _, ok := nodes[handler]; !ok {
alert = alert + fmt.Sprintf(".%s()", handler)
}
}
for handler, node := range nodes {
service, err := toKapaFunc(handler, node.Args)
if err != nil {
return "", nil
}
alert = alert + service
for _, prop := range node.Properties {
alertProperty, err := toKapaFunc(prop.Name, prop.Args)
if err != nil {
return "", nil
}
alert = alert + alertProperty
}
}
return alert, nil
}
// AlertServices generates alert chaining methods to be attached to an alert from all rule Services // AlertServices generates alert chaining methods to be attached to an alert from all rule Services
func AlertServices(rule chronograf.AlertRule) (string, error) { func AlertServices(rule chronograf.AlertRule) (string, error) {
alert := "" node, err := addAlertNodes(rule)
for _, service := range rule.Alerts {
srv, err := kapaService(service)
if err != nil { if err != nil {
return "", err return "", err
} }
if err := ValidateAlert(srv); err != nil {
if err := ValidateAlert(node); err != nil {
return "", err return "", err
} }
alert = alert + fmt.Sprintf(".%s()", srv) return node, nil
}
return alert, nil
} }

View File

@ -49,6 +49,103 @@ func TestAlertServices(t *testing.T) {
.slack() .slack()
`, `,
}, },
{
name: "Test single valid service and property",
rule: chronograf.AlertRule{
Alerts: []string{"slack"},
AlertNodes: []chronograf.KapacitorNode{
{
Name: "slack",
Properties: []chronograf.KapacitorProperty{
{
Name: "channel",
Args: []string{"#general"},
},
},
},
},
},
want: `alert()
.slack()
.channel('#general')
`,
},
{
name: "Test tcp",
rule: chronograf.AlertRule{
AlertNodes: []chronograf.KapacitorNode{
{
Name: "tcp",
Args: []string{"myaddress:22"},
},
},
},
want: `alert()
.tcp('myaddress:22')
`,
},
{
name: "Test tcp no argument",
rule: chronograf.AlertRule{
AlertNodes: []chronograf.KapacitorNode{
{
Name: "tcp",
},
},
},
wantErr: true,
},
{
name: "Test tcp no argument with other services",
rule: chronograf.AlertRule{
Alerts: []string{"slack", "tcp", "email"},
AlertNodes: []chronograf.KapacitorNode{
{
Name: "tcp",
},
},
},
wantErr: true,
},
{
name: "Test http as post",
rule: chronograf.AlertRule{
AlertNodes: []chronograf.KapacitorNode{
{
Name: "http",
Args: []string{"http://myaddress"},
},
},
},
want: `alert()
.post('http://myaddress')
`,
},
{
name: "Test post",
rule: chronograf.AlertRule{
AlertNodes: []chronograf.KapacitorNode{
{
Name: "post",
Args: []string{"http://myaddress"},
},
},
},
want: `alert()
.post('http://myaddress')
`,
},
{
name: "Test http no arguments",
rule: chronograf.AlertRule{
AlertNodes: []chronograf.KapacitorNode{
{
Name: "http",
},
},
},
wantErr: true,
},
} }
for _, tt := range tests { for _, tt := range tests {
got, err := AlertServices(tt.rule) got, err := AlertServices(tt.rule)

View File

@ -18,7 +18,7 @@ func ValidateAlert(service string) error {
// Simple tick script to check alert service. // Simple tick script to check alert service.
// If a pipeline cannot be created then we know this is an invalid // If a pipeline cannot be created then we know this is an invalid
// service. At least with this version of kapacitor! // service. At least with this version of kapacitor!
script := fmt.Sprintf("stream|from()|alert().%s()", service) script := fmt.Sprintf("stream|from()|alert()%s", service)
return validateTick(chronograf.TICKScript(script)) return validateTick(chronograf.TICKScript(script))
} }

View File

@ -11,12 +11,12 @@ func TestValidateAlert(t *testing.T) {
}{ }{
{ {
name: "Test valid template alert", name: "Test valid template alert",
service: "slack", service: ".slack()",
wantErr: false, wantErr: false,
}, },
{ {
name: "Test invalid template alert", name: "Test invalid template alert",
service: "invalid", service: ".invalid()",
wantErr: true, wantErr: true,
}, },
} }