From aa4b6fb689dce97fecdcf0d37f9711c46876b03a Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Wed, 8 Feb 2017 22:18:23 -0600 Subject: [PATCH 1/6] Add kapacitor alert node structs --- chronograf.go | 33 ++++++++++++++++++++++++--------- server/swagger.json | 2 ++ 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/chronograf.go b/chronograf.go index b3c3e1c1e..309ef998e 100644 --- a/chronograf.go +++ b/chronograf.go @@ -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"` // 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 diff --git a/server/swagger.json b/server/swagger.json index 309b10e53..37d4b8fec 100644 --- a/server/swagger.json +++ b/server/swagger.json @@ -1714,6 +1714,8 @@ "items": { "type": "string", "enum": [ + "post", + "tcp", "hipchat", "opsgenie", "pagerduty", From bd6f3e1a6b8054ebe6b07ed3067e68432396d6ef Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 9 Feb 2017 00:10:23 -0600 Subject: [PATCH 2/6] Add kapa alert arguments and properties --- chronograf.go | 26 +++++----- kapacitor/alerts.go | 86 ++++++++++++++++++++++++++------- kapacitor/alerts_test.go | 97 ++++++++++++++++++++++++++++++++++++++ kapacitor/validate.go | 2 +- kapacitor/validate_test.go | 4 +- 5 files changed, 183 insertions(+), 32 deletions(-) diff --git a/chronograf.go b/chronograf.go index 309ef998e..f5f9f732c 100644 --- a/chronograf.go +++ b/chronograf.go @@ -104,16 +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"` // Alerts name all the services to notify (e.g. pagerduty) - AlertNodes []KapacitorNode `json:"alertNodes"` // 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 + 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 @@ -176,9 +176,9 @@ type QueryConfig struct { // KapacitorNode adds arguments and properties to an alert type KapacitorNode struct { - Name string `json:"name"` - Args []string `json:"args"` - Properties []*KapacitorProperty `json:"properties"` + Name string `json:"name"` + Args []string `json:"args"` + Properties []KapacitorProperty `json:"properties"` // In the future we could add chaining methods here. } diff --git a/kapacitor/alerts.go b/kapacitor/alerts.go index 861ea598e..b61594ce9 100644 --- a/kapacitor/alerts.go +++ b/kapacitor/alerts.go @@ -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 "sensu", "slack", "email", "talk", "telegram", "post", "tcp": + 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 } diff --git a/kapacitor/alerts_test.go b/kapacitor/alerts_test.go index 5dbb33b77..10a8214bc 100644 --- a/kapacitor/alerts_test.go +++ b/kapacitor/alerts_test.go @@ -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) diff --git a/kapacitor/validate.go b/kapacitor/validate.go index da7d0a590..ed8dca32a 100644 --- a/kapacitor/validate.go +++ b/kapacitor/validate.go @@ -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)) } diff --git a/kapacitor/validate_test.go b/kapacitor/validate_test.go index 701fe6708..06576585a 100644 --- a/kapacitor/validate_test.go +++ b/kapacitor/validate_test.go @@ -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, }, } From 8a62a15e3943eee8bfd40b7a190e4dc7564caa23 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 9 Feb 2017 00:23:46 -0600 Subject: [PATCH 3/6] Update kapacitor alert generation to support alerta --- kapacitor/alerts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kapacitor/alerts.go b/kapacitor/alerts.go index b61594ce9..7d33be964 100644 --- a/kapacitor/alerts.go +++ b/kapacitor/alerts.go @@ -21,7 +21,7 @@ func kapaHandler(handler string) (string, error) { return "email", nil case "http": return "post", nil - case "sensu", "slack", "email", "talk", "telegram", "post", "tcp": + case "alerta", "sensu", "slack", "email", "talk", "telegram", "post", "tcp": return handler, nil default: return "", fmt.Errorf("Unsupported alert handler %s", handler) From 9754f98dce9b510cadc0a0091042372d4ff1a4d0 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 9 Feb 2017 00:29:19 -0600 Subject: [PATCH 4/6] Add exec to supported kapacitor alerts --- kapacitor/alerts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kapacitor/alerts.go b/kapacitor/alerts.go index 7d33be964..50062fc4c 100644 --- a/kapacitor/alerts.go +++ b/kapacitor/alerts.go @@ -21,7 +21,7 @@ func kapaHandler(handler string) (string, error) { return "email", nil case "http": return "post", nil - case "alerta", "sensu", "slack", "email", "talk", "telegram", "post", "tcp": + case "alerta", "sensu", "slack", "email", "talk", "telegram", "post", "tcp", "exec": return handler, nil default: return "", fmt.Errorf("Unsupported alert handler %s", handler) From bc73f5704ee527431150f8585652b562aa1b324d Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 9 Feb 2017 00:30:35 -0600 Subject: [PATCH 5/6] Update README to support kapacitor tcp and post/http --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d4242923e..b19795d99 100644 --- a/README.md +++ b/README.md @@ -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 From 1402a4916df740b5f263a179213ce07ddca97737 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 9 Feb 2017 00:50:59 -0600 Subject: [PATCH 6/6] Update swagger spec to include alertNodes for kapacitor --- server/swagger.json | 65 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/server/swagger.json b/server/swagger.json index 37d4b8fec..5fc59101c 100644 --- a/server/swagger.json +++ b/server/swagger.json @@ -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,21 +1761,31 @@ "items": { "type": "string", "enum": [ + "alerta", "post", - "tcp", + "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." @@ -2303,4 +2360,4 @@ } } } -} +} \ No newline at end of file