flapjack 0.7.7 → 0.7.8

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/CHANGELOG.md CHANGED
@@ -1,6 +1,12 @@
1
1
  ## Flapjack Changelog
2
2
 
3
- # 0.7.7 - 2012-05-22
3
+ # 0.7.8 - 2013-05-30
4
+ - Feature: support multiline check output (thanks @bs-github) gh-100 (@jessereynolds)
5
+ - Bug: notification rule with no entities or entity tags not being allowed gh-193 (@jessereynolds)
6
+ - Bug: correct jabber alias in example acknowledgement strings gh-189 (@jessereynolds)
7
+ - Bug: entity lists in Web UI should be sorted alphabetically gh-195 (@jessereynolds)
8
+
9
+ # 0.7.7 - 2013-05-22
4
10
  - Bug: relax notification rule validations somewhat gh-185 (@jessereynolds)
5
11
  - Chore: log notification rule validation errors from api gh-183 (@jessereynolds)
6
12
 
@@ -25,11 +25,11 @@ def process_input(opts)
25
25
  skip unless line
26
26
  split_line = line.split("\t")
27
27
 
28
- object_type, timestamp, entity, check, state, check_time, check_latency, check_output, check_perfdata = split_line
28
+ object_type, timestamp, entity, check, state, check_time, check_latency, check_output, check_perfdata, check_long_output = split_line
29
29
 
30
30
  case
31
- when split_line.length != 9
32
- puts "ERROR - rejecting this line as it doesn't split into 9 tab separated strings: [#{line}]"
31
+ when ! (split_line.length > 8)
32
+ puts "ERROR - rejecting this line as it doesn't split into at least 9 tab separated strings: [#{line}]"
33
33
  next
34
34
  when (timestamp !~ /^\d+$/)
35
35
  puts "ERROR - rejecting this line as second string doesn't look like a timestamp: [#{line}]"
@@ -39,16 +39,18 @@ def process_input(opts)
39
39
  next
40
40
  end
41
41
 
42
- puts "#{object_type}, #{timestamp}, #{entity}, #{check}, #{state}, #{check_output}"
42
+ puts "#{object_type}, #{timestamp}, #{entity}, #{check}, #{state}, #{check_output}, #{check_long_output}"
43
43
 
44
44
  state = 'ok' if state.downcase == 'up'
45
45
  state = 'critical' if state.downcase == 'down'
46
+ details = check_long_output ? check_long_output.gsub(/\\n/, "\n") : nil
46
47
  event = {
47
48
  'entity' => entity,
48
49
  'check' => check,
49
50
  'type' => 'service',
50
51
  'state' => state,
51
52
  'summary' => check_output,
53
+ 'details' => details,
52
54
  'timestamp' => timestamp,
53
55
  }.to_json
54
56
  redis.lpush 'events', event
@@ -63,7 +65,7 @@ def main(opts)
63
65
  redis = Redis.new(opts[:redis_options])
64
66
  while true
65
67
  process_input(:redis => redis, :fifo => fifo)
66
- puts "Whoops, restarting main loop in 10 seconds"
68
+ puts "Whoops with the fifo, restarting main loop in 10 seconds"
67
69
  sleep 10
68
70
  end
69
71
  end
@@ -6,22 +6,45 @@ Feature: Notification rules on a per contact basis
6
6
  | id | first_name | last_name | email | sms | timezone |
7
7
  | 1 | Malak | Al-Musawi | malak@example.com | +61400000001 | Asia/Baghdad |
8
8
  | 2 | Imani | Farooq | imani@example.com | +61400000002 | Europe/Moscow |
9
+ | 3 | Vera | Дурейко | vera@example.com | +61400000003 | Europe/Paris |
9
10
 
10
11
  And the following entities exist:
11
12
  | id | name | contacts |
12
13
  | 1 | foo | 1 |
13
- | 2 | bar | 1,2 |
14
- | 3 | baz | 1 |
14
+ | 2 | bar | 1,2,3 |
15
+ | 3 | baz | 1,3 |
16
+ | 4 | buf | 1,2,3 |
15
17
 
