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.
- checksums.yaml +4 -4
- data/.gitignore +1 -2
- data/.ruby-version +1 -0
- data/CHANGELOG.md +20 -0
- data/CONTRIBUTING.md +2 -2
- data/Gemfile +1 -1
- data/README.md +6 -16
- data/build.sh +13 -1
- data/etc/flapjack_config.yaml.example +98 -12
- data/features/cli.feature +8 -8
- data/features/cli_flapjack-nagios-receiver.feature +29 -37
- data/features/cli_flapper.feature +24 -12
- data/features/cli_simulate-failed-check.feature +2 -2
- data/features/notifications.feature +18 -1
- data/features/steps/cli_steps.rb +2 -2
- data/features/steps/notifications_steps.rb +71 -0
- data/features/support/env.rb +7 -6
- data/flapjack.gemspec +3 -1
- data/lib/flapjack/cli/flapper.rb +74 -25
- data/lib/flapjack/cli/import.rb +3 -4
- data/lib/flapjack/cli/maintenance.rb +182 -0
- data/lib/flapjack/cli/receiver.rb +110 -121
- data/lib/flapjack/cli/server.rb +30 -26
- data/lib/flapjack/cli/simulate.rb +2 -3
- data/lib/flapjack/data/contact.rb +1 -1
- data/lib/flapjack/data/entity.rb +425 -32
- data/lib/flapjack/data/entity_check.rb +212 -14
- data/lib/flapjack/data/event.rb +1 -1
- data/lib/flapjack/gateways/aws_sns.rb +134 -0
- data/lib/flapjack/gateways/aws_sns/alert.text.erb +5 -0
- data/lib/flapjack/gateways/aws_sns/rollup.text.erb +2 -0
- data/lib/flapjack/gateways/jabber.rb +2 -2
- data/lib/flapjack/gateways/jsonapi/check_methods.rb +1 -1
- data/lib/flapjack/gateways/jsonapi/contact_methods.rb +1 -1
- data/lib/flapjack/gateways/jsonapi/entity_methods.rb +15 -1
- data/lib/flapjack/gateways/jsonapi/metrics_methods.rb +4 -3
- data/lib/flapjack/gateways/jsonapi/report_methods.rb +1 -1
- data/lib/flapjack/gateways/web.rb +35 -16
- data/lib/flapjack/gateways/web/public/css/tablesort.css +0 -16
- data/lib/flapjack/gateways/web/public/js/backbone.jsonapi.js +1 -1
- data/lib/flapjack/gateways/web/public/js/jquery.tablesorter.widgets.js +0 -45
- data/lib/flapjack/gateways/web/public/js/modules/contact.js +2 -2
- data/lib/flapjack/gateways/web/public/js/modules/entity.js +2 -2
- data/lib/flapjack/gateways/web/public/js/modules/medium.js +4 -4
- data/lib/flapjack/gateways/web/public/js/self_stats.js +1 -1
- data/lib/flapjack/gateways/web/views/check.html.erb +10 -10
- data/lib/flapjack/gateways/web/views/checks.html.erb +1 -1
- data/lib/flapjack/gateways/web/views/contact.html.erb +5 -1
- data/lib/flapjack/gateways/web/views/edit_contacts.html.erb +3 -4
- data/lib/flapjack/gateways/web/views/entities.html.erb +1 -1
- data/lib/flapjack/gateways/web/views/index.html.erb +2 -2
- data/lib/flapjack/gateways/web/views/layout.erb +3 -3
- data/lib/flapjack/gateways/web/views/self_stats.html.erb +5 -6
- data/lib/flapjack/notifier.rb +4 -1
- data/lib/flapjack/patches.rb +8 -2
- data/lib/flapjack/pikelet.rb +3 -1
- data/lib/flapjack/version.rb +1 -1
- data/libexec/httpbroker.go +1 -1
- data/spec/lib/flapjack/coordinator_spec.rb +3 -3
- data/spec/lib/flapjack/data/contact_spec.rb +2 -2
- data/spec/lib/flapjack/data/entity_check_spec.rb +805 -53
- data/spec/lib/flapjack/data/entity_spec.rb +661 -0
- data/spec/lib/flapjack/gateways/aws_sns_spec.rb +123 -0
- data/spec/lib/flapjack/gateways/jabber_spec.rb +1 -1
- data/spec/lib/flapjack/gateways/jsonapi/check_methods_spec.rb +1 -1
- data/spec/lib/flapjack/gateways/jsonapi/entity_methods_spec.rb +2 -2
- data/spec/lib/flapjack/gateways/pagerduty_spec.rb +1 -1
- data/spec/lib/flapjack/gateways/web_spec.rb +11 -11
- data/spec/support/profile_all_formatter.rb +10 -10
- data/spec/support/uncolored_doc_formatter.rb +66 -4
- data/src/flapjack/event.go +1 -1
- data/tasks/benchmarks.rake +24 -20
- data/tasks/entities.rake +148 -0
- data/tmp/dummy_contacts.json +43 -0
- data/tmp/dummy_entities.json +37 -1
- metadata +43 -7
- data/tmp/test_entities.json +0 -1
data/lib/flapjack/cli/server.rb
CHANGED
@@ -22,8 +22,25 @@ module Flapjack
|
|
22
22
|
@config_env = @config.all
|
23
23
|
|
24
24
|
if @config_env.nil? || @config_env.empty?
|
25
|
-
|
26
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
115
|
-
|
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
|
-
|
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 '
|
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]
|
data/lib/flapjack/data/entity.rb
CHANGED
@@ -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.
|
25
|
-
k =~ /^entity_id:(.+)$/; entity_name = $1
|
26
|
-
|
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
|
-
#
|
31
|
-
#
|
32
|
-
|
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
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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 =>
|
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.
|
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.
|
527
|
+
def self.find_all_with_failing_checks(options)
|
135
528
|
raise "Redis connection not set" unless redis = options[:redis]
|
136
|
-
|
529
|
+
Flapjack::Data::EntityCheck.find_current_failing_by_entity(:redis => redis).keys
|
137
530
|
end
|
138
531
|
|
139
532
|
def contacts
|