flapjack 1.0.0rc3 → 1.0.0rc5

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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -2
  3. data/.ruby-version +1 -0
  4. data/CHANGELOG.md +20 -0
  5. data/CONTRIBUTING.md +2 -2
  6. data/Gemfile +1 -1
  7. data/README.md +6 -16
  8. data/build.sh +13 -1
  9. data/etc/flapjack_config.yaml.example +98 -12
  10. data/features/cli.feature +8 -8
  11. data/features/cli_flapjack-nagios-receiver.feature +29 -37
  12. data/features/cli_flapper.feature +24 -12
  13. data/features/cli_simulate-failed-check.feature +2 -2
  14. data/features/notifications.feature +18 -1
  15. data/features/steps/cli_steps.rb +2 -2
  16. data/features/steps/notifications_steps.rb +71 -0
  17. data/features/support/env.rb +7 -6
  18. data/flapjack.gemspec +3 -1
  19. data/lib/flapjack/cli/flapper.rb +74 -25
  20. data/lib/flapjack/cli/import.rb +3 -4
  21. data/lib/flapjack/cli/maintenance.rb +182 -0
  22. data/lib/flapjack/cli/receiver.rb +110 -121
  23. data/lib/flapjack/cli/server.rb +30 -26
  24. data/lib/flapjack/cli/simulate.rb +2 -3
  25. data/lib/flapjack/data/contact.rb +1 -1
  26. data/lib/flapjack/data/entity.rb +425 -32
  27. data/lib/flapjack/data/entity_check.rb +212 -14
  28. data/lib/flapjack/data/event.rb +1 -1
  29. data/lib/flapjack/gateways/aws_sns.rb +134 -0
  30. data/lib/flapjack/gateways/aws_sns/alert.text.erb +5 -0
  31. data/lib/flapjack/gateways/aws_sns/rollup.text.erb +2 -0
  32. data/lib/flapjack/gateways/jabber.rb +2 -2
  33. data/lib/flapjack/gateways/jsonapi/check_methods.rb +1 -1
  34. data/lib/flapjack/gateways/jsonapi/contact_methods.rb +1 -1
  35. data/lib/flapjack/gateways/jsonapi/entity_methods.rb +15 -1
  36. data/lib/flapjack/gateways/jsonapi/metrics_methods.rb +4 -3
  37. data/lib/flapjack/gateways/jsonapi/report_methods.rb +1 -1
  38. data/lib/flapjack/gateways/web.rb +35 -16
  39. data/lib/flapjack/gateways/web/public/css/tablesort.css +0 -16
  40. data/lib/flapjack/gateways/web/public/js/backbone.jsonapi.js +1 -1
  41. data/lib/flapjack/gateways/web/public/js/jquery.tablesorter.widgets.js +0 -45
  42. data/lib/flapjack/gateways/web/public/js/modules/contact.js +2 -2
  43. data/lib/flapjack/gateways/web/public/js/modules/entity.js +2 -2
  44. data/lib/flapjack/gateways/web/public/js/modules/medium.js +4 -4
  45. data/lib/flapjack/gateways/web/public/js/self_stats.js +1 -1
  46. data/lib/flapjack/gateways/web/views/check.html.erb +10 -10
  47. data/lib/flapjack/gateways/web/views/checks.html.erb +1 -1
  48. data/lib/flapjack/gateways/web/views/contact.html.erb +5 -1
  49. data/lib/flapjack/gateways/web/views/edit_contacts.html.erb +3 -4
  50. data/lib/flapjack/gateways/web/views/entities.html.erb +1 -1
  51. data/lib/flapjack/gateways/web/views/index.html.erb +2 -2
  52. data/lib/flapjack/gateways/web/views/layout.erb +3 -3
  53. data/lib/flapjack/gateways/web/views/self_stats.html.erb +5 -6
  54. data/lib/flapjack/notifier.rb +4 -1
  55. data/lib/flapjack/patches.rb +8 -2
  56. data/lib/flapjack/pikelet.rb +3 -1
  57. data/lib/flapjack/version.rb +1 -1
  58. data/libexec/httpbroker.go +1 -1
  59. data/spec/lib/flapjack/coordinator_spec.rb +3 -3
  60. data/spec/lib/flapjack/data/contact_spec.rb +2 -2
  61. data/spec/lib/flapjack/data/entity_check_spec.rb +805 -53
  62. data/spec/lib/flapjack/data/entity_spec.rb +661 -0
  63. data/spec/lib/flapjack/gateways/aws_sns_spec.rb +123 -0
  64. data/spec/lib/flapjack/gateways/jabber_spec.rb +1 -1
  65. data/spec/lib/flapjack/gateways/jsonapi/check_methods_spec.rb +1 -1
  66. data/spec/lib/flapjack/gateways/jsonapi/entity_methods_spec.rb +2 -2
  67. data/spec/lib/flapjack/gateways/pagerduty_spec.rb +1 -1
  68. data/spec/lib/flapjack/gateways/web_spec.rb +11 -11
  69. data/spec/support/profile_all_formatter.rb +10 -10
  70. data/spec/support/uncolored_doc_formatter.rb +66 -4
  71. data/src/flapjack/event.go +1 -1
  72. data/tasks/benchmarks.rake +24 -20
  73. data/tasks/entities.rake +148 -0
  74. data/tmp/dummy_contacts.json +43 -0
  75. data/tmp/dummy_entities.json +37 -1
  76. metadata +43 -7
  77. data/tmp/test_entities.json +0 -1