16
18
  And user 1 has the following notification intervals:
17
19
  | email | sms |
18
20
  | 15 | 60 |
19
21
 
22
+ And user 2 has the following notification intervals:
23
+ | email | sms |
24
+ | 15 | 60 |
25
+
26
+ And user 3 has the following notification intervals:
27
+ | email | sms |
28
+ | 15 | 60 |
29
+
20
30
  And user 1 has the following notification rules:
21
- | id | entities | entity_tags | warning_media | critical_media | warning_blackhole | critical_blackhole | time_restrictions |
22
- | 1 | foo | | email | sms,email | | | 8-18 weekdays |
23
- | 2 | bar | | | sms,email | true | | |
24
- | 3 | baz | | email | sms,email | | | |
31
+ | entities | entity_tags | warning_media | critical_media | warning_blackhole | critical_blackhole | time_restrictions |
32
+ | foo | | email | sms,email | | | 8-18 weekdays |
33
+ | bar | | | sms,email | true | | |
34
+ | baz | | email | sms,email | | | |
35
+
36
+ And user 2 has the following notification rules:
37
+ | entities | entity_tags | warning_media | critical_media | warning_blackhole | critical_blackhole | time_restrictions |
38
+ | | | email | email | | | |
39
+ | | | sms | sms | | | |
40
+ | bar | | email | email,sms | | | |
41
+
42
+ And user 3 has the following notification rules:
43
+ | entities | entity_tags | warning_media | critical_media | warning_blackhole | critical_blackhole | time_restrictions |
44
+ | buf | | email | email | | | |
45
+ | buf | | sms | sms | | | |
46
+ | | | email | email | | | |
47
+ | baz | | sms | sms | | | |
25
48
 
26
49
  @time_restrictions @time
27
50
  Scenario: Alerts only during specified time restrictions
@@ -152,3 +175,58 @@ Feature: Notification rules on a per contact basis
152
175
  And a critical event is received
153
176
  Then 3 email alerts should be queued for malak@example.com
154
177
  And 3 sms alerts should be queued for +61400000001
178
+
179
+ @time
180
+ Scenario: Contact with only entity specific rules should not be notified for other entities they are a contact for
181
+ Given the check is check 'ping' on entity 'buf'
182
+ And the check is in an ok state
183
+ When a critical event is received
184
+ And 1 minute passes
185
+ And a critical event is received
186
+ Then no email alerts should be queued for malak@example.com
187
+
188
+ @time
189
+ Scenario: Contact with entity specific rules and general rules should be notified for other entities they are a contact for
190
+ Given the check is check 'ping' on entity 'buf'
191
+ And the check is in an ok state
192
+ When a critical event is received
193
+ And 1 minute passes
194
+ And a critical event is received
195
+ Then 1 email alert should be queued for imani@example.com
196
+
197
+ @time
198
+ Scenario: Mutiple rules for an entity should be additive
199
+ Given the check is check 'ping' on entity 'buf'
200
+ And the check is in an ok state
201
+ When a critical event is received
202
+ And 1 minute passes
203
+ And a critical event is received
204
+ Then 1 email alert should be queued for vera@example.com
205
+ Then 1 sms alert should be queued for +61400000003
206
+
207
+ @time
208
+ Scenario: Multiple general rules should be additive
209
+ Given the check is check 'ping' on entity 'buf'
210
+ And the check is in an ok state
211
+ When a critical event is received
212
+ And 1 minute passes
213
+ And a critical event is received
214
+ Then 1 email alert should be queued for imani@example.com
215
+ Then 1 sms alert should be queued for +61400000002
216
+
217
+ @time
218
+ Scenario: An entity specific rule should override general rules
219
+ Given the check is check 'ping' on entity 'baz'
220
+ And the check is in an ok state
221
+ When a critical event is received
222
+ And 1 minute passes
223
+ And a critical event is received
224
+ Then 0 email alerts should be queued for vera@example.com
225
+ Then 1 sms alert should be queued for +61400000003
226
+
227
+ @time
228
+ Scenario: A blackhole rule on an entity should override another matching entity specific rule
229
+
230
+ @time
231
+ Scenario: A blackhole rule on an entity should override another matching general rule
232
+
@@ -94,6 +94,7 @@ module Flapjack
94
94
  event = { 'type' => 'action',
95
95
  'state' => 'test_notifications',
96
96
  'summary' => options['summary'],
97
+ 'details' => options['details'],
97
98
  'entity' => entity.name,
98
99
  'check' => check
99
100
  }
