interferon 0.0.20 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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