flapjack 0.7.11 → 0.7.12

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,10 @@
1
1
  ## Flapjack Changelog
2
2
 
3
+ # 0.7.12 - 2013-06-12
4
+ - Feature: auto-generate a general notification rule for contacts that don't have any gh-199 (@ali-graham)
5
+ - Bug: no recovery for unknown for contact with notification rules gh-203 (@jessereynolds)
6
+ - Bug: UNKNOWN -> OK (brief) -> [any problem state] alert is masked by notification interval gh-204 (@jessereynolds)
7
+
3
8
  # 0.7.11 - 2013-06-11
4
9
  - Bug: unknown events not fully treated as problems - no notification delay blocking (second swing) gh-154 (@jessereynolds)
5
10
  - Bug: correct jabber alias in example acknowledgement strings (second swing) gh-189 (@jessereynolds)
@@ -29,6 +29,7 @@ Feature: Notification rules on a per contact basis
29
29
 
30
30
  And user 1 has the following notification rules:
31
31
  | entities | entity_tags | warning_media | critical_media | warning_blackhole | critical_blackhole | time_restrictions |
32
+ | | | email | sms,email | true | true | |
32
33
  | foo | | email | sms,email | | | 8-18 weekdays |
33
34
  | bar | | | sms,email | true | | |
34
35
  | baz | | email | sms,email | | | |
@@ -41,10 +42,10 @@ Feature: Notification rules on a per contact basis
41
42
 
42
43
  And user 3 has the following notification rules:
43
44
  | entities | entity_tags | warning_media | critical_media | warning_blackhole | critical_blackhole | time_restrictions |
44
- | buf | | email | email | | | |
45
- | buf | | sms | sms | | | |
46
45
  | | | email | email | | | |
47
46
  | baz | | sms | sms | | | |
47
+ | buf | | email | email | | | |
48
+ | buf | | sms | sms | | | |
48
49
 
49
50
  @time_restrictions @time
50
51
  Scenario: Alerts only during specified time restrictions
@@ -157,6 +158,22 @@ Feature: Notification rules on a per contact basis
157
158
  And a critical event is received
158
159
  Then 2 email alerts should be queued for malak@example.com
159
160
 
161
+ @intervals @time
162
+ Scenario: Alerts according to custom interval with unknown
163
+ Given the check is check 'ping' on entity 'bar'
164
+ And the check is in an ok state
165
+ When an unknown event is received
166
+ Then no email alerts should be queued for malak@example.com
167
+ When 1 minute passes
168
+ And an unknown event is received
169
+ Then 1 email alert should be queued for malak@example.com
170
+ When 10 minutes passes
171
+ And an unknown event is received
172
+ Then 1 email alert should be queued for malak@example.com
173
+ When 5 minutes passes
174
+ And an unknown event is received
175
+ Then 2 email alerts should be queued for malak@example.com
176
+
160
177
  @intervals @time
161
178
  Scenario: Problem directly after Recovery should alert despite notification intervals
162
179
  Given the check is check 'ping' on entity 'baz'
@@ -176,6 +193,25 @@ Feature: Notification rules on a per contact basis
176
193
  Then 3 email alerts should be queued for malak@example.com
177
194
  And 3 sms alerts should be queued for +61400000001
178
195
 
196
+ @intervals @time
197
+ Scenario: Problem directly after Recovery should alert despite notification intervals with unknown
198
+ Given the check is check 'ping' on entity 'baz'
199
+ And the check is in an ok state
200
+ When an unknown event is received
201
+ And 1 minute passes
202
+ And an unknown event is received
203
+ Then 1 email alert should be queued for malak@example.com
204
+ And 1 sms alert should be queued for +61400000001
205
+ When an ok event is received
206
+ Then 2 email alert should be queued for malak@example.com
207
+ And 2 sms alerts should be queued for +61400000001
208
+ When 1 minute passes
209
+ And an unknown event is received
210
+ And 1 minute passes
211
+ And an unknown event is received
212
+ Then 3 email alerts should be queued for malak@example.com
213
+ And 3 sms alerts should be queued for +61400000001
214
+
179
215
  @time
180
216
  Scenario: Contact with only entity specific rules should not be notified for other entities they are a contact for
181
217
  Given the check is check 'ping' on entity 'buf'
