interferon 0.0.20 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.travis.yml CHANGED
@@ -1,8 +1,9 @@
1
1
  language: ruby
2
+ sudo: false
2
3
  cache: bundler
3
4
  script: bundle exec rspec
4
5
  rvm:
5
6
  - 1.9.3
6
- - 2.1.5
7
- - 2.2.1
8
-
7
+ - 2.1.10
8
+ - 2.2.6
9
+ - 2.3.3
data/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  This repo contains the interferon gem.
6
6
  This gem enables you to store your alerts configuration in code.
7
7
  You should create your own repository, with a `Gemfile` which imports the interferon gem.
8
- For an example of such a repository, along with example configuration and alerts files, see https://www.github.com/airbnb/alerts_example
8
+ For an example of such a repository, along with example configuration and alerts files, see https://www.github.com/airbnb/alerts
9
9
 
10
10
  ## Running This Gem ##
11
11
 
@@ -16,7 +16,6 @@ module Interferon
16
16
  def evaluate(hostinfo)
17
17
  dsl = AlertDSL.new(hostinfo)
18
18
  dsl.instance_eval(@text, @filename, 1)
19
- dsl.name(dsl.name.strip)
20
19
  @dsl = dsl
21
20
 
22
21
  # return the alert and not the DSL object, which is private
@@ -19,12 +19,12 @@ module Interferon
19
19
  if val.nil? && block.nil?
20
20
  f = instance_variable_get(field)
21
21
  f.nil? ? default : f
22
- elsif val.nil?
23
- instance_variable_set(field, block.call)
24
- elsif block.nil?
25
- instance_variable_set(field, val)
26
- else
22
+ elsif !val.nil? && !block.nil?
27
23
  raise ArgumentError, "You must pass either a value or a block but not both to #{field}"
24
+ else
25
+ f = val.nil? ? block.call : val
26
+ f = yield(f) if block_given?
27
+ instance_variable_set(field, f)
28
28
  end
29
29
  end
30
30
  end
@@ -33,19 +33,31 @@ module Interferon
33
33
  include DSLMixin
34
34
 
35
35
  def name(v = nil, &block)
36
- get_or_set(:@name, v, block, '')
36
+ get_or_set(:@name, v, block, '') { |val| val.strip }
37
37
  end
38
38
 
39
39
  def message(v = nil, &block)
40
40
  get_or_set(:@message, v, block, '')
41
41
  end
42
42
 
43
- def silenced(v = nil, &block)
44
- get_or_set(:@silenced, v, block, false)
43
+ def monitor_type(v = nil, &block)
44
+ get_or_set(:@monitor_type, v, block, 'metric alert')
45
45
  end
46
46
 
47
- def silenced_until(v = nil, &block)
48
- get_or_set(:@silenced_until, v && Time.parse(v), block, Time.at(0))
47
+ def applies(v = nil, &block)
48
+ get_or_set(:@applies, v, block, false)
49
+ end
50
+
51
+ def silenced(v = nil, &block)
52
+ get_or_set(:@silenced, v, block, {}) do |val|
53
+ if val.is_a? Hash
54
+ val
55
+ elsif val == true
56
+ { "*" => nil }
57
+ else
58
+ {}
59
+ end
60
+ end
49
61
  end
50
62
 
51
63
  def is_work_hour?(args = {})
@@ -62,15 +74,24 @@ module Interferon
62
74
  end
63
75
 
64
76
  def no_data_timeframe(v = nil, &block)
65
- get_or_set(:@no_data_timeframe, v, block, false)
77
+ get_or_set(:@no_data_timeframe, v, block, nil)
66
78
  end
67
79
 
68
80
  def timeout(v = nil, &block)
69
- get_or_set(:@timeout, v, block, false)
81
+ get_or_set(:@timeout, v, block, nil)
70
82
  end
71
83
 
72
- def applies(v = nil, &block)
73
- get_or_set(:@applies, v, block, false)
84
+ def timeout_h
85
+ # timeout is in seconds, but set it to 1 hour at least
86
+ timeout ? [1, timeout.to_i / 3600].max : nil
87
+ end
88
+
89
+ def thresholds(v = nil, &block)
90
+ get_or_set(:@thresholds, v, block, nil)
91
+ end
92
+
93
+ def require_full_window(v = nil, &block)
94
+ get_or_set(:@require_full_window, v, block, nil)
74
95
  end
75
96
 
76
97
  def notify(v = nil)
@@ -92,13 +113,17 @@ module Interferon
92
113
  def groups(v = nil, &block)
93
114
  get_or_set(:@groups, v, block, [])
94
115
  end
116
+
117
+ def audit(v = nil, &block)
118
+ get_or_set(:@audit, v, block, true)
119
+ end
95
120
  end
96
121
 
97
122
  class MetricDSL
98
123
  include DSLMixin
99
124
 
100
125
  def datadog_query(v = nil, &block)
101
- get_or_set(:@datadog_query, v, block, '')
126
+ get_or_set(:@datadog_query, v, block, '') { |val| val.strip }
102
127
  end
103
128
  end
104
129
  end
@@ -1,6 +1,8 @@
1
1
  require 'diffy'
2
2
  require 'dogapi'
3
+ require 'parallel'
3
4
  require 'set'