@@ -221,6 +222,7 @@ module Flapjack
221
222
  timestamp = options[:timestamp] || Time.now.to_i
222
223
  client = options[:client]
223
224
  summary = options[:summary]
225
+ details = options[:details]
224
226
  count = options[:count]
225
227
 
226
228
  # Note the current state (for speedy lookups)
@@ -233,6 +235,7 @@ module Flapjack
233
235
  @redis.rpush("#{@key}:states", timestamp)
234
236
  @redis.set("#{@key}:#{timestamp}:state", state)
235
237
  @redis.set("#{@key}:#{timestamp}:summary", summary) if summary
238
+ @redis.set("#{@key}:#{timestamp}:details", details) if details
236
239
  @redis.set("#{@key}:#{timestamp}:count", count) if count
237
240
 
238
241
  @redis.zadd("#{@key}:sorted_state_timestamps", timestamp, timestamp)
@@ -349,6 +352,11 @@ module Flapjack
349
352
  @redis.get("#{@key}:#{timestamp}:summary")
350
353
  end
351
354
 
355
+ def details
356
+ timestamp = @redis.lindex("#{@key}:states", -1)
357
+ @redis.get("#{@key}:#{timestamp}:details")
358
+ end
359
+
352
360
  # Returns a list of states for this entity check, sorted by timestamp.
353
361
  #
354
362
  # start_time and end_time should be passed as integer timestamps; these timestamps
@@ -368,7 +376,8 @@ module Flapjack
368
376
  state_data = state_ts.collect {|ts|
369
377
  {:timestamp => ts.to_i,
370
378
  :state => r.get("#{@key}:#{ts}:state"),
371
- :summary => r.get("#{@key}:#{ts}:summary")}
379
+ :summary => r.get("#{@key}:#{ts}:summary"),
380
+ :details => r.get("#{@key}:#{ts}:details")}
372
381
  }
373
382
  end
374
383
 
@@ -392,7 +401,8 @@ module Flapjack
392
401
  return if ts.nil? || ts.empty?
393
402
  {:timestamp => ts.first.to_i,
394
403
  :state => @redis.get("#{@key}:#{ts.first}:state"),
395
- :summary => @redis.get("#{@key}:#{ts.first}:summary")}
404
+ :summary => @redis.get("#{@key}:#{ts.first}:summary"),
405
+ :details => @redis.get("#{@key}:#{ts.first}:details")}
396
406
  end
397
407
 
398
408
  # Returns a list of maintenance periods (either unscheduled or scheduled) for this
@@ -126,6 +126,10 @@ module Flapjack
126
126
  @attrs['summary']
127
127
  end
128
128
 
129
+ def details
130
+ @attrs['details']
131
+ end
132
+
129
133
  def time
130
134
  return unless @attrs['time']
131
135
  @attrs['time'].to_i
@@ -39,6 +39,7 @@ module Flapjack
39
39
  @contents ||= {'event_id' => event.id,
40
40
  'state' => event.state,
41
41
  'summary' => event.summary,
42
+ 'details' => event.details,
42
43
  'time' => event.time,
43
44
  'duration' => event.duration || nil,
44
45
  'notification_type' => type,
@@ -218,13 +218,13 @@ module Flapjack
218
218
  d[:entity_tags].all? {|et| et.is_a?(String)} ) } =>
