flapjack 0.7.2 → 0.7.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +7 -0
- data/etc/flapjack_config.yaml.example +2 -0
- data/features/notification_rules.feature +4 -4
- data/features/steps/events_steps.rb +28 -15
- data/lib/flapjack/coordinator.rb +9 -1
- data/lib/flapjack/data/contact.rb +1 -0
- data/lib/flapjack/data/notification_rule.rb +197 -93
- data/lib/flapjack/executive.rb +11 -20
- data/lib/flapjack/gateways/api.rb +247 -363
- data/lib/flapjack/gateways/web.rb +28 -16
- data/lib/flapjack/gateways/web/views/_foot.haml +2 -2
- data/lib/flapjack/gateways/web/views/entity.haml +8 -2
- data/lib/flapjack/gateways/web/views/self_stats.haml +1 -1
- data/lib/flapjack/patches.rb +20 -1
- data/lib/flapjack/pikelet.rb +5 -14
- data/lib/flapjack/utility.rb +16 -0
- data/lib/flapjack/version.rb +1 -1
- data/spec/lib/flapjack/coordinator_spec.rb +29 -0
- data/spec/lib/flapjack/data/contact_spec.rb +1 -4
- data/spec/lib/flapjack/data/notification_rule_spec.rb +102 -9
- data/spec/lib/flapjack/gateways/api_spec.rb +9 -11
- data/spec/lib/flapjack/gateways/web_spec.rb +15 -19
- data/spec/lib/flapjack/pikelet_spec.rb +2 -56
- metadata +8 -2
@@ -71,12 +71,13 @@ module Flapjack
|
|
71
71
|
end
|
72
72
|
|
73
73
|
get '/' do
|
74
|
-
|
74
|
+
check_stats
|
75
|
+
entity_stats
|
75
76
|
haml :index
|
76
77
|
end
|
77
78
|
|
78
79
|
get '/checks_all' do
|
79
|
-
|
80
|
+
check_stats
|
80
81
|
@adjective = 'all'
|
81
82
|
|
82
83
|
# TODO (?) recast as Entity.all do |e|; e.checks.do |ec|; ...
|
@@ -89,7 +90,7 @@ module Flapjack
|
|
89
90
|
end
|
90
91
|
|
91
92
|
get '/checks_failing' do
|
92
|
-
|
93
|
+
check_stats
|
93
94
|
@adjective = 'failing'
|
94
95
|
|
95
96
|
@states = redis.zrange('failed_checks', 0, -1).map {|key|
|
@@ -102,11 +103,15 @@ module Flapjack
|
|
102
103
|
|
103
104
|
get '/self_stats' do
|
104
105
|
self_stats
|
106
|
+
entity_stats
|
107
|
+
check_stats
|
105
108
|
haml :self_stats
|
106
109
|
end
|
107
110
|
|
108
111
|
get '/self_stats.json' do
|
109
112
|
self_stats
|
113
|
+
entity_stats
|
114
|
+
check_stats
|
110
115
|
{
|
111
116
|
'events_queued' => @events_queued,
|
112
117
|
'all_entities' => @count_all_entities,
|
@@ -128,7 +133,7 @@ module Flapjack
|
|
128
133
|
'average' => @event_rate_all,
|
129
134
|
}
|
130
135
|
},
|
131
|
-
'total_keys' => @
|
136
|
+
'total_keys' => @dbsize,
|
132
137
|
'uptime' => @uptime_string,
|
133
138
|
'boottime' => @boot_time,
|
134
139
|
'current_time' => Time.now,
|
@@ -137,22 +142,22 @@ module Flapjack
|
|
137
142
|
end
|
138
143
|
|
139
144
|
get '/entities_all' do
|
140
|
-
|
145
|
+
entity_stats
|
141
146
|
@adjective = 'all'
|
142
147
|
@entities = Flapjack::Data::Entity.find_all_with_checks(:redis => redis)
|
143
148
|
haml :entities
|
144
149
|
end
|
145
150
|
|
146
151
|
get '/entities_failing' do
|
147
|
-
|
152
|
+
entity_stats
|
148
153
|
@adjective = 'failing'
|
149
154
|
@entities = Flapjack::Data::Entity.find_all_with_failing_checks(:redis => redis)
|
150
155
|
haml :entities
|
151
156
|
end
|
152
157
|
|
153
158
|
get '/entity/:entity' do
|
154
|
-
self_stats
|
155
159
|
@entity = params[:entity]
|
160
|
+
entity_stats
|
156
161
|
@states = redis.keys("#{@entity}:*:states").map { |r|
|
157
162
|
parts = r.split(':')[0..1]
|
158
163
|
[parts[0], parts[1]] + entity_check_state(parts[0], parts[1])
|
@@ -167,7 +172,8 @@ module Flapjack
|
|
167
172
|
entity_check = get_entity_check(@entity, @check)
|
168
173
|
return 404 if entity_check.nil?
|
169
174
|
|
170
|
-
|
175
|
+
check_stats
|
176
|
+
|
171
177
|
last_change = entity_check.last_change
|
172
178
|
|
173
179
|
@check_state = entity_check.state
|
@@ -263,13 +269,13 @@ module Flapjack
|
|
263
269
|
end
|
264
270
|
|
265
271
|
get '/contacts' do
|
266
|
-
self_stats
|
272
|
+
#self_stats
|
267
273
|
@contacts = Flapjack::Data::Contact.all(:redis => redis)
|
268
274
|
haml :contacts
|
269
275
|
end
|
270
276
|
|
271
277
|
get "/contacts/:contact" do
|
272
|
-
self_stats
|
278
|
+
#self_stats
|
273
279
|
contact_id = params[:contact]
|
274
280
|
|
275
281
|
if contact_id
|
@@ -332,13 +338,8 @@ module Flapjack
|
|
332
338
|
@fqdn = `/bin/hostname -f`.chomp
|
333
339
|
@pid = Process.pid
|
334
340
|
@instance_id = "#{@fqdn}:#{@pid}"
|
335
|
-
@version = Flapjack::VERSION
|
336
341
|
|
337
|
-
@
|
338
|
-
@count_all_checks = redis.keys('check:*:*').length
|
339
|
-
@count_failing_checks = redis.zcard 'failed_checks'
|
340
|
-
@count_all_entities = Flapjack::Data::Entity.find_all_with_checks(:redis => redis).length
|
341
|
-
@count_failing_entities = Flapjack::Data::Entity.find_all_with_failing_checks(:redis => redis).length
|
342
|
+
@dbsize = redis.dbsize
|
342
343
|
@executive_instances = redis.keys("executive_instance:*").map {|i|
|
343
344
|
[ i.match(/executive_instance:(.*)/)[1], redis.hget(i, 'boot_time').to_i ]
|
344
345
|
}.sort {|a, b| b[1] <=> a[1]}
|
@@ -352,6 +353,17 @@ module Flapjack
|
|
352
353
|
@events_queued = redis.llen('events')
|
353
354
|
end
|
354
355
|
|
356
|
+
def entity_stats
|
357
|
+
@count_all_entities = Flapjack::Data::Entity.find_all_with_checks(:redis => redis).length
|
358
|
+
@count_failing_entities = Flapjack::Data::Entity.find_all_with_failing_checks(:redis => redis).length
|
359
|
+
end
|
360
|
+
|
361
|
+
def check_stats
|
362
|
+
@count_all_checks = redis.keys('check:*:*').length
|
363
|
+
@count_failing_checks = redis.zcard 'failed_checks'
|
364
|
+
end
|
365
|
+
|
366
|
+
|
355
367
|
end
|
356
368
|
|
357
369
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
%div{:class => "container"}
|
2
2
|
%p{:class => "muted credit"}
|
3
|
-
= "Flapjack version #{
|
3
|
+
= "Flapjack version #{::Flapjack::VERSION} | "
|
4
4
|
%a{:href => 'http://flapjack-project.com'}
|
5
5
|
Flapjack Project
|
6
6
|
= " | "
|
7
7
|
%a{:href => 'https://github.com/flpjck/flapjack'}
|
8
|
-
Flapjack on Github
|
8
|
+
Flapjack on Github
|
@@ -21,11 +21,17 @@
|
|
21
21
|
%th Last Update
|
22
22
|
%th Last Notification
|
23
23
|
- @states.each do |entity, check, status, changed, updated, in_unscheduled_outage, in_scheduled_outage, notified_kind, notified|
|
24
|
-
|
24
|
+
- colour_class = status
|
25
|
+
- case status
|
26
|
+
- when 'critical', 'unknown'
|
27
|
+
- colour_class = 'error'
|
28
|
+
- when 'ok', 'up'
|
29
|
+
- colour_class = 'success'
|
30
|
+
%tr{:class => colour_class}
|
25
31
|
%td
|
26
32
|
- link = "/check?entity=" + CGI.escape(entity) + "&check=" + CGI.escape(check)
|
27
33
|
%a(title='check detail' href=link) #{check}
|
28
|
-
%td
|
34
|
+
%td
|
29
35
|
= status.upcase
|
30
36
|
= " (Acknowledged)" if in_unscheduled_outage
|
31
37
|
= " (Scheduled Maintenance)" if in_scheduled_outage
|
data/lib/flapjack/patches.rb
CHANGED
@@ -44,8 +44,27 @@ class Hash
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
-
# we don't want to stop the entire EM reactor when we stop a web server
|
48
47
|
module Thin
|
48
|
+
|
49
|
+
# see https://github.com/flpjck/flapjack/issues/169
|
50
|
+
class Request
|
51
|
+
class EqlTempfile < ::Tempfile
|
52
|
+
def eql?(obj)
|
53
|
+
obj.equal?(self) && (obj == self)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def move_body_to_tempfile
|
58
|
+
current_body = @body
|
59
|
+
current_body.rewind
|
60
|
+
@body = Thin::Request::EqlTempfile.new(BODY_TMPFILE)
|
61
|
+
@body.binmode
|
62
|
+
@body << current_body.read
|
63
|
+
@env[RACK_INPUT] = @body
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# we don't want to stop the entire EM reactor when we stop a web server
|
49
68
|
module Backends
|
50
69
|
class Base
|
51
70
|
def stop!
|
data/lib/flapjack/pikelet.rb
CHANGED
@@ -110,13 +110,7 @@ module Flapjack
|
|
110
110
|
|
111
111
|
def start
|
112
112
|
@fiber = Fiber.new {
|
113
|
-
|
114
|
-
@pikelet.start
|
115
|
-
rescue Exception => e
|
116
|
-
trace = e.backtrace.join("\n")
|
117
|
-
@logger.fatal "#{e.message}\n#{trace}"
|
118
|
-
stop
|
119
|
-
end
|
113
|
+
@pikelet.start
|
120
114
|
}
|
121
115
|
super
|
122
116
|
@fiber.resume
|
@@ -172,13 +166,7 @@ module Flapjack
|
|
172
166
|
|
173
167
|
def start
|
174
168
|
@fiber = Fiber.new {
|
175
|
-
|
176
|
-
@worker.work(0.1)
|
177
|
-
rescue Exception => e
|
178
|
-
trace = e.backtrace.join("\n")
|
179
|
-
@logger.fatal "#{e.message}\n#{trace}"
|
180
|
-
stop
|
181
|
-
end
|
169
|
+
@worker.work(0.1)
|
182
170
|
}
|
183
171
|
super
|
184
172
|
@klass.start if @klass.respond_to?(:start)
|
@@ -225,11 +213,14 @@ module Flapjack
|
|
225
213
|
if @config
|
226
214
|
@port = @config['port']
|
227
215
|
@port = @port.nil? ? nil : @port.to_i
|
216
|
+
@timeout = @config['timeout']
|
217
|
+
@timeout = @timeout.nil? ? 300 : @timeout.to_i
|
228
218
|
end
|
229
219
|
@port = 3001 if (@port.nil? || @port <= 0 || @port > 65535)
|
230
220
|
|
231
221
|
@server = ::Thin::Server.new('0.0.0.0', @port,
|
232
222
|
@klass, :signals => false)
|
223
|
+
@server.timeout = @timeout
|
233
224
|
end
|
234
225
|
|
235
226
|
def start
|
data/lib/flapjack/utility.rb
CHANGED
@@ -52,5 +52,21 @@ module Flapjack
|
|
52
52
|
return obj
|
53
53
|
end
|
54
54
|
|
55
|
+
# The passed block will be provided each value from the args
|
56
|
+
# and must return array pairs [key, value] representing members of
|
57
|
+
# the hash this method returns. Keys should be unique -- if they're
|
58
|
+
# not, the earlier pair for that key will be overwritten.
|
59
|
+
def hashify(*args, &block)
|
60
|
+
key_value_pairs = args.map {|a| yield(a) }
|
61
|
+
|
62
|
+
# if using Ruby 1.9,
|
63
|
+
# Hash[ key_value_pairs ]
|
64
|
+
# is all that's needed, but for Ruby 1.8 compatability, these must
|
65
|
+
# be flattened and the resulting array unpacked. flatten(1) only
|
66
|
+
# flattens the arrays constructed in the block, it won't mess up
|
67
|
+
# any values (or keys) that are themselves arrays/hashes.
|
68
|
+
Hash[ *( key_value_pairs.flatten(1) )]
|
69
|
+
end
|
70
|
+
|
55
71
|
end
|
56
72
|
end
|
data/lib/flapjack/version.rb
CHANGED
@@ -54,6 +54,34 @@ describe Flapjack::Coordinator do
|
|
54
54
|
fc.stop
|
55
55
|
end
|
56
56
|
|
57
|
+
it "handles an exception raised by a pikelet and shuts down" do
|
58
|
+
setup_logger
|
59
|
+
logger.should_receive(:fatal)
|
60
|
+
|
61
|
+
cfg = {'executive' => {'enabled' => 'yes'}}
|
62
|
+
EM.should_receive(:synchrony).and_yield
|
63
|
+
config.should_receive(:for_redis).and_return({})
|
64
|
+
config.should_receive(:all).and_return(cfg)
|
65
|
+
|
66
|
+
executive = mock('executive')
|
67
|
+
executive.should_receive(:start).and_raise(RuntimeError)
|
68
|
+
executive.should_receive(:stop)
|
69
|
+
executive.should_receive(:update_status)
|
70
|
+
executive.should_receive(:status).exactly(3).times.and_return('stopped')
|
71
|
+
|
72
|
+
fc = Flapjack::Coordinator.new(config)
|
73
|
+
Flapjack::Pikelet.should_receive(:create).with('executive',
|
74
|
+
:config => cfg['executive'], :redis_config => {}).and_return(executive)
|
75
|
+
|
76
|
+
fiber.should_receive(:resume)
|
77
|
+
Fiber.should_receive(:new).and_yield.and_return(fiber)
|
78
|
+
|
79
|
+
EM.should_receive(:stop)
|
80
|
+
|
81
|
+
fc.start(:signals => false)
|
82
|
+
fc.stop
|
83
|
+
end
|
84
|
+
|
57
85
|
it "traps system signals and shuts down" do
|
58
86
|
setup_logger
|
59
87
|
|
@@ -143,6 +171,7 @@ describe Flapjack::Coordinator do
|
|
143
171
|
new_config.should_receive(:all).and_return(new_cfg)
|
144
172
|
|
145
173
|
executive = mock('executive')
|
174
|
+
executive.should_not_receive(:start)
|
146
175
|
executive.should_receive(:type).exactly(3).times.and_return('executive')
|
147
176
|
executive.should_receive(:reload).with(new_cfg['executive']).and_return(true)
|
148
177
|
executive.should_not_receive(:stop)
|
@@ -9,13 +9,10 @@ require 'flapjack/data/notification_rule'
|
|
9
9
|
|
10
10
|
describe Flapjack::Data::Contact, :redis => true do
|
11
11
|
|
12
|
-
weekdays_8_18 = IceCube::Schedule.new(Time.local(2013,2,1,8,0,0), :duration => 60 * 60 * 10)
|
13
|
-
weekdays_8_18.add_recurrence_rule(IceCube::Rule.weekly.day(:monday, :tuesday, :wednesday, :thursday, :friday))
|
14
|
-
|
15
12
|
let(:notification_rule_data) {
|
16
13
|
{:entity_tags => ["database","physical"],
|
17
14
|
:entities => ["foo-app-01.example.com"],
|
18
|
-
:time_restrictions => [
|
15
|
+
:time_restrictions => [],
|
19
16
|
:warning_media => ["email"],
|
20
17
|
:critical_media => ["sms", "email"],
|
21
18
|
:warning_blackhole => false,
|
@@ -1,22 +1,115 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'flapjack/data/notification_rule'
|
3
3
|
|
4
|
-
describe Flapjack::Data::NotificationRule do
|
4
|
+
describe Flapjack::Data::NotificationRule, :redis => true do
|
5
5
|
|
6
|
-
|
6
|
+
let(:weekdays_8_18) {
|
7
|
+
wd = IceCube::Schedule.new(Time.local(2013,2,1,8,0,0), :duration => 60 * 60 * 10)
|
8
|
+
wd.add_recurrence_rule(IceCube::Rule.weekly.day(:monday, :tuesday, :wednesday, :thursday, :friday))
|
9
|
+
wd = wd.to_hash
|
10
|
+
wd[:start_time] = wd.delete(:start_date)
|
11
|
+
wd[:rrules].first[:rule_type] = wd[:rrules].first[:rule_type].sub(/\AIceCube::(\w+)Rule\z/, '\1')
|
12
|
+
wd
|
13
|
+
}
|
7
14
|
|
8
|
-
|
15
|
+
let(:rule_data) {
|
16
|
+
{:contact_id => '23',
|
17
|
+
:entity_tags => ["database","physical"],
|
18
|
+
:entities => ["foo-app-01.example.com"],
|
19
|
+
:time_restrictions => [ weekdays_8_18 ],
|
20
|
+
:warning_media => ["email"],
|
21
|
+
:critical_media => ["sms", "email"],
|
22
|
+
:warning_blackhole => false,
|
23
|
+
:critical_blackhole => false
|
24
|
+
}
|
25
|
+
}
|
9
26
|
|
10
|
-
|
27
|
+
let(:rule_id) { 'ABC123' }
|
11
28
|
|
12
|
-
|
29
|
+
let(:timezone) { ActiveSupport::TimeZone.new("Europe/Moscow") }
|
13
30
|
|
14
|
-
|
31
|
+
let(:existing_rule) {
|
32
|
+
Flapjack::Data::NotificationRule.add(rule_data, :redis => @redis)
|
33
|
+
}
|
15
34
|
|
16
|
-
it "checks
|
35
|
+
it "checks that a notification rule exists" do
|
36
|
+
Flapjack::Data::NotificationRule.exists_with_id?(existing_rule.id, :redis => @redis).should be_true
|
37
|
+
Flapjack::Data::NotificationRule.exists_with_id?('not_there', :redis => @redis).should be_false
|
38
|
+
end
|
17
39
|
|
18
|
-
it "
|
40
|
+
it "returns a notification rule if it exists" do
|
41
|
+
rule = Flapjack::Data::NotificationRule.find_by_id(existing_rule.id, :redis => @redis)
|
42
|
+
rule.should_not be_nil
|
43
|
+
end
|
19
44
|
|
20
|
-
it "
|
45
|
+
it "does not return a notification rule if it does not exist" do
|
46
|
+
rule = Flapjack::Data::NotificationRule.find_by_id('not_there', :redis => @redis)
|
47
|
+
rule.should be_nil
|
48
|
+
end
|
49
|
+
|
50
|
+
it "updates a notification rule" do
|
51
|
+
rule = existing_rule
|
52
|
+
|
53
|
+
expect {
|
54
|
+
rule_data[:warning_blackhole] = true
|
55
|
+
success = rule.update(rule_data)
|
56
|
+
success.should be_true
|
57
|
+
}.to change { rule.warning_blackhole }.from(false).to(true)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "converts time restriction data to an IceCube schedule" do
|
61
|
+
sched = Flapjack::Data::NotificationRule.
|
62
|
+
time_restriction_to_icecube_schedule(weekdays_8_18, timezone)
|
63
|
+
sched.should_not be_nil
|
64
|
+
end
|
65
|
+
|
66
|
+
it "generates a JSON string representing its data" do
|
67
|
+
rule = existing_rule
|
68
|
+
# bit of extra hackery for the inserted ID values
|
69
|
+
rule.to_json.should == {:id => rule.id}.merge(rule_data).to_json
|
70
|
+
end
|
71
|
+
|
72
|
+
it "checks whether entity names match" do
|
73
|
+
rule = existing_rule
|
74
|
+
|
75
|
+
rule.match_entity?('foo-app-01.example.com').should be_true
|
76
|
+
rule.match_entity?('foo-app-02.example.com').should be_false
|
77
|
+
end
|
78
|
+
|
79
|
+
pending "check whether entity tags match"
|
80
|
+
|
81
|
+
it "checks if blackhole settings for a rule match a severity level" do
|
82
|
+
rule_data[:warning_blackhole] = true
|
83
|
+
rule = Flapjack::Data::NotificationRule.add(rule_data, :redis => @redis)
|
84
|
+
|
85
|
+
rule.blackhole?('warning').should be_true
|
86
|
+
rule.blackhole?('critical').should be_false
|
87
|
+
end
|
88
|
+
|
89
|
+
it "returns the media settings for a rule's severity level" do
|
90
|
+
rule = existing_rule
|
91
|
+
rule.media_for_severity('warning').should == ['email']
|
92
|
+
rule.media_for_severity('critical').should =~ ['email', 'sms']
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'validation' do
|
96
|
+
|
97
|
+
it "fails to add a notification rule with invalid data" do
|
98
|
+
rule_data[:entities] = []
|
99
|
+
rule_data[:entity_tags] = []
|
100
|
+
rule = Flapjack::Data::NotificationRule.add(rule_data, :redis => @redis)
|
101
|
+
rule.should be_nil
|
102
|
+
end
|
103
|
+
|
104
|
+
it "fails to update a notification rule with invalid data" do
|
105
|
+
rule = Flapjack::Data::NotificationRule.add(rule_data, :redis => @redis)
|
106
|
+
expect {
|
107
|
+
rule_data[:entities] = [57]
|
108
|
+
success = rule.update(rule_data)
|
109
|
+
success.should be_false
|
110
|
+
}.not_to change { rule.entities }
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
21
114
|
|
22
115
|
end
|