Merge pull request #857 from influxdata/feature/kapacitor-alert-nodes
Backend support for Kapacitor Alert arguments and Propertiespull/867/head
commit
740fae3528
|
@ -91,14 +91,16 @@ A UI for [Kapacitor](https://github.com/influxdata/kapacitor) alert creation and
|
|||
* Simply generate threshold, relative, and deadman alerts
|
||||
* Preview data and alert boundaries while creating an alert
|
||||
* Configure alert destinations - Currently, Chronograf supports sending alerts to:
|
||||
* HTTP/Post
|
||||
* HipChat
|
||||
* OpsGenie
|
||||
* PagerDuty
|
||||
* Sensu
|
||||
* Slack
|
||||
* SMTP
|
||||
* SMTP/email
|
||||
* Talk
|
||||
* Telegram
|
||||
* TCP
|
||||
* VictorOps
|
||||
* View all active alerts at a glance on the alerting dashboard
|
||||
|
||||
|
|
|
@ -104,15 +104,16 @@ type SourcesStore interface {
|
|||
|
||||
// AlertRule represents rules for building a tickscript alerting task
|
||||
type AlertRule struct {
|
||||
ID string `json:"id,omitempty"` // ID is the unique ID of 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
|
||||
Alerts []string `json:"alerts"` // AlertServices name all the services to notify (e.g. pagerduty)
|
||||
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.
|
||||
Trigger string `json:"trigger"` // Trigger is a type that defines when to trigger the alert
|
||||
TriggerValues TriggerValues `json:"values"` // Defines the values that cause the alert to trigger
|
||||
Name string `json:"name"` // Name is the user-defined name for the alert
|
||||
ID string `json:"id,omitempty"` // ID is the unique ID of 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
|
||||
Alerts []string `json:"alerts"` // Alerts name all the services to notify (e.g. pagerduty)
|
||||
AlertNodes []KapacitorNode `json:"alertNodes,omitempty"` // AlertNodes define additional arguments to alerts
|
||||
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.
|
||||
Trigger string `json:"trigger"` // Trigger is a type that defines when to trigger the alert
|
||||
TriggerValues TriggerValues `json:"values"` // Defines the values that cause the alert to trigger
|
||||
Name string `json:"name"` // Name is the user-defined name for the alert
|
||||
}
|
||||
|
||||
// AlertRulesStore stores rules for building tickscript alerting tasks
|
||||
|
@ -173,6 +174,20 @@ type QueryConfig struct {
|
|||
RawText string `json:"rawText,omitempty"`
|
||||
}
|
||||
|
||||
// KapacitorNode adds arguments and properties to an alert
|
||||
type KapacitorNode struct {
|
||||
Name string `json:"name"`
|
||||
Args []string `json:"args"`
|
||||
Properties []KapacitorProperty `json:"properties"`
|
||||
// In the future we could add chaining methods here.
|
||||
}
|
||||
|
||||
// KapacitorProperty modifies the node they are called on
|
||||
type KapacitorProperty struct {
|
||||
Name string `json:"name"`
|
||||
Args []string `json:"args"`
|
||||
}
|
||||
|
||||
// Server represents a proxy connection to an HTTP server
|
||||
type Server struct {
|
||||
ID int // ID is the unique ID of the server
|
||||
|
|
|
@ -2,12 +2,13 @@ package kapacitor
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/influxdata/chronograf"
|
||||
)
|
||||
|
||||
func kapaService(alert string) (string, error) {
|
||||
switch alert {
|
||||
func kapaHandler(handler string) (string, error) {
|
||||
switch handler {
|
||||
case "hipchat":
|
||||
return "hipChat", nil
|
||||
case "opsgenie":
|
||||
|
@ -18,25 +19,78 @@ func kapaService(alert string) (string, error) {
|
|||
return "victorOps", nil
|
||||
case "smtp":
|
||||
return "email", nil
|
||||
case "sensu", "slack", "email", "talk", "telegram":
|
||||
return alert, nil
|
||||
case "http":
|
||||
return "post", nil
|
||||
case "alerta", "sensu", "slack", "email", "talk", "telegram", "post", "tcp", "exec":
|
||||
return handler, nil
|
||||
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
|
||||
func AlertServices(rule chronograf.AlertRule) (string, error) {
|
||||
alert := ""
|
||||
for _, service := range rule.Alerts {
|
||||
srv, err := kapaService(service)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := ValidateAlert(srv); err != nil {
|
||||
return "", err
|
||||
}
|
||||
alert = alert + fmt.Sprintf(".%s()", srv)
|
||||
node, err := addAlertNodes(rule)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return alert, nil
|
||||
|
||||
if err := ValidateAlert(node); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
|
|
@ -49,6 +49,103 @@ func TestAlertServices(t *testing.T) {
|
|||
.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 {
|
||||
got, err := AlertServices(tt.rule)
|
||||
|
|
|
@ -18,7 +18,7 @@ func ValidateAlert(service string) error {
|
|||
// Simple tick script to check alert service.
|
||||
// If a pipeline cannot be created then we know this is an invalid
|
||||
// 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))
|
||||
}
|
||||
|
||||
|
|
|
@ -11,12 +11,12 @@ func TestValidateAlert(t *testing.T) {
|
|||
}{
|
||||
{
|
||||
name: "Test valid template alert",
|
||||
service: "slack",
|
||||
service: ".slack()",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Test invalid template alert",
|
||||
service: "invalid",
|
||||
service: ".invalid()",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"info": {
|
||||
"title": "Chronograf",
|
||||
"description": "API endpoints for Chronograf",
|
||||
"version": "1.1.0"
|
||||
"version": "1.2.0"
|
||||
},
|
||||
"schemes": [
|
||||
"http"
|
||||
|
@ -1684,6 +1684,53 @@
|
|||
"fields"
|
||||
]
|
||||
},
|
||||
"KapacitorNode": {
|
||||
"type": "object",
|
||||
"description": "Represents a node in the kapacitor TICKscript graph",
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Name of the kapacitor node e.g. slack"
|
||||
},
|
||||
"args": {
|
||||
"type": "array",
|
||||
"description": "All arguments to the named node",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"type": "array",
|
||||
"description": "All properties attached to the kapacitor node",
|
||||
"items": {
|
||||
"$ref": "#/definitions/KapacitorProperty"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"KapacitorProperty": {
|
||||
"type": "object",
|
||||
"description": "Represents a property attached to a node in the kapacitor TICKscript graph",
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Name of the kapacitor property e.g. channel for a slack ndoe"
|
||||
},
|
||||
"args": {
|
||||
"type": "array",
|
||||
"description": "All arguments to the named property",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Rule": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
@ -1714,19 +1761,31 @@
|
|||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"alerta",
|
||||
"post",
|
||||
"http",
|
||||
"hipchat",
|
||||
"opsgenie",
|
||||
"pagerduty",
|
||||
"victorops",
|
||||
"smtp",
|
||||
"email",
|
||||
"exec",
|
||||
"sensu",
|
||||
"slack",
|
||||
"talk",
|
||||
"telegram"
|
||||
"telegram",
|
||||
"tcp"
|
||||
]
|
||||
}
|
||||
},
|
||||
"alertNodes": {
|
||||
"type": "array",
|
||||
"description": "Arguments and properties to add to alert",
|
||||
"items": {
|
||||
"$ref": "#/definitions/KapacitorNode"
|
||||
}
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "Message to send when alert occurs."
|
||||
|
@ -2301,4 +2360,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue