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.
- data/CHANGELOG.md +5 -0
- data/features/notification_rules.feature +38 -2
- data/features/steps/events_steps.rb +7 -0
- data/lib/flapjack/data/contact.rb +24 -6
- data/lib/flapjack/data/entity_check.rb +3 -0
- data/lib/flapjack/data/message.rb +16 -10
- data/lib/flapjack/data/notification.rb +115 -24
- data/lib/flapjack/data/notification_rule.rb +5 -0
- data/lib/flapjack/executive.rb +24 -147
- data/lib/flapjack/version.rb +1 -1
- data/spec/lib/flapjack/data/contact_spec.rb +54 -13
- data/spec/lib/flapjack/data/message_spec.rb +3 -8
- data/spec/lib/flapjack/data/notification_spec.rb +19 -31
- metadata +4 -4
data/CHANGELOG.md
CHANGED
@@ -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}").
|
166
|
-
|
167
|
-
|
168
|
-
|
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
|
-
|
14
|
+
attr_reader :medium, :address, :duration, :contact
|
15
15
|
|
16
|
-
def self.for_contact(opts = {})
|
17
|
-
self.new(: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
|
-
|
33
|
-
|
34
|
-
|
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
|
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
|
-
|
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
|
20
|
-
contacts
|
21
|
-
|
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
|
-
|
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
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
data/lib/flapjack/executive.rb
CHANGED
@@ -279,171 +279,49 @@ module Flapjack
|
|
279
279
|
end
|
280
280
|
|
281
281
|
notification = Flapjack::Data::Notification.for_event(
|
282
|
-
event, :type => notification_type,
|
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
|
-
|
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
|
-
|
291
|
+
address = message.address
|
292
|
+
event_id = event.id
|
420
293
|
|
421
294
|
@notifylog.info("#{Time.now.to_s} | #{event_id} | " +
|
422
|
-
"#{
|
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 #{
|
302
|
+
@logger.info("Enqueueing #{media_type} alert for #{event_id} to #{address}")
|
430
303
|
|
431
|
-
if
|
304
|
+
if event.ok?
|
432
305
|
message.contact.update_sent_alert_keys(
|
433
|
-
:media =>
|
434
|
-
:check =>
|
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 =>
|
439
|
-
:check =>
|
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 =>
|
445
|
-
:check =>
|
446
|
-
: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
|
data/lib/flapjack/version.rb
CHANGED
@@ -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(
|
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
|
-
|
87
|
-
nr.should_not
|
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
|
-
|
105
|
-
|
106
|
-
|
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
|
-
|
114
|
-
|
115
|
-
|
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(
|
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(
|
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(
|
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
|
-
|
17
|
-
|
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)
|
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
|
-
|
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.
|
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-
|
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: -
|
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: -
|
565
|
+
hash: -3589199594353387110
|
566
566
|
requirements: []
|
567
567
|
rubyforge_project:
|
568
568
|
rubygems_version: 1.8.23
|