@@ -285,6 +285,13 @@ Given /^user (\d+) has the following notification intervals:$/ do |contact_id, i
285
285
  end
286
286
 
287
287
  Given /^user (\d+) has the following notification rules:$/ do |contact_id, rules|
288
+ contact = Flapjack::Data::Contact.find_by_id(contact_id, :redis => @redis)
289
+ # delete any autogenerated rules, and do it using redis directly so no new
290
+ # ones will be created
291
+ contact.notification_rules.each do |nr|
292
+ @redis.srem("contact_notification_rules:#{contact_id}", nr.id)
293
+ @redis.del("notification_rule:#{nr.id}")
294
+ end
288
295
  rules.hashes.each do |rule|
289
296
  entities = rule['entities'].split(',').map { |x| x.strip }
290
297
  entity_tags = rule['entity_tags'].split(',').map { |x| x.strip }
@@ -55,7 +55,10 @@ module Flapjack
55
55
  end
56
56
 
57
57
  self.add_or_update(contact_id, contact_data, :redis => redis)
58
- self.find_by_id(contact_id, :redis => redis)
58
+ if contact = self.find_by_id(contact_id, :redis => redis)
59
+ contact.notification_rules # invoke to create general rule
60
+ end
61
+ contact
59
62
  end
60
63
 
61
64
  def self.delete_all(options = {})
@@ -161,11 +164,26 @@ module Flapjack
161
164
  end
162
165
 
163
166
  # return an array of the notification rules of this contact
164
- def notification_rules
165
- @redis.smembers("contact_notification_rules:#{self.id}").collect { |rule_id|
166
- next if (rule_id.nil? || rule_id == '')
167
- Flapjack::Data::NotificationRule.find_by_id(rule_id, {:redis => @redis })
168
- }.compact
167
+ def notification_rules(opts = {})
168
+ rules = @redis.smembers("contact_notification_rules:#{self.id}").inject([]) do |ret, rule_id|
169
+ unless (rule_id.nil? || rule_id == '')
170
+ ret << Flapjack::Data::NotificationRule.find_by_id(rule_id, :redis => @redis)
171
+ end
172
+ ret
173
+ end
174
+ if rules.all? {|r| r.is_specific? } # also true if empty
175
+ rule = self.add_notification_rule({
176
+ :entities => [],
177
+ :entity_tags => [],
178
+ :time_restrictions => [],
179
+ :warning_media => ['email', 'sms', 'jabber', 'pagerduty'],
180
+ :critical_media => ['email', 'sms', 'jabber', 'pagerduty'],
181
+ :warning_blackhole => false,
182
+ :critical_blackhole => false,
183
+ }, :logger => opts[:logger])
184
+ rules.unshift(rule)
185
+ end
186
+ rules
169
187
  end
170
188
 
171
189
  def add_notification_rule(rule_data, opts = {})
@@ -322,6 +322,9 @@ module Flapjack
322
322
  last_warning = last_warning_notification
323
323
  return STATE_WARNING if last_warning && (last_warning > last_recovery)
324
324
 
325
+ last_unknown = last_unknown_notification
326
+ return STATE_UNKNOWN if last_unknown && (last_unknown > last_recovery)
327
+
325
328
  nil
326
329
  end
327
330
 
@@ -11,10 +11,13 @@ module Flapjack
11
11
  module Data
12
12
  class Message
13
13
 
14
- attr_accessor :medium, :address, :id, :duration, :contact, :notification
14
+ attr_reader :medium, :address, :duration, :contact
15
15
 
16
- def self.for_contact(opts = {})
17
- self.new(:contact => opts[:contact])
16
+ def self.for_contact(contact, opts = {})
17
+ self.new(:contact => contact,
18
+ :notification_contents => opts[:notification_contents],
19
+ :medium => opts[:medium], :address => opts[:address],
20
+ :duration => opts[:duration])
18
21
  end
19
22
 
20
23
  def id
@@ -28,20 +31,23 @@ module Flapjack
28
31
  def contents
29
32
  c = {'media' => medium,
30
33
  'address' => address,
31
- 'id' => id}
32
- if contact
33
- c.update('contact_id' => contact.id,
34
- 'contact_first_name' => contact.first_name,
35
- 'contact_last_name' => contact.last_name)
36
- end
34
+ 'id' => id,
35
+ 'contact_id' => contact.id,
36
+ 'contact_first_name' => contact.first_name,
37
+ 'contact_last_name' => contact.last_name}
37
38
  c['duration'] = duration if duration
