flapjack 0.7.18 → 0.7.19

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.
Files changed (100) hide show
  1. data/CHANGELOG.md +7 -0
  2. data/bin/flapjack +3 -0
  3. data/bin/flapjack-nagios-receiver +4 -1
  4. data/bin/flapjack-netsaint-parser +2 -1
  5. data/bin/flapjack-populator +6 -3
  6. data/bin/receive-events +3 -1
  7. data/bin/simulate-failed-check +2 -1
  8. data/etc/flapjack_config.yaml.example +20 -0
  9. data/features/events.feature +1 -1
  10. data/features/events_check_names.feature +1 -1
  11. data/features/notification_rules.feature +1 -1
  12. data/features/notifications.feature +1 -1
  13. data/features/steps/events_steps.rb +18 -17
  14. data/features/steps/flapjack-netsaint-parser_steps.rb +1 -2
  15. data/features/steps/notifications_steps.rb +14 -1
  16. data/features/support/env.rb +27 -10
  17. data/flapjack.gemspec +1 -3
  18. data/lib/flapjack/coordinator.rb +30 -20
  19. data/lib/flapjack/data/contact.rb +3 -2
  20. data/lib/flapjack/data/entity.rb +3 -3
  21. data/lib/flapjack/data/entity_check.rb +116 -43
  22. data/lib/flapjack/data/event.rb +10 -10
  23. data/lib/flapjack/data/message.rb +3 -6
  24. data/lib/flapjack/data/notification.rb +122 -57
  25. data/lib/flapjack/data/notification_rule.rb +11 -11
  26. data/lib/flapjack/filters/acknowledgement.rb +2 -2
  27. data/lib/flapjack/filters/ok.rb +1 -1
  28. data/lib/flapjack/gateways/api/entity_check_presenter.rb +1 -0
  29. data/lib/flapjack/gateways/api/entity_methods.rb +4 -6
  30. data/lib/flapjack/gateways/api/rack/json_params_parser.rb +1 -1
  31. data/lib/flapjack/gateways/email.rb +3 -5
  32. data/lib/flapjack/gateways/email/{alert.html.haml → alert.html.erb} +0 -0
  33. data/lib/flapjack/gateways/jabber.rb +66 -35
  34. data/lib/flapjack/gateways/oobetet.rb +5 -7
  35. data/lib/flapjack/gateways/pagerduty.rb +7 -7
  36. data/lib/flapjack/gateways/web.rb +101 -41
  37. data/lib/flapjack/gateways/web/public/css/flapjack.css +1 -1
  38. data/lib/flapjack/gateways/web/views/{_css.haml → _css.html.erb} +2 -1
  39. data/lib/flapjack/gateways/web/views/_foot.html.erb +3 -0
  40. data/lib/flapjack/gateways/web/views/_head.html.erb +4 -0
  41. data/lib/flapjack/gateways/web/views/_nav.html.erb +9 -0
  42. data/lib/flapjack/gateways/web/views/check.html.erb +204 -0
  43. data/lib/flapjack/gateways/web/views/checks.html.erb +77 -0
  44. data/lib/flapjack/gateways/web/views/contact.html.erb +114 -0
  45. data/lib/flapjack/gateways/web/views/contacts.html.erb +42 -0
  46. data/lib/flapjack/gateways/web/views/entities.html.erb +39 -0
  47. data/lib/flapjack/gateways/web/views/entity.html.erb +67 -0
  48. data/lib/flapjack/gateways/web/views/index.html.erb +27 -0
  49. data/lib/flapjack/gateways/web/views/self_stats.html.erb +97 -0
  50. data/lib/flapjack/logger.rb +71 -23
  51. data/lib/flapjack/notifier.rb +157 -0
  52. data/lib/flapjack/patches.rb +1 -41
  53. data/lib/flapjack/pikelet.rb +4 -2
  54. data/lib/flapjack/{executive.rb → processor.rb} +32 -145
  55. data/lib/flapjack/version.rb +1 -1
  56. data/spec/lib/flapjack/coordinator_spec.rb +134 -71
  57. data/spec/lib/flapjack/data/contact_spec.rb +1 -0
  58. data/spec/lib/flapjack/data/entity_check_spec.rb +146 -30
  59. data/spec/lib/flapjack/data/entity_spec.rb +4 -4
  60. data/spec/lib/flapjack/data/event_spec.rb +4 -4
  61. data/spec/lib/flapjack/data/message_spec.rb +2 -3
  62. data/spec/lib/flapjack/data/notification_spec.rb +13 -19
  63. data/spec/lib/flapjack/gateways/api/entity_methods_spec.rb +2 -2
  64. data/spec/lib/flapjack/gateways/jabber_spec.rb +34 -0
  65. data/spec/lib/flapjack/gateways/pagerduty_spec.rb +0 -2
  66. data/spec/lib/flapjack/gateways/web/views/{check.haml_spec.rb → check.html.erb_spec.rb} +2 -2
  67. data/spec/lib/flapjack/gateways/web/views/{contact.haml_spec.rb → contact.html.erb_spec.rb} +3 -3
  68. data/spec/lib/flapjack/gateways/web/views/index.html.erb_spec.rb +14 -0
  69. data/spec/lib/flapjack/gateways/web_spec.rb +20 -8
  70. data/spec/lib/flapjack/logger_spec.rb +30 -28
  71. data/spec/lib/flapjack/notifier_spec.rb +6 -0
  72. data/spec/lib/flapjack/pikelet_spec.rb +8 -8
  73. data/spec/lib/flapjack/{executive_spec.rb → processor_spec.rb} +4 -4
  74. data/spec/spec_helper.rb +1 -13
  75. data/spec/support/erb_view_helper.rb +23 -0
  76. data/tasks/profile.rake +1 -1
  77. data/tmp/acknowledge.rb +3 -1
  78. data/tmp/create_event_ok.rb +3 -1
  79. data/tmp/create_event_unknown.rb +3 -1
  80. data/tmp/create_events_failure.rb +3 -1
  81. data/tmp/create_events_ok.rb +3 -1
  82. data/tmp/create_events_ok_fail_ack_ok.rb +3 -1
  83. data/tmp/create_events_ok_failure.rb +3 -1
  84. data/tmp/create_events_ok_failure_ack.rb +3 -1
  85. data/tmp/test_json_post.rb +5 -3
  86. data/tmp/test_notification_rules_api.rb +5 -3
  87. metadata +32 -61
  88. data/lib/flapjack/gateways/web/views/_foot.haml +0 -8
  89. data/lib/flapjack/gateways/web/views/_head.haml +0 -10
  90. data/lib/flapjack/gateways/web/views/_nav.haml +0 -14
  91. data/lib/flapjack/gateways/web/views/check.haml +0 -191
  92. data/lib/flapjack/gateways/web/views/checks.haml +0 -49
  93. data/lib/flapjack/gateways/web/views/contact.haml +0 -85
  94. data/lib/flapjack/gateways/web/views/contacts.haml +0 -30
  95. data/lib/flapjack/gateways/web/views/entities.haml +0 -28
  96. data/lib/flapjack/gateways/web/views/entity.haml +0 -50
  97. data/lib/flapjack/gateways/web/views/index.haml +0 -32
  98. data/lib/flapjack/gateways/web/views/self_stats.haml +0 -70
  99. data/spec/lib/flapjack/gateways/web/views/index.haml_spec.rb +0 -13
  100. 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]