5
+ require 'thread'
4
6
 
5
7
  Diffy::Diff.default_format = :text
6
8
 
@@ -37,8 +39,11 @@ module Interferon::Destinations
37
39
  @existing_alerts = nil
38
40
  @dry_run = options['dry_run']
39
41
 
40
- # create datadog alerts 10 at a time
42
+ # Datadog communication threads
41
43
  @concurrency = options['concurrency'] || 10
44
+ # Fetch page size
45
+ @page_size = options['page_size'] || 1000
46
+
42
47
  # configure retries
43
48
  @retries = options['retries'] || 3
44
49
 
@@ -66,25 +71,39 @@ module Interferon::Destinations
66
71
  end
67
72
 
68
73
  def get_existing_alerts
69
- resp = @dog.get_all_alerts()
74
+ alerts = Queue.new
75
+ has_more = true
76
+
77
+ Parallel.map_with_index(-> { has_more || Parallel::Stop },
78
+ in_threads: @concurrency) do |_, page|
79
+ successful = false
80
+ @retries.downto(0) do
81
+ resp = @dog.get_all_monitors(page: page, page_size: @page_size)
82
+ code = resp[0].to_i
83
+ if code != 200
84
+ log.info("Failed to retrieve existing alerts from datadog. #{code}: #{resp[1].inspect}")
85
+ else
86
+ alerts_page = resp[1]
87
+ if alerts_page.length < @page_size
88
+ has_more = false
89
+ end
90
+ alerts_page.map { |alert| alerts.push(alert) }
91
+ successful = true
92
+ break
93
+ end
94
+ end
70
95
 
71
- code = resp[0].to_i
72
- if code != 200
73
- raise "Failed to retrieve existing alerts from datadog. #{code}: #{resp[1].inspect}"
96
+ if !successful
97
+ # Out of retries
98
+ raise "Retries exceeded for fetching data from datadog."
99
+ end
74
100
  end
75
- resp[1]['alerts']
101
+ alerts.size.times.map { alerts.pop }
76
102
  end
77
103
 
78
104
  def existing_alerts
79
105
  unless @existing_alerts
80
- retries = @retries
81
- begin
82
- alerts = get_existing_alerts
83
- rescue
84
- retries -= 1
85
- retry if retries >= 0
86
- raise
87
- end
106
+ alerts = get_existing_alerts
88
107
 
89
108
  # key alerts by name
90
109
  @existing_alerts = {}
@@ -108,83 +127,43 @@ module Interferon::Destinations
108
127
  ]
109
128
  end
110
129
 
111
- return @existing_alerts
130
+ @existing_alerts
112
131
  end
113
132
 
114
133
  def create_alert(alert, people)
115
134
  # create a message which includes the notifications
135
+ # Datadog may have a race condition where alerts created in a bad state may be triggered
136
+ # during the dry-run creation process. Delete people from dry-run alerts to avoid this
116
137
  message = generate_message(alert['message'], people)
117
138
 
118
139
  # create the hash of options to send to datadog
119
- alert_opts = {
120
- :name => alert['name'],
121
- :message => message,
122
- :silenced => false,
140
+ alert_options = {
141
+ :notify_audit => alert['notify']['audit'],
123
142
  :notify_no_data => alert['notify_no_data'],
124
- :timeout_h => nil,
143
+ :no_data_timeframe => alert['no_data_timeframe'],
144
+ :silenced => alert['silenced'],
145
+ :timeout_h => alert['timeout_h'],
125
146
  }
126
147
 
127
- if @dry_run
128
- # Datadog may have a race condition where alerts created in a bad state may be triggered
129
- # during the dry-run creation process. Delete people from dry-run alerts to avoid this
130
- alert_opts[:message] = generate_message(alert['message'], [])
148
+ if !alert['require_full_window'].nil?
149
+ alert_options[:require_full_window] = alert['require_full_window']
131
150
  end
132
151
 
133
- # Set alert to be silenced if there is a silenced set or silenced_until set
134
- if alert['silenced'] || alert['silenced_until'] > Time.now
135
- alert_opts[:silenced] = true
152
+ if !alert['thresholds'].nil?
153
+ alert_options[:thresholds] = alert['thresholds']
136
154
  end
137
155
 
138
- # allow an optional timeframe for "no data" alerts to be specified
139
- # (this feature is supported, even though it's not documented)
140
- alert_opts[:no_data_timeframe] = alert['no_data_timeframe'] if alert['no_data_timeframe']
141
-
142
- # timeout is in seconds, but set it to 1 hour at least
143
- alert_opts[:timeout_h] = [1, (alert['timeout'].to_i / 3600)].max if alert['timeout']
144
-
145
- datadog_query = alert['metric']['datadog_query'].strip
156
+ datadog_query = alert['metric']['datadog_query']
146
157
  existing_alert = existing_alerts[alert['name']]
147
158
 
148
159
  # new alert, create it
149
160
  if existing_alert.nil?
150
161
  action = :creating
151
- @stats[:alerts_to_be_created] += 1
152
- new_alert_text = "Query: #{datadog_query} Message: #{message.split().join(' ')}"
153
- log.info("creating new alert #{alert['name']}: #{new_alert_text}")
154
-
155
- resp = @dog.alert(
156
- alert['metric']['datadog_query'].strip,
157
- alert_opts,
158
- )
159
-
160
- # existing alert, modify it
162
+ resp = create_datadog_alert(alert, datadog_query, message, alert_options)
161
163
  else
164
+ # existing alert, modify it
162
165
  action = :updating
163
- @stats[:alerts_to_be_updated] += 1
164
- id = existing_alert['id'][0]
165
-
166
- new_alert_text = "Query:\n#{datadog_query}\nMessage:\n#{message}"
167
- existing_alert_text = "Query:\n#{existing_alert['query']}\nMessage:\n#{existing_alert['message']}\n"
168
- diff = Diffy::Diff.new(existing_alert_text, new_alert_text, :context=>1)
169
- log.info("updating existing alert #{id} (#{alert['name']}): #{diff}")
170
-
171
- if @dry_run
172
- resp = @dog.alert(
173
- alert['metric']['datadog_query'].strip,
174
- alert_opts,
175
- )
176
- else
177
- resp = @dog.update_alert(
178
- id,
179
- alert['metric']['datadog_query'].strip,
180
- alert_opts
181
- )
182
- # Unmute existing alerts that have been unsilenced.
183
- # Datadog does not allow updates to silencing via the update_alert API call.
184
- if existing_alert['silenced'] && !alert_opts[:silenced]
185
- @dog.unmute_monitor(id)
186
- end
187
- end
166
+ resp = update_datadog_alert(alert, datadog_query, message, alert_options, existing_alert)
188
167
  end
189
168
 
190
169
  # log whenever we've encountered errors
@@ -196,14 +175,100 @@ module Interferon::Destinations
196
175
  # assume this was a success
197
176
  @stats[:alerts_created] += 1 if action == :creating
198
177
  @stats[:alerts_updated] += 1 if action == :updating
199
- @stats[:alerts_silenced] += 1 if alert_opts[:silenced]
178
+ @stats[:alerts_silenced] += 1 if !alert_options[:silenced].empty?
200
179
  end
201
180
 
202
181
  id = resp[1].nil? ? nil : [resp[1]['id']]
203
182
  # lets key alerts by their name
204
- return [alert['name'], id]
183
+ [alert['name'], id]
184
+ end
185
+
186
+ def create_datadog_alert(alert, datadog_query, message, alert_options)
187
+ @stats[:alerts_to_be_created] += 1
188
+ new_alert_text = <<-EOM
189
+ Query:
190
+ #{datadog_query}
191
+ Message:
192
+ #{message}
193
+ Options:
194
+ #{alert_options}
195
+ EOM
196
+ log.info("creating new alert #{alert['name']}: #{new_alert_text}")
197
+
198
+ @dog.monitor(
199
+ alert['monitor_type'],
200
+ datadog_query,
201
+ :name => alert['name'],
202
+ :message => @dry_run ? generate_message(alert, []) : message,
203
+ :options => alert_options,
204
+ )
205
+ end
206
+
207
+ def update_datadog_alert(alert, datadog_query, message, alert_options, existing_alert)
208
+ @stats[:alerts_to_be_updated] += 1
209
+ id = existing_alert['id'][0]
210
+
211
+ new_alert_text = <<-EOM.strip
212
+ Query:
213
+ #{datadog_query.strip}
214
+ Message:
215
+ #{message.strip}
216
+ Options:
217
+ #{alert_options}
218
+ EOM
219
+ existing_alert_text = <<-EOM.strip
220
+ Query:
221
+ #{existing_alert['query'].strip}
222
+ Message:
223
+ #{existing_alert['message'].strip}
224
+ Options:
225
+ #{alert_options}
226
+ EOM
227
+ diff = Diffy::Diff.new(existing_alert_text, new_alert_text, :context=>1)
228
+ log.info("updating existing alert #{id} (#{alert['name']}):\n#{diff}")
229
+
230
+ if @dry_run
231
+ resp = @dog.monitor(
232
+ alert['monitor_type'],
233
+ datadog_query,
234
+ :name => alert['name'],
235
+ :message => generate_message(alert, []),
236
+ :options => alert_options,
237
+ )
238
+ else
239
+ if alert['monitor_type'] == existing_alert['type']
240
+ resp = @dog.update_monitor(
241
+ id,
242
+ datadog_query,
243
+ :name => alert['name'],
244
+ :message => message,
245
+ :options => alert_options,
246
+ )
247
+
248
+ # Unmute existing alerts that have been unsilenced.
249
+ # Datadog does not allow updates to silencing via the update_alert API call.
250
+ if !existing_alert['options']['silenced'].empty? && alert_options[:silenced].empty?
251
+ @dog.unmute_monitor(id)
252
+ end
253
+ else
254
+ # Need to recreate alert with new monitor type
255
+ resp = @dog.delete_monitor(id)
256
+ code = resp[0].to_i
257
+ if !(code >= 300 || code == -1)
258
+ resp = @dog.monitor(
259
+ alert['monitor_type'],
260
+ datadog_query,
261
+ :name => alert['name'],
262
+ :message => message,
263
+ :options => alert_options,
264
+ )
265
+ end
266
+ end
267
+ end
268
+ resp
205
269
  end
206
270
 
271
+
207
272
  def remove_alert(alert)
208
273
  if alert['message'].include?(ALERT_KEY)
209
274
  @stats[:alerts_to_be_deleted] += 1
@@ -211,7 +276,7 @@ module Interferon::Destinations
211
276
 
212
277
  if !@dry_run
213
278
  alert['id'].each do |alert_id|
214
- resp = @dog.delete_alert(alert_id)
279
+ resp = @dog.delete_monitor(alert_id)
215
280
  code = resp[0].to_i
216
281
  log_datadog_response_code(resp, code, :deleting)
217
282
 
@@ -244,7 +309,7 @@ module Interferon::Destinations
244
309
  def remove_alert_by_id(alert_id)
245
310
  # This should only be used by dry-run to clean up created dry-run alerts
246
311
  log.debug("deleting alert, id: #{alert_id}")
247
- resp = @dog.delete_alert(alert_id)
312
+ resp = @dog.delete_monitor(alert_id)
248
313
  code = resp[0].to_i
249
314
  log_datadog_response_code(resp, code, :deleting)
250
315
  end
@@ -263,7 +328,7 @@ module Interferon::Destinations
263
328
  statsd.gauge('datadog.api.client_error', 1, :tags => ["alert:#{alert}"])
264
329
  statsd.gauge('datadog.api.success', 0, :tags => ["alert:#{alert}"])
265
330
  log.error("client error while #{action} alert '#{alert['name']}';" \
266
- " query was '#{alert['metric']['datadog_query'].strip}'" \
331
+ " query was '#{alert['metric']['datadog_query']}'" \
267
332
  " response was #{resp[0]}:'#{resp[1].inspect}'")
268
333
  end
269
334
 
@@ -275,7 +340,7 @@ module Interferon::Destinations
275
340
  statsd.gauge('datadog.api.client_error', 0, :tags => ["alert:#{alert}"])
276
341
  statsd.gauge('datadog.api.success', 0, :tags => ["alert:#{alert}"])
277
342
  log.error("unknown error while #{action} alert '#{alert['name']}':" \
278
- " query was '#{alert['metric']['datadog_query'].strip}'" \
343
+ " query was '#{alert['metric']['datadog_query']}'" \
279
344
  " response was #{resp[0]}:'#{resp[1].inspect}'")
280
345
  end
281
346
  else
@@ -1,3 +1,3 @@
1
1
  module Interferon
2
- VERSION = "0.0.20"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/interferon.rb CHANGED
@@ -391,27 +391,46 @@ module Interferon
391
391
  end
392
392
  end
393
393
 
394
+ def self.normalize_monitor_type(monitor_type)
395
+ # Convert 'query alert' type to 'metric alert' type. They can used interchangeably when
396
+ # submitting monitors to Datadog. Datadog will automatically do the conversion to 'query
397
+ # alert' for a "complex" query that includes multiple metrics/tags while using 'metric alert'
398
+ # for monitors that include a single scope/metric.
399
+ monitor_type == 'query alert' ? 'metric alert' : monitor_type
400
+ end
401
+
394
402
  def self.same_alerts(dest, alert_people_pair, alert_api_json)
395
403
  alert, people = alert_people_pair
396
404
 
397
405
  prev_alert = {
406
+ :monitor_type => normalize_monitor_type(alert_api_json['type']),
398
407
  :query => alert_api_json['query'].strip,
399
408
  :message => alert_api_json['message'].strip,
400
- :notify_no_data => alert_api_json['notify_no_data'],
401
- :silenced => alert_api_json['silenced'],
402
- :timeout => alert_api_json['timeout_h'],
403
- :no_data_timeframe => alert_api_json['no_data_timeframe']
409
+ :notify_no_data => alert_api_json['options']['notify_no_data'],
410
+ :notify_audit => alert_api_json['options']['notify_audit'],
411
+ :no_data_timeframe => alert_api_json['options']['no_data_timeframe'],
412
+ :silenced => alert_api_json['options']['silenced'],
413
+ :thresholds => alert_api_json['options']['thresholds'],
414
+ :timeout_h => alert_api_json['options']['timeout_h'],
404
415
  }
405
416
 
406
417
  new_alert = {
407
- :query => alert['metric']['datadog_query'].strip,
418
+ :monitor_type => normalize_monitor_type(alert['monitor_type']),
419
+ :query => alert['metric']['datadog_query'],
408
420
  :message => dest.generate_message(alert['message'], people).strip,
409
421
  :notify_no_data => alert['notify_no_data'],
410
- :silenced => alert['silenced'] || alert['silenced_until'] > Time.now,
411
- :timeout => alert['timeout'] ? [1, alert['timeout'].to_i / 3600].max : nil,
412
- :no_data_timeframe => alert['no_data_timeframe'] || nil
422
+ :notify_audit => alert['notify']['audit'],
423
+ :no_data_timeframe => alert['no_data_timeframe'],
424
+ :silenced => alert['silenced'],
425
+ :thresholds => alert['thresholds'],
426
+ :timeout_h => alert['timeout_h']
413
427
  }
414
428
 
429
+ if !alert['require_full_window'].nil?
430
+ pre_alert[:require_full_window] = alert_api_json['options']['require_full_window']
431
+ new_alert[:require_full_window] = alert['require_full_window']
432
+ end
433
+
415
434
  prev_alert == new_alert
416
435
  end
417
436
 
@@ -25,85 +25,100 @@ describe Interferon::Destinations::Datadog do
25
25
  'name' => 'Test Alert',
26
26
  'message' => "Test Message",
27
27
  'metric' => { 'datadog_query' => 'avg:metric{*}' },
28
- 'silenced' => false,
29
- 'silenced_until' => Time.at(0),
28
+ 'silenced' => {},
29
+ 'notify' => {},
30
30
  }
