sensu 0.9.7.beta.2 → 0.9.7.beta.3

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -15,8 +15,16 @@ AMQP handlers can no longer use `"send_only_check_output": true`, but
15
15
  instead have access to the built-in mutators `"mutator": "only_check_output"` and
16
16
  `"mutator": "only_check_output_split"`.
17
17
 
18
+ Ruby 1.8.7-p249 is no longer supported, as the AMQP library no longer
19
+ does. Please use the Sensu APT/YUM packages which contain an embedded
20
+ Ruby.
21
+
18
22
  ### Other
19
23
 
24
+ Improved RabbitMQ and Redis connection recovery.
25
+
26
+ Fixed API POST input validation.
27
+
20
28
  Redis client connection heartbeat.
21
29
 
22
30
  Improved graceful process termination.
data/lib/sensu/api.rb CHANGED
@@ -8,57 +8,116 @@ module Sensu
8
8
  class API < Sinatra::Base
9
9
  register Sinatra::Async
10
10
 
11
- def self.run(options={})
12
- EM::run do
13
- self.setup(options)
14
-
15
- Thin::Logging.silent = true
16
- Thin::Server.start(self, $settings[:api][:port])
11
+ class << self
12
+ def run(options={})
13
+ EM::run do
14
+ bootstrap(options)
15
+ start
16
+ trap_signals
17
+ end
18
+ end
17
19
 
18
- %w[INT TERM].each do |signal|
19
- Signal.trap(signal) do
20
- self.stop(signal)
20
+ def bootstrap(options={})
21
+ $logger = Cabin::Channel.get
22
+ base = Sensu::Base.new(options)
23
+ $settings = base.settings
24
+ if $settings[:api][:user] && $settings[:api][:password]
25
+ use Rack::Auth::Basic do |user, password|
26
+ user == $settings[:api][:user] && password == $settings[:api][:password]
21
27
  end
22
28
  end
23
29
  end
24
- end
25
30
 
26
- def self.setup(options={})
27
- $logger = Cabin::Channel.get
28
- base = Sensu::Base.new(options)
29
- $settings = base.settings
30
- $logger.debug('connecting to redis', {
31
- :settings => $settings[:redis]
32
- })
33
- $redis = Sensu::Redis.connect($settings[:redis])
34
- $redis.on_disconnect = Proc.new do
35
- if $redis.connection_established?
36
- $logger.warn('reconnecting to redis')
37
- $redis.reconnect!
38
- else
31
+ def setup_redis
32
+ $logger.debug('connecting to redis', {
33
+ :settings => $settings[:redis]
34
+ })
35
+ connection_failure = Proc.new do
39
36
  $logger.fatal('cannot connect to redis', {
40
37
  :settings => $settings[:redis]
41
38
  })
42
39
  $logger.fatal('SENSU NOT RUNNING!')
40
+ if $rabbitmq
41
+ $rabbitmq.close
42
+ end
43
43
  exit 2
44
44
  end
45
+ $redis = Sensu::Redis.connect($settings[:redis], :on_tcp_connection_failure => connection_failure)
46
+ $redis.on_tcp_connection_loss do |connection, settings|
47
+ $logger.warn('reconnecting to redis')
48
+ connection.reconnect(false, 10)
49
+ end
45
50
  end