38
- c.update(notification.contents) if notification
39
+ return c if @notification_contents.nil?
40
+ c.merge(@notification_contents)
39
41
  end
40
42
 
41
43
  private
42
44
 
43
45
  def initialize(opts = {})
44
46
  @contact = opts[:contact]
47
+ @notification_contents = opts[:notification_contents]
48
+ @medium = opts[:medium]
49
+ @address = opts[:address]
50
+ @duration = opts[:duration]
45
51
  end
46
52
 
47
53
  end
@@ -8,42 +8,109 @@ module Flapjack
8
8
  module Data
9
9
  class Notification
10
10
 
11
- attr_accessor :event, :type, :max_notified_severity
11
+ attr_reader :event, :type, :max_notified_severity, :contacts,
12
+ :default_timezone
12
13
 
13
14
  def self.for_event(event, opts = {})
14
15
  self.new(:event => event,
15
16
  :type => opts[:type],
16
- :max_notified_severity => opts[:max_notified_severity])
17
+ :max_notified_severity => opts[:max_notified_severity],
18
+ :contacts => opts[:contacts],
19
+ :default_timezone => opts[:default_timezone],
20
+ :logger => opts[:logger])
17
21
  end
18
22
 
19
- def messages(opts = {})
20
- contacts = opts[:contacts]
21
- return [] if contacts.nil?
23
+ def messages
24
+ return [] if contacts.nil? || contacts.empty?
25
+
26
+ event_id = event.id
27
+ event_state = event.state
28
+
29
+ severity = if ([event_state, max_notified_severity] & ['critical', 'unknown']).any?
30
+ 'critical'
31
+ elsif [event_state, max_notified_severity].include?('warning')
32
+ 'warning'
33
+ else
34
+ 'ok'
35
+ end
36
+
37
+ contents = {'event_id' => event_id,
38
+ 'state' => event_state,
39
+ 'summary' => event.summary,
40
+ 'details' => event.details,
41
+ 'time' => event.time,
42
+ 'duration' => event.duration || nil,
43
+ 'notification_type' => type,
44
+ 'max_notified_severity' => max_notified_severity }
45
+
22
46
  @messages ||= contacts.collect {|contact|
47
+ contact_id = contact.id
48
+ rules = contact.notification_rules
49
+ media = contact.media
50
+
51
+ @logger.debug "considering messages for contact id #{contact_id} #{event_id} #{event_state} (media) #{media.inspect}"
52
+ rlen = rules.length
53
+ @logger.debug "found #{rlen} rule#{(rlen == 1) ? '' : 's'} for contact"
54
+
55
+ media_to_use = if rules.empty?
56
+ media
57
+ else
58
+ # matchers are rules of the contact that have matched the current event
59
+ # for time and entity
60
+ matchers = rules.select do |rule|
61
+ rule.match_entity?(event_id) &&
62
+ rule_occurring_now?(rule, :contact => contact, :default_timezone => default_timezone)
63
+ end
64
+
65
+ @logger.debug "#{matchers.length} matchers remain for this contact:"
66
+ matchers.each do |matcher|
67
+ @logger.debug "matcher: #{matcher.to_json}"
68
+ end
69
+
70
+ # delete any matchers for all entities if there are more specific matchers
71
+ if matchers.any? {|matcher| matcher.is_specific? }
72
+
73
+ @logger.debug("general removal: found #{matchers.length} entity specific matchers")
74
+ num_matchers = matchers.length
75
+
76
+ matchers.reject! {|matcher| !matcher.is_specific? }
77
+
78
+ if num_matchers != matchers.length
79
+ @logger.debug("notification: removal of general matchers when entity specific matchers are present: number of matchers changed from #{num_matchers} to #{matchers.length} for contact id: #{contact_id}")
80
+ end
81
+ end
82
+
83
+ # delete media based on blackholes
84
+ next if matchers.any? {|matcher| matcher.blackhole?(event_state) }
23
85
 
24
- # TODO move the message filtering logic from executive into this
25
- # class and apply here, don't generate message if it won't be sent
86
+ @logger.debug "notification: num matchers after removing blackhole matchers: #{matchers.size}"
26
87
 
27
- contact.media.each_pair.inject([]) { |ret, (k, v)|
28
- m = Flapjack::Data::Message.for_contact(:contact => contact)
29
- m.notification = self
30
- m.medium = k
31
- m.address = v
88
+ rule_media = matchers.collect{|matcher|
89
+ matcher.media_for_severity(severity)
90
+ }.flatten.uniq
91
+
92
+ @logger.debug "notification: collected media_for_severity(#{severity}): #{rule_media}"
93
+ rule_media = rule_media.flatten.uniq.reject {|medium|
94
+ contact.drop_notifications?(:media => medium,
95
+ :check => event_id,
96
+ :state => event_state)
97
+ }
98
+
99
+ @logger.debug "notification: media after contact_drop?: #{rule_media}"
100
+
101
+ media.select {|medium, address| rule_media.include?(medium) }
102
+ end
103
+
104
+ @logger.debug "notification: media_to_use: #{media_to_use}"
105
+
106
+ media_to_use.each_pair.inject([]) { |ret, (k, v)|
107
+ m = Flapjack::Data::Message.for_contact(contact,
108
+ :notification_contents => contents,
109
+ :medium => k, :address => v)
32
110
  ret << m
33
111
  ret
34
112
  }
35
- }.flatten
36
- end
37
-
38
- def contents
39
- @contents ||= {'event_id' => event.id,
40
- 'state' => event.state,
41
- 'summary' => event.summary,
42
- 'details' => event.details,
43
- 'time' => event.time,
44
- 'duration' => event.duration || nil,
45
- 'notification_type' => type,
46
- 'max_notified_severity' => max_notified_severity }
113
+ }.compact.flatten
47
114
  end
