interferon 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1c7f74fc8e23da780fdf783cdb98387321d20513
4
- data.tar.gz: 5aa81e287ab880c2d3e7af7bd96a035d41b3db21
3
+ metadata.gz: 1e06dab2517dc06edf86da60cb55249bf63d031d
4
+ data.tar.gz: e370782305dda94dca3215c57dfc8bceb633ed77
5
5
  SHA512:
6
- metadata.gz: 82c25b9118d41092d6ab131dd7edba612f40e68d4959bd0849efb4d0d2483bd5056b806dfc1c6f7e21381badd83cfb1e4f4e62fba8497bb3e6c52c4f33479dd2
7
- data.tar.gz: 24740188b929de430442f21501ef75de6a7fd148835c3491da1827c77f62b169c2ccca38507ee400f4c938d87ac273935ce63bb9c966c6da4851e6c49d0cc6c9
6
+ metadata.gz: bef7463ead0dc65ab0c2e8018feb13a85a419ecc02e2403e9ee15150cc29089d8d471609ae9c7ae421c0f6c112fd39f5746b9cff7288c43535ffb32cf4c88d2d
7
+ data.tar.gz: f8b07e1239591b50e07facca30186412c74eda5b19bd4d128b634bf1a8c477b449aaa7894ff6fc3181ab781f1011901778e37a663f6d337a56abc21dc9bd8462
data/README.md CHANGED
@@ -29,6 +29,7 @@ It accepts the following parameters:
29
29
  * `group_sources` -- a list of sources which can return groups of people to alert
30
30
  * `host_sources` -- a list of sources which can read inventory systems and return lists of hosts to monitor
31
31
  * `destinations` -- a list of alerting providers, which can monitor metrics and dispatch alerts as specified in your alerts dsl files
32
+ * `processes` -- number of processes to run the alert generation on (optional; default is to use all available cores)
32
33
 
33
34
  For more information, see [config.example.yaml](config.example.yaml) file in this repo.
34
35
 
@@ -10,11 +10,11 @@ options = {}
10
10
  optparse = OptionParser.new do |opts|
11
11
  opts.banner = %(Usage: interferon --config /path/to/interferon/config)
12
12
 
13
- opts.on('-c config', '--config config', String, 'Path to interferon config') do |key, _value|
13
+ opts.on('-c', '--config config', String, 'Path to interferon config') do |key|
14
14
  options[:config] = key
15
15
  end
16
16
 
17
- opts.on('-n', '--dry-run', "Don\'t update alert destinations") do
17
+ opts.on('-n', '--dry-run', "Don't update alert destinations") do
18
18
  options[:dry_run] = true
19
19
  end
20
20
 
@@ -26,7 +26,7 @@ end
26
26
 
27
27
  def parseconfig(filename)
28
28
  begin
29
- c = YAML.parse(File.read(filename))
29
+ config = YAML.parse(File.read(filename))
30
30
  rescue Errno::ENOENT => e
31
31
  raise ArgumentError, "config file does not exist:\n#{e.inspect}"
32
32
  rescue Errno::EACCES => e
@@ -34,7 +34,7 @@ def parseconfig(filename)
34
34
  rescue YAML::SyntaxError => e
35
35
  raise "config file #{filename} contains invalid YAML:\n#{e.inspect}"
36
36
  end
37
- c.to_ruby
37
+ config.to_ruby
38
38
  end
39
39
 
40
40
  # parse command line arguments
@@ -55,13 +55,7 @@ end
55
55
 
56
56
  ENV['DEBUG'] = '1' if config['verbose_logging']
57
57
 
58
- a = Interferon::Interferon.new(
59
- config['alerts_repo_path'],
60
- config['group_sources'] || {},
61
- config['host_sources'],
62
- config['destinations']
63
- )
64
-
65
- a.run(options[:dry_run])
58
+ interferon = Interferon::Interferon.new(config, options[:dry_run])
59
+ interferon.run
66
60
 
67
61
  puts 'interferon signaling complete!'
@@ -23,28 +23,26 @@ module Interferon
23
23
  # groups_sources is a hash from type => options for each group source
24
24
  # host_sources is a hash from type => options for each host source
25
25
  # destinations is a similar hash from type => options for each alerter
26
- def initialize(alerts_repo_path, groups_sources, host_sources, destinations,
27
- dry_run = false, processes = nil)
28
- @alerts_repo_path = alerts_repo_path
29
- @groups_sources = groups_sources
30
- @host_sources = host_sources
31
- @destinations = destinations
26
+ def initialize(config, dry_run = false)
27
+ @alerts_repo_path = config['alerts_repo_path']
28
+ @group_sources = config['group_sources'] || {}
29
+ @host_sources = config['host_sources']
30
+ @destinations = config['destinations']
31
+ @processes = config['processes']
32
32
  @dry_run = dry_run
33
- @processes = processes
34
33
  @request_shutdown = false
35
34
  end
36
35
 
37
- def run(dry_run = false)
36
+ def run
38
37
  Signal.trap('TERM') do