46
- $logger.debug('connecting to rabbitmq', {
47
- :settings => $settings[:rabbitmq]
48
- })
49
- $rabbitmq = AMQP.connect($settings[:rabbitmq])
50
- $rabbitmq.on_disconnect = Proc.new do
51
- $logger.fatal('cannot connect to rabbitmq', {
51
+
52
+ def setup_rabbitmq
53
+ $logger.debug('connecting to rabbitmq', {
52
54
  :settings => $settings[:rabbitmq]
53
55
  })
54
- $logger.fatal('SENSU NOT RUNNING!')
56
+ connection_failure = Proc.new do
57
+ $logger.fatal('cannot connect to rabbitmq', {
58
+ :settings => $settings[:rabbitmq]
59
+ })
60
+ $logger.fatal('SENSU NOT RUNNING!')
61
+ $redis.close
62
+ exit 2
63
+ end
64
+ $rabbitmq = AMQP.connect($settings[:rabbitmq], :on_tcp_connection_failure => connection_failure)
65
+ $rabbitmq.on_tcp_connection_loss do |connection, settings|
66
+ $logger.warn('reconnecting to rabbitmq')
67
+ connection.reconnect(false, 10)
68
+ end
69
+ $amq = AMQP::Channel.new($rabbitmq)
70
+ $amq.auto_recovery = true
71
+ end
72
+
73
+ def start
74
+ setup_redis
75
+ setup_rabbitmq
76
+ Thin::Logging.silent = true
77
+ Thin::Server.start(self, $settings[:api][:port])
78
+ end
79
+
80
+ def stop
81
+ $logger.warn('stopping')
82
+ $rabbitmq.close
55
83
  $redis.close
56
- exit 2
84
+ $logger.warn('stopping reactor')
85
+ EM::stop_event_loop
86
+ end
87
+
88
+ def trap_signals
89
+ %w[INT TERM].each do |signal|
90
+ Signal.trap(signal) do
91
+ $logger.warn('received signal', {
92
+ :signal => signal
93
+ })
94
+ stop
95
+ end
96
+ end
57
97
  end
58
- $amq = AMQP::Channel.new($rabbitmq)
59
- if $settings[:api][:user] && $settings[:api][:password]
60
- use Rack::Auth::Basic do |user, password|
61
- user == $settings[:api][:user] && password == $settings[:api][:password]
98
+
99
+ def run_test(options={}, &block)
100
+ bootstrap(options)
101
+ start
102
+ $settings[:client][:timestamp] = Time.now.to_i
103
+ $redis.set('client:' + $settings[:client][:name], $settings[:client].to_json).callback do
104
+ $redis.sadd('clients', $settings[:client][:name]).callback do
105
+ $redis.hset('events:' + $settings[:client][:name], 'test', {
106
+ :output => 'CRITICAL',
107
+ :status => 2,
108
+ :issued => Time.now.to_i,
109
+ :flapping => false,
110
+ :occurrences => 1
111
+ }.to_json).callback do
112
+ $redis.set('stash:test/test', {:key => 'value'}.to_json).callback do
113
+ $redis.sadd('stashes', 'test/test').callback do
114
+ EM::Timer.new(0.5) do
115
+ block.call
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
62
121
  end
63
122
  end
64
123
  end
@@ -96,6 +155,31 @@ module Sensu
96
155
  end
97
156
  end
98
157
 
158
+ def bad_request!
159
+ status 400
160
+ body ''
161
+ end
162
+
163
+ def not_found!
164
+ status 404
165
+ body ''
166
+ end
167
+
168
+ def created!
169
+ status 201
170
+ body ''
171
+ end
172
+
173
+ def accepted!
174
+ status 202
175
+ body ''
176
+ end
177
+
178
+ def no_content!
179
+ status 204
180
+ body ''
181
+ end
182
+
99
183
  def event_hash(event_json, client_name, check_name)
100
184
  JSON.parse(event_json, :symbolize_names => true).merge(
101
185
  :client => client_name,
@@ -163,8 +247,7 @@ module Sensu
163
247
  unless client_json.nil?
164
248
  body client_json
165
249
  else
166
- status 404
167
- body ''
250
+ not_found!
168
251
  end
169
252
  end
170
253
  end
@@ -191,12 +274,10 @@ module Sensu
191
274
  $redis.del('history:' + client_name)
192
275
  end
193
276
  end
194
- status 202
195
- body ''
277
+ accepted!
196
278
  end
197
279
  else
198
- status 404
199
- body ''
280
+ not_found!
200
281
  end
201
282
  end
202
283
  end
@@ -210,8 +291,7 @@ module Sensu
210
291
  response = $settings[:checks][check_name].merge(:name => check_name)
211
292
  body response.to_json
212
293
  else
213
- status 404
214
- body ''
294
+ not_found!
215
295
  end
216
296
  end
217
297
 
@@ -220,27 +300,25 @@ module Sensu
220
300
  post_body = JSON.parse(request.body.read, :symbolize_names => true)
221
301
  check_name = post_body[:check]
222
302
  subscribers = post_body[:subscribers]
223
- rescue JSON::ParserError, TypeError
224
- status 400
225
- body ''
226
- end
227
- if check_name.is_a?(String) && subscribers.is_a?(Array)
228
- payload = {
229
- :name => check_name,
230
- :issued => Time.now.to_i
231
- }
232
- $logger.info('publishing check request', {
233
- :payload => payload,
234
- :subscribers => subscribers
235
- })
236
- subscribers.uniq.each do |exchange_name|
237
- $amq.fanout(exchange_name).publish(payload.to_json)
303
+ if check_name.is_a?(String) && subscribers.is_a?(Array)
304
+ payload = {
305
+ :name => check_name,
306
+ :issued => Time.now.to_i
307
+ }
308
+ $logger.info('publishing check request', {
309
+ :payload => payload,
310
+ :subscribers => subscribers
311
+ })
312
+ subscribers.uniq.each do |exchange_name|
313
+ $amq.fanout(exchange_name).publish(payload.to_json)
314
+ end
315
+ created!
316
+ else
317
+ bad_request!
238
318
  end
239
- status 201
240
- else
241
- status 400
319
+ rescue JSON::ParserError, TypeError
320
+ bad_request!
242
321
  end
243
- body ''
244
322
  end
245
323
 
246
324
  aget '/events' do
@@ -279,8 +357,7 @@ module Sensu
279
357
  unless event_json.nil?
280
358
  body event_hash(event_json, client_name, check_name).to_json
281
359
  else
282
- status 404
283
- body ''
360
+ not_found!
284
361
  end
285
362
  end
286
363
  end
@@ -289,11 +366,10 @@ module Sensu
289
366
  $redis.hgetall('events:' + client_name).callback do |events|
290
367
  if events.include?(check_name)
291
368
  resolve_event(client_name, check_name)
292
- status 202
369
+ accepted!
293
370
  else
294
- status 404
371
+ not_found!
295
372
  end
296
- body ''
297
373
  end
298
374
  end
299
375
 
@@ -302,48 +378,42 @@ module Sensu
302
378
  post_body = JSON.parse(request.body.read, :symbolize_names => true)
303
379
  client_name = post_body[:client]
304
380
  check_name = post_body[:check]
305
- rescue JSON::ParserError, TypeError
306
- status 400
307
- body ''
308
- end
309
- if client_name.is_a?(String) && check_name.is_a?(String)
310
- $redis.hgetall('events:' + client_name).callback do |events|
311
- if events.include?(check_name)
312
- resolve_event(client_name, check_name)
313
- status 202
314
- else
315
- status 404
381
+ if client_name.is_a?(String) && check_name.is_a?(String)
382
+ $redis.hgetall('events:' + client_name).callback do |events|
383
+ if events.include?(check_name)
384
+ resolve_event(client_name, check_name)
385
+ accepted!
386
+ else
387
+ not_found!
388
+ end
316
389
  end
317
- body ''
390
+ else
391
+ bad_request!
318
392
  end
319
- else
320
- status 400
321
- body ''
393
+ rescue JSON::ParserError, TypeError
394
+ bad_request!
322
395
  end
323
396
  end
324
397
 
325
398
  apost %r{/stash(?:es)?/(.*)} do |path|
326
399
  begin
327
400
  post_body = JSON.parse(request.body.read)
328
- rescue JSON::ParserError
329
- status 400
330
- body ''
331
- end
332
- $redis.set('stash:' + path, post_body.to_json).callback do
333
- $redis.sadd('stashes', path).callback do
334
- status 201
335
- body ''
401
+ $redis.set('stash:' + path, post_body.to_json).callback do
402
+ $redis.sadd('stashes', path).callback do
403
+ created!
404
+ end
336
405
  end
406
+ rescue JSON::ParserError
407
+ bad_request!
337
408
  end
338
409
  end
339
410
 
340
411
  aget %r{/stash(?:es)?/(.*)} do |path|
341
412
  $redis.get('stash:' + path).callback do |stash_json|
342
- if stash_json.nil?
343
- status 404
344
- body ''
345
- else
413
+ unless stash_json.nil?
346
414
  body stash_json
415
+ else
416
+ not_found!
347
417
  end
348
418
  end
349
419
  end
@@ -353,13 +423,11 @@ module Sensu
353
423
  if stash_exists
354
424
  $redis.srem('stashes', path).callback do
355
425
  $redis.del('stash:' + path).callback do
356
- status 204
357
- body ''
426
+ no_content!
358
427
  end
359
428
  end
360
429
  else
361
- status 404
362
- body ''
430
+ not_found!
363
431
  end
364
432
  end
365
433
  end
@@ -373,62 +441,24 @@ module Sensu
373
441
  apost '/stashes' do
374
442
  begin
375
443
  post_body = JSON.parse(request.body.read)
376
- rescue JSON::ParserError
377
- status 400
378
- body ''
379
- end
380
- response = Hash.new
381
- if post_body.is_a?(Array) && post_body.size > 0
382
- post_body.each_with_index do |path, index|
383
- $redis.get('stash:' + path).callback do |stash_json|
384
- unless stash_json.nil?
385
- response[path] = JSON.parse(stash_json)
386
- end
387
- if index == post_body.size - 1
388
- body response.to_json
389
- end
390
- end
391
- end
392
- else
393
- status 400
394
- body ''
395
- end
396
- end
397
-
398
- def self.run_test(options={}, &block)
399
- self.setup(options)
400
- $settings[:client][:timestamp] = Time.now.to_i
401
- $redis.set('client:' + $settings[:client][:name], $settings[:client].to_json).callback do
402
- $redis.sadd('clients', $settings[:client][:name]).callback do
403
- $redis.hset('events:' + $settings[:client][:name], 'test', {
404
- :output => 'CRITICAL',
405
- :status => 2,
406
- :issued => Time.now.to_i,
407
- :flapping => false,
408
- :occurrences => 1
409
- }.to_json).callback do
410
- $redis.set('stash:test/test', {:key => 'value'}.to_json).callback do
411
- $redis.sadd('stashes', 'test/test').callback do
412
- Thin::Logging.silent = true
413
- Thin::Server.start(self, $settings[:api][:port])
414
- EM::Timer.new(0.5) do
415
- block.call
416
- end
444
+ if post_body.is_a?(Array) && post_body.size > 0
445
+ response = Hash.new
446
+ post_body.each_with_index do |path, index|
447
+ $redis.get('stash:' + path).callback do |stash_json|
448
+ unless stash_json.nil?
449
+ response[path] = JSON.parse(stash_json)
450
+ end
451
+ if index == post_body.size - 1
452
+ body response.to_json
417
453
  end
418
454
  end
419
455
  end
456
+ else
457
+ bad_request!
420
458
  end
459
+ rescue JSON::ParserError
460
+ bad_request!
421
461
  end
422
462
  end
423
-
424
- def self.stop(signal)
425
- $logger.warn('received signal', {
426
- :signal => signal
427
- })
428
- $logger.warn('stopping')
429
- $redis.close
430
- $logger.warn('stopping reactor')
431
- EM::stop_event_loop
432
- end
433
463
  end
434
464
  end
data/lib/sensu/base.rb CHANGED
@@ -10,7 +10,6 @@ require 'cabin'
10
10
  require 'amqp'
11
11
 
12
12
  require File.join(File.dirname(__FILE__), 'patches', 'ruby')
13
- require File.join(File.dirname(__FILE__), 'patches', 'amqp')
14
13
 
15
14
  require File.join(File.dirname(__FILE__), 'constants')
16
15
  require File.join(File.dirname(__FILE__), 'cli')
data/lib/sensu/client.rb CHANGED
@@ -6,18 +6,8 @@ module Sensu
6
6
  def self.run(options={})
7
7
  client = self.new(options)
8
8
  EM::run do
9
- client.setup_rabbitmq
10
- client.setup_keepalives
11
- client.setup_subscriptions
12
- client.setup_rabbitmq_monitor
13
- client.setup_standalone
14
- client.setup_sockets
15
-
16
- %w[INT TERM].each do |signal|
17
- Signal.trap(signal) do
18
- client.stop(signal)
19
- end
20
- end
9
+ client.start
10
+ client.trap_signals
21
11
  end
22
12
  end
23
13
 
@@ -33,15 +23,20 @@ module Sensu
33
23
  @logger.debug('connecting to rabbitmq', {
34
24
  :settings => @settings[:rabbitmq]
35
25
  })
36
- @rabbitmq = AMQP.connect(@settings[:rabbitmq])
37
- @rabbitmq.on_disconnect = Proc.new do
26
+ connection_failure = Proc.new do
38
27
  @logger.fatal('cannot connect to rabbitmq', {
39
28
  :settings => @settings[:rabbitmq]
40
29
  })
41
30
  @logger.fatal('SENSU NOT RUNNING!')
42
31
  exit 2
43
32
  end
33
+ @rabbitmq = AMQP.connect(@settings[:rabbitmq], :on_tcp_connection_failure => connection_failure)
34
+ @rabbitmq.on_tcp_connection_loss do |connection, settings|
35
+ @logger.warn('reconnecting to rabbitmq')
36
+ connection.reconnect(false, 10)
37
+ end
44
38
  @amq = AMQP::Channel.new(@rabbitmq)
39
+ @amq.auto_recovery = true
45
40
  end
46
41
 
47
42
  def publish_keepalive
@@ -186,20 +181,6 @@ module Sensu
186
181
  end
187
182
  end
188
183
 
189
- def setup_rabbitmq_monitor
190
- @logger.debug('monitoring rabbitmq connection')
191
- @timers << EM::PeriodicTimer.new(5) do
192
- if @rabbitmq.connected?
193
- unless @check_request_queue.subscribed?
194
- @logger.warn('re-subscribing to client subscriptions')
195
- setup_subscriptions
196
- end
197
- else
198
- @logger.warn('reconnecting to rabbitmq')
199
- end
200
- end
201
- end
202
-
203
184
  def setup_standalone
204
185
  @logger.debug('scheduling standalone checks')
205
186
  standalone_check_count = 0
@@ -238,45 +219,56 @@ module Sensu
238
219
  end
239
220
 
240
221
  def unsubscribe(&block)
241
- if @rabbitmq.connected?
242
- @logger.warn('unsubscribing from client subscriptions')
243
- @check_request_queue.unsubscribe
244
- end
245
- if block
246
- block.call
247
- end
222
+ @logger.warn('unsubscribing from client subscriptions')
223
+ @check_request_queue.unsubscribe
224
+ block.call
248
225
  end
249
226
 
250
227
  def complete_checks_in_progress(&block)
251
228
  @logger.info('completing checks in progress', {
252
229
  :checks_in_progress => @checks_in_progress
253
230
  })
254
- if block
255
- retry_until_true do
256
- if @checks_in_progress.empty?
257
- block.call
258
- true
259
- end
231
+ retry_until_true do
232
+ if @checks_in_progress.empty?
233
+ block.call
234
+ true
260
235
  end
261
236
  end
262
237
  end
263
238
 
264
- def stop(signal)
265
- @logger.warn('received signal', {
266
- :signal => signal
267
- })
239
+ def start
240
+ setup_rabbitmq
241
+ setup_keepalives
242
+ setup_subscriptions
243
+ setup_standalone
244
+ setup_sockets
245
+ end
246
+
247
+ def stop
268
248
  @logger.warn('stopping')
269
249
  @timers.each do |timer|
270
250
  timer.cancel
271
251
  end
272
252
  unsubscribe do
273
253
  complete_checks_in_progress do
254
+ @rabbitmq.close
274
255
  @logger.warn('stopping reactor')
275
256
  EM::stop_event_loop
276
257
  end
277
258
  end
278
259
  end
279
260
 
261
+ def trap_signals
262
+ %w[INT TERM].each do |signal|
263
+ Signal.trap(signal) do
264
+ @logger.warn('received signal', {
265
+ :signal => signal
266
+ })
267
+ stop
268
+ end
269
+ end
270
+ end
271
+
280
272
  private
281
273
 
282
274
  def testing?
@@ -1,6 +1,6 @@
1
1
  module Sensu
2
2
  unless defined?(Sensu::VERSION)
3
- VERSION = '0.9.7.beta.2'
3
+ VERSION = '0.9.7.beta.3'
4
4
  end
5
5
 
6
6
  unless defined?(Sensu::DEFAULT_OPTIONS)
data/lib/sensu/logger.rb CHANGED
@@ -2,6 +2,7 @@ module Sensu
2
2
  class Logger
3
3
  def initialize(options={})
4
4
  @logger = Cabin::Channel.get
5
+ STDOUT.sync = true
5
6
  @logger.subscribe(STDOUT)
6
7
  @logger.level = options[:verbose] ? :debug : options[:log_level] || :info
7
8
  @log_file = options[:log_file]
data/lib/sensu/redis.rb CHANGED
@@ -2,13 +2,17 @@ require 'redis'
2
2
 
3
3
  module Sensu
4
4
  class Redis < Redis::Client
5
- attr_accessor :host, :port, :password, :on_disconnect
5
+ attr_accessor :settings, :on_tcp_connection_failure
6
+
7
+ alias :em_reconnect :reconnect
6
8
 
7
9
  def initialize(*arguments)
8
10
  super
9
11
  @logger = Cabin::Channel.get
12
+ @settings = Hash.new
10
13
  @connection_established = false
11
14
  @connected = false
15
+ @reconnecting = false
12
16
  @closing_connection = false
13
17
  end
14
18
 
@@ -23,8 +27,9 @@ module Sensu
23
27
  def connection_completed
24
28
  @connection_established = true
25
29
  @connected = true
26
- if @password
27
- auth(@password).callback do |reply|
30
+ @reconnecting = false
31
+ if @settings[:password]
32
+ auth(@settings[:password]).callback do |reply|
28
33
  unless reply == 'OK'
29
34
  @logger.fatal('redis authentication failed')
30
35
  close_connection
@@ -41,9 +46,14 @@ module Sensu
41
46
  setup_heartbeat
42
47
  end
43
48
 
44
- def reconnect!
45
- EM::Timer.new(1) do
46
- reconnect(@host, @port)
49
+ def reconnect(immediate=false, wait=10)
50
+ if @reconnecting && !immediate
51
+ EM::Timer.new(wait) do
52
+ em_reconnect(@settings[:host], @settings[:port])
53
+ end
54
+ else
55
+ @reconnecting = true
56
+ em_reconnect(@settings[:host], @settings[:port])
47
57
  end
48
58
  end
49
59
 
@@ -52,23 +62,33 @@ module Sensu
52
62
  close_connection
53
63
  end
54
64
 
65
+ def on_tcp_connection_loss(&block)
66
+ if block.respond_to?(:call)
67
+ @on_tcp_connection_loss = block
68
+ end
69
+ end
70
+
55
71
  def unbind
56
72
  @connected = false
57
73
  super
58
- if @on_disconnect && !@closing_connection
59
- @on_disconnect.call
74
+ unless @closing_connection
75
+ if @connection_established
76
+ if @on_tcp_connection_loss
77
+ @on_tcp_connection_loss.call(self, @settings)
78
+ end
79
+ else
80
+ if @on_tcp_connection_failure
81
+ @on_tcp_connection_failure.call(self, @settings)
82
+ end
83
+ end
60
84
  end
61
85
  end
62
86
 
63
- def connection_established?
64
- @connection_established
65
- end
66
-
67
87
  def connected?
68
88
  @connected
69
89
  end
70
90
 
71
- def self.connect(options)
91
+ def self.connect(options, additional={})
72
92
  options ||= Hash.new
73
93
  if options.is_a?(String)
74
94
  begin
@@ -86,11 +106,17 @@ module Sensu
86
106
  port = options[:port] || 6379
87
107
  password = options[:password]
88
108
  end
89
- EM::connect(host, port, self) do |redis|
90
- redis.host = host
91
- redis.port = port
92
- redis.password = password
109
+ connection = EM::connect(host, port, self) do |redis|
110
+ redis.settings = {
111
+ :host => host,
112
+ :port => port,
113
+ :password => password
114
+ }
115
+ end
116
+ if additional[:on_tcp_connection_failure].respond_to?(:call)
117
+ connection.on_tcp_connection_failure = additional[:on_tcp_connection_failure]
93
118
  end
119
+ connection
94
120
  end
95
121
  end
96
122
  end
data/lib/sensu/server.rb CHANGED
@@ -8,18 +8,8 @@ module Sensu
8
8
  def self.run(options={})
9
9
  server = self.new(options)
10
10
  EM::run do
11
- server.setup_redis
12
- server.setup_rabbitmq
13
- server.setup_keepalives
14
- server.setup_results
15
- server.setup_master_monitor
16
- server.setup_rabbitmq_monitor
17
-
18
- %w[INT TERM].each do |signal|
19
- Signal.trap(signal) do
20
- server.stop(signal)
21
- end
22
- end
11
+ server.start
12
+ server.trap_signals
23
13
  end
24
14
  end
25
15
 
@@ -28,26 +18,30 @@ module Sensu
28
18
  base = Sensu::Base.new(options)
29
19
  @settings = base.settings
30
20
  @timers = Array.new
21
+ @master_timers = Array.new
31
22
  @handlers_in_progress_count = 0
23
+ @is_master = false
32
24
  end
33
25
 
34
26
  def setup_redis
35
27
  @logger.debug('connecting to redis', {
36
28
  :settings => @settings[:redis]
37
29
  })
38
- @redis = Sensu::Redis.connect(@settings[:redis])
39
- unless testing?
40
- @redis.on_disconnect = Proc.new do
41
- if @redis.connection_established?
42
- @logger.fatal('redis connection closed')
43
- stop('TERM')
44
- else
45
- @logger.fatal('cannot connect to redis', {
46
- :settings => @settings[:redis]
47
- })
48
- @logger.fatal('SENSU NOT RUNNING!')
49
- exit 2
50
- end
30
+ connection_failure = Proc.new do
31
+ @logger.fatal('cannot connect to redis', {
32
+ :settings => @settings[:redis]
33
+ })
34
+ @logger.fatal('SENSU NOT RUNNING!')
35
+ if @rabbitmq
36
+ @rabbitmq.close
37
+ end
38
+ exit 2
39
+ end
40
+ @redis = Sensu::Redis.connect(@settings[:redis], :on_tcp_connection_failure => connection_failure)
41
+ @redis.on_tcp_connection_loss do
42
+ unless testing?
43
+ @logger.fatal('redis connection closed')
44
+ stop
51
45
  end
52
46
  end
53
47
  end
@@ -56,8 +50,7 @@ module Sensu
56
50
  @logger.debug('connecting to rabbitmq', {
57
51
  :settings => @settings[:rabbitmq]
58
52
  })
59
- @rabbitmq = AMQP.connect(@settings[:rabbitmq])
60
- @rabbitmq.on_disconnect = Proc.new do
53
+ connection_failure = Proc.new do
61
54
  @logger.fatal('cannot connect to rabbitmq', {
62
55
  :settings => @settings[:rabbitmq]
63
56
  })
@@ -65,7 +58,15 @@ module Sensu
65
58
  @redis.close
66
59
  exit 2
67
60
  end
61
+ @rabbitmq = AMQP.connect(@settings[:rabbitmq], :on_tcp_connection_failure => connection_failure)
62
+ @rabbitmq.on_tcp_connection_loss do |connection, settings|
63
+ @logger.warn('reconnecting to rabbitmq')
64
+ resign_as_master do
65
+ connection.reconnect(false, 10)
66
+ end
67
+ end
68
68
  @amq = AMQP::Channel.new(@rabbitmq)
69
+ @amq.auto_recovery = true
69
70
  end
70
71
 
71
72
  def setup_keepalives
@@ -86,16 +87,16 @@ module Sensu
86
87
  subdue = false
87
88
  if check[:subdue].is_a?(Hash)
88
89
  if check[:subdue].has_key?(:start) && check[:subdue].has_key?(:end)
89
- start = Time.parse(check[:subdue][:start])
90
- stop = Time.parse(check[:subdue][:end])
91
- if stop < start
92
- if Time.now < stop
93
- start = Time.parse('12:00:00 AM')
90
+ start_time = Time.parse(check[:subdue][:start])
91
+ end_time = Time.parse(check[:subdue][:end])
92
+ if end_time < start_time
93
+ if Time.now < end_time
94
+ start_time = Time.parse('12:00:00 AM')
94
95
  else
95
- stop = Time.parse('11:59:59 PM')
96
+ end_time = Time.parse('11:59:59 PM')
96
97
  end
97
98
  end
98
- if Time.now >= start && Time.now <= stop
99
+ if Time.now >= start_time && Time.now <= end_time
99
100
  subdue = true
100
101
  end
101
102
  end
@@ -422,22 +423,20 @@ module Sensu
422
423
  @settings.checks.each do |check|
423
424
  unless check[:publish] == false || check[:standalone]
424
425
  check_count += 1
425
- @timers << EM::Timer.new(stagger * check_count) do
426
+ @master_timers << EM::Timer.new(stagger * check_count) do
426
427
  interval = testing? ? 0.5 : check[:interval]
427
- @timers << EM::PeriodicTimer.new(interval) do
428
+ @master_timers << EM::PeriodicTimer.new(interval) do
428
429
  unless check_subdued?(check, :publisher)
429
- if @rabbitmq.connected?
430
- payload = {
431
- :name => check[:name],
432
- :issued => Time.now.to_i
433
- }
434
- @logger.info('publishing check request', {
435
- :payload => payload,
436
- :subscribers => check[:subscribers]
437
- })
438
- check[:subscribers].uniq.each do |exchange_name|
439
- @amq.fanout(exchange_name).publish(payload.to_json)
440
- end
430
+ payload = {
431
+ :name => check[:name],
432
+ :issued => Time.now.to_i
433
+ }
434
+ @logger.info('publishing check request', {
435
+ :payload => payload,
436
+ :subscribers => check[:subscribers]
437
+ })
438
+ check[:subscribers].uniq.each do |exchange_name|
439
+ @amq.fanout(exchange_name).publish(payload.to_json)
441
440
  end
442
441
  end
443
442
  end
@@ -459,34 +458,32 @@ module Sensu
459
458
 
460
459
  def setup_keepalive_monitor
461
460
  @logger.debug('monitoring client keepalives')
462
- @timers << EM::PeriodicTimer.new(30) do
463
- if @rabbitmq.connected?
464
- @logger.debug('checking for stale client info')
465
- @redis.smembers('clients').callback do |clients|
466
- clients.each do |client_name|
467
- @redis.get('client:' + client_name).callback do |client_json|
468
- client = JSON.parse(client_json, :symbolize_names => true)
469
- check = {
470
- :name => 'keepalive',
471
- :issued => Time.now.to_i
472
- }
473
- time_since_last_keepalive = Time.now.to_i - client[:timestamp]
474
- case
475
- when time_since_last_keepalive >= 180
476
- check[:output] = 'No keep-alive sent from client in over 180 seconds'
477
- check[:status] = 2
478
- publish_result(client, check)
479
- when time_since_last_keepalive >= 120
480
- check[:output] = 'No keep-alive sent from client in over 120 seconds'
481
- check[:status] = 1
482
- publish_result(client, check)
483
- else
484
- @redis.hexists('events:' + client[:name], 'keepalive').callback do |exists|
485
- if exists
486
- check[:output] = 'Keep-alive sent from client'
487
- check[:status] = 0
488
- publish_result(client, check)
489
- end
461
+ @master_timers << EM::PeriodicTimer.new(30) do
462
+ @logger.debug('checking for stale client info')
463
+ @redis.smembers('clients').callback do |clients|
464
+ clients.each do |client_name|
465
+ @redis.get('client:' + client_name).callback do |client_json|
466
+ client = JSON.parse(client_json, :symbolize_names => true)
467
+ check = {
468
+ :name => 'keepalive',
469
+ :issued => Time.now.to_i
470
+ }
471
+ time_since_last_keepalive = Time.now.to_i - client[:timestamp]
472
+ case
473
+ when time_since_last_keepalive >= 180
474
+ check[:output] = 'No keep-alive sent from client in over 180 seconds'
475
+ check[:status] = 2
476
+ publish_result(client, check)
477
+ when time_since_last_keepalive >= 120
478
+ check[:output] = 'No keep-alive sent from client in over 120 seconds'
479
+ check[:status] = 1
480
+ publish_result(client, check)
481
+ else
482
+ @redis.hexists('events:' + client[:name], 'keepalive').callback do |exists|
483
+ if exists
484
+ check[:output] = 'Keep-alive sent from client'
485
+ check[:status] = 0
486
+ publish_result(client, check)
490
487
  end
491
488
  end
492
489
  end
@@ -502,7 +499,6 @@ module Sensu
502
499
  end
503
500
 
504
501
  def request_master_election
505
- @is_master ||= false
506
502
  @redis.setnx('lock:master', Time.now.to_i).callback do |created|
507
503
  if created
508
504
  @is_master = true
@@ -524,32 +520,6 @@ module Sensu
524
520
  end
525
521
  end
526
522
 
527
- def resign_as_master(&block)
528
- if @redis.connected? && @is_master
529
- @redis.del('lock:master').callback do
530
- @logger.warn('resigned as master')
531
- @is_master = false
532
- end
533
- if block
534
- timestamp = Time.now.to_i
535
- retry_until_true do
536
- if !@is_master
537
- block.call
538
- true
539
- elsif Time.now.to_i - timestamp >= 5
540
- @logger.warn('failed to resign as master')
541
- block.call
542
- true
543
- end
544
- end
545
- end
546
- else
547
- if block
548
- block.call
549
- end
550
- end
551
- end
552
-
553
523
  def setup_master_monitor
554
524
  request_master_election
555
525
  @timers << EM::PeriodicTimer.new(20) do
@@ -557,53 +527,59 @@ module Sensu
557
527
  @redis.set('lock:master', Time.now.to_i).callback do
558
528
  @logger.debug('updated master lock timestamp')
559
529
  end
560
- else
530
+ elsif @rabbitmq.connected?
561
531
  request_master_election
562
532
  end
563
533
  end
564
534
  end
565
535
 
566
- def setup_rabbitmq_monitor
567
- @logger.debug('monitoring rabbitmq connection')
568
- @timers << EM::PeriodicTimer.new(5) do
569
- if @rabbitmq.connected?
570
- unless @keepalive_queue.subscribed?
571
- @logger.warn('re-subscribing to keepalives')
572
- setup_keepalives
573
- end
574
- unless @result_queue.subscribed?
575
- @logger.warn('re-subscribing to results')
576
- setup_results
536
+ def resign_as_master(&block)
537
+ if @is_master
538
+ @logger.warn('resigning as master')
539
+ @master_timers.each do |timer|
540
+ timer.cancel
541
+ end
542
+ @master_timers = Array.new
543
+ @redis.del('lock:master').callback do
544
+ @logger.info('removed master lock')
545
+ @is_master = false
546
+ end
547
+ timestamp = Time.now.to_i
548
+ retry_until_true do
549
+ if !@is_master
550
+ block.call
551
+ true
552
+ elsif !@redis.connected? || Time.now.to_i - timestamp >= 5
553
+ @logger.warn('failed to remove master lock')
554
+ @is_master = false
555
+ block.call
556
+ true
577
557
  end
578
- else
579
- @logger.warn('reconnecting to rabbitmq')
580
558
  end
559
+ else
560
+ @logger.debug('not currently master')
561
+ block.call
581
562
  end
582
563
  end
583
564
 
584
565
  def unsubscribe(&block)
566
+ @logger.warn('unsubscribing from keepalive and result queues')
567
+ @keepalive_queue.unsubscribe
568
+ @result_queue.unsubscribe
585
569
  if @rabbitmq.connected?
586
- @logger.warn('unsubscribing from keepalives')
587
- @keepalive_queue.unsubscribe
588
- @logger.warn('unsubscribing from results')
589
- @result_queue.unsubscribe
590
- if block
591
- timestamp = Time.now.to_i
592
- retry_until_true do
593
- if !@keepalive_queue.subscribed? && !@result_queue.subscribed?
594
- block.call
595
- true
596
- elsif Time.now.to_i - timestamp >= 5
597
- @logger.warn('failed to unsubscribe from keepalives and results')
598
- block.call
599
- true
600
- end
570
+ timestamp = Time.now.to_i
571
+ retry_until_true do
572
+ if !@keepalive_queue.subscribed? && !@result_queue.subscribed?
573
+ block.call
574
+ true
575
+ elsif Time.now.to_i - timestamp >= 5
576
+ @logger.warn('failed to unsubscribe from keepalive and result queues')
577
+ block.call
578
+ true
601
579
  end
602
580
  end
603
581
  else
604
- if block
605
- block.call
606
- end
582
+ block.call
607
583
  end
608
584
  end
609
585
 
@@ -611,20 +587,23 @@ module Sensu
611
587
  @logger.info('completing handlers in progress', {
612
588
  :handlers_in_progress_count => @handlers_in_progress_count
613
589
  })
614
- if block
615
- retry_until_true do
616
- if @handlers_in_progress_count == 0
617
- block.call
618
- true
619
- end
590
+ retry_until_true do
591
+ if @handlers_in_progress_count == 0
592
+ block.call
593
+ true
620
594
  end
621
595
  end
622
596
  end
623
597
 
624
- def stop(signal)
625
- @logger.warn('received signal', {
626
- :signal => signal
627
- })
598
+ def start
599
+ setup_redis
600
+ setup_rabbitmq
601
+ setup_keepalives
602
+ setup_results
603
+ setup_master_monitor
604
+ end
605
+
606
+ def stop
628
607
  @logger.warn('stopping')
629
608
  @timers.each do |timer|
630
609
  timer.cancel
@@ -632,6 +611,7 @@ module Sensu
632
611
  unsubscribe do
633
612
  resign_as_master do
634
613
  complete_handlers_in_progress do
614
+ @rabbitmq.close
635
615
  @redis.close
636
616
  @logger.warn('stopping reactor')
637
617
  EM::stop_event_loop
@@ -640,6 +620,17 @@ module Sensu
640
620
  end
641
621
  end
642
622
 
623
+ def trap_signals
624
+ %w[INT TERM].each do |signal|
625
+ Signal.trap(signal) do
626
+ @logger.warn('received signal', {
627
+ :signal => signal
628
+ })
629
+ stop
630
+ end
631
+ end
632
+ end
633
+
643
634
  private
644
635
 
645
636
  def testing?
data/sensu.gemspec CHANGED
@@ -13,8 +13,8 @@ Gem::Specification.new do |s|
13
13
  s.has_rdoc = false
14
14
 
15
15
  s.add_dependency('eventmachine', '1.0.0.rc.4')
16
- s.add_dependency('amqp', '0.7.4')
17
- s.add_dependency('json', '1.7.3')
16
+ s.add_dependency('amqp', '0.9.7')
17
+ s.add_dependency('json')
18
18
  s.add_dependency('cabin', '0.4.4')
19
19
  s.add_dependency('ruby-redis', '0.0.2')
20
20
  s.add_dependency('async_sinatra', '1.0.0')
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sensu
3
3
  version: !ruby/object:Gem::Version
4
- hash: 62196239
4
+ hash: 62196237
5
5
  prerelease: true
6
6
  segments:
7
7
  - 0
8
8
  - 9
9
9
  - 7
10
10
  - beta
11
- - 2
12
- version: 0.9.7.beta.2
11
+ - 3
12
+ version: 0.9.7.beta.3
13
13
  platform: ruby
14
14
  authors:
15
15
  - Sean Porter
@@ -18,7 +18,7 @@ autorequire:
18
18
  bindir: bin
19
19
  cert_chain: []
20
20
 
21
- date: 2012-08-10 00:00:00 -07:00
21
+ date: 2012-08-25 00:00:00 -07:00
22
22
  default_executable:
23
23
  dependencies:
24
24
  - !ruby/object:Gem::Dependency
@@ -47,12 +47,12 @@ dependencies:
47
47
  requirements:
48
48
  - - "="
49
49
  - !ruby/object:Gem::Version
50
- hash: 11
50
+ hash: 53
51
51
  segments:
52
52
  - 0
53
+ - 9
53
54
  - 7
54
- - 4
55
- version: 0.7.4
55
+ version: 0.9.7
56
56
  type: :runtime
57
57
  version_requirements: *id002
58
58
  - !ruby/object:Gem::Dependency
@@ -61,14 +61,12 @@ dependencies:
61
61
  requirement: &id003 !ruby/object:Gem::Requirement
62
62
  none: false
63
63
  requirements:
64
- - - "="
64
+ - - ">="
65
65
  - !ruby/object:Gem::Version
66
- hash: 13
66
+ hash: 3
67
67
  segments:
68
- - 1
69
- - 7
70
- - 3
71
- version: 1.7.3
68
+ - 0
69
+ version: "0"
72
70
  type: :runtime
73
71
  version_requirements: *id003
74
72
  - !ruby/object:Gem::Dependency
@@ -199,7 +197,6 @@ files:
199
197
  - lib/sensu/client.rb
200
198
  - lib/sensu/constants.rb
201
199
  - lib/sensu/logger.rb
202
- - lib/sensu/patches/amqp.rb
203
200
  - lib/sensu/patches/ruby.rb
204
201
  - lib/sensu/process.rb
205
202
  - lib/sensu/redis.rb
@@ -1,5 +0,0 @@
1
- module AMQP
2
- module Client
3
- attr_accessor :on_disconnect
4
- end
5
- end