Skip to content

Commit

Permalink
Merge pull request #2430 from bonitoo-io/fix/servicenow-recommended-api
Browse files Browse the repository at this point in the history
fix(servicenow): use recommended event API
  • Loading branch information
docmerlin authored Nov 20, 2020
2 parents d96b023 + 957751d commit 9bedd7e
Show file tree
Hide file tree
Showing 9 changed files with 152 additions and 79 deletions.
15 changes: 8 additions & 7 deletions alert.go
Original file line number Diff line number Diff line change
Expand Up @@ -521,13 +521,14 @@ func newAlertNode(et *ExecutingTask, n *pipeline.AlertNode, d NodeDiagnostic) (a

for _, s := range n.ServiceNowHandlers {
c := servicenow.HandlerConfig{
URL: s.URL,
Source: s.Source,
Node: s.Node,
Type: s.Type,
Resource: s.Resource,
MetricName: s.MetricName,
MessageKey: s.MessageKey,
URL: s.URL,
Source: s.Source,
Node: s.Node,
Type: s.Type,
Resource: s.Resource,
MetricName: s.MetricName,
MessageKey: s.MessageKey,
AdditionalInfo: s.AdditionalInfoMap,
}
h := et.tm.ServiceNowService.Handler(c, ctx...)
an.handlers = append(an.handlers, h)
Expand Down
4 changes: 2 additions & 2 deletions etc/kapacitor/kapacitor.conf
Original file line number Diff line number Diff line change
Expand Up @@ -512,8 +512,8 @@ default-retention-policy = ""
[servicenow]
# Configure ServiceNow.
enabled = false
# The ServiceNow URL for target table. Replace instance with actual hostname.
url = "https://instance.service-now.com/api/now/v1/table/em_alert"
# The ServiceNow events web service URL. Replace instance with actual hostname.
url = "https://instance.service-now.com/api/global/em/jsonv2"
# Default source identification.
source = "Kapacitor"
# Username for HTTP BASIC authentication
Expand Down
45 changes: 28 additions & 17 deletions integrations/streamer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10291,12 +10291,14 @@ stream
.resource('CPU-Total')
.metricName('{{ index .Tags "type" }}')
.messageKey('Alert: {{ .ID }}')
.additionalInfo('link', 'http://push/alert?id={{ .ID }}')
.additionalInfo('ticks', 666)
`
tmInit := func(tm *kapacitor.TaskMaster) {

c := servicenow.NewConfig()
c.Enabled = true
c.URL = ts.URL
c.URL = ts.URL + "/api/global/em/jsonv2"
c.Source = "Kapacitor"
sl := servicenow.NewService(c, diagService.NewServiceNowHandler())
tm.ServiceNowService = sl
Expand All @@ -10306,25 +10308,34 @@ stream

exp := []interface{}{
servicenowtest.Request{
URL: "/",
Alert: servicenow.Alert{
Source: "Kapacitor",
Node: "serverA",
Type: "CPU", // literal since there is no tag for this in the testdata
Resource: "CPU-Total", // literal since there is no tag for this in the testdata
MetricName: "idle",
MessageKey: "Alert: kapacitor/cpu/serverA",
Severity: "1",
Description: "kapacitor/cpu/serverA is CRITICAL",
URL: "/api/global/em/jsonv2",
Alerts: servicenow.Events{
Records: []servicenow.Event{
{
Source: "Kapacitor",
Node: "serverA",
Type: "CPU", // literal since there is no tag for this in the testdata
Resource: "CPU-Total", // literal since there is no tag for this in the testdata
MetricName: "idle",
MessageKey: "Alert: kapacitor/cpu/serverA",
Severity: "1",
Description: "kapacitor/cpu/serverA is CRITICAL",
AdditionalInfo: "{\"link\":\"http://push/alert?id=kapacitor/cpu/serverA\",\"ticks\":\"666\"}",
},
},
},
},
servicenowtest.Request{
URL: "/",
Alert: servicenow.Alert{
Source: "Kapacitor",
MessageKey: "kapacitor/cpu/serverA",
Severity: "1",
Description: "kapacitor/cpu/serverA is CRITICAL",
URL: "/api/global/em/jsonv2",
Alerts: servicenow.Events{
Records: []servicenow.Event{
{
Source: "Kapacitor",
MessageKey: "kapacitor/cpu/serverA",
Severity: "1",
Description: "kapacitor/cpu/serverA is CRITICAL",
},
},
},
},
}
Expand Down
20 changes: 17 additions & 3 deletions pipeline/alert.go
Original file line number Diff line number Diff line change
Expand Up @@ -2130,7 +2130,7 @@ type TeamsHandler struct {
// Example:
// [serviceNow]
// enabled = true
// url = "https://instance.service-now.com/api/now/v1/table/em_alert"
// url = "https://instance.service-now.com/api/global/em/jsonv2"
//
// In order to not post a message every alert interval
// use AlertNode.StateChangesOnly so that only events
Expand All @@ -2148,7 +2148,7 @@ type TeamsHandler struct {
// Example:
// [serviceNow]
// enabled = true
// url = "https://instance.service-now.com/api/now/v1/table/em_alert"
// url = "https://instance.service-now.com/api/global/em/jsonv2"
// global = true
// state-changes-only = true
//
Expand Down Expand Up @@ -2199,5 +2199,19 @@ type ServiceNowHandler struct {
MetricName string `json:"metric_name"`

// Message key.
MessageKey string `json:"messageKey"`
MessageKey string `json:"message_key"`

// Addition info.
// tick:ignore
AdditionalInfoMap map[string]interface{} `tick:"AdditionalInfo" json:"additional_info"`
}

// AdditionalInfo adds key values pairs to the request.
// tick:property
func (s *ServiceNowHandler) AdditionalInfo(key string, value interface{}) *ServiceNowHandler {
if s.AdditionalInfoMap == nil {
s.AdditionalInfoMap = make(map[string]interface{})
}
s.AdditionalInfoMap[key] = value
return s
}
10 changes: 10 additions & 0 deletions pipeline/tick/alert.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,16 @@ func (n *AlertNode) Build(a *pipeline.AlertNode) (ast.Node, error) {
Dot("resource", h.Resource).
Dot("metricName", h.MetricName).
Dot("messageKey", h.MessageKey)

// Use stable key order
keys := make([]string, 0, len(h.AdditionalInfoMap))
for k := range h.AdditionalInfoMap {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
n.Dot("additionalInfo", k, h.AdditionalInfoMap[k])
}
}

for _, h := range a.SlackHandlers {
Expand Down
30 changes: 17 additions & 13 deletions server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7737,7 +7737,7 @@ func TestServer_UpdateConfig(t *testing.T) {
{
section: "servicenow",
setDefaults: func(c *server.Config) {
c.ServiceNow.URL = "https://instance.service-now.com/api/now/v1/table/em_alert"
c.ServiceNow.URL = "https://instance.service-now.com/api/global/em/jsonv2"
c.ServiceNow.Source = "Kapacitor"
c.ServiceNow.Username = ""
c.ServiceNow.Password = ""
Expand All @@ -7750,7 +7750,7 @@ func TestServer_UpdateConfig(t *testing.T) {
"enabled": false,
"global": false,
"state-changes-only": false,
"url": "https://instance.service-now.com/api/now/v1/table/em_alert",
"url": "https://instance.service-now.com/api/global/em/jsonv2",
"source": "Kapacitor",
"username": "",
"password": false,
Expand All @@ -7766,7 +7766,7 @@ func TestServer_UpdateConfig(t *testing.T) {
"enabled": false,
"global": false,
"state-changes-only": false,
"url": "https://instance.service-now.com/api/now/v1/table/em_alert",
"url": "https://instance.service-now.com/api/global/em/jsonv2",
"source": "Kapacitor",
"username": "",
"password": false,
Expand All @@ -7780,7 +7780,7 @@ func TestServer_UpdateConfig(t *testing.T) {
updateAction: client.ConfigUpdateAction{
Set: map[string]interface{}{
"enabled": true,
"url": "https://dev12345.service-now.com/api/now/v1/table/em_alert",
"url": "https://dev12345.service-now.com/api/global/em/jsonv2",
"username": "dev",
"password": "12345",
},
Expand All @@ -7793,7 +7793,7 @@ func TestServer_UpdateConfig(t *testing.T) {
"enabled": true,
"global": false,
"state-changes-only": false,
"url": "https://dev12345.service-now.com/api/now/v1/table/em_alert",
"url": "https://dev12345.service-now.com/api/global/em/jsonv2",
"source": "Kapacitor",
"username": "dev",
"password": true,
Expand All @@ -7809,7 +7809,7 @@ func TestServer_UpdateConfig(t *testing.T) {
"enabled": true,
"global": false,
"state-changes-only": false,
"url": "https://dev12345.service-now.com/api/now/v1/table/em_alert",
"url": "https://dev12345.service-now.com/api/global/em/jsonv2",
"source": "Kapacitor",
"username": "dev",
"password": true,
Expand Down Expand Up @@ -10449,20 +10449,24 @@ func TestServer_AlertHandlers(t *testing.T) {
ctxt := context.WithValue(context.Background(), "server", ts)

c.ServiceNow.Enabled = true
c.ServiceNow.URL = ts.URL
c.ServiceNow.URL = ts.URL + "/api/global/em/jsonv2"
c.ServiceNow.Source = "Kapacitor"
return ctxt, nil
},
result: func(ctxt context.Context) error {
ts := ctxt.Value("server").(*servicenowtest.Server)
ts.Close()
exp := []servicenowtest.Request{{
URL: "/",
Alert: servicenow.Alert{
Source: "Kapacitor",
Severity: "1",
Description: "message",
MessageKey: "id",
URL: "/api/global/em/jsonv2",
Alerts: servicenow.Events{
Records: []servicenow.Event{
{
Source: "Kapacitor",
Severity: "1",
Description: "message",
MessageKey: "id",
},
},
},
}}
got := ts.Requests()
Expand Down
8 changes: 5 additions & 3 deletions services/servicenow/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
type Config struct {
// Whether ServiceNow integration is enabled.
Enabled bool `toml:"enabled" override:"enabled"`
// ServiceNow alerts API URL.
// ServiceNow events API URL.
URL string `toml:"url" override:"url"`
// Event source.
Source string `toml:"source" override:"source"`
Expand All @@ -25,12 +25,14 @@ type Config struct {
}

func NewConfig() Config {
return Config{}
return Config{
URL: "https://instance.service-now.com/api/global/em/jsonv2", // dummy default
}
}

func (c Config) Validate() error {
if c.Enabled && c.URL == "" {
return errors.New("must specify Alerts URL")
return errors.New("must specify events URL")
}
if _, err := url.Parse(c.URL); err != nil {
return errors.Wrapf(err, "invalid url %q", c.URL)
Expand Down
Loading

0 comments on commit 9bedd7e

Please sign in to comment.