48
115
 
49
116
  private
@@ -53,6 +120,30 @@ module Flapjack
53
120
  @event = event
54
121
  @type = opts[:type]
55
122
  @max_notified_severity = opts[:max_notified_severity]
123
+ @contacts = opts[:contacts]
124
+ @default_timezone = opts[:default_timezone]
125
+ @logger = opts[:logger]
126
+ end
127
+
128
+ # # time restrictions match?
129
+ # nil rule.time_restrictions matches
130
+ # times (start, end) within time restrictions will have any UTC offset removed and will be
131
+ # considered to be in the timezone of the contact
132
+ def rule_occurring_now?(rule, options = {})
133
+ contact = options[:contact]
134
+ default_timezone = options[:default_timezone]
135
+
136
+ return true if rule.time_restrictions.nil? or rule.time_restrictions.empty?
137
+
138
+ timezone = contact.timezone(:default => default_timezone)
139
+ usertime = timezone.now
140
+
141
+ rule.time_restrictions.any? do |tr|
142
+ # add contact's timezone to the time restriction schedule
143
+ schedule = Flapjack::Data::NotificationRule.
144
+ time_restriction_to_icecube_schedule(tr, timezone)
145
+ schedule && schedule.occurring_at?(usertime)
146
+ end
56
147
  end
57
148
 
58
149
  end
@@ -95,6 +95,11 @@ module Flapjack
95
95
  end
96
96
  end
97
97
 
98
+ def is_specific?
99
+ (!@entities.nil? && !@entities.empty?) ||
100
+ (!@entity_tags.nil? && !@entity_tags.empty?)
101
+ end
102
+
98
103
  private
99
104
 
100
105
  def initialize(rule_data, opts = {})
@@ -279,171 +279,49 @@ module Flapjack
279
279
  end
280
280
 
281
281
  notification = Flapjack::Data::Notification.for_event(
282
- event, :type => notification_type, :max_notified_severity => max_notified_severity)
282
+ event, :type => notification_type,
283
+ :max_notified_severity => max_notified_severity,
284
+ :contacts => contacts,
285
+ :default_timezone => @default_contact_timezone,
286
+ :logger => @logger)
283
287
 
