flapjack 0.7.18 → 0.7.19
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +7 -0
- data/bin/flapjack +3 -0
- data/bin/flapjack-nagios-receiver +4 -1
- data/bin/flapjack-netsaint-parser +2 -1
- data/bin/flapjack-populator +6 -3
- data/bin/receive-events +3 -1
- data/bin/simulate-failed-check +2 -1
- data/etc/flapjack_config.yaml.example +20 -0
- data/features/events.feature +1 -1
- data/features/events_check_names.feature +1 -1
- data/features/notification_rules.feature +1 -1
- data/features/notifications.feature +1 -1
- data/features/steps/events_steps.rb +18 -17
- data/features/steps/flapjack-netsaint-parser_steps.rb +1 -2
- data/features/steps/notifications_steps.rb +14 -1
- data/features/support/env.rb +27 -10
- data/flapjack.gemspec +1 -3
- data/lib/flapjack/coordinator.rb +30 -20
- data/lib/flapjack/data/contact.rb +3 -2
- data/lib/flapjack/data/entity.rb +3 -3
- data/lib/flapjack/data/entity_check.rb +116 -43
- data/lib/flapjack/data/event.rb +10 -10
- data/lib/flapjack/data/message.rb +3 -6
- data/lib/flapjack/data/notification.rb +122 -57
- data/lib/flapjack/data/notification_rule.rb +11 -11
- data/lib/flapjack/filters/acknowledgement.rb +2 -2
- data/lib/flapjack/filters/ok.rb +1 -1
- data/lib/flapjack/gateways/api/entity_check_presenter.rb +1 -0
- data/lib/flapjack/gateways/api/entity_methods.rb +4 -6
- data/lib/flapjack/gateways/api/rack/json_params_parser.rb +1 -1
- data/lib/flapjack/gateways/email.rb +3 -5
- data/lib/flapjack/gateways/email/{alert.html.haml → alert.html.erb} +0 -0
- data/lib/flapjack/gateways/jabber.rb +66 -35
- data/lib/flapjack/gateways/oobetet.rb +5 -7
- data/lib/flapjack/gateways/pagerduty.rb +7 -7
- data/lib/flapjack/gateways/web.rb +101 -41
- data/lib/flapjack/gateways/web/public/css/flapjack.css +1 -1
- data/lib/flapjack/gateways/web/views/{_css.haml → _css.html.erb} +2 -1
- data/lib/flapjack/gateways/web/views/_foot.html.erb +3 -0
- data/lib/flapjack/gateways/web/views/_head.html.erb +4 -0
- data/lib/flapjack/gateways/web/views/_nav.html.erb +9 -0
- data/lib/flapjack/gateways/web/views/check.html.erb +204 -0
- data/lib/flapjack/gateways/web/views/checks.html.erb +77 -0
- data/lib/flapjack/gateways/web/views/contact.html.erb +114 -0
- data/lib/flapjack/gateways/web/views/contacts.html.erb +42 -0
- data/lib/flapjack/gateways/web/views/entities.html.erb +39 -0
- data/lib/flapjack/gateways/web/views/entity.html.erb +67 -0
- data/lib/flapjack/gateways/web/views/index.html.erb +27 -0
- data/lib/flapjack/gateways/web/views/self_stats.html.erb +97 -0
- data/lib/flapjack/logger.rb +71 -23
- data/lib/flapjack/notifier.rb +157 -0
- data/lib/flapjack/patches.rb +1 -41
- data/lib/flapjack/pikelet.rb +4 -2
- data/lib/flapjack/{executive.rb → processor.rb} +32 -145
- data/lib/flapjack/version.rb +1 -1
- data/spec/lib/flapjack/coordinator_spec.rb +134 -71
- data/spec/lib/flapjack/data/contact_spec.rb +1 -0
- data/spec/lib/flapjack/data/entity_check_spec.rb +146 -30
- data/spec/lib/flapjack/data/entity_spec.rb +4 -4
- data/spec/lib/flapjack/data/event_spec.rb +4 -4
- data/spec/lib/flapjack/data/message_spec.rb +2 -3
- data/spec/lib/flapjack/data/notification_spec.rb +13 -19
- data/spec/lib/flapjack/gateways/api/entity_methods_spec.rb +2 -2
- data/spec/lib/flapjack/gateways/jabber_spec.rb +34 -0
- data/spec/lib/flapjack/gateways/pagerduty_spec.rb +0 -2
- data/spec/lib/flapjack/gateways/web/views/{check.haml_spec.rb → check.html.erb_spec.rb} +2 -2
- data/spec/lib/flapjack/gateways/web/views/{contact.haml_spec.rb → contact.html.erb_spec.rb} +3 -3
- data/spec/lib/flapjack/gateways/web/views/index.html.erb_spec.rb +14 -0
- data/spec/lib/flapjack/gateways/web_spec.rb +20 -8
- data/spec/lib/flapjack/logger_spec.rb +30 -28
- data/spec/lib/flapjack/notifier_spec.rb +6 -0
- data/spec/lib/flapjack/pikelet_spec.rb +8 -8
- data/spec/lib/flapjack/{executive_spec.rb → processor_spec.rb} +4 -4
- data/spec/spec_helper.rb +1 -13
- data/spec/support/erb_view_helper.rb +23 -0
- data/tasks/profile.rake +1 -1
- data/tmp/acknowledge.rb +3 -1
- data/tmp/create_event_ok.rb +3 -1
- data/tmp/create_event_unknown.rb +3 -1
- data/tmp/create_events_failure.rb +3 -1
- data/tmp/create_events_ok.rb +3 -1
- data/tmp/create_events_ok_fail_ack_ok.rb +3 -1
- data/tmp/create_events_ok_failure.rb +3 -1
- data/tmp/create_events_ok_failure_ack.rb +3 -1
- data/tmp/test_json_post.rb +5 -3
- data/tmp/test_notification_rules_api.rb +5 -3
- metadata +32 -61
- data/lib/flapjack/gateways/web/views/_foot.haml +0 -8
- data/lib/flapjack/gateways/web/views/_head.haml +0 -10
- data/lib/flapjack/gateways/web/views/_nav.haml +0 -14
- data/lib/flapjack/gateways/web/views/check.haml +0 -191
- data/lib/flapjack/gateways/web/views/checks.haml +0 -49
- data/lib/flapjack/gateways/web/views/contact.haml +0 -85
- data/lib/flapjack/gateways/web/views/contacts.haml +0 -30
- data/lib/flapjack/gateways/web/views/entities.haml +0 -28
- data/lib/flapjack/gateways/web/views/entity.haml +0 -50
- data/lib/flapjack/gateways/web/views/index.haml +0 -32
- data/lib/flapjack/gateways/web/views/self_stats.haml +0 -70
- data/spec/lib/flapjack/gateways/web/views/index.haml_spec.rb +0 -13
- data/spec/support/haml_view_helper.rb +0 -15
@@ -235,10 +235,11 @@ module Flapjack
|
|
235
235
|
end
|
236
236
|
|
237
237
|
# drop notifications for
|
238
|
-
def drop_notifications?(opts)
|
238
|
+
def drop_notifications?(opts = {})
|
239
239
|
media = opts[:media]
|
240
240
|
check = opts[:check]
|
241
241
|
state = opts[:state]
|
242
|
+
|
242
243
|
# build it and they will come
|
243
244
|
@redis.exists("drop_alerts_for_contact:#{self.id}") ||
|
244
245
|
(media && @redis.exists("drop_alerts_for_contact:#{self.id}:#{media}")) ||
|
@@ -248,7 +249,7 @@ module Flapjack
|
|
248
249
|
@redis.exists("drop_alerts_for_contact:#{self.id}:#{media}:#{check}:#{state}"))
|
249
250
|
end
|
250
251
|
|
251
|
-
def update_sent_alert_keys(opts)
|
252
|
+
def update_sent_alert_keys(opts = {})
|
252
253
|
media = opts[:media]
|
253
254
|
check = opts[:check]
|
254
255
|
state = opts[:state]
|
data/lib/flapjack/data/entity.rb
CHANGED
@@ -97,12 +97,12 @@ module Flapjack
|
|
97
97
|
|
98
98
|
def self.find_all_with_checks(options)
|
99
99
|
raise "Redis connection not set" unless redis = options[:redis]
|
100
|
-
redis.
|
100
|
+
redis.zrange("current_entities", 0, -1)
|
101
101
|
end
|
102
102
|
|
103
103
|
def self.find_all_with_failing_checks(options)
|
104
104
|
raise "Redis connection not set" unless redis = options[:redis]
|
105
|
-
|
105
|
+
Flapjack::Data::EntityCheck.find_all_failing_by_entity(:redis => redis).keys
|
106
106
|
end
|
107
107
|
|
108
108
|
def contacts
|
@@ -119,7 +119,7 @@ module Flapjack
|
|
119
119
|
end
|
120
120
|
|
121
121
|
def check_list
|
122
|
-
@redis.
|
122
|
+
@redis.zrange("current_checks:#{@name}", 0, -1)
|
123
123
|
end
|
124
124
|
|
125
125
|
def check_count
|
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'oj'
|
4
4
|
|
5
5
|
require 'flapjack/patches'
|
6
6
|
|
@@ -53,6 +53,67 @@ module Flapjack
|
|
53
53
|
self.new(entity, check, :redis => redis)
|
54
54
|
end
|
55
55
|
|
56
|
+
def self.find_all_for_entity_name(entity_name, options = {})
|
57
|
+
raise "Redis connection not set" unless redis = options[:redis]
|
58
|
+
redis.zrange("current_checks:#{entity_name}", 0, -1)
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.find_all(options = {})
|
62
|
+
raise "Redis connection not set" unless redis = options[:redis]
|
63
|
+
self.conflate_to_keys(self.find_all_by_entity(:redis => redis))
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.find_all_by_entity(options = {})
|
67
|
+
raise "Redis connection not set" unless redis = options[:redis]
|
68
|
+
d = {}
|
69
|
+
redis.zrange("current_entities", 0, -1).each {|entity|
|
70
|
+
d[entity] = redis.zrange("current_checks:#{entity}", 0, -1)
|
71
|
+
}
|
72
|
+
d
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.count_all(options = {})
|
76
|
+
raise "Redis connection not set" unless redis = options[:redis]
|
77
|
+
redis.zrange("current_entities", 0, -1).inject(0) {|memo, entity|
|
78
|
+
memo + redis.zcount("current_checks:#{entity}", '-inf', '+inf')
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.find_all_failing(options = {})
|
83
|
+
raise "Redis connection not set" unless redis = options[:redis]
|
84
|
+
self.conflate_to_keys(self.find_all_failing_by_entity(:redis => redis))
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.find_all_failing_by_entity(options = {})
|
88
|
+
raise "Redis connection not set" unless redis = options[:redis]
|
89
|
+
redis.zrange("failed_checks", 0, -1).inject({}) do |memo, key|
|
90
|
+
entity, check = key.split(':', 2)
|
91
|
+
if !!redis.zscore("current_checks:#{entity}", check)
|
92
|
+
memo[entity] ||= []
|
93
|
+
memo[entity] << check
|
94
|
+
end
|
95
|
+
memo
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.count_all_failing(options = {})
|
100
|
+
raise "Redis connection not set" unless redis = options[:redis]
|
101
|
+
redis.zrange("failed_checks", 0, -1).count do |key|
|
102
|
+
entity, check = key.split(':', 2)
|
103
|
+
!!redis.zscore("current_checks:#{entity}", check)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.conflate_to_keys(entity_checks_hash)
|
108
|
+
result = []
|
109
|
+
entity_checks_hash.each {|entity, checks|
|
110
|
+
checks.each {|check|
|
111
|
+
result << "#{entity}:#{check}"
|
112
|
+
}
|
113
|
+
}
|
114
|
+
result
|
115
|
+
end
|
116
|
+
|
56
117
|
def entity_name
|
57
118
|
entity.name
|
58
119
|
end
|
@@ -69,7 +130,7 @@ module Flapjack
|
|
69
130
|
end
|
70
131
|
|
71
132
|
# return data about current maintenance (scheduled or unscheduled, as specified)
|
72
|
-
def current_maintenance(opts)
|
133
|
+
def current_maintenance(opts = {})
|
73
134
|
sched = opts[:scheduled] ? 'scheduled' : 'unscheduled'
|
74
135
|
ts = @redis.get("#{@key}:#{sched}_maintenance")
|
75
136
|
return unless ts
|
@@ -79,14 +140,14 @@ module Flapjack
|
|
79
140
|
}
|
80
141
|
end
|
81
142
|
|
82
|
-
def create_unscheduled_maintenance(opts = {})
|
83
|
-
|
143
|
+
def create_unscheduled_maintenance(start_time, duration, opts = {})
|
144
|
+
raise ArgumentError, 'start time must be provided as a Unix timestamp' unless start_time && start_time.is_a?(Integer)
|
145
|
+
raise ArgumentError, 'duration in seconds must be provided' unless duration && duration.is_a?(Integer) && (duration > 0)
|
84
146
|
|
85
|
-
start_time = opts[:start_time] # unix timestamp
|
86
|
-
duration = opts[:duration] # seconds
|
87
147
|
summary = opts[:summary]
|
88
148
|
time_remaining = (start_time + duration) - Time.now.to_i
|
89
149
|
if time_remaining > 0
|
150
|
+
end_unscheduled_maintenance(start_time) if in_unscheduled_maintenance?
|
90
151
|
@redis.setex("#{@key}:unscheduled_maintenance", time_remaining, start_time)
|
91
152
|
end
|
92
153
|
@redis.zadd("#{@key}:unscheduled_maintenances", duration, start_time)
|
@@ -96,19 +157,14 @@ module Flapjack
|
|
96
157
|
end
|
97
158
|
|
98
159
|
# ends any unscheduled maintenance
|
99
|
-
def end_unscheduled_maintenance(
|
100
|
-
|
101
|
-
:end_time => Time.now.to_i
|
102
|
-
}
|
103
|
-
options = defaults.merge(opts)
|
104
|
-
end_time = options[:end_time]
|
160
|
+
def end_unscheduled_maintenance(end_time)
|
161
|
+
raise ArgumentError, 'end time must be provided as a Unix timestamp' unless end_time && end_time.is_a?(Integer)
|
105
162
|
|
106
163
|
if (um_start = @redis.get("#{@key}:unscheduled_maintenance"))
|
107
164
|
duration = end_time - um_start.to_i
|
108
165
|
@logger.debug("ending unscheduled downtime for #{@key} at #{Time.at(end_time).to_s}") if @logger
|
109
166
|
@redis.del("#{@key}:unscheduled_maintenance")
|
110
|
-
@redis.zadd("#{@key}:unscheduled_maintenances", duration, um_start)
|
111
|
-
@redis.zadd("#{@key}:sorted_unscheduled_maintenance_timestamps", um_start, um_start)
|
167
|
+
@redis.zadd("#{@key}:unscheduled_maintenances", duration, um_start) # updates existing UM 'score'
|
112
168
|
else
|
113
169
|
@logger.debug("end_unscheduled_maintenance called for #{@key} but none found") if @logger
|
114
170
|
end
|
@@ -118,10 +174,11 @@ module Flapjack
|
|
118
174
|
# TODO: consider adding some validation to the data we're adding in here
|
119
175
|
# eg start_time is a believable unix timestamp (not in the past and not too
|
120
176
|
# far in the future), duration is within some bounds...
|
121
|
-
def create_scheduled_maintenance(opts = {})
|
122
|
-
|
123
|
-
duration
|
124
|
-
|
177
|
+
def create_scheduled_maintenance(start_time, duration, opts = {})
|
178
|
+
raise ArgumentError, 'start time must be provided as a Unix timestamp' unless start_time && start_time.is_a?(Integer)
|
179
|
+
raise ArgumentError, 'duration in seconds must be provided' unless duration && duration.is_a?(Integer) && (duration > 0)
|
180
|
+
|
181
|
+
summary = opts[:summary]
|
125
182
|
@redis.zadd("#{@key}:scheduled_maintenances", duration, start_time)
|
126
183
|
@redis.set("#{@key}:#{start_time}:scheduled_maintenance:summary", summary)
|
127
184
|
|
@@ -131,6 +188,30 @@ module Flapjack
|
|
131
188
|
update_current_scheduled_maintenance(:revalidate => true)
|
132
189
|
end
|
133
190
|
|
191
|
+
# if not in scheduled maintenance, looks in scheduled maintenance list for a check to see if
|
192
|
+
# current state should be set to scheduled maintenance, and sets it as appropriate
|
193
|
+
def update_current_scheduled_maintenance(opts = {})
|
194
|
+
if opts[:revalidate]
|
195
|
+
@redis.del("#{@key}:scheduled_maintenance")
|
196
|
+
else
|
197
|
+
return if in_scheduled_maintenance?
|
198
|
+
end
|
199
|
+
|
200
|
+
# are we within a scheduled maintenance period?
|
201
|
+
current_time = Time.now.to_i
|
202
|
+
current_sched_ms = maintenances(nil, nil, :scheduled => true).select {|sm|
|
203
|
+
(sm[:start_time] <= current_time) && (current_time < sm[:end_time])
|
204
|
+
}
|
205
|
+
return if current_sched_ms.empty?
|
206
|
+
|
207
|
+
# yes! so set current scheduled maintenance
|
208
|
+
# if multiple scheduled maintenances found, find the end_time furthest in the future
|
209
|
+
most_futuristic = current_sched_ms.max {|sm| sm[:end_time] }
|
210
|
+
start_time = most_futuristic[:start_time]
|
211
|
+
duration = most_futuristic[:duration]
|
212
|
+
@redis.setex("#{@key}:scheduled_maintenance", duration.to_i, start_time)
|
213
|
+
end
|
214
|
+
|
134
215
|
# TODO allow summary to be changed as part of the termination
|
135
216
|
def end_scheduled_maintenance(start_time)
|
136
217
|
raise ArgumentError, 'start time must be supplied as a Unix timestamp' unless start_time && start_time.is_a?(Integer)
|
@@ -162,32 +243,8 @@ module Flapjack
|
|
162
243
|
|
163
244
|
return true
|
164
245
|
end
|
165
|
-
|
166
|
-
false
|
167
|
-
end
|
168
246
|
|
169
|
-
|
170
|
-
# current state should be set to scheduled maintenance, and sets it as appropriate
|
171
|
-
def update_current_scheduled_maintenance(opts = {})
|
172
|
-
if opts[:revalidate]
|
173
|
-
@redis.del("#{@key}:scheduled_maintenance")
|
174
|
-
else
|
175
|
-
return if in_scheduled_maintenance?
|
176
|
-
end
|
177
|
-
|
178
|
-
# are we within a scheduled maintenance period?
|
179
|
-
current_time = Time.now.to_i
|
180
|
-
current_sched_ms = maintenances(nil, nil, :scheduled => true).select {|sm|
|
181
|
-
(sm[:start_time] <= current_time) && (current_time < sm[:end_time])
|
182
|
-
}
|
183
|
-
return if current_sched_ms.empty?
|
184
|
-
|
185
|
-
# yes! so set current scheduled maintenance
|
186
|
-
# if multiple scheduled maintenances found, find the end_time furthest in the future
|
187
|
-
most_futuristic = current_sched_ms.max {|sm| sm[:end_time] }
|
188
|
-
start_time = most_futuristic[:start_time]
|
189
|
-
duration = most_futuristic[:duration]
|
190
|
-
@redis.setex("#{@key}:scheduled_maintenance", duration.to_i, start_time)
|
247
|
+
false
|
191
248
|
end
|
192
249
|
|
193
250
|
# returns nil if no previous state; this must be considered as a possible
|
@@ -248,6 +305,22 @@ module Flapjack
|
|
248
305
|
|
249
306
|
def last_update=(timestamp)
|
250
307
|
@redis.hset("check:#{@key}", 'last_update', timestamp)
|
308
|
+
@redis.zadd("current_checks:#{entity.name}", timestamp, check)
|
309
|
+
@redis.zadd("current_entities", timestamp, entity.name)
|
310
|
+
end
|
311
|
+
|
312
|
+
# disables a check (removes currency)
|
313
|
+
def disable!
|
314
|
+
@logger.debug("disabling check [#{@key}]") if @logger
|
315
|
+
@redis.zrem("current_checks:#{entity.name}", check)
|
316
|
+
if @redis.zcount("current_checks:#{entity.name}", '-inf', '+inf') == 0
|
317
|
+
@redis.zrem("current_checks:#{entity.name}", check)
|
318
|
+
@redis.zrem("current_entities", entity.name)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def enabled?
|
323
|
+
!! @redis.zscore("current_checks:#{entity.name}", check)
|
251
324
|
end
|
252
325
|
|
253
326
|
def last_change
|
data/lib/flapjack/data/event.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'oj'
|
4
4
|
|
5
5
|
module Flapjack
|
6
6
|
module Data
|
7
7
|
class Event
|
8
8
|
|
9
|
-
attr_accessor :previous_state, :previous_state_duration
|
9
|
+
attr_accessor :counter, :previous_state, :previous_state_duration
|
10
10
|
|
11
11
|
attr_reader :check, :summary, :details, :acknowledgement_id
|
12
12
|
|
@@ -20,7 +20,7 @@ module Flapjack
|
|
20
20
|
# Calling next with :block => false, will return a nil if there are no
|
21
21
|
# events on the queue.
|
22
22
|
#
|
23
|
-
def self.next(opts={})
|
23
|
+
def self.next(queue, opts = {})
|
24
24
|
raise "Redis connection not set" unless redis = opts[:redis]
|
25
25
|
|
26
26
|
defaults = { :block => true,
|
@@ -31,23 +31,23 @@ module Flapjack
|
|
31
31
|
if options[:archive_events]
|
32
32
|
dest = "events_archive:#{Time.now.utc.strftime "%Y%m%d%H"}"
|
33
33
|
if options[:block]
|
34
|
-
raw = redis.brpoplpush(
|
34
|
+
raw = redis.brpoplpush(queue, dest, 0)
|
35
35
|
else
|
36
|
-
raw = redis.rpoplpush(
|
36
|
+
raw = redis.rpoplpush(queue, dest)
|
37
37
|
return unless raw
|
38
38
|
end
|
39
39
|
redis.expire(dest, options[:events_archive_maxage])
|
40
40
|
else
|
41
41
|
if options[:block]
|
42
|
-
raw = redis.brpop(
|
42
|
+
raw = redis.brpop(queue, 0)[1]
|
43
43
|
else
|
44
|
-
raw = redis.rpop(
|
44
|
+
raw = redis.rpop(queue)
|
45
45
|
return unless raw
|
46
46
|
end
|
47
47
|
end
|
48
48
|
begin
|
49
|
-
parsed = ::
|
50
|
-
rescue => e
|
49
|
+
parsed = ::Oj.load( raw )
|
50
|
+
rescue Oj::Error => e
|
51
51
|
if options[:logger]
|
52
52
|
options[:logger].warn("Error deserialising event json: #{e}, raw json: #{raw.inspect}")
|
53
53
|
end
|
@@ -65,7 +65,7 @@ module Flapjack
|
|
65
65
|
raise "Redis connection not set" unless redis = opts[:redis]
|
66
66
|
|
67
67
|
evt['time'] = Time.now.to_i if evt['time'].nil?
|
68
|
-
redis.rpush('events', ::
|
68
|
+
redis.rpush('events', ::Oj.dump(evt))
|
69
69
|
end
|
70
70
|
|
71
71
|
# Provide a count of the number of events on the queue to be processed.
|
@@ -5,7 +5,6 @@
|
|
5
5
|
# contact+media recipient.
|
6
6
|
|
7
7
|
require 'flapjack/data/contact'
|
8
|
-
require 'flapjack/data/notification'
|
9
8
|
|
10
9
|
module Flapjack
|
11
10
|
module Data
|
@@ -15,8 +14,8 @@ module Flapjack
|
|
15
14
|
|
16
15
|
def self.for_contact(contact, opts = {})
|
17
16
|
self.new(:contact => contact,
|
18
|
-
:
|
19
|
-
:
|
17
|
+
:medium => opts[:medium],
|
18
|
+
:address => opts[:address],
|
20
19
|
:duration => opts[:duration])
|
21
20
|
end
|
22
21
|
|
@@ -36,15 +35,13 @@ module Flapjack
|
|
36
35
|
'contact_first_name' => contact.first_name,
|
37
36
|
'contact_last_name' => contact.last_name}
|
38
37
|
c['duration'] = duration if duration
|
39
|
-
|
40
|
-
c.merge(@notification_contents)
|
38
|
+
c
|
41
39
|
end
|
42
40
|
|
43
41
|
private
|
44
42
|
|
45
43
|
def initialize(opts = {})
|
46
44
|
@contact = opts[:contact]
|
47
|
-
@notification_contents = opts[:notification_contents]
|
48
45
|
@medium = opts[:medium]
|
49
46
|
@address = opts[:address]
|
50
47
|
@duration = opts[:duration]
|
@@ -1,5 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
require 'oj'
|
4
|
+
|
3
5
|
require 'flapjack/data/contact'
|
4
6
|
require 'flapjack/data/event'
|
5
7
|
require 'flapjack/data/message'
|
@@ -8,52 +10,110 @@ module Flapjack
|
|
8
10
|
module Data
|
9
11
|
class Notification
|
10
12
|
|
11
|
-
attr_reader :
|
12
|
-
:default_timezone, :last_state
|
13
|
-
|
14
|
-
def self.for_event(event, opts = {})
|
15
|
-
self.new(:event => event,
|
16
|
-
:type => opts[:type],
|
17
|
-
:max_notified_severity => opts[:max_notified_severity],
|
18
|
-
:contacts => opts[:contacts],
|
19
|
-
:default_timezone => opts[:default_timezone],
|
20
|
-
:last_state => opts[:last_state],
|
21
|
-
:logger => opts[:logger])
|
22
|
-
end
|
23
|
-
|
24
|
-
def messages
|
25
|
-
return [] if contacts.nil? || contacts.empty?
|
13
|
+
attr_reader :type, :event_id, :event_state, :event_count
|
26
14
|
|
27
|
-
|
28
|
-
|
15
|
+
def self.type_for_event(event)
|
16
|
+
case event.type
|
17
|
+
when 'service'
|
18
|
+
case event.state
|
19
|
+
when 'ok'
|
20
|
+
'recovery'
|
21
|
+
when 'warning', 'critical', 'unknown'
|
22
|
+
'problem'
|
23
|
+
end
|
24
|
+
when 'action'
|
25
|
+
case event.state
|
26
|
+
when 'acknowledgement'
|
27
|
+
'acknowledgement'
|
28
|
+
when 'test_notifications'
|
29
|
+
'test'
|
30
|
+
end
|
31
|
+
else
|
32
|
+
'unknown'
|
33
|
+
end
|
34
|
+
end
|
29
35
|
|
30
|
-
|
36
|
+
def self.severity_for_event(event, max_notified_severity)
|
37
|
+
if ([event.state, max_notified_severity] & ['critical', 'unknown', 'test_notifications']).any?
|
31
38
|
'critical'
|
32
|
-
elsif [
|
39
|
+
elsif [event.state, max_notified_severity].include?('warning')
|
33
40
|
'warning'
|
34
41
|
else
|
35
42
|
'ok'
|
36
43
|
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.add(queue, event, opts = {})
|
47
|
+
raise "Redis connection not set" unless redis = opts[:redis]
|
48
|
+
|
49
|
+
last_state = opts[:last_state] || {}
|
50
|
+
|
51
|
+
notif = {'event_id' => event.id,
|
52
|
+
'state' => event.state,
|
53
|
+
'summary' => event.summary,
|
54
|
+
'last_state' => last_state[:state],
|
55
|
+
'last_summary' => last_state[:summary],
|
56
|
+
'details' => event.details,
|
57
|
+
'time' => event.time,
|
58
|
+
'duration' => event.duration || nil,
|
59
|
+
'type' => opts[:type] || type_for_event(event),
|
60
|
+
'severity' => opts[:severity],
|
61
|
+
'count' => event.counter }
|
62
|
+
|
63
|
+
redis.rpush(queue, Oj.dump(notif))
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.next(queue, opts = {})
|
67
|
+
raise "Redis connection not set" unless redis = opts[:redis]
|
68
|
+
|
69
|
+
defaults = { :block => true }
|
70
|
+
options = defaults.merge(opts)
|
71
|
+
|
72
|
+
if options[:block]
|
73
|
+
raw = redis.blpop(queue, 0)[1]
|
74
|
+
else
|
75
|
+
raw = redis.lpop(queue)
|
76
|
+
return unless raw
|
77
|
+
end
|
78
|
+
begin
|
79
|
+
parsed = ::Oj.load( raw )
|
80
|
+
rescue Oj::Error => e
|
81
|
+
if options[:logger]
|
82
|
+
options[:logger].warn("Error deserialising notification json: #{e}, raw json: #{raw.inspect}")
|
83
|
+
end
|
84
|
+
return nil
|
85
|
+
end
|
86
|
+
self.new( parsed )
|
87
|
+
end
|
88
|
+
|
89
|
+
def contents
|
90
|
+
@contents ||= {'event_id' => @event_id,
|
91
|
+
'state' => @event_state,
|
92
|
+
'summary' => @event_summary,
|
93
|
+
'last_state' => @last_event_state,
|
94
|
+
'last_summary' => @last_event_summary,
|
95
|
+
'details' => @event_details,
|
96
|
+
'time' => @event_time,
|
97
|
+
'duration' => @event_duration,
|
98
|
+
'notification_type' => @type,
|
99
|
+
'event_count' => @event_count
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
def messages(contacts, opts = {})
|
104
|
+
return [] if contacts.nil? || contacts.empty?
|
37
105
|
|
38
|
-
|
39
|
-
|
40
|
-
'summary' => event.summary,
|
41
|
-
'last_state' => @last_state ? @last_state[:state] : nil,
|
42
|
-
'last_summary' => @last_state ? @last_state[:summary] : nil,
|
43
|
-
'details' => event.details,
|
44
|
-
'time' => event.time,
|
45
|
-
'duration' => event.duration || nil,
|
46
|
-
'notification_type' => type,
|
47
|
-
'max_notified_severity' => max_notified_severity }
|
106
|
+
default_timezone = opts[:default_timezone]
|
107
|
+
logger = opts[:logger]
|
48
108
|
|
49
109
|
@messages ||= contacts.collect {|contact|
|
50
110
|
contact_id = contact.id
|
51
111
|
rules = contact.notification_rules
|
52
112
|
media = contact.media
|
53
113
|
|
54
|
-
|
114
|
+
logger.debug "considering messages for contact id #{contact_id} #{@event_id} #{@event_state} (media) #{media.inspect}"
|
55
115
|
rlen = rules.length
|
56
|
-
|
116
|
+
logger.debug "found #{rlen} rule#{(rlen == 1) ? '' : 's'} for contact"
|
57
117
|
|
58
118
|
media_to_use = if rules.empty?
|
59
119
|
media
|
@@ -61,55 +121,54 @@ module Flapjack
|
|
61
121
|
# matchers are rules of the contact that have matched the current event
|
62
122
|
# for time and entity
|
63
123
|
matchers = rules.select do |rule|
|
64
|
-
rule.match_entity?(event_id) &&
|
124
|
+
rule.match_entity?(@event_id) &&
|
65
125
|
rule_occurring_now?(rule, :contact => contact, :default_timezone => default_timezone)
|
66
126
|
end
|
67
127
|
|
68
|
-
|
128
|
+
logger.debug "#{matchers.length} matchers remain for this contact:"
|
69
129
|
matchers.each do |matcher|
|
70
|
-
|
130
|
+
logger.debug "matcher: #{matcher.to_json}"
|
71
131
|
end
|
72
132
|
|
73
133
|
# delete any matchers for all entities if there are more specific matchers
|
74
134
|
if matchers.any? {|matcher| matcher.is_specific? }
|
75
135
|
|
76
|
-
|
136
|
+
logger.debug("general removal: found #{matchers.length} entity specific matchers")
|
77
137
|
num_matchers = matchers.length
|
78
138
|
|
79
139
|
matchers.reject! {|matcher| !matcher.is_specific? }
|
80
140
|
|
81
141
|
if num_matchers != matchers.length
|
82
|
-
|
142
|
+
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}")
|
83
143
|
end
|
84
144
|
end
|
85
145
|
|
86
146
|
# delete media based on blackholes
|
87
|
-
next if matchers.any? {|matcher| matcher.blackhole?(event_state) }
|
147
|
+
next if matchers.any? {|matcher| matcher.blackhole?(@event_state) }
|
88
148
|
|
89
|
-
|
149
|
+
logger.debug "notification: num matchers after removing blackhole matchers: #{matchers.size}"
|
90
150
|
|
91
151
|
rule_media = matchers.collect{|matcher|
|
92
|
-
matcher.media_for_severity(severity)
|
152
|
+
matcher.media_for_severity(@severity)
|
93
153
|
}.flatten.uniq
|
94
154
|
|
95
|
-
|
96
|
-
rule_media = rule_media.
|
155
|
+
logger.debug "notification: collected media_for_severity(#{@severity}): #{rule_media}"
|
156
|
+
rule_media = rule_media.reject {|medium|
|
97
157
|
contact.drop_notifications?(:media => medium,
|
98
|
-
:check => event_id,
|
99
|
-
:state => event_state)
|
158
|
+
:check => @event_id,
|
159
|
+
:state => @event_state)
|
100
160
|
}
|
101
161
|
|
102
|
-
|
162
|
+
logger.debug "notification: media after contact_drop?: #{rule_media}"
|
103
163
|
|
104
164
|
media.select {|medium, address| rule_media.include?(medium) }
|
105
165
|
end
|
106
166
|
|
107
|
-
|
167
|
+
logger.debug "notification: media_to_use: #{media_to_use}"
|
108
168
|
|
109
169
|
media_to_use.each_pair.inject([]) { |ret, (k, v)|
|
110
170
|
m = Flapjack::Data::Message.for_contact(contact,
|
111
|
-
|
112
|
-
:medium => k, :address => v)
|
171
|
+
:medium => k, :address => v)
|
113
172
|
ret << m
|
114
173
|
ret
|
115
174
|
}
|
@@ -118,15 +177,21 @@ module Flapjack
|
|
118
177
|
|
119
178
|
private
|
120
179
|
|
180
|
+
# created from parsed JSON, so opts keys are in strings
|
121
181
|
def initialize(opts = {})
|
122
|
-
|
123
|
-
@
|
124
|
-
@
|
125
|
-
@
|
126
|
-
@
|
127
|
-
@
|
128
|
-
@
|
129
|
-
|
182
|
+
@event_id = opts['event_id']
|
183
|
+
@event_state = opts['state']
|
184
|
+
@event_summary = opts['summary']
|
185
|
+
@event_details = opts['details']
|
186
|
+
@event_time = opts['time']
|
187
|
+
@event_duration = opts['duration']
|
188
|
+
@event_count = opts['count']
|
189
|
+
|
190
|
+
@last_event_state = opts['last_state']
|
191
|
+
@last_event_summary = opts['last_summary']
|
192
|
+
|
193
|
+
@type = opts['type']
|
194
|
+
@severity = opts['severity']
|
130
195
|
end
|
131
196
|
|
132
197
|
# # time restrictions match?
|
@@ -135,11 +200,11 @@ module Flapjack
|
|
135
200
|
# considered to be in the timezone of the contact
|
136
201
|
def rule_occurring_now?(rule, options = {})
|
137
202
|
contact = options[:contact]
|
138
|
-
|
203
|
+
def_tz = options[:default_timezone]
|
139
204
|
|
140
205
|
return true if rule.time_restrictions.nil? or rule.time_restrictions.empty?
|
141
206
|
|
142
|
-
timezone = contact.timezone(:default =>
|
207
|
+
timezone = contact.timezone(:default => def_tz)
|
143
208
|
usertime = timezone.now
|
144
209
|
|
145
210
|
rule.time_restrictions.any? do |tr|
|