39
- log.info 'SIGTERM received. shutting down gracefully...'
38
+ log.info('SIGTERM received. shutting down gracefully...')
40
39
  @request_shutdown = true
41
40
  end
42
- @dry_run = dry_run
43
41
  run_desc = @dry_run ? 'dry run' : 'run'
44
- log.info "beginning alerts #{run_desc}"
42
+ log.info("beginning alerts #{run_desc}")
45
43
 
46
44
  alerts = read_alerts
47
- groups = read_groups(@groups_sources)
45
+ groups = read_groups(@group_sources)
48
46
  hosts = read_hosts(@host_sources)
49
47
 
50
48
  @destinations.each do |dest|
@@ -55,9 +53,9 @@ module Interferon
55
53
  update_alerts(@destinations, hosts, alerts, groups)
56
54
 
57
55
  if @request_shutdown
58
- log.info "interferon #{run_desc} shut down by SIGTERM"
56
+ log.info("interferon #{run_desc} shut down by SIGTERM")
59
57
  else
60
- log.info "interferon #{run_desc} complete"
58
+ log.info("interferon #{run_desc} complete")
61
59
  end
62
60
  end
63
61
 
@@ -75,14 +73,14 @@ module Interferon
75
73
  begin
76
74
  alert = Alert.new(alert_file)
77
75
  rescue StandardError => e
78
- log.warn "error reading alert file #{alert_file}: #{e}"
76
+ log.warn("error reading alert file #{alert_file}: #{e}")
79
77
  failed += 1
80
78
  else
81
79
  alerts << alert
82
80
  end
83
81
  end
84
82
 
85
- log.info "read #{alerts.count} alerts files from #{path}"
83
+ log.info("read #{alerts.count} alerts files from #{path}")
86
84
 
87
85
  statsd.gauge('alerts.read.count', alerts.count)
88
86
  statsd.gauge('alerts.read.failed', failed)
@@ -106,12 +104,16 @@ module Interferon
106
104
  people_count += people.count
107
105
  end
108
106
 
109
- log.info "read #{people_count} people in #{source_groups.count} groups " \
110
- "from source #{source.class.name}"
107
+ log.info(
108
+ "read #{people_count} people in #{source_groups.count} groups " \
109
+ "from source #{source.class.name}"
110
+ )
111
111
  end
112
112
 
113
- log.info "total of #{groups.values.flatten.count} people in #{groups.count} groups " \
114
- "from #{sources.count} sources"
113
+ log.info(
114
+ "total of #{groups.values.flatten.count} people in #{groups.count} groups " \
115
+ "from #{sources.count} sources"
116
+ )
115
117
 
116
118
  statsd.gauge('groups.sources', sources.count)
117
119
  statsd.gauge('groups.count', groups.count)
@@ -131,36 +133,37 @@ module Interferon
131
133
  hosts << source_hosts
132
134
 
133
135
  statsd.gauge('hosts.count', source_hosts.count, tags: ["source:#{source.class.name}"])
134
- log.info "read #{source_hosts.count} hosts from source #{source.class.name}"
136
+ log.info("read #{source_hosts.count} hosts from source #{source.class.name}")
135
137
  end
136
138
 
137
139
  hosts.flatten!
138
- log.info "total of #{hosts.count} entities from #{sources.count} sources"
140
+ log.info("total of #{hosts.count} entities from #{sources.count} sources")
139
141
 
140
142
  hosts
141
143
  end
142
144
 
143
145
  def update_alerts(destinations, hosts, alerts, groups)
146
+ alerts_queue, alert_errors = build_alerts_queue(hosts, alerts, groups)
147
+ if @dry_run && !alert_errors.empty?
148
+ raise "Alerts failed to apply or evaluate for all hosts: #{alerts.map(&:to_s).join(', ')}"
149
+ end
150
+
144
151
  loader = DestinationsLoader.new([@alerts_repo_path])
145
152
  loader.get_all(destinations).each do |dest|
146
153
  break if @request_shutdown
147
- log.info "updating alerts on #{dest.class.name}"
148
- update_alerts_on_destination(dest, hosts, alerts, groups)
154
+ log.info("updating alerts on #{dest.class.name}")
155
+ update_alerts_on_destination(dest, alerts_queue)
149
156
  end
150
157
  end
151
158
 
152
- def update_alerts_on_destination(dest, hosts, alerts, groups)
159
+ def update_alerts_on_destination(dest, alerts_queue)
153
160
  # track some counters/stats per destination
154
161
  start_time = Time.new.to_f
155
162
 
156
163
  # get already-defined alerts
157
164
  existing_alerts = dest.existing_alerts
158
165
 
159
- if @dry_run
160
- do_dry_run_update(dest, hosts, alerts, existing_alerts, groups)
161
- else
162
- do_regular_update(dest, hosts, alerts, existing_alerts, groups)
163
- end
166
+ run_update(dest, alerts_queue, existing_alerts)
164
167
 
165
168
  unless @request_shutdown
166
169
  # run time summary
@@ -170,7 +173,7 @@ module Interferon
170
173
  run_time,
171
174
  tags: ["destination:#{dest.class.name}"]