219
219
  "entity_tags must be a list of strings",
220
220
 
221
- proc { (d.has_key?(:entities) &&
222
- d[:entities].is_a?(Array) &&
223
- (d[:entities].size > 0)) ||
224
- (d.has_key?(:entity_tags) &&
225
- d[:entity_tags].is_a?(Array) &&
226
- (d[:entity_tags].size > 0)) } =>
227
- "entities or entity tags must have at least one value",
221
+ #proc { (d.has_key?(:entities) &&
222
+ # d[:entities].is_a?(Array) &&
223
+ # (d[:entities].size > 0)) ||
224
+ # (d.has_key?(:entity_tags) &&
225
+ # d[:entity_tags].is_a?(Array) &&
226
+ # (d[:entity_tags].size > 0)) } =>
227
+ #"entities or entity tags must have at least one value",
228
228
 
229
229
  proc { !d.has_key?(:time_restrictions) ||
230
230
  ( d[:time_restrictions].nil? ||
@@ -208,7 +208,7 @@ module Flapjack
208
208
  if event.state != event.previous_state
209
209
  entity_check.update_state(event.state, :timestamp => timestamp,
210
210
  :summary => event.summary, :client => event.client,
211
- :count => @event_count)
211
+ :count => @event_count, :details => event.details)
212
212
  end
213
213
 
214
214
  # No state change, and event is ok, so no need to run through filters
@@ -314,16 +314,23 @@ module Flapjack
314
314
  # don't consider notification rules if the contact has none
315
315
 
316
316
  tuple = messages.map do |message|
317
- @logger.debug "considering message for contact: #{message.contact.id} #{message.medium} #{message.notification.event.id} #{message.notification.event.state}"
318
317
  rules = message.contact.notification_rules
319
- @logger.debug "found #{rules.length} rules for this message's contact"
320
318
  event_id = message.notification.event.id
319
+
320
+ @logger.debug "considering message for contact: #{message.contact.id} #{message.medium} #{event_id} #{message.notification.event.state}"
321
+ @logger.debug "found #{rules.length} rules for this message's contact"
322
+
321
323
  options = {}
322
324
  options[:no_rules_for_contact] = true if rules.empty?
325
+
323
326
  # filter based on entity, tags, severity, time of day
324
327
  matchers = rules.find_all do |rule|
325
328
  rule.match_entity?(event_id) && rule_occurring_now?(rule, :contact => message.contact)
326
329
  end
330
+ @logger.debug "#{matchers.length} matchers remain for this message:"
331
+ matchers.each {|matcher|
332
+ @logger.debug "matcher: #{matcher.to_json}"
333
+ }
327
334
  [message, matchers, options]
328
335
  end
329
336
 
@@ -332,19 +339,26 @@ module Flapjack
332
339
 
333
340
  @logger.debug "apply_notification_rules: num messages after entity and time matching: #{tuple.size}"
334
341
 
335
- # delete the matcher for all entities if there are more specific matchers
342
+ # delete any matchers for all entities if there are more specific matchers
336
343
  tuple = tuple.map do |message, matchers, options|
344
+ num_matchers = matchers.length
337
345
  if matchers.length > 1
346
+ @logger.debug("general removal when entity specific: #{matchers.length} matchers")
338
347
  have_specific = matchers.detect do |matcher|
339
- matcher.entities or matcher.entity_tags
348
+ ( matcher.entities && !matcher.entities.empty? ) or
349
+ ( matcher.entity_tags && !matcher.entity_tags.empty? )
340
350
  end
341
351
  if have_specific
342
- # delete the rule for all entities
352
+ # delete any rules for all entities
343
353
  matchers.reject! do |matcher|
344
- matcher.entities.nil? && matcher.entity_tags.nil?
354
+ ( matcher.entities.nil? || matcher.entities.empty? ) &&
355
+ ( matcher.entity_tags.nil? || matcher.entity_tags.empty? )
345
356
  end
346
357
  end
347
358
  end
359
+ if num_matchers != matchers.length
360
+ @logger.debug("apply_notification_rules: removal of general matchers when entity specific matchers are present: number of matchers changed from #{num_matchers} to #{matchers.length} for message id: #{message.contact.id}, medium: #{message.medium}")
361
+ end
348
362
  [message, matchers, options]
349
363
  end
350
364
 
@@ -361,6 +375,7 @@ module Flapjack
361
375
  state = message.notification.event.state
362
376
  max_notified_severity = message.notification.max_notified_severity
363
377
 
378
+ @logger.debug "apply_notification_rules severity-media constraints: considering message: #{message.contact.id} #{message.medium} #{state}"
364
379
  # use EntityCheck#max_notified_severity_of_current_failure
365
380
  # as calculated prior to updating the last_notification* keys
366
381
  # if it's a higher severity than the current state
@@ -371,10 +386,13 @@ module Flapjack
371
386
  when [state, max_notified_severity].include?('warning')
372
387
  severity = 'warning'
373
388
  end
374
- options[:no_rules_for_contact] ||
375
- matchers.any? {|matcher|
389
+
390
+ no_rules_for_contact = !!options[:no_rules_for_contact]
391
+ sev_media_matchers = matchers.any? {|matcher|
376
392
  (matcher.media_for_severity(severity) || []).include?(message.medium)
377
393
  }
394
+ @logger.debug "apply_notification_rules severity-media constraints: no_rules_for_contact: #{no_rules_for_contact}, sev_media_matchers: #{sev_media_matchers}"
395
+ no_rules_for_contact || sev_media_matchers
378
396
  end
379
397
 
380
398
  @logger.debug "apply_notification_rules: num messages after pruning for severity-media constraints: #{tuple.size}"
@@ -32,6 +32,7 @@ module Flapjack
32
32
  @contact_last_name = notification['contact_last_name']
33
33
  @state = notification['state']
34
34
  @summary = notification['summary']
35
+ @details = notification['details']
35
36
  @time = notification['time']
36
37
  @entity_name, @check = notification['event_id'].split(':')
37
38
 
@@ -33,6 +33,11 @@
33
33
  %td
34
34
  %strong Summary
35
35
  %td= @summary
36
+ - if @details
37
+ %tr
38
+ %td
39
+ %strong Details
40
+ %td= @details
36
41
  %tr
37
42
  %td
38
43
  %strong Time
@@ -128,7 +128,7 @@ module Flapjack
128
128
  dur = nil
129
129
 
130
130
  if comment.nil? || (comment.length == 0)
131
- error = "please provide a comment, eg \"flapjack: ACKID #{$1} AL looking\""
131
+ error = "please provide a comment, eg \"#{@config['alias']}: ACKID #{$1} AL looking\""
132
132
  elsif duration_str
133
133
  # a fairly liberal match above, we'll let chronic_duration do the heavy lifting
134
134
  dur = ChronicDuration.parse(duration_str)
@@ -180,6 +180,7 @@ module Flapjack
180
180
  @check_last_update = entity_check.last_update
181
181
  @check_last_change = last_change
182
182
  @check_summary = entity_check.summary
183
+ @check_details = entity_check.details
183
184
  @last_notifications = entity_check.last_notifications_of_each_type
184
185
  @scheduled_maintenances = entity_check.maintenances(nil, nil, :scheduled => true)
185
186
  @acknowledgement_id = entity_check.failed? ?
@@ -34,6 +34,7 @@
34
34
  - if @current_scheduled_maintenance
35
35
  %h4 (Scheduled Maintenance - #{@current_scheduled_maintenance[:summary]})
36
36
  %h3 Output: #{@check_summary}
37
+ %p #{@check_details}
37
38
  %table{:class => "table table-hover table-condensed"}
38
39
  %tr
39
40
  %td Last state change:
@@ -17,7 +17,7 @@
17
17
  %tr
18
18
  %th Name
19
19
  %th Email
20
- - @contacts.sort_by {|c| [c.last_name, c.first_name] }.each do |contact|
20
+ - @contacts.sort_by {|c| [c.first_name, c.last_name] }.each do |contact|
21
21
  %tr
22
22
  %td
23
23
  - link = "/contacts/#{contact.id}"
@@ -15,7 +15,7 @@
15
15
  %p #{@count_failing_entities} failing out of #{@count_all_entities}
16
16
  %table{:class => "table table-bordered table-hover table-condensed"}
17
17
  - if @entities.length > 0
18
- - @entities.each do |entity|
18
+ - @entities.sort.each do |entity|
19
19
  %tr
20
20
  %td
21
21
  - link = "/entity/#{CGI.escape(entity)}"
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  module Flapjack
4
- VERSION = "0.7.7"
4
+ VERSION = "0.7.8"
5
5
  end
@@ -95,8 +95,7 @@ describe Flapjack::Data::NotificationRule, :redis => true do
95
95
  context 'validation' do
96
96
 
97
97
  it "fails to add a notification rule with invalid data" do
98
- rule_data[:entities] = []
99
- rule_data[:entity_tags] = []
98
+ rule_data[:entities] = [1, {}]
100
99
  rule = Flapjack::Data::NotificationRule.add(rule_data, :redis => @redis)
101
100
  rule.should be_nil
102
101
  end
@@ -112,4 +111,4 @@ describe Flapjack::Data::NotificationRule, :redis => true do
112
111
 
113
112
  end
114
113
 
115
- end
114
+ end
@@ -45,12 +45,14 @@ describe Flapjack::Data::Notification, :redis => true do
45
45
  event.should_receive(:id).and_return('example.com:ping')
46
46
  event.should_receive(:state).and_return('ok')
47
47
  event.should_receive(:summary).and_return('Shiny & happy')
48
+ event.should_receive(:details).and_return('Really Shiny & happy')
48
49
  event.should_receive(:time).and_return(t)
49
50
  event.should_receive(:duration).and_return(nil)
50
51
 
51
52
  notification.contents.should == {'event_id' => 'example.com:ping',
52
53
  'state' => 'ok',
53
54
  'summary' => 'Shiny & happy',
55
+ 'details' => 'Really Shiny & happy',
54
56
  'time' => t,
55
57
  'duration' => nil,
56
58
  'notification_type' => 'problem',
@@ -58,4 +60,4 @@ describe Flapjack::Data::Notification, :redis => true do
58
60
 
59
61
  end
60
62
 
61
- end
63
+ end
@@ -122,6 +122,7 @@ describe Flapjack::Gateways::Web, :sinatra => true, :logger => true do
122
122
  entity_check.should_receive(:last_update).and_return(time - (3 * 60 * 60))
123
123
  entity_check.should_receive(:last_change).and_return(time - (3 * 60 * 60))
124
124
  entity_check.should_receive(:summary).and_return('all good')
125
+ entity_check.should_receive(:details).and_return('seriously, all very wonderful')
125
126
  entity_check.should_receive(:last_notifications_of_each_type).and_return(last_notifications)
126
127
  entity_check.should_receive(:maintenances).with(nil, nil, :scheduled => true).and_return([])
127
128
  entity_check.should_receive(:failed?).and_return(false)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flapjack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.7
4
+ version: 0.7.8
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2013-05-22 00:00:00.000000000 Z
14
+ date: 2013-05-30 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: dante
@@ -553,7 +553,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
553
553
  version: '0'
554
554
  segments:
555
555
  - 0
556
- hash: 4574905656823869764
556
+ hash: 3433474727648053171
557
557
  required_rubygems_version: !ruby/object:Gem::Requirement
558
558
  none: false
559
559
  requirements:
@@ -562,7 +562,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
562
562
  version: '0'
563
563
  segments:
564
564
  - 0
565
- hash: 4574905656823869764
565
+ hash: 3433474727648053171
566
566
  requirements: []
567
567
  rubyforge_project:
568
568
  rubygems_version: 1.8.23