284
- messages = notification.messages(:contacts => contacts)
285
- messages = apply_notification_rules(messages, event.state)
286
- enqueue_messages(messages)
287
- end
288
-
289
- # time restrictions match?
290
- # nil rule.time_restrictions matches
291
- # times (start, end) within time restrictions will have any UTC offset removed and will be
292
- # considered to be in the timezone of the contact
293
- def rule_occurring_now?(rule, opts)
294
- contact = opts[:contact]
295
- return true if rule.time_restrictions.nil? or rule.time_restrictions.empty?
296
-
297
- timezone = contact.timezone(:default => @default_contact_timezone)
298
- usertime = timezone.now
299
-
300
- match = rule.time_restrictions.any? do |tr|
301
- # add contact's timezone to the time restriction schedule
302
- schedule = Flapjack::Data::NotificationRule.
303
- time_restriction_to_icecube_schedule(tr, timezone)
304
- schedule && schedule.occurring_at?(usertime)
305
- end
306
- !!match
307
- end
308
-
309
- # delete messages based on entity name(s), tags, severity, time of day
310
- def apply_notification_rules(messages, severity)
311
- # first get all rules matching entity and time
312
- @logger.debug "apply_notification_rules: got messages with size #{messages.size}"
313
-
314
- # don't consider notification rules if the contact has none
315
-
316
- tuple = messages.map do |message|
317
- rules = message.contact.notification_rules
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
-
323
- options = {}
324
- options[:no_rules_for_contact] = true if rules.empty?
325
-
326
- # filter based on entity, tags, severity, time of day
327
- matchers = rules.find_all do |rule|
328
- rule.match_entity?(event_id) && rule_occurring_now?(rule, :contact => message.contact)
329
- end
330
- @logger.debug "#{matchers.length} matchers remain for this message:"
331
- matchers.each {|matcher|
332
- @logger.debug "matcher: #{matcher.to_json}"
333
- }
334
- [message, matchers, options]
335
- end
336
-
337
- # matchers are rules of the contact that have matched the current event
338
- # for time and entity
339
-
340
- @logger.debug "apply_notification_rules: num messages after entity and time matching: #{tuple.size}"
341
-
342
- # delete any matchers for all entities if there are more specific matchers
343
- tuple = tuple.map do |message, matchers, options|
344
- num_matchers = matchers.length
345
- if matchers.length > 1
346
- @logger.debug("general removal when entity specific: #{matchers.length} matchers")
347
- have_specific = matchers.detect do |matcher|
348
- ( matcher.entities && !matcher.entities.empty? ) or
349
- ( matcher.entity_tags && !matcher.entity_tags.empty? )
350
- end
351
- if have_specific
352
- # delete any rules for all entities
353
- matchers.reject! do |matcher|
354
- ( matcher.entities.nil? || matcher.entities.empty? ) &&
355
- ( matcher.entity_tags.nil? || matcher.entity_tags.empty? )
356
- end
357
- end
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
362
- [message, matchers, options]
363
- end
364
-
365
- # delete media based on blackholes
366
- tuple = tuple.find_all do |message, matchers, options|
367
- # or use message.notification.contents['state']
368
- matchers.none? {|matcher| matcher.blackhole?(severity) }
369
- end
370
-
371
- @logger.debug "apply_notification_rules: num messages after removing blackhole matches: #{tuple.size}"
372
-
373
- # delete any media that doesn't meet severity<->media constraints
374
- tuple = tuple.find_all do |message, matchers, options|
375
- state = message.notification.event.state
376
- max_notified_severity = message.notification.max_notified_severity
377
-
378
- @logger.debug "apply_notification_rules severity-media constraints: considering message: #{message.contact.id} #{message.medium} #{state}"
379
- # use EntityCheck#max_notified_severity_of_current_failure
380
- # as calculated prior to updating the last_notification* keys
381
- # if it's a higher severity than the current state
382
- severity = 'ok'
383
- case
384
- when ([state, max_notified_severity] & ['critical', 'unknown']).any?
385
- severity = 'critical'
386
- when [state, max_notified_severity].include?('warning')
387
- severity = 'warning'
388
- end
389
-
390
- no_rules_for_contact = !!options[:no_rules_for_contact]
391
- sev_media_matchers = matchers.any? {|matcher|
392
- (matcher.media_for_severity(severity) || []).include?(message.medium)
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
396
- end
397
-
398
- @logger.debug "apply_notification_rules: num messages after pruning for severity-media constraints: #{tuple.size}"
399
-
400
- # delete media based on notification interval
401
- tuple = tuple.find_all do |message, matchers, options|
402
- not message.contact.drop_notifications?(:media => message.medium,
403
- :check => message.notification.event.id,
404
- :state => message.notification.event.state)
405
- end
406
-
407
- @logger.debug "apply_notification_rules: num messages after pruning for notification intervals: #{tuple.size}"
408
-
409
- tuple.map do |message, matchers, options|
410
- message
411
- end
412
- end
413
-
414
- def enqueue_messages(messages)
415
-
416
- messages.each do |message|
288
+ notification.messages.each do |message|
417
289
  media_type = message.medium
418
290
  contents = message.contents
419
- event_id = message.notification.event.id
291
+ address = message.address
292
+ event_id = event.id
420
293
 
421
294
  @notifylog.info("#{Time.now.to_s} | #{event_id} | " +
422
- "#{message.notification.type} | #{message.contact.id} | #{media_type} | #{message.address}")
295
+ "#{notification_type} | #{message.contact.id} | #{media_type} | #{address}")
423
296
 