172
175
  )
173
- log.info "#{dest.class.name} : run completed in %.2f seconds" % run_time
176
+ log.info("#{dest.class.name} : run completed in %.2f seconds" % run_time)
174
177
 
175
178
  # report destination stats
176
179
  dest.report_stats
@@ -179,73 +182,7 @@ module Interferon
179
182
  raise dest.api_errors.to_s if @dry_run && !dest.api_errors.empty?
180
183
  end
181
184
 
182
- def do_dry_run_update(dest, hosts, alerts, existing_alerts, groups)
183
- # Track these to clean up dry-run alerts from previous runs
184
- existing_dry_run_alerts = []
185
- existing_alerts.each do |name, alert|
186
- if name.start_with?(DRY_RUN_ALERTS_NAME_PREFIX)
187
- existing_dry_run_alerts << [alert['name'], [alert['id']]]
188
- existing_alerts.delete(name)
189
- end
190
- end
191
-
192
- alerts_queue = build_alerts_queue(hosts, alerts, groups)
193
- updates_queue = alerts_queue.reject do |_name, alert_people_pair|
194
- !dest.need_update(alert_people_pair, existing_alerts)
195
- end
196
-
197
- # Add dry-run prefix to alerts and delete id to avoid impacting real alerts
198
- existing_alerts.keys.each do |name|
199
- existing_alert = existing_alerts[name]
200
- dry_run_alert_name = DRY_RUN_ALERTS_NAME_PREFIX + name
201
- existing_alert['name'] = dry_run_alert_name
202
- existing_alert['id'] = [nil]
203
- existing_alerts[dry_run_alert_name] = existing_alerts.delete(name)
204
- end
205
-
206
- # Build new queue with dry-run prefixes and ensure they are silenced
207
- alerts_queue.each do |_name, alert_people_pair|
208
- alert, _people = alert_people_pair
209
- dry_run_alert_name = DRY_RUN_ALERTS_NAME_PREFIX + alert['name']
210
- alert.change_name(dry_run_alert_name)
211
- alert.silence
212
- end
213
-
214
- # Create alerts in destination
215
- created_alerts = create_alerts(dest, updates_queue)
216
-
217
- # Existing alerts are pruned until all that remains are
218
- # alerts that aren't being generated anymore
219
- to_remove = existing_alerts.dup
220
- alerts_queue.each do |_name, alert_people_pair|
221
- alert, _people = alert_people_pair
222
- old_alerts = to_remove[alert['name']]
223
-
224
- next if old_alerts.nil?
225
- if old_alerts['id'].length == 1
226
- to_remove.delete(alert['name'])
227
- else
228
- old_alerts['id'] = old_alerts['id'].drop(1)
229
- end
230
- end
231
-
232
- # Clean up alerts not longer being generated
233
- to_remove.each do |_name, alert|
234
- break if @request_shutdown
235
- dest.remove_alert(alert)
236
- end
237
-
238
- # Clean up dry-run created alerts
239
- (created_alerts + existing_dry_run_alerts).each do |alert_id_pair|
240
- alert_ids = alert_id_pair[1]
241
- alert_ids.each do |alert_id|
242
- dest.remove_alert_by_id(alert_id)
243
- end
244
- end
245
- end
246
-
247
- def do_regular_update(dest, hosts, alerts, existing_alerts, groups)
248
- alerts_queue = build_alerts_queue(hosts, alerts, groups)
185
+ def run_update(dest, alerts_queue, existing_alerts)
249
186
  updates_queue = alerts_queue.reject do |_name, alert_people_pair|
250
187
  !dest.need_update(alert_people_pair, existing_alerts)
251
188
  end
@@ -253,6 +190,9 @@ module Interferon
253
190
  # Create alerts in destination
254
191
  create_alerts(dest, updates_queue)
255
192
 
193
+ # Do not continue to remove alerts during dry-run
194
+ return if @dry_run
195
+
256
196
  # Existing alerts are pruned until all that remains are
257
197
  # alerts that aren't being generated anymore
258
198
  to_remove = existing_alerts.dup
@@ -281,12 +221,12 @@ module Interferon
281
221
  concurrency = dest.concurrency || 10
282
222
  unless @request_shutdown
283
223
  threads = Array.new(concurrency) do |i|
284
- log.info "thread #{i} created"
224
+ log.info("thread #{i} created")
285
225
  t = Thread.new do
286
226
  while (name = alerts_to_create.shift)
287
227
  break if @request_shutdown
288
228
  cur_alert, people = alerts_queue[name]
289
- log.debug "creating alert for #{cur_alert[:name]}"
229
+ log.debug("creating alert for #{cur_alert[:name]}")
290
230
  alert_key_ids << dest.create_alert(cur_alert, people)
291
231
  end
292
232
  end
@@ -300,16 +240,20 @@ module Interferon
300
240
 
301
241
  def build_alerts_queue(hosts, alerts, groups)
302
242
  alerts_queue = {}
243
+ all_alert_generation_errors = []
244
+
303
245
  # create or update alerts; mark when we've done that