31
31
  }
32
32
  let(:mock_people) { ['foo', 'bar', 'baz'] }
33
+ let(:mock_response) {
34
+ {
35
+ "Test Alert" => {
36
+ "id" => 567,
37
+ "name" => 'Test Alert',
38
+ 'message' => "Test Message",
39
+ "query" => 'avg:metric{*}',
40
+ "options" => {
41
+ "silenced" => {}
42
+ }
43
+ }
44
+ }
45
+ }
33
46
 
34
47
  describe ".get_existing_alerts" do
35
- it "calls dogapi get_all_alerts" do
36
- expect_any_instance_of(Dogapi::Client).to receive(:get_all_alerts).and_return([200, ""])
48
+ it "calls dogapi get_all_monitors" do
49
+ expect_any_instance_of(Dogapi::Client).to receive(:get_all_monitors).and_return([200, []])
37
50
  datadog.get_existing_alerts
38
51
  end
39
52
  end
40
53
 
41
54
  describe ".existing_alerts" do
42
- it "retries dogapi get_all_alerts" do
55
+ it "retries dogapi get_all_monitors" do
43
56
  return_vals = [[400, ""]] * (retries + 1)
44
- expect_any_instance_of(Dogapi::Client).to receive(:get_all_alerts).and_return(*return_vals)
57
+ expect_any_instance_of(Dogapi::Client).to receive(:get_all_monitors).and_return(*return_vals)
45
58
  expect { datadog.existing_alerts }.to raise_error RuntimeError