424
297
  unless @queues[media_type.to_sym]
425
298
  @logger.error("no queue for media type: #{media_type}")
426
299
  return
427
300
  end
428
301
 
429
- @logger.info("Enqueueing #{media_type} alert for #{event_id} to #{message.address}")
302
+ @logger.info("Enqueueing #{media_type} alert for #{event_id} to #{address}")
430
303
 
431
- if message.notification.event.state == 'ok'
304
+ if event.ok?
432
305
  message.contact.update_sent_alert_keys(
433
- :media => message.medium,
434
- :check => message.notification.event.id,
306
+ :media => media_type,
307
+ :check => event_id,
435
308
  :state => 'warning',
436
309
  :delete => true)
437
310
  message.contact.update_sent_alert_keys(
438
- :media => message.medium,
439
- :check => message.notification.event.id,
311
+ :media => media_type,
312
+ :check => event_id,
440
313
  :state => 'critical',
441
314
  :delete => true)
315
+ message.contact.update_sent_alert_keys(
316
+ :media => media_type,
317
+ :check => event_id,
318
+ :state => 'unknown',
319
+ :delete => true)
442
320
  else
443
321
  message.contact.update_sent_alert_keys(
444
- :media => message.medium,
445
- :check => message.notification.event.id,
446
- :state => message.notification.event.state)
322
+ :media => media_type,
323
+ :check => event_id,
324
+ :state => event.state)
447
325
  end
448
326
 
449
327
  # TODO consider changing Resque jobs to use raw blpop like the others
@@ -460,8 +338,7 @@ module Flapjack
460
338
  @redis.rpush(@queues[:pagerduty], Yajl::Encoder.encode(contents))
461
339
  end
462
340
  end
463
-
464
- end
341
+ end
465
342
 
466
343
  end
467
344
  end
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  module Flapjack
4
- VERSION = "0.7.11"
4
+ VERSION = "0.7.12"
5
5
  end
@@ -20,6 +20,16 @@ describe Flapjack::Data::Contact, :redis => true do
20
20
  }
21
21
  }
22
22
 
23
+ let(:general_notification_rule_data) {
24
+ {:entities => [],
25
+ :entity_tags => [],
26
+ :time_restrictions => [],
27
+ :warning_media => ['email', 'sms', 'jabber', 'pagerduty'],
28
+ :critical_media => ['email', 'sms', 'jabber', 'pagerduty'],
29
+ :warning_blackhole => false,
30
+ :critical_blackhole => false}
31
+ }
32
+
23
33
  before(:each) do