304
246
  result = Parallel.map(alerts, in_processes: @processes) do |alert|
305
247
  break if @request_shutdown
306
248
  alerts_generated = {}
249
+ alert_generation_errors = []
307
250
  counters = {
308
251
  errors: 0,
309
252
  evals: 0,
310
253
  applies: 0,
311
254
  hosts: hosts.length,
312
255
  }
256
+
313
257
  last_eval_error = nil
314
258
 
315
259
  hosts.each do |hostinfo|
@@ -317,7 +261,7 @@ module Interferon
317
261
  alert.evaluate(hostinfo)
318
262
  counters[:evals] += 1
319
263
  rescue StandardError => e
320
- log.debug "Evaluation of alert #{alert} failed in the context of host #{hostinfo}"
264
+ log.debug("Evaluation of alert #{alert} failed in the context of host #{hostinfo}")
321
265
  counters[:errors] += 1
322
266
  last_eval_error = e
323
267
  next
@@ -325,7 +269,7 @@ module Interferon
325
269
 
326
270
  # don't define an alert that doesn't apply to this hostinfo
327
271
  unless alert[:applies]
328
- log.debug "alert #{alert[:name]} doesn't apply to #{hostinfo.inspect}"
272
+ log.debug("alert #{alert[:name]} doesn't apply to #{hostinfo.inspect}")
329
273
  next
330
274
  end
331
275
 
@@ -348,17 +292,19 @@ module Interferon
348
292
  statsd.gauge('alerts.evaluate.applies', counters[:applies], tags: ["alert:#{alert}"])
349
293
 
350
294
  if counters[:applies] > 0
351
- log.info "alert #{alert} applies to #{counters[:applies]} of #{counters[:hosts]} hosts"
295
+ log.info("alert #{alert} applies to #{counters[:applies]} of #{counters[:hosts]} hosts")
352
296
  end
353
297
 
354
298
  # did the alert fail to evaluate on all hosts?
355
299
  if counters[:errors] == counters[:hosts] && !last_eval_error.nil?
356
- log.error "alert #{alert} failed to evaluate in the context of all hosts!"
357
- log.error "last error on alert #{alert}: #{last_eval_error}"
358
-
300
+ log.error("alert #{alert} failed to evaluate in the context of all hosts!")
301
+ log.error("last error on alert #{alert}: #{last_eval_error}")
359
302
  statsd.gauge('alerts.evaluate.failed_on_all', 1, tags: ["alert:#{alert}"])
360
- log.debug "alert #{alert}: " \
361
- "error #{last_eval_error}\n#{last_eval_error.backtrace.join("\n")}"
303
+ log.debug(
304
+ "alert #{alert}: " \
305
+ "error #{last_eval_error}\n#{last_eval_error.backtrace.join("\n")}"
306
+ )
307
+ alert_generation_errors << alert
362
308
  else
363
309
  statsd.gauge('alerts.evaluate.failed_on_all', 0, tags: ["alert:#{alert}"])
364
310
  end
@@ -366,17 +312,19 @@ module Interferon
366
312
  # did the alert apply to any hosts?
367
313
  if counters[:applies] == 0
368
314
  statsd.gauge('alerts.evaluate.never_applies', 1, tags: ["alert:#{alert}"])
369
- log.warn "alert #{alert} did not apply to any hosts"
315
+ log.warn("alert #{alert} did not apply to any hosts")
316
+ alert_generation_errors << alert
370
317
  else
371
318
  statsd.gauge('alerts.evaluate.never_applies', 0, tags: ["alert:#{alert}"])
372
319
  end
373
- alerts_generated
320
+ [alerts_generated, alert_generation_errors]
374
321
  end
375
322
 
376
- result.each do |alerts_generated|
377
- alerts_queue.merge! alerts_generated
323
+ result.each do |generated_alerts, alert_generation_errors|
324
+ alerts_queue.merge!(generated_alerts)
325
+ all_alert_generation_errors += alert_generation_errors
378
326
  end
379
- alerts_queue
327
+ [alerts_queue, all_alert_generation_errors]
380
328
  end
381
329
  end
382
330
  end
@@ -119,10 +119,10 @@ module Interferon::Destinations
119
119
  @stats[:manually_created_alerts] = \
120
120
  @existing_alerts.reject { |_n, a| a['message'].include?(ALERT_KEY) }.length
121
121
 
122
- log.info 'datadog: found %d existing alerts; %d were manually created' % [
123
- @existing_alerts.length,
124
- @stats[:manually_created_alerts],
125
- ]
122
+ log.info(
123
+ "datadog: found #{@existing_alerts.length} existing alerts; " \
124
+ "#{@stats[:manually_created_alerts]} were manually created"
125
+ )
126
126
  end
127
127
 
128
128
  @existing_alerts
@@ -197,13 +197,25 @@ Options:
197
197
  EOM
198
198
  log.info("creating new alert #{alert['name']}: #{new_alert_text}")
199
199
 