46
59
  end
47
60
  end
48
61
 
49
62
  describe ".create_alert" do
50
- it "calls dogapi alert" do
51
- expect_any_instance_of(Dogapi::Client).to receive(:alert).and_return([200, ""])
63
+ it "calls dogapi monitor" do
64
+ expect_any_instance_of(Dogapi::Client).to receive(:monitor).and_return([200, ""])
52
65
  expect(datadog).to receive(:existing_alerts).and_return({})
53
66
  datadog.create_alert(mock_alert, mock_people)
54
67
  end
55
68
 
56
- it "calls dogapi update_alert when alert name is found" do
57
- expect_any_instance_of(Dogapi::Client).to receive(:update_alert).and_return([200, ""])
58
- expect(datadog).to receive(:existing_alerts).and_return(
59
- {
60
- "Test Alert" => {
61
- "id" => 567,
62
- "name" => 'Test Alert',
63
- }
64
- }
65
- )
69
+ it "calls dogapi update_monitor when alert name is found" do
70
+ expect_any_instance_of(Dogapi::Client).to receive(:update_monitor).and_return([200, ""])
71
+ expect(datadog).to receive(:existing_alerts).and_return(mock_response)
66
72
  datadog.create_alert(mock_alert, mock_people)
67
73
  end
68
74
 
69
- it "always calls alert in dry-run" do
70
- expect_any_instance_of(Dogapi::Client).to receive(:alert).and_return([200, ""])
71
- expect(datadog_dry_run).to receive(:existing_alerts).and_return(
72
- {
73
- "Test Alert" => {
74
- "id" => 567,
75
- "name" => 'Test Alert',
76
- }
77
- }
78
- )
75
+ it "calls dogapi to delete and recreate when alert name is found" do
76
+ expect_any_instance_of(Dogapi::Client).to receive(:delete_monitor).and_return([200, ""])
77
+ expect_any_instance_of(Dogapi::Client).to receive(:monitor).and_return([200, ""])
78
+ mock_response["Test Alert"]["type"] = "event_alert"
79
+ expect(datadog).to receive(:existing_alerts).and_return(mock_response)
80
+ datadog.create_alert(mock_alert, mock_people)
81
+ end
82
+
83
+ it "calls dogapi to unmute when exiting alert is muted" do
84
+ expect_any_instance_of(Dogapi::Client).to receive(:update_monitor).and_return([200, ""])
85
+ expect_any_instance_of(Dogapi::Client).to receive(:unmute_monitor).and_return([200, ""])
86
+ mock_response["Test Alert"]["options"]["silenced"] = { '*' => nil }
87
+ expect(datadog).to receive(:existing_alerts).and_return(mock_response)
88
+ datadog.create_alert(mock_alert, mock_people)
89
+ end
90
+
91
+ it "always calls monitor in dry-run" do
92
+ expect_any_instance_of(Dogapi::Client).to receive(:monitor).and_return([200, ""])
93
+ expect(datadog_dry_run).to receive(:existing_alerts).and_return(mock_response)
79
94
  datadog_dry_run.create_alert(mock_alert, mock_people)
80
95
  end
81
96
  end
82
97
 
83
98
  describe ".remove_alert" do