24
34
  Flapjack::Data::Contact.add({'id' => '362',
25
35
  'first_name' => 'John',
@@ -71,7 +81,7 @@ describe Flapjack::Data::Contact, :redis => true do
71
81
 
72
82
  nr = contact.notification_rules
73
83
  nr.should_not be_nil
74
- nr.should have(1).notification_rule
84
+ nr.should have(2).notification_rules
75
85
 
76
86
  Flapjack::Data::Contact.add({'id' => '363',
77
87
  'first_name' => 'Smithy',
@@ -82,10 +92,9 @@ describe Flapjack::Data::Contact, :redis => true do
82
92
  contact = Flapjack::Data::Contact.find_by_id('363', :redis => @redis)
83
93
  contact.should_not be_nil
84
94
  contact.name.should == 'Smithy Smith'
85
-
86
- nr = contact.notification_rules
87
- nr.should_not be_nil
88
- nr.should be_empty
95
+ rules = contact.notification_rules
96
+ rules.should have(1).notification_rule
97
+ nr.map(&:id).should_not include(rules.first.id)
89
98
  end
90
99
 
91
100
  it "updates a contact and clears their media settings" do
@@ -101,18 +110,19 @@ describe Flapjack::Data::Contact, :redis => true do
101
110
 
102
111
  contact.add_notification_rule(notification_rule_data)
103
112
 
104
- nr = contact.notification_rules
105
- nr.should_not be_nil
106
- nr.should have(1).notification_rule
113
+ nr1 = contact.notification_rules
114
+ nr1.should_not be_nil
115
+ nr1.should have(2).notification_rules
107
116
 
108
117
  contact.update('first_name' => 'John',
109
118
  'last_name' => 'Smith',
110
119
  'email' => 'johns@example.com')
111
120
  contact.name.should == 'John Smith'
112
121
 
113
- nr = contact.notification_rules
114
- nr.should_not be_nil
115
- nr.should have(1).notification_rule
122
+ nr2 = contact.notification_rules
123
+ nr2.should_not be_nil
124
+ nr2.should have(2).notification_rules
125
+ nr1.map(&:id).should == nr2.map(&:id)
116
126
  end
117
127
 
118
128
  it "adds a notification rule for a contact" do
@@ -121,7 +131,7 @@ describe Flapjack::Data::Contact, :redis => true do
121
131
 
122
132
  expect {
123
133
  contact.add_notification_rule(notification_rule_data)
124
- }.to change { contact.notification_rules.size }.from(0).to(1)
134
+ }.to change { contact.notification_rules.size }.from(1).to(2)
125
135
  end
126
136
 
127
137
  it "removes a notification rule from a contact" do
@@ -132,7 +142,38 @@ describe Flapjack::Data::Contact, :redis => true do
132
142
 
133
143
  expect {
134
144
  contact.delete_notification_rule(rule)
135
- }.to change { contact.notification_rules.size }.from(1).to(0)
145
+ }.to change { contact.notification_rules.size }.from(2).to(1)
146
+ end
147
+
148
+ it "creates a general notification rule for a pre-existing contact if none exists" do
149
+ contact = Flapjack::Data::Contact.find_by_id('363', :redis => @redis)
150
+
151
+ @redis.smembers("contact_notification_rules:363").each do |rule_id|
152
+ @redis.srem("contact_notification_rules:363", rule_id)
153
+ end
154
+ @redis.smembers("contact_notification_rules:363").should be_empty
155
+
156
+ rules = contact.notification_rules
157
+ rules.should have(1).rule
158
+ rule = rules.first
159
+ [:entities, :entity_tags, :time_restrictions,
160
+ :warning_media, :critical_media,
161
+ :warning_blackhole, :critical_blackhole].each do |k|
162
+ rule.send(k).should == general_notification_rule_data[k]
163
+ end
164
+ end
165
+
166
+ it "creates a general notification rule for a pre-existing contact if the existing general one was changed" do
167
+ contact = Flapjack::Data::Contact.find_by_id('363', :redis => @redis)
168
+ rules = contact.notification_rules
169
+ rules.should have(1).notification_rule
170
+ rule = rules.first
171
+
172
+ rule.update(notification_rule_data)
173
+
174
+ rules = contact.notification_rules
175
+ rules.should have(2).notification_rules
176
+ rules.select {|r| r.is_specific? }.should have(1).rule
136
177
  end
137
178
 
138
179
  it "deletes a contact by id, including linked entities, checks, tags and notification rules" do
@@ -6,20 +6,15 @@ describe Flapjack::Data::Message do
6
6
  let(:contact) { mock(Flapjack::Data::Contact) }
7
7
 
8
8
  it "assigns itself an ID" do
9
- message = Flapjack::Data::Message.for_contact(:contact => contact)
9
+ message = Flapjack::Data::Message.for_contact(contact)
10
10
  mid = message.id
11
11
  mid.should_not be_nil
12
12
  mid.should be_a(String)
13
13
  end
14
14
 
15
15
  it "returns its contained data" do
16
- notification = mock(Flapjack::Data::Notification)
17
- notification.should_receive(:contents).and_return('notification' => 'contents')
18
-
19
- message = Flapjack::Data::Message.for_contact(:contact => contact)
20
- message.notification = notification
21
- message.medium = 'email'
22
- message.address = 'jja@example.com'
16
+ message = Flapjack::Data::Message.for_contact(contact, :medium => 'email',
17
+ :address => 'jja@example.com', :notification_contents => {'notification' => 'contents'})
23
18
 
24
19
  contact.should_receive(:id).and_return('23')
25
20
  contact.should_receive(:first_name).and_return('James')
@@ -1,15 +1,18 @@
1
1
  require 'spec_helper'
2
2
  require 'flapjack/data/notification'
3
3
 
4
- describe Flapjack::Data::Notification, :redis => true do
4
+ describe Flapjack::Data::Notification, :redis => true, :logger => true do
5
5
 
6
- let(:event) { mock(Flapjack::Data::Event) }
6
+ let(:event) { mock(Flapjack::Data::Event) }
7
7
 
8
8
  let(:contact) { mock(Flapjack::Data::Contact) }
9
9
 
10
+ let(:timezone) { mock('timezone') }
11
+
10
12
  it "generates a notification for an event" do
11
13
  notification = Flapjack::Data::Notification.for_event(event, :type => 'problem',
12
- :max_notified_severity => nil)
14
+ :max_notified_severity => nil, :contacts => [contact],
15
+ :default_timezone => timezone, :logger => @logger)
13
16
  notification.should_not be_nil
14
17
  notification.event.should == event
15
18
  notification.type.should == 'problem'
@@ -17,47 +20,32 @@ describe Flapjack::Data::Notification, :redis => true do
17
20
 
18
21
  it "generates messages for contacts" do
19
22
  notification = Flapjack::Data::Notification.for_event(event, :type => 'problem',
20
- :max_notified_severity => nil)
23
+ :max_notified_severity => nil, :contacts => [contact],
24
+ :default_timezone => timezone, :logger => @logger)
25
+
26
+ contact.should_receive(:id).and_return('23')
27
+ contact.should_receive(:notification_rules).and_return([])
21
28
  contact.should_receive(:media).and_return('email' => 'example@example.com',
22
29
  'sms' => '0123456789')
