flapjack 0.7.11 → 0.7.12

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.
@@ -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