84
- it "calls dogapi delete_alert with the correct alert id" do
99
+ it "calls dogapi delete_monitor with the correct alert id" do
85
100
  mock_alert["message"] += Interferon::Destinations::Datadog::ALERT_KEY
86
- expect_any_instance_of(Dogapi::Client).to receive(:delete_alert).
101
+ expect_any_instance_of(Dogapi::Client).to receive(:delete_monitor).
87
102
  with(mock_alert_id).and_return([200, ""])
88
103
  datadog.remove_alert(mock_alert)
89
104
  end
90
105
 
91
- it "does not call dogapi delete_alert in dry_run" do
106
+ it "does not call dogapi delete_monitor in dry_run" do
92
107
  mock_alert["message"] += Interferon::Destinations::Datadog::ALERT_KEY
93
- expect_any_instance_of(Dogapi::Client).to_not receive(:delete_alert)
108
+ expect_any_instance_of(Dogapi::Client).to_not receive(:delete_monitor)
94
109
  datadog_dry_run.remove_alert(mock_alert)
95
110
  end
96
111
 
97
- it "does not call dogapi delete_alert when ALERT_KEY is missing" do
98
- expect_any_instance_of(Dogapi::Client).to_not receive(:delete_alert)
112
+ it "does not call dogapi delete_monitor when ALERT_KEY is missing" do
113
+ expect_any_instance_of(Dogapi::Client).to_not receive(:delete_monitor)
99
114
  datadog.remove_alert(mock_alert)
100
115
  end
101
116
 
102
117
  end
103
118
 
104
119
  describe ".remove_alert_by_id" do
105
- it "calls dogapi delete_alert" do
106
- expect_any_instance_of(Dogapi::Client).to receive(:delete_alert).
120
+ it "calls dogapi delete_monitor" do
121
+ expect_any_instance_of(Dogapi::Client).to receive(:delete_monitor).
107
122
  with(mock_alert_id).and_return([200, ""])
108
123
  datadog.remove_alert_by_id(mock_alert_id)
109
124
  end
@@ -1,6 +1,5 @@
1
1
  require 'spec_helper'
2
2
  require 'helpers/mock_alert'
3
- require 'helpers/dsl_helper'
4
3
  require 'interferon/destinations/datadog'
5
4
 
6
5
  include Interferon
@@ -26,22 +25,33 @@ describe Interferon::Interferon do
26
25
  end
27
26
 
28
27
  it "detects a change if alert notify_no_data is different" do
29
- alert1 = create_test_alert('name1', 'testquery1', 'message1', false)
30
- alert2 = mock_alert_json('name2', 'testquery2', 'message2', true)
28
+ alert1 = create_test_alert('name1', 'testquery1', 'message1', { :notify_no_data => false })
29
+ alert2 = mock_alert_json(
30
+ 'name2',
31
+ 'testquery2',
32
+ 'message2',
33
+ nil,
34
+ [1],
35
+ { :notify_no_data => true }
36
+ )
31
37
 
32
38
  expect(Interferon::Interferon.same_alerts(dest, [alert1, []], alert2)).to be false
33
39
  end
34
40
 
35
41
  it "detects a change if alert silenced is different" do
36
- alert1 = create_test_alert('name1', 'testquery1', 'message1', false, true)
37
- alert2 = mock_alert_json('name2', 'testquery2', 'message2', false, false)
42
+ alert1 = create_test_alert('name1', 'testquery1', 'message1', { :silenced => true })
43
+ alert2 = mock_alert_json('name2', 'testquery2', 'message2', nil, [1], { :silenced => {} })
38
44
 
39
45
  expect(Interferon::Interferon.same_alerts(dest, [alert1, []], alert2)).to be false
40
46
  end
41
47
 
42
48
  it "does not detect a change when alert datadog query and message are the same" do
43
49
  alert1 = create_test_alert('name1', 'testquery1', 'message1')
44
- alert2 = mock_alert_json('name1', 'testquery1', "message1\nThis alert was created via the alerts framework")
50
+ alert2 = mock_alert_json(
51
+ 'name1',
52
+ 'testquery1',
53
+ "message1\nThis alert was created via the alerts framework"
54
+ )
45
55
 
46
56
  expect(Interferon::Interferon.same_alerts(dest, [alert1, []], alert2)).to be true
47
57
  end
@@ -62,7 +72,12 @@ describe Interferon::Interferon do
62
72
  expect(dest).not_to receive(:create_alert)
63
73
  expect(dest).not_to receive(:remove_alert_by_id)
64
74
 
65
- interferon.update_alerts_on_destination(dest, ['host'], [alerts['name1'], alerts['name2']], {})
75
+ interferon.update_alerts_on_destination(
76
+ dest,
77
+ ['host'],
78
+ [alerts['name1'], alerts['name2']],
79
+ {}
80
+ )
66
81
  end
67
82
 
68
83
  it 'runs added alerts' do
@@ -71,7 +86,12 @@ describe Interferon::Interferon do
71
86
  expect(dest).to receive(:create_alert).once.and_call_original
72
87
  expect(dest).to receive(:remove_alert_by_id).with('3').once
73
88
 