200
- @dog.monitor(
201
- alert['monitor_type'],
202
- datadog_query,
200
+ monitor_options = {
203
201
  name: alert['name'],
204
- message: @dry_run ? self.class.generate_message(alert, []) : message,
205
- options: alert_options
206
- )
202
+ message: message,
203
+ options: alert_options,
204
+ }
205
+
206
+ if @dry_run
207
+ @dog.validate_monitor(
208
+ alert['monitor_type'],
209
+ datadog_query,
210
+ monitor_options
211
+ )
212
+ else
213
+ @dog.monitor(
214
+ alert['monitor_type'],
215
+ datadog_query,
216
+ monitor_options
217
+ )
218
+ end
207
219
  end
208
220
 
209
221
  def update_datadog_alert(alert, datadog_query, message, alert_options, existing_alert)
@@ -229,21 +241,23 @@ EOM
229
241
  diff = Diffy::Diff.new(existing_alert_text, new_alert_text, context: 1)
230
242
  log.info("updating existing alert #{id} (#{alert['name']}):\n#{diff}")
231
243
 
244
+ monitor_options = {
245
+ name: alert['name'],
246
+ message: message,
247
+ options: alert_options,
248
+ }
249
+
232
250
  if @dry_run
233
- resp = @dog.monitor(
251
+ resp = @dog.validate_monitor(
234
252
  alert['monitor_type'],
235
253
  datadog_query,
236
- name: alert['name'],
237
- message: self.class.generate_message(alert, []),
238
- options: alert_options
254
+ monitor_options
239
255
  )
240
256
  elsif self.class.same_monitor_type(alert['monitor_type'], existing_alert['type'])
241
257
  resp = @dog.update_monitor(
242
258
  id,
243
259
  datadog_query,
244
- name: alert['name'],
245
- message: message,
246
- options: alert_options
260
+ monitor_options
247
261
  )
248
262
 
249
263
  # Unmute existing alerts that have been unsilenced.
@@ -259,9 +273,7 @@ EOM
259
273
  resp = @dog.monitor(
260
274
  alert['monitor_type'],
261
275
  datadog_query,
262
- name: alert['name'],
263
- message: message,
264
- options: alert_options
276
+ monitor_options
265
277
  )
266
278
  end
267
279
  end
@@ -273,6 +285,7 @@ EOM
273
285
  @stats[:alerts_to_be_deleted] += 1
274
286
  log.info("deleting alert: #{alert['name']}")
275
287
 
288
+ # Safety to protect aginst accident dry_run deletion
276
289
  unless @dry_run
277
290
  alert['id'].each do |alert_id|
278
291
  resp = @dog.delete_monitor(alert_id)
@@ -290,14 +303,6 @@ EOM
290
303
  end
291
304
  end
292
305
 
293
- def remove_alert_by_id(alert_id)
294
- # This should only be used by dry-run to clean up created dry-run alerts
295
- log.debug("deleting alert, id: #{alert_id}")
296
- resp = @dog.delete_monitor(alert_id)
297
- code = resp[0].to_i
298
- log_datadog_response_code(resp, code, :deleting)
299
- end
300
-
301
306
  def need_update(alert_people_pair, existing_alerts_from_api)
302
307
  alert, people = alert_people_pair
303
308
  existing = existing_alerts_from_api[alert['name']]
@@ -386,7 +391,7 @@ EOM
386
391
  " response was #{resp[0]}:'#{resp[1].inspect}'")
387
392
  end
388
393
 
389
- # unknown (prob. datadog) error:
394
+ # unknown (prob. datadog) error:
390
395
  elsif code > 400 || code == -1
391
396
  @stats[:api_unknown_errors] += 1
392
397
  unless alert.nil?
@@ -16,7 +16,7 @@ module Interferon::GroupSources
16
16
  @paths.each do |path|
17
17
  path = File.expand_path(path)
18
18
  unless Dir.exist?(path)
19
- log.warn "no such directory #{path} for reading group files"
19
+ log.warn("no such directory #{path} for reading group files")
20
20
  next
21
21
  end
22
22
 
@@ -24,9 +24,9 @@ module Interferon::GroupSources
24
24
  begin
25
25
  group = YAML.parse(File.read(group_file))
26
26
  rescue YAML::SyntaxError => e
27
- log.error "syntax error in group file #{group_file}: #{e}"
27
+ log.error("syntax error in group file #{group_file}: #{e}")
28
28
  rescue StandardError => e
29
- log.warn "error reading group file #{group_file}: #{e}"
29
+ log.warn("error reading group file #{group_file}: #{e}")
30
30
  else
31
31
  group = group.to_ruby
32
32
  if group['people']
@@ -44,7 +44,7 @@ module Interferon::GroupSources
44
44
  if groups.include?(group)
45
45
  groups[aliased_group] = groups[group]
46
46
  else
47
- log.warn "Alias not found for #{group} but used by #{aliased_group} in #{group_file}"
47
+ log.warn("Alias not found for #{group} but used by #{aliased_group} in #{group_file}")
48
48
  end
49
49
  end
50
50
 
@@ -35,12 +35,12 @@ module Interferon
35
35
  options = source['options'] || {}
36
36
 
37
37
  if type.nil?