@@ -22,8 +22,25 @@ module Flapjack
22
22
  @config_env = @config.all
23
23
 
24
24
  if @config_env.nil? || @config_env.empty?
25
- puts "No config data for environment '#{FLAPJACK_ENV}' found in '#{global_options[:config]}'"
26
- exit 1
25
+ exit_now! "No config data for environment '#{FLAPJACK_ENV}' found in '#{global_options[:config]}'"
26
+ end
27
+
28
+ @pidfile = case
29
+ when !@options[:pidfile].nil?
30
+ @options[:pidfile]
31
+ when !@config_env['pid_dir'].nil?
32
+ @config_env['pid_dir'] + 'flapjack.pid'
33
+ else
34
+ "/var/run/flapjack/flapjack.pid"
35
+ end
36
+
37
+ @logfile = case
38
+ when !@options[:logfile].nil?
39
+ @options[:logfile]
40
+ when !@config_env['log_dir'].nil?
41
+ @config_env['log_dir'] + 'flapjack.log'
42
+ else
43
+ "/var/run/flapjack/flapjack.log"
27
44
  end
28
45
 
29
46
  if options[:rbtrace]
@@ -42,11 +59,12 @@ module Flapjack
42
59
  return_value = start_server
43
60
  }
44
61
  puts " done."
45
- exit(return_value) unless return_value.nil?
62
+ exit_now!(return_value) unless return_value.nil?
46
63
  end
47
64
  end
48
65
 
49
66
  def stop
67
+ pid = get_pid
50
68
  if runner.daemon_running?
51
69
  print "Flapjack stopping..."
52
70
  runner.execute(:kill => true)
@@ -54,16 +72,17 @@ module Flapjack
54
72
  else
55
73
  puts "Flapjack is not running."
56
74
  end
57
- exit 1 unless wait_pid_gone(get_pid)
75
+ exit_now! "Failed to stop Flapjack #{pid}" unless wait_pid_gone(pid)
58
76
  end
59
77
 
60
78
  def restart
79
+ pid = get_pid
61
80
  if runner.daemon_running?
62
81
  print "Flapjack stopping..."
63
82
  runner.execute(:kill => true)
64
83
  puts " done."
65
84
  end
66
- exit 1 unless wait_pid_gone(get_pid)
85
+ exit_now! "Failed to stop Flapjack #{pid}" unless wait_pid_gone(pid)
67
86
 
68
87
  @runner = nil
69
88
 
@@ -85,22 +104,17 @@ module Flapjack
85
104
  puts " couldn't send HUP to pid '#{pid}'."
86
105
  end
87
106
  else
88
- puts "Flapjack is not running daemonized."
89
- exit 1
107
+ exit_now! "Flapjack is not running daemonized."
90
108
  end
91
109
  end
92
110
 
93
111
  def status
94
- pidfile = @options[:pidfile] || @config_env['pid_file'] ||
95
- "/var/run/flapjack/flapjack.pid"
96
-
97
- uptime = (runner.daemon_running?) ? Time.now - File.stat(pidfile).ctime : 0
98
112
  if runner.daemon_running?