74
- interferon.update_alerts_on_destination(dest, ['host'], [alerts['name1'], alerts['name2'], added], {})
89
+ interferon.update_alerts_on_destination(
90
+ dest,
91
+ ['host'],
92
+ [alerts['name1'], alerts['name2'], added],
93
+ {}
94
+ )
75
95
  end
76
96
 
77
97
  it 'runs updated alerts' do
@@ -89,7 +109,7 @@ describe Interferon::Interferon do
89
109
  end
90
110
 
91
111
  it 'deletes duplicate old alerts' do
92
- alert1 = mock_alert_json('name1', 'testquery1', '', false, false, [1, 2, 3])
112
+ alert1 = mock_alert_json('name1', 'testquery1', '', nil, [1, 2, 3])
93
113
  alert2 = mock_alert_json('name2', 'testquery2', '')
94
114
  existing_alerts = {'name1' => alert1, 'name2' => alert2}
95
115
  dest = MockDest.new(existing_alerts)
@@ -104,7 +124,7 @@ describe Interferon::Interferon do
104
124
  end
105
125
 
106
126
  it 'deletes duplicate old alerts when creating new alert' do
107
- alert1 = mock_alert_json('name1', 'testquery1', '', false, false, [1, 2, 3])
127
+ alert1 = mock_alert_json('name1', 'testquery1', '', nil, [1, 2, 3])
108
128
  alert2 = mock_alert_json('name2', 'testquery2', '')
109
129
  existing_alerts = {'name1' => alert1, 'name2' => alert2}
110
130
  dest = MockDest.new(existing_alerts)
@@ -116,7 +136,7 @@ describe Interferon::Interferon do
116
136
 
117
137
  # Since we change id to nil we will not be attempting to delete duplicate alerts
118
138
  # during dry run
119
- # expect(dest).to receive(:remove_alert).with(existing_alerts['name1'])
139
+ expect(dest).to_not receive(:remove_alert).with(existing_alerts['name1'])
120
140
  expect(dest).to receive(:remove_alert).with(existing_alerts['name2'])
121
141
 
122
142
  interferon.update_alerts_on_destination(dest, ['host'], [added], {})
@@ -147,7 +167,12 @@ describe Interferon::Interferon do
147
167
  expect(dest).to receive(:create_alert).once.and_call_original
148
168
  expect(dest).not_to receive(:remove_alert_by_id).with('3')
149
169
 
150
- interferon.update_alerts_on_destination(dest, ['host'], [alerts['name1'], alerts['name2'], added], {})
170
+ interferon.update_alerts_on_destination(
171
+ dest,
172
+ ['host'],
173
+ [alerts['name1'], alerts['name2'], added],
174
+ {}
175
+ )
151
176
  end
152
177
 
153
178
  it 'runs updated alerts' do
@@ -167,7 +192,7 @@ describe Interferon::Interferon do
167
192
  end
168
193
 
169
194
  it 'deletes duplicate old alerts' do
170
- alert1 = mock_alert_json('name1', 'testquery1', '', false, false, [1, 2, 3])
195
+ alert1 = mock_alert_json('name1', 'testquery1', '', nil, [1, 2, 3])
171
196
  alert2 = mock_alert_json('name2', 'testquery2', '')
172
197
  existing_alerts = {'name1' => alert1, 'name2' => alert2}
173
198
  dest = MockDest.new(existing_alerts)
@@ -182,7 +207,7 @@ describe Interferon::Interferon do
182
207
  end
183
208
 
184
209
  it 'deletes duplicate old alerts when creating new alert' do
185
- alert1 = mock_alert_json('name1', 'testquery1', '', false, false, [1, 2, 3])
210
+ alert1 = mock_alert_json('name1', 'testquery1', '', nil, [1, 2, 3])
186
211
  alert2 = mock_alert_json('name2', 'testquery2', '')
187
212
  existing_alerts = {'name1' => alert1, 'name2' => alert2}
188
213
  dest = MockDest.new(existing_alerts)
@@ -190,7 +215,13 @@ describe Interferon::Interferon do
190
215
 
191
216
  added = create_test_alert('name1', 'testquery1', '')
192
217
 
193
- expect(dest).to receive(:remove_alert).with(mock_alert_json('name1', 'testquery1', '', false, false, [2, 3]))
218
+ expect(dest).to receive(:remove_alert).with(mock_alert_json(
219
+ 'name1',
220
+ 'testquery1',
221
+ '',
222
+ nil,
223
+ [2, 3]
224
+ ))
194
225
  expect(dest).to receive(:remove_alert).with(existing_alerts['name2'])
195
226
 
196
227
  interferon.update_alerts_on_destination(dest, ['host'], [added], {})
@@ -204,8 +235,6 @@ describe Interferon::Interferon do
204
235
  end
205
236
 
206
237
  class MockDest < Interferon::Destinations::Datadog
207
- @existing_alerts
208
-
209
238
  def initialize(the_existing_alerts)
210
239
  @existing_alerts = the_existing_alerts
211
240
  end
@@ -221,29 +250,53 @@ describe Interferon::Interferon do
221
250
  end
222
251
  end
223
252
 