38
- log.warn "#{@loader_for} ##{idx} does not have a 'type' set; 'type' is required"
38
+ log.warn("#{@loader_for} ##{idx} does not have a 'type' set; 'type' is required")
39
39
  next
40
40
  end
41
41
 
42
42
  unless enabled
43
- log.info "skipping #{@loader_for} #{type} because it's not enabled"
43
+ log.info("skipping #{@loader_for} #{type} because it's not enabled")
44
44
  next
45
45
  end
46
46
 
@@ -68,9 +68,11 @@ module Interferon
68
68
  require full_path
69
69
  klass = @module.const_get(class_name)
70
70
  rescue LoadError => e
71
- log.debug "LoadError looking for #{@loader_for} file #{type} at #{full_path}: #{e}"
71
+ log.debug("LoadError looking for #{@loader_for} file #{type} at #{full_path}: #{e}")
72
72
  rescue NameError => e
73
- log.debug "NameError looking for #{@loader_for} class #{class_name} in #{full_path}: #{e}"
73
+ log.debug(
74
+ "NameError looking for #{@loader_for} class #{class_name} in #{full_path}: #{e}"
75
+ )
74
76
  end
75
77
 
76
78
  break if klass
@@ -1,3 +1,3 @@
1
1
  module Interferon
2
- VERSION = '0.1.4'.freeze
2
+ VERSION = '0.2.0'.freeze
3
3
  end
@@ -88,8 +88,8 @@ describe Interferon::Destinations::Datadog do
88
88
  datadog.create_alert(mock_alert, mock_people)
89
89
  end
90
90
 
91
- it 'always calls monitor in dry-run' do
92
- expect_any_instance_of(Dogapi::Client).to receive(:monitor).and_return([200, ''])
91
+ it 'calls validate monitor in dry-run' do
92
+ expect_any_instance_of(Dogapi::Client).to receive(:validate_monitor).and_return([200, ''])
93
93
  expect(datadog_dry_run).to receive(:existing_alerts).and_return(mock_response)
94
94
  datadog_dry_run.create_alert(mock_alert, mock_people)
95
95
  end
@@ -114,12 +114,4 @@ describe Interferon::Destinations::Datadog do
114
114
  datadog.remove_alert(mock_alert)
115
115
  end
116
116
  end
117
-
118
- describe '.remove_alert_by_id' do
119
- it 'calls dogapi delete_monitor' do
120
- expect_any_instance_of(Dogapi::Client).to receive(:delete_monitor)
121
- .with(mock_alert_id).and_return([200, ''])
122
- datadog.remove_alert_by_id(mock_alert_id)
123
- end
124
- end
125
117
  end
@@ -71,130 +71,135 @@ describe Interferon::Destinations::Datadog do
71
71
  end
72
72
 
73
73
  context 'dry_run_update_alerts_on_destination' do
74
- let(:interferon) { Interferon::Interferon.new(nil, nil, nil, nil, true, 0) }
74
+ let(:interferon) { Interferon::Interferon.new({ 'processes' => 0 }, true) }
75
75
 
76
76
  before do
77
77
  allow_any_instance_of(MockAlert).to receive(:evaluate)
78
78
  allow(dest).to receive(:remove_alert)
79
- allow(dest).to receive(:remove_alert_by_id)
80
79
  allow(dest).to receive(:report_stats)
81
80
  end
82
81
 
83
82
  it 'does not re-run existing alerts' do
84
- alerts = mock_existing_alerts
83
+ mock_alerts = mock_existing_alerts
85
84
  expect(dest).not_to receive(:create_alert)
86
- expect(dest).not_to receive(:remove_alert_by_id)
87
85
 
