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.
@@ -71,12 +71,13 @@ module Flapjack
71
71
  end
72
72
 
73
73
  get '/' do
74
- self_stats
74
+ check_stats
75
+ entity_stats
75
76
  haml :index
76
77
  end
77
78
 
78
79
  get '/checks_all' do
79
- self_stats
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
- self_stats
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' => @keys.length,
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
- self_stats
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
- self_stats
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
- self_stats
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
- @keys = redis.keys '*'
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 #{@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
- %tr
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{:class => status}
34
+ %td
29
35
  = status.upcase
30
36
  = " (Acknowledged)" if in_unscheduled_outage
31
37
  = " (Scheduled Maintenance)" if in_scheduled_outage
@@ -39,7 +39,7 @@
39
39
  %td #{@event_rate_all} events per second
40
40
  %tr
41
41
  %td Total keys in redis
42
- %td #{@keys.length}
42
+ %td #{@dbsize}
43
43
  %tr
44
44
  %td Uptime
45
45
  %td= @uptime_string
@@ -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!
@@ -110,13 +110,7 @@ module Flapjack
110
110
 
111
111
  def start
112
112
  @fiber = Fiber.new {
113
- begin
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
- begin
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
@@ -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
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  module Flapjack
4
- VERSION = "0.7.2"
4
+ VERSION = "0.7.3"
5
5
  end
@@ -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 => [ weekdays_8_18.to_hash ],
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
- it "checks that a notification rule exists"
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
- it "returns a notification rule if it exists"
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
- it "does not return a notification rule if it does not exist"
27
+ let(:rule_id) { 'ABC123' }
11
28
 
12
- it "updates a notification rule"
29
+ let(:timezone) { ActiveSupport::TimeZone.new("Europe/Moscow") }
13
30
 
14
- it "checks whether tag or entity names match"
31
+ let(:existing_rule) {
32
+ Flapjack::Data::NotificationRule.add(rule_data, :redis => @redis)
33
+ }
15
34
 
16
- it "checks whether times match"
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 "checks if blackhole settings for a rule match a severity level"
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 "returns the media settings for a rule's severity level"
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