224
- def mock_alert_json(name, datadog_query, message, notify_no_data=false, silenced=false, id=nil)
225
- { 'name'=> name,
226
- 'query'=> datadog_query,
227
- 'message'=> message,
228
- 'notify_no_data' => notify_no_data,
229
- 'silenced' => silenced,
230
- 'id' => id.nil? ? [name[-1]] : id
253
+ DEFAULT_OPTIONS = {
254
+ 'notify_audit' => true,
255
+ 'notify_no_data' => false,
256
+ 'silenced' => {},
257
+ 'thresholds' => nil,
258
+ 'no_data_timeframe' => nil,
259
+ 'require_full_window' => nil,
260
+ 'timeout' => nil,
261
+ }
262
+
263
+ def mock_alert_json(name, datadog_query, message, type="metric alert", id=nil, options={})
264
+ options = DEFAULT_OPTIONS.merge(options)
265
+
266
+ {
267
+ 'name'=> name,
268
+ 'query' => datadog_query,
269
+ 'type' => type,
270
+ 'message' => message,
271
+ 'id' => id.nil? ? [name[-1]] : id,
272
+ 'options' => options,
231
273
  }
232
274
  end
233
275
 
234
- def create_test_alert(name, datadog_query, message, notify_no_data=false, silenced=false)
235
- alert_dsl = MockAlertDSL.new
236
- metric_dsl = MockMetricDSL.new
276
+ def create_test_alert(name, datadog_query, message, options={})
277
+ options = DEFAULT_OPTIONS.merge(options)
278
+
279
+ alert_dsl = AlertDSL.new({})
280
+
281
+ metric_dsl = MetricDSL.new({})
237
282
  metric_dsl.datadog_query(datadog_query)
238
- alert_dsl.metric(metric_dsl)
283
+ alert_dsl.instance_variable_set(:@metric, metric_dsl)
284
+
285
+ notify_dsl = NotifyDSL.new({})
286
+ notify_dsl.groups(['a'])
287
+ alert_dsl.instance_variable_set(:@notify, notify_dsl)
288
+
239
289
  alert_dsl.name(name)
240
290
  alert_dsl.applies(true)
241
291
  alert_dsl.message(message)
242
- alert_dsl.silenced(silenced)
243
- alert_dsl.notify_no_data(notify_no_data)
244
- notify_dsl = MockNotifyDSL.new
245
- notify_dsl.groups(['a'])
246
- alert_dsl.notify(notify_dsl)
292
+
293
+ alert_dsl.no_data_timeframe(options['no_data_timeframe'])
294
+ alert_dsl.notify_no_data(options['notify_no_data'])
295
+ alert_dsl.require_full_window(options['require_full_window'])
296
+ alert_dsl.thresholds(options['thresholds'])
297
+ alert_dsl.timeout(options['timeout'])
298
+ alert_dsl.silenced(options['silenced'])
299
+
247
300
  MockAlert.new(alert_dsl)
248
301
  end
249
302
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: interferon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.20
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2017-04-03 00:00:00.000000000 Z
13
+ date: 2017-04-27 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: dogapi
@@ -231,7 +231,6 @@ files:
231
231
  - spec/fixtures/loaders2/test_sources/order_test_source.rb
232
232
  - spec/fixtures/loaders2/test_sources/secondary_source.rb
233
233
  - spec/fixtures/loaders2/test_sources/test_source.rb
234
- - spec/helpers/dsl_helper.rb
235
234
  - spec/helpers/loader_helper.rb
236
235
  - spec/helpers/logging_helper.rb
237
236
  - spec/helpers/mock_alert.rb
@@ -276,7 +275,6 @@ test_files:
276
275
  - spec/fixtures/loaders2/test_sources/order_test_source.rb
277
276
  - spec/fixtures/loaders2/test_sources/secondary_source.rb
278
277
  - spec/fixtures/loaders2/test_sources/test_source.rb
279
- - spec/helpers/dsl_helper.rb
280
278
  - spec/helpers/loader_helper.rb
281
279
  - spec/helpers/logging_helper.rb
282
280
  - spec/helpers/mock_alert.rb
@@ -1,48 +0,0 @@
1
- module Interferon
2
- module MockDSLMixin
3
- def initialize
4
- end
5
-
6
- def get_or_set(field, val, block, default)
7
- @hash ||= Hash.new
8
- if val.nil?
9
- f = @hash[field]
10
- f.nil? ? default : f
11
- else
12
- @hash[field] = val
13
- end
14
- end
15
- end
16
-
17
- class MockAlertDSL < AlertDSL
18
- include MockDSLMixin
19
-
20
- def notify(v = nil)
21
- get_or_set(:notify, v, nil, nil)
22
- end
23
-
24
- def metric(v = nil)
25
- get_or_set(:metric, v, nil, nil)
26
- end
27
-
28
- def id(v = nil, &block)
29
- get_or_set(:@id, v, block, '')
30
- end
31
-
32
- def silenced(v = nil, &block)
33
- get_or_set(:@silenced, v, block, false)
34
- end
35
-
36
- def silenced_until(v = nil, &block)
37
- get_or_set(:@silenced_until, v && Time.parse(v), block, Time.at(0))
38
- end
39
- end
40
-
41
- class MockNotifyDSL < NotifyDSL
42
- include MockDSLMixin
43
- end
44
-
45
- class MockMetricDSL < MetricDSL
46
- include MockDSLMixin
47
- end
48
- end