@@ -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.keys("check:*").map {|s| s.sub(/^check:/, '').split(':', 2).first }.to_set
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
- redis.zrange("failed_checks", 0, -1).map {|s| s.split(':', 2).first }.to_set
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.keys("check:#{@name}:*").map {|k| k =~ /^check:#{@name}:(.+)$/; $1}
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 'yajl/json_gem'
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
- end_unscheduled_maintenance if in_unscheduled_maintenance?
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(opts = {})
100
- defaults = {
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
- start_time = opts[:start_time] # unix timestamp
123
- duration = opts[:duration] # seconds
124
- summary = opts[:summary]
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
- # if not in scheduled maintenance, looks in scheduled maintenance list for a check to see if
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
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'yajl/json_gem'
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('events', dest, 0)
34
+ raw = redis.brpoplpush(queue, dest, 0)
35
35
  else
36
- raw = redis.rpoplpush('events', dest)
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('events', 0)[1]
42
+ raw = redis.brpop(queue, 0)[1]
43
43
  else
44
- raw = redis.rpop('events')
44
+ raw = redis.rpop(queue)
45
45
  return unless raw
46
46
  end
47
47
  end
48
48
  begin
49
- parsed = ::JSON.parse( raw )
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', ::Yajl::Encoder.encode(evt))
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
- :notification_contents => opts[:notification_contents],
19
- :medium => opts[:medium], :address => opts[:address],
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
- return c if @notification_contents.nil?
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 :event, :type, :max_notified_severity, :contacts,
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
- event_id = event.id
28
- event_state = event.state
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
- severity = if ([event_state, max_notified_severity] & ['critical', 'unknown', 'test_notifications']).any?
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 [event_state, max_notified_severity].include?('warning')
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
- contents = {'event_id' => event_id,
39
- 'state' => event_state,
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
- @logger.debug "considering messages for contact id #{contact_id} #{event_id} #{event_state} (media) #{media.inspect}"
114
+ logger.debug "considering messages for contact id #{contact_id} #{@event_id} #{@event_state} (media) #{media.inspect}"
55
115
  rlen = rules.length
56
- @logger.debug "found #{rlen} rule#{(rlen == 1) ? '' : 's'} for contact"
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
- @logger.debug "#{matchers.length} matchers remain for this contact:"
128
+ logger.debug "#{matchers.length} matchers remain for this contact:"
69
129
  matchers.each do |matcher|
70
- @logger.debug "matcher: #{matcher.to_json}"
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
- @logger.debug("general removal: found #{matchers.length} entity specific matchers")
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
- @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}")
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
- @logger.debug "notification: num matchers after removing blackhole matchers: #{matchers.size}"
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
- @logger.debug "notification: collected media_for_severity(#{severity}): #{rule_media}"
96
- rule_media = rule_media.flatten.uniq.reject {|medium|
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
- @logger.debug "notification: media after contact_drop?: #{rule_media}"
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
- @logger.debug "notification: media_to_use: #{media_to_use}"
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
- :notification_contents => contents,
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
- raise "Event not passed" unless event = opts[:event]
123
- @event = event
124
- @type = opts[:type]
125
- @max_notified_severity = opts[:max_notified_severity]
126
- @contacts = opts[:contacts]
127
- @default_timezone = opts[:default_timezone]
128
- @last_state = opts[:last_state]
129
- @logger = opts[:logger]
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
- default_timezone = options[:default_timezone]
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 => default_timezone)
207
+ timezone = contact.timezone(:default => def_tz)
143
208
  usertime = timezone.now
144
209
 
145
210
  rule.time_restrictions.any? do |tr|