99
113
  pid = get_pid
114
+ uptime = Time.now - File.stat(@pidfile).ctime
100
115
  puts "Flapjack is running: pid #{pid}, uptime #{uptime}"
101
116
  else
102
- puts "Flapjack is not running"
103
- exit 3
117
+ exit_now! "Flapjack is not running"
104
118
  end
105
119
  end
106
120
 
@@ -111,16 +125,8 @@ module Flapjack
111
125
 
112
126
  self.class.skip_dante_traps
113
127
 
114
- pidfile = @options[:pidfile].nil? ?
115
- (@config_env['pid_file'] || "/var/run/flapjack/flapjack.pid") :
116
- @options[:pidfile]
117
-
118
- logfile = @options[:logfile].nil? ?
119
- (@config_env['log_file'] || "/var/log/flapjack/flapjack.log") :
120
- @options[:logfile]
121
-
122
- @runner = Dante::Runner.new('flapjack', :pid_path => pidfile,
123
- :log_path => logfile)
128
+ @runner = Dante::Runner.new('flapjack', :pid_path => @pidfile,
129
+ :log_path => @logfile)
124
130
  @runner
125
131
  end
126
132
 
@@ -166,7 +172,7 @@ module Flapjack
166
172
  end
167
173
 
168
174
  def get_pid
169
- IO.read(pidfile).chomp.to_i
175
+ IO.read(@pidfile).chomp.to_i
170
176
  rescue StandardError
171
177
  pid = nil
172
178
  end
@@ -252,5 +258,3 @@ command :server do |server|
252
258
  end
253
259
 
254
260
  end
255
-
256
-
@@ -21,8 +21,7 @@ module Flapjack
21
21
  @config_env = config.all
22
22
 
23
23
  if @config_env.nil? || @config_env.empty?
24
- puts "No config data for environment '#{FLAPJACK_ENV}' found in '#{global_options[:config]}'"
25
- exit 1
24
+ exit_now! "No config data for environment '#{FLAPJACK_ENV}' found in '#{global_options[:config]}'"
26
25
  end
27
26
 
28
27
  @redis_options = config.for_redis
@@ -101,7 +100,7 @@ module Flapjack
101
100
  end
102
101
  end
103
102
 
104
- desc 'Generate streams of events in various states'
103
+ desc 'Simulates a check by creating a stream of events for Flapjack to process'
105
104
  command :simulate do |simulate|
106
105
 
107
106
  simulate.desc 'Generate a stream of failure events'
@@ -22,7 +22,7 @@ module Flapjack
22
22
  :media_intervals, :media_rollup_thresholds, :pagerduty_credentials
23
23
 
24
24
  TAG_PREFIX = 'contact_tag'
25
- ALL_MEDIA = ['email', 'sms', 'jabber', 'pagerduty']
25
+ ALL_MEDIA = ['email', 'sms', 'jabber', 'pagerduty', 'sns']
26
26
 
27
27
  def self.all(options = {})
28
28
  raise "Redis connection not set" unless redis = options[:redis]
@@ -18,29 +18,432 @@ module Flapjack
18
18
 
19
19
  def self.all(options = {})
20
20
  raise "Redis connection not set" unless redis = options[:redis]
21
+
22
+ current_entity_names = (options.has_key?(:enabled) && !options[:enabled].nil?) ?
23
+ Flapjack::Data::Entity.current_names(:redis => redis) : nil
24
+
21
25
  keys = redis.keys("entity_id:*")
22
26
  return [] unless keys.any?
23
27
  ids = redis.mget(keys)
