flapjack 0.7.2 → 0.7.3
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.
- 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
|