88
- interferon.update_alerts_on_destination(
89
- dest, ['host'], [alerts['name1'], alerts['name2']], {}
86
+ alerts_queue, _error_count = interferon.build_alerts_queue(
87
+ ['host'],
88
+ [mock_alerts['name1'], mock_alerts['name2']].map { |x| test_alert_from_json(x) },
89
+ {}
90
90
  )
91
+
92
+ interferon.update_alerts_on_destination(dest, alerts_queue)
91
93
  end
92
94
 
93
95
  it 'runs added alerts' do
94
- alerts = mock_existing_alerts
95
- added = create_test_alert('name3', 'testquery3', '')
96
+ mock_alerts = mock_existing_alerts
97
+ alerts = [mock_alerts['name1'], mock_alerts['name2']].map { |x| test_alert_from_json(x) }
98
+ alerts << create_test_alert('name3', 'testquery3', '')
99
+
100
+ alerts_queue, _error_count = interferon.build_alerts_queue(['host'], alerts, {})
101
+
96
102
  expect(dest).to receive(:create_alert).once.and_call_original
97
- expect(dest).to receive(:remove_alert_by_id).with('3').once
98
103
 
99
- interferon.update_alerts_on_destination(
100
- dest, ['host'], [alerts['name1'], alerts['name2'], added], {}
101
- )
104
+ interferon.update_alerts_on_destination(dest, alerts_queue)
102
105
  end
103
106
 
104
107
  it 'runs updated alerts' do
105
108
  added = create_test_alert('name1', 'testquery3', '')
109
+ alerts_queue, _error_count = interferon.build_alerts_queue(['host'], [added], {})
106
110
  expect(dest).to receive(:create_alert).once.and_call_original
107
- expect(dest).to receive(:remove_alert_by_id).with('1').once
108
111
 
109
- interferon.update_alerts_on_destination(dest, ['host'], [added], {})
112
+ interferon.update_alerts_on_destination(dest, alerts_queue)
110
113
  end
111
114
 
112
- it 'deletes old alerts' do
113
- expect(dest).to receive(:remove_alert).twice
115
+ it 'does not delete old alerts' do
116
+ expect(dest).to_not receive(:remove_alert)
117
+ alerts_queue, _error_count = interferon.build_alerts_queue(['host'], [], {})
114
118
 
115
- interferon.update_alerts_on_destination(dest, ['host'], [], {})
119
+ interferon.update_alerts_on_destination(dest, alerts_queue)
116
120
  end
117
121
 
118
- it 'deletes duplicate old alerts' do
122
+ it 'does not delete duplicate old alerts' do
119
123
  alert1 = mock_alert_json('name1', 'testquery1', '', nil, [1, 2, 3])
120
124
  alert2 = mock_alert_json('name2', 'testquery2', '')
121
125
  existing_alerts = { 'name1' => alert1, 'name2' => alert2 }
126
+
122
127
  dest = MockDest.new(existing_alerts)
123
- allow(dest).to receive(:remove_alert)
124
- allow(dest).to receive(:remove_alert_by_id)
125
128
  allow(dest).to receive(:report_stats)
126
129
 
127
- expect(dest).to receive(:remove_alert).with(existing_alerts['name1'])
128
- expect(dest).to receive(:remove_alert).with(existing_alerts['name2'])
130
+ alerts_queue, _error_count = interferon.build_alerts_queue(['host'], [], {})
131
+
132
+ expect(dest).to_not receive(:remove_alert)
129
133
 
130
- interferon.update_alerts_on_destination(dest, ['host'], [], {})
134
+ interferon.update_alerts_on_destination(dest, alerts_queue)
131
135
  end
132
136
 
133
- it 'deletes duplicate old alerts when creating new alert' do
137
+ it 'does not delete duplicate old alerts when creating new alert' do
134
138
  alert1 = mock_alert_json('name1', 'testquery1', '', nil, [1, 2, 3])
135
139
  alert2 = mock_alert_json('name2', 'testquery2', '')
136
140
  existing_alerts = { 'name1' => alert1, 'name2' => alert2 }
141
+
137
142
  dest = MockDest.new(existing_alerts)
138
- allow(dest).to receive(:remove_alert)
139
- allow(dest).to receive(:remove_alert_by_id)
140
143
  allow(dest).to receive(:report_stats)
141
144
 
142
145
  added = create_test_alert('name1', 'testquery1', '')
146
+ alerts_queue, _error_count = interferon.build_alerts_queue(['host'], [added], {})
143
147
 
144
- # Since we change id to nil we will not be attempting to delete duplicate alerts
145
- # during dry run
146
- expect(dest).to_not receive(:remove_alert).with(existing_alerts['name1'])
147
- expect(dest).to receive(:remove_alert).with(existing_alerts['name2'])
148
+ expect(dest).to_not receive(:remove_alert)
148
149
 
149
- interferon.update_alerts_on_destination(dest, ['host'], [added], {})
150
+ interferon.update_alerts_on_destination(dest, alerts_queue)
150
151
  end
151
152
  end
152
153
 
153
154
  context 'update_alerts_on_destination' do
154
- let(:interferon) { Interferon::Interferon.new(nil, nil, nil, nil, false, 0) }
155
+ let(:interferon) { Interferon::Interferon.new({ 'processes' => 0 }, false) }
155
156
 
156
157
  before do
157
158
  allow_any_instance_of(MockAlert).to receive(:evaluate)
158
159
  allow(dest).to receive(:remove_alert)
159
- allow(dest).to receive(:remove_alert_by_id)
160
160
  allow(dest).to receive(:report_stats)
161
161
  end
162
162
 
163
163
  it 'does not re-run existing alerts' do
164
- alerts = mock_existing_alerts
164
+ mock_alerts = mock_existing_alerts
165
165
  expect(dest).not_to receive(:create_alert)
166
- expect(dest).not_to receive(:remove_alert_by_id)
167
166
 
168
- interferon.update_alerts_on_destination(
169
- dest, ['host'], [alerts['name1'], alerts['name2']], {}
167
+ alerts_queue, _error_count = interferon.build_alerts_queue(
168
+ ['host'],
169
+ [mock_alerts['name1'], mock_alerts['name2']].map { |x| test_alert_from_json(x) },
170
+ {}
170
171
  )
172
+
173
+ interferon.update_alerts_on_destination(dest, alerts_queue)
171
174
  end
172
175
 
173
176
  it 'runs added alerts' do
174
- alerts = mock_existing_alerts
175
- added = create_test_alert('name3', 'testquery3', '')
177
+ mock_alerts = mock_existing_alerts
178
+ alerts = [mock_alerts['name1'], mock_alerts['name2']].map { |x| test_alert_from_json(x) }
179
+ alerts << create_test_alert('name3', 'testquery3', '')
180
+
181
+ alerts_queue, _error_count = interferon.build_alerts_queue(['host'], alerts, {})
182
+
176
183
  expect(dest).to receive(:create_alert).once.and_call_original
177
- expect(dest).not_to receive(:remove_alert_by_id).with('3')
178
184
 
179
- interferon.update_alerts_on_destination(
180
- dest, ['host'], [alerts['name1'], alerts['name2'], added], {}
181
- )
185
+ interferon.update_alerts_on_destination(dest, alerts_queue)
182
186
  end
183
187
 
184
188
  it 'runs updated alerts' do
185
189
  added = create_test_alert('name1', 'testquery3', '')
190
+ alerts_queue, _error_count = interferon.build_alerts_queue(['host'], [added], {})
186
191
  expect(dest).to receive(:create_alert).once.and_call_original
187
- expect(dest).not_to receive(:remove_alert_by_id).with('1')
188
192
 
189
- interferon.update_alerts_on_destination(dest, ['host'], [added], {})
193
+ interferon.update_alerts_on_destination(dest, alerts_queue)
190
194
  end
191
195
 
192
196
  it 'deletes old alerts' do
193
197
  alerts = mock_existing_alerts
198
+ alerts_queue, _error_count = interferon.build_alerts_queue(['host'], [], {})
194
199
  expect(dest).to receive(:remove_alert).with(alerts['name1'])
195
200
  expect(dest).to receive(:remove_alert).with(alerts['name2'])
196
201
 
197
- interferon.update_alerts_on_destination(dest, ['host'], [], {})
202
+ interferon.update_alerts_on_destination(dest, alerts_queue)
198
203
  end
199
204
 
200
205
  it 'deletes duplicate old alerts' do
@@ -203,13 +208,14 @@ describe Interferon::Destinations::Datadog do
203
208
  existing_alerts = { 'name1' => alert1, 'name2' => alert2 }
204
209
  dest = MockDest.new(existing_alerts)
205
210
  allow(dest).to receive(:remove_alert)
206
- allow(dest).to receive(:remove_alert_by_id)
207
211
  allow(dest).to receive(:report_stats)
208
212
 
213
+ alerts_queue, _error_count = interferon.build_alerts_queue(['host'], [], {})
214
+
209
215
  expect(dest).to receive(:remove_alert).with(existing_alerts['name1'])
210
216
  expect(dest).to receive(:remove_alert).with(existing_alerts['name2'])
211
217
 
212
- interferon.update_alerts_on_destination(dest, ['host'], [], {})
218
+ interferon.update_alerts_on_destination(dest, alerts_queue)
213
219
  end
214
220
 
215
221
  it 'deletes duplicate old alerts when creating new alert' do
@@ -220,19 +226,21 @@ describe Interferon::Destinations::Datadog do
220
226
  allow(dest).to receive(:report_stats)
221
227
 
222
228
  added = create_test_alert('name1', 'testquery1', '')
229
+ alerts_queue, _error_count = interferon.build_alerts_queue(['host'], [added], {})
223
230
 
224
231
  expect(dest).to receive(:remove_alert).with(
225
232
  mock_alert_json('name1', 'testquery1', '', nil, [2, 3])
226
233
  )
227
234
  expect(dest).to receive(:remove_alert).with(existing_alerts['name2'])
228
235
 
229
- interferon.update_alerts_on_destination(dest, ['host'], [added], {})
236
+ interferon.update_alerts_on_destination(dest, alerts_queue)
230
237
  end
231
238
  end
232
239
 
233
240
  def mock_existing_alerts
234
- alert1 = mock_alert_json('name1', 'testquery1', '')
235
- alert2 = mock_alert_json('name2', 'testquery2', '')
241
+ mock_message = Interferon::Destinations::Datadog::ALERT_KEY
242
+ alert1 = mock_alert_json('name1', 'testquery1', mock_message)
243
+ alert2 = mock_alert_json('name2', 'testquery2', mock_message)
236
244
  { 'name1' => alert1, 'name2' => alert2 }
237
245
  end
238
246
 
@@ -274,6 +282,15 @@ describe Interferon::Destinations::Datadog do
274
282
  }
275
283
  end
276
284
 
285
+ def test_alert_from_json(mock_alert_json)
286
+ create_test_alert(
287
+ mock_alert_json['name'],
288
+ mock_alert_json['query'],
289
+ mock_alert_json['message'].sub(/#{Interferon::Destinations::Datadog::ALERT_KEY}$/, ''),
290
+ mock_alert_json['options']
291
+ )
292
+ end
293
+
277
294
  def create_test_alert(name, datadog_query, message, options = {})
278
295
  options = DEFAULT_OPTIONS.merge(options)
279
296
 
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.1.4
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Igor Serebryany