24
- keys.collect {|k|
25
- k =~ /^entity_id:(.+)$/; entity_name = $1
26
- self.new(:name => entity_name, :id => ids.shift, :redis => redis)
28
+ keys.inject([]) {|memo, k|
29
+ k =~ /^entity_id:(.+)$/; entity_name = $1; entity_id = ids.shift
30
+
31
+ if options[:enabled].nil? ||
32
+ (options[:enabled].is_a?(TrueClass) && current_entity_names.include?(entity_name) ) ||
33
+ (options[:enabled].is_a?(FalseClass) && !current_entity_names.include?(entity_name))
34
+
35
+ memo << self.new(:name => entity_name, :id => entity_id, :redis => redis)
36
+ end
37
+
38
+ memo
27
39
  }.sort_by(&:name)
28
40
  end
29
41
 
30
- # NB: should probably be called in the context of a Redis multi block; not doing so
31
- # here as calling classes may well be adding/updating multiple records in the one
32
- # operation
42
+ # no way to lock all data operations, so hit & hope... at least the renames
43
+ # should be atomic
44
+ def self.rename(existing_name, entity_name, options = {})
45
+ raise "Redis connection not set" unless redis = options[:redis]
46
+
47
+ check_state_keys = redis.keys("check:#{existing_name}:*")
48
+
49
+ check_history_keys = redis.keys("#{existing_name}:*:states") +
50
+ redis.keys("#{existing_name}:*:state") +
51
+ redis.keys("#{existing_name}:*:summary") +
52
+ redis.keys("#{existing_name}:*:sorted_state_timestamps")
53
+
54
+ action_keys = redis.keys("#{existing_name}:*:actions")
55
+
56
+ maint_keys = redis.keys("#{existing_name}:*:*scheduled_maintenance*") +
57
+
58
+ notif_keys = redis.keys("#{existing_name}:*:last_*_notification") +
59
+ redis.keys("#{existing_name}:*:*_notifications")
60
+
61
+ alerting_check_keys = redis.keys("contact_alerting_checks:*")
62
+
63
+ failed_checks = {}
64
+ hashes_to_remove = []
65
+ hashes_to_add = {}
66
+
67
+ alerting_to_remove = {}
68
+ alerting_to_add = {}
69
+
70
+ sha1 = Digest::SHA1.new
71
+
72
+ checks = check_state_keys.collect do |state_key|
73
+ state_key =~ /^check:#{Regexp.escape(existing_name)}:(.+)$/
74
+ $1
75
+ end
76
+
77
+ checks.each do |ch|
78
+ existing_check = "#{existing_name}:#{ch}"
79
+ new_check = "#{entity_name}:#{ch}"
80
+
81
+ ch_fail_score = redis.zscore("failed_checks", existing_check)
82
+ failed_checks[ch] = ch_fail_score unless ch_fail_score.nil?
83
+
84
+ hashes_to_remove << Digest.hexencode(sha1.digest(existing_check))[0..7].downcase
85
+ hashes_to_add[Digest.hexencode(sha1.digest(new_check))[0..7].downcase] = new_check
86
+
87
+ alerting_check_keys.each do |ack|
88
+ ack_score = redis.zscore(ack, existing_check)
89
+ unless ack_score.nil?
90
+ alerting_to_remove[ack] ||= []
91
+ alerting_to_remove[ack] << existing_check
92
+
93
+ alerting_to_add[ack] ||= {}
94
+ alerting_to_add[ack][new_check] = ack_score
95
+ end
96
+ end
97
+ end
98
+
99
+ current_score = redis.zscore('current_entities', existing_name)
100
+
101
+ block_keys = redis.keys("drop_alerts_for_contact:*:*:#{existing_name}:*:*")
102
+
103
+ redis.multi
104
+
105
+ yield if block_given? # entity id -> name update from add()
106
+
107
+ check_state_keys.each do |csk|
108
+ redis.rename(csk, csk.sub(/^check:#{Regexp.escape(existing_name)}:/, "check:#{entity_name}:"))
109
+ end
110
+
111
+ (check_history_keys + action_keys + maint_keys + notif_keys).each do |chk|
112
+ redis.rename(chk, chk.sub(/^#{Regexp.escape(existing_name)}:/, "#{entity_name}:"))
113
+ end
114
+
115
+ # currently failing checks
116
+ failed_checks.each_pair do |ch, score|
117
+ redis.zrem('failed_checks', "#{existing_name}:#{ch}")
118
+ redis.zadd('failed_checks', score, "#{entity_name}:#{ch}")
119
+ end
120
+
121
+ redis.rename("current_checks:#{existing_name}", "current_checks:#{entity_name}")
122
+
123
+ unless current_score.nil?
124
+ redis.zrem('current_entities', existing_name)
125
+ redis.zadd('current_entities', current_score, entity_name)
126
+ end
127
+
128
+ block_keys.each do |blk|
129
+ redis.rename(blk, blk.sub(/^drop_alerts_for_contact:(.+):([^:]+):#{Regexp.escape(existing_name)}:(.+):([^:]+)$/,
130
+ "drop_alerts_for_contact:\\1:\\2:#{entity_name}:\\3:\\4"))
131
+ end
132
+
133
+ hashes_to_remove.each {|hash| redis.hdel('checks_by_hash', hash) }
134
+ hashes_to_add.each_pair {|hash, chk| redis.hset('checks_by_hash', hash, chk)}
135
+
136
+ alerting_to_remove.each_pair do |alerting, chks|
137
+ chks.each {|chk| redis.zrem(alerting, chk)}
138
+ end
139
+
140
+ alerting_to_add.each_pair do |alerting, chks|
141
+ chks.each_pair {|chk, score| redis.zadd(alerting, score, chk)}
142
+ end
143
+
144
+ redis.exec
145
+ end
146
+
147
+ # NB only used by the 'entities:reparent' Rake task, but kept in this
148
+ # class to be more easily testable
149
+ def self.merge(old_name, current_name, options = {})
150
+ raise "Redis connection not set" unless redis = options[:redis]
151
+
152
+ check_state_keys = redis.keys("check:#{old_name}:*")
153
+
154
+ checks = check_state_keys.collect do |state_key|
155
+ state_key =~ /^check:#{Regexp.escape(old_name)}:(.+)$/
156
+ $1
157
+ end
158
+
159
+ alerting_check_keys = redis.keys("contact_alerting_checks:*")
160
+
161
+ keys_to_delete = []
162
+ keys_to_rename = {}
163
+
164
+ failed_checks_to_remove = []
165
+ failed_checks_to_add = {}
166
+
167
+ action_data = {}
168
+
169
+ notification_types = ['problem', 'unknown', 'warning', 'critical',
170
+ 'recovery', 'acknowledgement']
171
+
172
+ alerting_check_keys = redis.keys("contact_alerting_checks:*")
173
+
174
+ alerting_to_remove = {}
175
+ alerting_to_add = {}
176
+
177
+ block_keys = redis.keys("drop_alerts_for_contact:*:*:#{old_name}:*:*")
178
+
179
+ checks.each do |ch|
180
+ old_check = "#{old_name}:#{ch}"
181
+ current_check = "#{current_name}:#{ch}"
182
+
183
+ old_states = "#{old_check}:states"
184
+ new_states = "#{current_check}:states"
185
+
186
+ failed_checks_to_remove << old_check
187
+
188
+ if redis.exists("check:#{current_check}")
189
+ keys_to_delete << "check:#{old_check}"
190
+
191
+ loop do
192
+ # pop from tail, append at head, matches ordering in EntityCheck#update_state
193
+ break if redis.rpoplpush(old_states, new_states).nil?
194
+ end
195
+
196
+ keys_to_delete << old_states
197
+ else
198
+
199
+ # can move a failing checks entry over, if it exists
200
+ ch_fail_score = redis.zscore("failed_checks", old_check)
201
+ failed_checks_to_add[current_check] = ch_fail_score unless ch_fail_score.nil?
202
+
203
+ keys_to_rename["check:#{old_check}"] = "check:#{current_check}"
204
+ keys_to_rename[old_states] = new_states
205
+ end
206
+
207
+ notification_types.each do |notif|
208
+
209
+ old_notif = "#{old_check}:#{notif}_notifications"
210
+ new_notif = "#{current_check}:#{notif}_notifications"
211
+
212
+ if redis.exists(new_notif)
213
+ loop do
214
+ # pop from tail, append at head
215
+ break if redis.rpoplpush(old_notif, new_notif).nil?
216
+ end
217
+
218
+ keys_to_delete << old_notif
219
+ else
220
+ keys_to_rename[old_notif] = new_notif
221
+ end
222
+ end
223
+
224
+ alerting_check_keys.each do |ack|
225
+ old_score = redis.zscore(ack, old_check)
226
+ new_score = redis.zscore(ack, current_check)
227
+
228
+ alerting_to_remove[ack] ||= []
229
+ alerting_to_remove[ack] << old_check
230
+
231
+ # nil.to_i == 0, which is good for a missing value
232
+ if !old_score.nil? && new_score.nil? &&
233
+ (redis.lindex("#{old_check}:problem_notifications", -1).to_i >
234
+ [redis.lindex("#{current_check}:recovery_notifications", -1).to_i,
235
+ redis.lindex("#{current_check}:acknowledgement_notifications", -1).to_i].max)
236
+
237
+ alerting_to_add[ack] ||= {}
238
+ alerting_to_add[ack][current_check] = old_score
239
+ end
240
+ end
241
+
242
+ end
243
+
244
+ if redis.exists("current_checks:#{current_name}")
245
+ keys_to_delete << "current_checks:#{old_name}"
246
+ else
247
+ keys_to_rename["current_checks:#{old_name}"] = "current_checks:#{current_name}"
248
+ end
249
+
250
+ current_score = redis.zscore('current_entities', current_name)
251
+ old_score = nil
252
+
253
+ if current_score.nil?
254
+ old_score = redis.zscore('current_entities', old_name)
255
+ end
256
+
257
+ check_timestamps_keys = redis.keys("#{old_name}:*:sorted_state_timestamps")
258
+ keys_to_delete += check_timestamps_keys
259
+
260
+ check_history_keys = redis.keys("#{old_name}:*:state") +
261
+ redis.keys("#{old_name}:*:summary")
262
+
263
+ action_keys = redis.keys("#{old_name}:*:actions")
264
+
265
+ action_keys.each do |old_actions|
266
+
267
+ old_actions =~ /^#{Regexp.escape(old_name)}:(.+):actions$/
268
+ current_actions = "#{current_name}:#{$1}:actions"
269
+
270
+ if redis.exists(current_actions)
271
+ action_data[current_actions] = redis.hgetall(old_actions)
272
+ keys_to_delete << old_actions
273
+ else
274
+ keys_to_rename[old_actions] = current_actions
275
+ end
276
+ end
277
+
278
+ maint_keys = redis.keys("#{old_name}:*:*scheduled_maintenance")
279
+
280
+ maints_to_delete = []
281
+ maints_to_set = {}
282
+
283
+ maint_keys.each do |maint_key|
284
+ maint_key =~ /^#{Regexp.escape(old_name)}:(.+):((?:un)?scheduled_maintenance)$/
285
+ maint_check = $1
286
+ maint_type = $2
287
+
288
+ new_maint_key = "#{current_name}:#{maint_check}:#{maint_type}"
289
+
290
+ # as keys are expiring, check all steps in case they have
291
+ old_time, new_time = redis.mget(maint_key, new_maint_key).map(&:to_i)
292
+
293
+ old_ttl = (old_time <= 0) ? -1 : redis.ttl(maint_key)
294
+ new_ttl = (new_time <= 0) ? -1 : redis.ttl(new_maint_key)
295
+
296
+ # TTL < 0 is a redis error code -- key not present, etc.
297
+ if (old_ttl >= 0) && ((new_ttl < 0) ||
298
+ ((old_time + old_ttl) > (new_time + new_ttl)))
299
+ keys_to_rename[maint_key] = new_maint_key
300
+ maints_to_set[new_maint_key] = redis.zscore("#{maint_key}s", old_time)
301
+ end
302
+
303
+ keys_to_delete << maint_key
304
+ end
305
+
306
+ blocks_to_set = {}
307
+
308
+ block_keys.each do |block_key|
309
+ block_key =~ /^drop_alerts_for_contact:(.+):([^:]+):#{Regexp.escape(old_name)}:(.+):([^:]+)$/
310
+ new_block_key = "drop_alerts_for_contact:#{$1}:#{$2}:#{current_name}:#{$3}:#{$4}"
311
+
312
+ # as keys may expire, check whether they have
313
+ old_start_ttl, new_start_ttl = redis.mget(block_key, new_block_key).map(&:to_i)
314
+
315
+ old_ttl = (old_start_ttl <= 0) ? -1 : redis.ttl(block_key)
316
+ new_ttl = (new_start_ttl <= 0) ? -1 : redis.ttl(new_block_key)
317
+
318
+ # TTL < 0 is a redis error code -- key not present, etc.
319
+ if (old_ttl >= 0) && ((new_ttl < 0) || (old_ttl > new_ttl))
320
+ blocks_to_set[new_block_key] = [Time.now.to_i + old_ttl, old_start_ttl]
321
+ end
322
+
323
+ keys_to_delete << block_key
324
+ end
325
+
326
+ stored_maint_keys = redis.keys("#{old_name}:*:*scheduled_maintenances") +
327
+ redis.keys("#{old_name}:*:sorted_*scheduled_maintenance_timestamps")
328
+ keys_to_delete += stored_maint_keys
329
+
330
+ notif_keys = redis.keys("#{old_name}:*:last_*_notification")
331
+
332
+ redis.multi
333
+
334
+ check_history_keys.each do |chk|
335
+ redis.renamenx(chk, chk.sub(/^#{Regexp.escape(old_name)}:/, "#{current_name}:"))
336
+ end
337
+
338
+ check_timestamps_keys.each do |ctk|
339
+ dest = ctk.sub(/^#{Regexp.escape(old_name)}:/, "#{current_name}:")
340
+ redis.zunionstore(dest, [ctk, dest], :aggregate => :max)
341
+ end
342
+
343
+ failed_checks_to_remove.each do |fctr|
344
+ redis.zrem('failed_checks', fctr)
345
+ end
346
+
347
+ failed_checks_to_add.each_pair do |fcta, score|
348
+ redis.zadd('failed_checks', score, fcta)
349
+ end
350
+
351
+ action_data.each_pair do |action_key, data|
352
+ data.each_pair do |k, v|
353
+ redis.hsetnx(action_key, k, v)
354
+ end
355
+ end
356
+
357
+ redis.zunionstore("current_checks:#{current_name}",
358
+ ["current_checks:#{old_name}", "current_checks:#{current_name}"],
359
+ :aggregate => :max)
360
+
361
+ redis.zrem('current_entities', old_name)
362
+ unless old_score.nil?
363
+ redis.zadd('current_entities', old_score, current_name)
364
+ end
365
+
366
+ maints_to_set.each_pair do |maint_key, score|
367
+ redis.zadd("#{maint_key}s", score, current_name)
368
+ end
369
+
370
+ stored_maint_keys.each do |stored_maint_key|
371
+ new_stored_maint_key = stored_maint_key.sub(/^#{Regexp.escape(old_name)}:/, "#{current_name}:")
372
+ redis.zunionstore(new_stored_maint_key,
373
+ [stored_maint_key, new_stored_maint_key],
374
+ :aggregate => :max)
375
+ end
376
+
377
+ notif_keys.each do |nk|
378
+ dest = nk.sub(/^#{Regexp.escape(old_name)}:/, "#{current_name}:")
379
+ redis.renamenx(nk, dest)
380
+ redis.del(nk)
381
+ end
382
+
383
+ alerting_to_remove.each_pair do |alerting, chks|
384
+ chks.each {|chk| redis.zrem(alerting, chk)}
385
+ end
386
+
387
+ alerting_to_add.each_pair do |alerting, chks|
388
+ chks.each_pair {|chk, score| redis.zadd(alerting, score, chk)}
389
+ end
390
+
391
+ blocks_to_set.each_pair do |block_key, (timestamp, value)|
392
+ redis.setex(block_key, (timestamp - Time.now.to_i), value)
393
+ end
394
+
395
+ keys_to_rename.each_pair do |old_key, new_key|
396
+ redis.rename(old_key, new_key)
397
+ end
398
+
399
+ redis.del(*keys_to_delete) unless keys_to_delete.empty?
400
+
401
+ redis.exec
402
+ end
403
+
404
+ # NB: If entities are renamed in imported data before they are
405
+ # renamed in monitoring sources, data for old entities may still
406
+ # arrive and be stored under those names.
33
407
  def self.add(entity, options = {})
34
408
  raise "Redis connection not set" unless redis = options[:redis]
35
- raise "Entity name not provided" unless entity['name'] && !entity['name'].empty?
409
+ entity_name = entity['name']
410
+ raise "Entity name not provided" if entity_name.nil? || entity_name.empty?
411
+
412
+ entity_id = entity['id']
413
+
414
+ if entity_id.nil?
415
+ # likely to be from monitoring data
416
+
417
+ # if an entity exists with the same name as the incoming data,
418
+ # use its id; failing that allocate a random one
419
+ entity_id = redis.get("entity_id:#{entity_name}")
36
420
 
37
- #FIXME: should probably raise an exception if trying to create a new entity with the
38
- # same name or id as an existing entity. (Go away and use update instead.)
39
- entity_id = entity['id'] ? entity['id'] : SecureRandom.uuid
40
- existing_name = redis.hget("entity:#{entity_id}", 'name')
41
- redis.del("entity_id:#{existing_name}") unless existing_name == entity['name']
42
- redis.set("entity_id:#{entity['name']}", entity_id)
43
- redis.hset("entity:#{entity_id}", 'name', entity['name'])
421
+ if entity_id.nil? || entity_id.empty?
422
+ entity_id = SecureRandom.uuid
423
+ redis.set("entity_id:#{entity_name}", entity_id)
424
+ redis.hset("entity:#{entity_id}", 'name', entity_name)
425
+ end
426
+ else
427
+ # most likely from API import
428
+ existing_name = redis.hget("entity:#{entity_id}", 'name')
429
+
430
+ if existing_name.nil?
431
+
432
+ # if there's an entity with a matching name, this will change its
433
+ # id; if no entity exists it creates a new one
434
+ redis.set("entity_id:#{entity_name}", entity_id)
435
+ redis.hset("entity:#{entity_id}", 'name', entity_name)
436
+
437
+ elsif existing_name != entity_name
438
+ if redis.renamenx("entity_id:#{existing_name}", "entity_id:#{entity_name}")
439
+ rename(existing_name, entity_name, :redis => redis) {
440
+ redis.hset("entity:#{entity_id}", 'name', entity_name)
441
+ }
442
+ else
443
+ merge(existing_name, entity_name, :redis => redis)
444
+ end
445
+ end
446
+ end
44
447
 
45
448
  redis.del("contacts_for:#{entity_id}")
46
449
  if entity['contacts'] && entity['contacts'].respond_to?(:each)
@@ -49,7 +452,7 @@ module Flapjack
49
452
  redis.sadd("contacts_for:#{entity_id}", contact_id)
50
453
  }
51
454
  end
52
- self.new(:name => entity['name'],
455
+ self.new(:name => entity_name,
53
456
  :id => entity_id,
54
457
  :redis => redis)
55
458
  end
@@ -57,14 +460,14 @@ module Flapjack
57
460
  def self.find_by_name(entity_name, options = {})
58
461
  raise "Redis connection not set" unless redis = options[:redis]
59
462
  entity_id = redis.get("entity_id:#{entity_name}")
60
- if entity_id.nil?
463
+ if entity_id.nil? || entity_id.empty?
61
464
  # key doesn't exist
62
465
  return unless options[:create]
466
+ # add returns an instantiated Entity
63
467
  self.add({'name' => entity_name}, :redis => redis)
468
+ else
469
+ self.new(:name => entity_name, :id => entity_id, :redis => redis)
64
470
  end
65
- self.new(:name => entity_name,
66
- :id => (entity_id.nil? || entity_id.empty?) ? nil : entity_id,
67
- :redis => redis)
68
471
  end
69
472
 
70
473
  def self.find_by_id(entity_id, options = {})
@@ -116,24 +519,14 @@ module Flapjack
116
519
  }.compact
117
520
  end
118
521
 
119
- def self.find_all_with_checks(options)
120
- raise "Redis connection not set" unless redis = options[:redis]
121
- redis.zrange("current_entities", 0, -1)
122
- end
123
-
124
- def self.find_all_with_failing_checks(options)
125
- raise "Redis connection not set" unless redis = options[:redis]
126
- Flapjack::Data::EntityCheck.find_all_failing_by_entity(:redis => redis).keys
127
- end
128
-
129
- def self.find_all_current(options)
522
+ def self.current_names(options = {})
130
523
  raise "Redis connection not set" unless redis = options[:redis]
131
524
  redis.zrange('current_entities', 0, -1)
132
525
  end
133
526
 
134
- def self.find_all_current_with_last_update(options)
527
+ def self.find_all_with_failing_checks(options)
135
528
  raise "Redis connection not set" unless redis = options[:redis]
136
- redis.zrange('current_entities', 0, -1, {:withscores => true})
529
+ Flapjack::Data::EntityCheck.find_current_failing_by_entity(:redis => redis).keys
137
530
  end
138
531
 
139
532
  def contacts