23
30
 
24
- messages = notification.messages(:contacts => [contact])
31
+ event.should_receive(:id).and_return('abc-123.com:ping')
32
+ event.should_receive(:state).and_return('critical')
33
+ event.should_receive(:summary).and_return('Shiny & happy')
34
+ event.should_receive(:details).and_return('Really Shiny & happy')
35
+ event.should_receive(:time).and_return(Time.now.to_i)
36
+ event.should_receive(:duration).and_return(nil)
37
+
38
+ messages = notification.messages
25
39
  messages.should_not be_nil
26
40
  messages.should have(2).items
27
41
 
28
- messages.first.notification.should == notification
29
42
  messages.first.contact.should == contact
30
43
  messages.first.medium.should == 'email'
31
44
  messages.first.address.should == 'example@example.com'
32
45
 
33
- messages.last.notification.should == notification
34
46
  messages.last.contact.should == contact
35
47
  messages.last.medium.should == 'sms'
36
48
  messages.last.address.should == '0123456789'
37
49
  end
38
50
 
39
- it "returns its contained data" do
40
- notification = Flapjack::Data::Notification.for_event(event, :type => 'problem',
41
- :max_notified_severity => nil)
42
-
43
- t = Time.now.to_i
44
-
45
- event.should_receive(:id).and_return('example.com:ping')
46
- event.should_receive(:state).and_return('ok')
47
- event.should_receive(:summary).and_return('Shiny & happy')
48
- event.should_receive(:details).and_return('Really Shiny & happy')
49
- event.should_receive(:time).and_return(t)
50
- event.should_receive(:duration).and_return(nil)
51
-
52
- notification.contents.should == {'event_id' => 'example.com:ping',
53
- 'state' => 'ok',
54
- 'summary' => 'Shiny & happy',
55
- 'details' => 'Really Shiny & happy',
56
- 'time' => t,
57
- 'duration' => nil,
58
- 'notification_type' => 'problem',
59
- 'max_notified_severity' => nil}
60
-
61
- end
62
-
63
51
  end
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.11
4
+ version: 0.7.12
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-06-11 00:00:00.000000000 Z
14
+ date: 2013-06-12 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: -3678389315543447731
556
+ hash: -3589199594353387110
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: -3678389315543447731
565
+ hash: -3589199594353387110
566
566
  requirements: []
567
567
  rubyforge_project:
568
568
  rubygems_version: 1.8.23