sensu 0.8.19 → 0.9.0.beta

Sign up to get free protection for your applications and to get access to all the features.
data/README.org CHANGED
@@ -9,8 +9,9 @@
9
9
  * Documentation
10
10
  Find it [[https://github.com/sonian/sensu/wiki][HERE]].
11
11
  * Other Projects
12
- - [[https://github.com/sonian/sensu-plugin][Sensu Community Plugins]]
12
+ - [[https://github.com/sonian/sensu-community-plugins][Sensu Community Plugins]]
13
13
  - [[https://github.com/sonian/sensu-dashboard][Sensu Dashboard]]
14
+ - [[https://github.com/sonian/sensu-plugin][Sensu Plugin & Handler Helper]]
14
15
  * License
15
16
  Sensu is released under the [[https://github.com/sonian/sensu/blob/master/MIT-LICENSE.txt][MIT license]].
16
17
  * Contributing
data/lib/sensu.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Sensu
2
- VERSION = "0.8.19"
2
+ VERSION = "0.9.0.beta"
3
3
  end
data/lib/sensu/api.rb CHANGED
@@ -26,11 +26,15 @@ module Sensu
26
26
  config = Sensu::Config.new(options)
27
27
  $settings = config.settings
28
28
  $logger = config.open_log
29
+ if options[:daemonize]
30
+ Process.daemonize
31
+ end
32
+ Process.write_pid(options[:pid_file])
29
33
  $logger.debug('[setup] -- connecting to redis')
30
- $redis = EM.connect($settings.redis.host, $settings.redis.port, Redis::Reconnect)
34
+ $redis = Redis.connect($settings.redis.to_hash.symbolize_keys)
31
35
  $logger.debug('[setup] -- connecting to rabbitmq')
32
- connection = AMQP.connect($settings.rabbitmq.to_hash.symbolize_keys)
33
- $amq = MQ.new(connection)
36
+ rabbitmq = AMQP.connect($settings.rabbitmq.to_hash.symbolize_keys)
37
+ $amq = AMQP::Channel.new(rabbitmq)
34
38
  end
35
39
 
36
40
  def self.stop(signal)
data/lib/sensu/client.rb CHANGED
@@ -3,8 +3,12 @@ require File.join(File.dirname(__FILE__), 'config')
3
3
  module Sensu
4
4
  class Client
5
5
  def self.run(options={})
6
+ client = self.new(options)
7
+ if options[:daemonize]
8
+ Process.daemonize
9
+ end
10
+ Process.write_pid(options[:pid_file])
6
11
  EM.run do
7
- client = self.new(options)
8
12
  client.setup_amqp
9
13
  client.setup_keepalives
10
14
  client.setup_subscriptions
@@ -34,30 +38,27 @@ module Sensu
34
38
 
35
39
  def setup_amqp
36
40
  @logger.debug('[amqp] -- connecting to rabbitmq')
37
- connection = AMQP.connect(@settings.rabbitmq.to_hash.symbolize_keys)
38
- @amq = MQ.new(connection)
41
+ rabbitmq = AMQP.connect(@settings.rabbitmq.to_hash.symbolize_keys)
42
+ @amq = AMQP::Channel.new(rabbitmq)
39
43
  end
40
44
 
41
45
  def publish_keepalive
46
+ @settings.client.timestamp = Time.now.to_i
42
47
  @logger.debug('[keepalive] -- publishing keepalive -- ' + @settings.client.timestamp.to_s)
43
- @keepalive_queue ||= @amq.queue('keepalives')
44
- @keepalive_queue.publish(@settings.client.to_json)
48
+ @amq.queue('keepalives').publish(@settings.client.to_json)
45
49
  end
46
50
 
47
51
  def setup_keepalives
48
52
  @logger.debug('[keepalive] -- setup keepalives')
49
- @settings.client.timestamp = Time.now.to_i
50
53
  publish_keepalive
51
54
  EM.add_periodic_timer(30) do
52
- @settings.client.timestamp = Time.now.to_i
53
55
  publish_keepalive
54
56
  end
55
57
  end
56
58
 
57
59
  def publish_result(check)
58
60
  @logger.info('[result] -- publishing check result -- ' + check.status.to_s + ' -- ' + check.name)
59
- @result_queue ||= @amq.queue('results')
60
- @result_queue.publish({
61
+ @amq.queue('results').publish({
61
62
  :client => @settings.client.name,
62
63
  :check => check.to_hash
63
64
  }.to_json)
@@ -115,7 +116,7 @@ module Sensu
115
116
  def setup_subscriptions
116
117
  @logger.debug('[subscribe] -- setup subscriptions')
117
118
  @check_queue = @amq.queue(String.unique, :exclusive => true)
118
- @settings.client.subscriptions.push('uchiwa')
119
+ @settings.client.subscriptions.push('uchiwa').uniq!
119
120
  @settings.client.subscriptions.each do |exchange|
120
121
  @logger.debug('[subscribe] -- queue binding to exchange -- ' + exchange)
121
122
  @check_queue.bind(@amq.fanout(exchange))
@@ -171,18 +172,18 @@ module Sensu
171
172
  def setup_socket
172
173
  @logger.debug('[socket] -- starting up socket server')
173
174
  EM.start_server('127.0.0.1', 3030, ClientSocket) do |socket|
175
+ socket.settings = @settings
174
176
  socket.logger = @logger
175
- socket.client_name = @settings.client.name
176
- socket.result_queue = @amq.queue('results')
177
+ socket.amq = @amq
177
178
  end
178
179
  end
179
180
  end
180
181
 
181
182
  class ClientSocket < EM::Connection
182
- attr_accessor :logger, :client_name, :result_queue
183
+ attr_accessor :settings, :logger, :amq
183
184
 
184
185
  def receive_data(data)
185
- @logger.debug('[socket] -- received data from client')
186
+ @logger.debug('[socket] -- new connection -- received data from external source')
186
187
  begin
187
188
  check = Hashie::Mash.new(JSON.parse(data))
188
189
  validates = %w[name status output].all? do |key|
@@ -190,21 +191,21 @@ module Sensu
190
191
  end
191
192
  if validates
192
193
  @logger.info('[socket] -- publishing check result -- ' + check.name)
193
- @result_queue.publish({
194
- :client => @client_name,
194
+ @amq.queue('results').publish({
195
+ :client => @settings.client.name,
195
196
  :check => check.to_hash
196
197
  }.to_json)
197
198
  else
198
199
  @logger.warn('[socket] -- a check name, exit status, and output are required -- e.g. {name: x, status: 0, output: "y"}')
199
200
  end
200
201
  rescue JSON::ParserError => error
201
- @logger.warn('[socket] -- check result must be valid JSON: ' + error)
202
+ @logger.warn('[socket] -- check result must be valid JSON: ' + error.to_s)
202
203
  end
203
204
  close_connection
204
205
  end
205
206
 
206
207
  def unbind
207
- @logger.debug('[socket] -- client disconnected')
208
+ @logger.debug('[socket] -- connection closed')
208
209
  end
209
210
  end
210
211
  end
data/lib/sensu/config.rb CHANGED
@@ -22,6 +22,8 @@ module Sensu
22
22
  :config_file => '/etc/sensu/config.json',
23
23
  :config_dir => '/etc/sensu/conf.d',
24
24
  :validate => true,
25
+ :pid_file => '/tmp/' + File.basename($0) + '.pid',
26
+ :daemonize => false
25
27
  }
26
28
 
27
29
  def initialize(options={})
@@ -33,7 +35,9 @@ module Sensu
33
35
  def open_log
34
36
  @logger = Cabin::Channel.new
35
37
  if File.writable?(@options[:log_file]) || !File.exist?(@options[:log_file]) && File.writable?(File.dirname(@options[:log_file]))
36
- ruby_logger = Logger.new(@options[:log_file])
38
+ STDOUT.reopen(@options[:log_file], 'a')
39
+ STDERR.reopen(STDOUT)
40
+ ruby_logger = Logger.new(STDOUT)
37
41
  else
38
42
  invalid_config('log file is not writable: ' + @options[:log_file])
39
43
  end
@@ -78,6 +82,26 @@ module Sensu
78
82
  unless @settings.handlers.include?('default')
79
83
  invalid_config('missing default handler')
80
84
  end
85
+ @settings.handlers.each do |name, details|
86
+ unless details.key?('type')
87
+ invalid_config('missing type for handler ' + name)
88
+ end
89
+ case details.type
90
+ when 'pipe'
91
+ unless details.key?('command')
92
+ invalid_config('missing command for pipe handler ' + name)
93
+ end
94
+ when 'amqp'
95
+ unless details.key?('exchange')
96
+ invalid_config('missing exchange details for amqp handler ' + name)
97
+ end
98
+ unless details.exchange.key?('name')
99
+ invalid_config('missing exchange name for amqp handler ' + name)
100
+ end
101
+ else
102
+ invalid_config('unknown type for handler ' + name)
103
+ end
104
+ end
81
105
  when 'api'
82
106
  has_keys(%w[redis api])
83
107
  when 'client'
@@ -139,12 +163,7 @@ module Sensu
139
163
  puts opts
140
164
  exit
141
165
  end
142
- current_process = $0.split('/').last
143
- if current_process == 'sensu-server' || current_process == 'rake'
144
- opts.on('-w', '--worker', 'Only consume jobs, no check publishing (default: false)') do
145
- options[:worker] = true
146
- end
147
- end
166
+ current_process = File.basename($0)
148
167
  opts.on('-c', '--config FILE', 'Sensu JSON config FILE (default: /etc/sensu/config.json)') do |file|
149
168
  options[:config_file] = file
150
169
  end
@@ -157,9 +176,15 @@ module Sensu
157
176
  opts.on('-v', '--verbose', 'Enable verbose logging (default: false)') do
158
177
  options[:verbose] = true
159
178
  end
179
+ opts.on('-b', '--background', 'Fork into backgaround (daemon mode) (default: false)') do
180
+ options[:daemonize] = true
181
+ end
182
+ opts.on('-p', '--pid_file FILE', 'Sensu PID FILE (default: ' + DEFAULT_OPTIONS[:pid_file] + ')') do |file|
183
+ options[:pid_file] = file
184
+ end
160
185
  end
161
186
  optparse.parse!(arguments)
162
- options
187
+ DEFAULT_OPTIONS.merge(options)
163
188
  end
164
189
  end
165
190
  end
@@ -1,5 +1,5 @@
1
1
  module Redis
2
- class Reconnect < Redis::Client
2
+ class Client
3
3
  def connection_completed
4
4
  @connected = true
5
5
  @port, @host = Socket.unpack_sockaddr_in(get_peername)
@@ -22,4 +22,10 @@ module Redis
22
22
  end
23
23
  end
24
24
  end
25
+
26
+ def self.connect(options={})
27
+ host = options[:host] || 'localhost'
28
+ port = options[:port] || 6379
29
+ EM.connect(host, port, Redis::Client)
30
+ end
25
31
  end
@@ -44,3 +44,37 @@ class String
44
44
  rand(36**chars).to_s(36)
45
45
  end
46
46
  end
47
+
48
+ module Process
49
+ def self.write_pid(pid_file)
50
+ begin
51
+ File.open(pid_file, 'w') do |file|
52
+ file.write(self.pid.to_s + "\n")
53
+ end
54
+ rescue
55
+ raise 'could not write to pid file: ' + pid_file
56
+ end
57
+ end
58
+
59
+ def self.daemonize
60
+ srand
61
+ fork and exit
62
+ unless session_id = self.setsid
63
+ raise 'cannot detach from controlling terminal'
64
+ end
65
+ trap 'SIGHUP', 'IGNORE'
66
+ exit if pid = fork
67
+ Dir.chdir "/"
68
+ ObjectSpace.each_object(IO) do |io|
69
+ unless [STDIN, STDOUT, STDERR].include?(io)
70
+ begin
71
+ unless io.closed?
72
+ io.close
73
+ end
74
+ rescue
75
+ end
76
+ end
77
+ end
78
+ return session_id
79
+ end
80
+ end
data/lib/sensu/server.rb CHANGED
@@ -6,20 +6,21 @@ require File.join(File.dirname(__FILE__), 'helpers', 'redis')
6
6
 
7
7
  module Sensu
8
8
  class Server
9
- attr_accessor :redis, :amq, :is_worker
9
+ attr_accessor :redis, :amq, :is_master
10
10
 
11
11
  def self.run(options={})
12
+ server = self.new(options)
13
+ if options[:daemonize]
14
+ Process.daemonize
15
+ end
16
+ Process.write_pid(options[:pid_file])
12
17
  EM.threadpool_size = 16
13
18
  EM.run do
14
- server = self.new(options)
15
19
  server.setup_redis
16
20
  server.setup_amqp
17
21
  server.setup_keepalives
18
22
  server.setup_results
19
- unless server.is_worker
20
- server.setup_publisher
21
- server.setup_keepalive_monitor
22
- end
23
+ server.setup_master_monitor
23
24
  server.setup_queue_monitor
24
25
 
25
26
  %w[INT TERM].each do |signal|
@@ -34,7 +35,6 @@ module Sensu
34
35
  config = Sensu::Config.new(options)
35
36
  @settings = config.settings
36
37
  @logger = config.open_log
37
- @is_worker = options[:worker]
38
38
  end
39
39
 
40
40
  def stop(signal)
@@ -46,13 +46,13 @@ module Sensu
46
46
 
47
47
  def setup_redis
48
48
  @logger.debug('[redis] -- connecting to redis')
49
- @redis = EM.connect(@settings.redis.host, @settings.redis.port, Redis::Reconnect)
49
+ @redis = Redis.connect(@settings.redis.to_hash.symbolize_keys)
50
50
  end
51
51
 
52
52
  def setup_amqp
53
53
  @logger.debug('[amqp] -- connecting to rabbitmq')
54
- connection = AMQP.connect(@settings.rabbitmq.to_hash.symbolize_keys)
55
- @amq = MQ.new(connection)
54
+ rabbitmq = AMQP.connect(@settings.rabbitmq.to_hash.symbolize_keys)
55
+ @amq = AMQP::Channel.new(rabbitmq)
56
56
  end
57
57
 
58
58
  def setup_keepalives
@@ -84,37 +84,35 @@ module Sensu
84
84
  handlers.each do |handler|
85
85
  if @settings.handlers.key?(handler)
86
86
  @logger.debug('[event] -- handling event -- ' + [handler, event.client.name, event.check.name].join(' -- '))
87
- if @settings.handlers[handler].is_a?(Hash)
88
- details = @settings.handlers[handler]
89
- case details.type
90
- when "amqp"
91
- exchange = details.exchange || 'events'
92
- @logger.debug('[event] -- publishing event to amqp exchange -- ' + [exchange, event.client.name, event.check.name].join(' -- '))
93
- message = details.send_only_check_output ? event.check.output : event.to_json
94
- @amq.direct(exchange).publish(message)
95
- else
96
- @logger.warn('[event] -- unknown handler type -- ' + details.type)
97
- end
98
- else
87
+ details = @settings.handlers[handler]
88
+ case details.type
89
+ when "pipe"
99
90
  handle = proc do
100
91
  output = ''
101
92
  Bundler.with_clean_env do
102
93
  begin
103
- IO.popen(@settings.handlers[handler] + ' 2>&1', 'r+') do |io|
94
+ IO.popen(details.command + ' 2>&1', 'r+') do |io|
104
95
  io.write(event.to_json)
105
96
  io.close_write
106
97
  output = io.read
107
98
  end
108
99
  rescue Errno::EPIPE => error
109
- output = handler + ' -- broken pipe: ' + error
100
+ output = handler + ' -- broken pipe: ' + error.to_s
110
101
  end
111
102
  end
112
103
  output
113
104
  end
114
105
  EM.defer(handle, report)
106
+ when "amqp"
107
+ exchange = details.exchange.name
108
+ exchange_type = details.exchange.key?('type') ? details.exchange.type.to_sym : :direct
109
+ exchange_options = details.exchange.reject { |key, value| %w[name type].include?(key) }
110
+ @logger.debug('[event] -- publishing event to amqp exchange -- ' + [exchange, event.client.name, event.check.name].join(' -- '))
111
+ payload = details.send_only_check_output ? event.check.output : event.to_json
112
+ @amq.method(exchange_type).call(exchange, exchange_options).publish(payload)
115
113
  end
116
114
  else
117
- @logger.warn('[event] -- handler does not exist -- ' + handler)
115
+ @logger.warn('[event] -- unknown handler -- ' + handler)
118
116
  end
119
117
  end
120
118
  end
@@ -210,14 +208,13 @@ module Sensu
210
208
  @result_queue = @amq.queue('results')
211
209
  @result_queue.subscribe do |result_json|
212
210
  result = Hashie::Mash.new(JSON.parse(result_json))
213
- @logger.info('[result] -- received result -- ' + result.client + ' -- ' + result.check.name)
211
+ @logger.info('[result] -- received result -- ' + [result.check.name, result.client, result.check.status, result.check.output].join(' -- '))
214
212
  process_result(result)
215
213
  end
216
214
  end
217
215
 
218
216
  def setup_publisher(options={})
219
217
  @logger.debug('[publisher] -- setup publisher')
220
- exchanges = Hash.new
221
218
  stagger = options[:test] ? 0 : 7
222
219
  @settings.checks.each_with_index do |(name, details), index|
223
220
  check_request = Hashie::Mash.new({:name => name})
@@ -231,12 +228,11 @@ module Sensu
231
228
  else
232
229
  exchange = target
233
230
  end
234
- exchanges[exchange] ||= @amq.fanout(exchange)
235
231
  interval = options[:test] ? 0.5 : details.interval
236
232
  EM.add_periodic_timer(interval) do
237
233
  check_request.issued = Time.now.to_i
238
234
  @logger.info('[publisher] -- publishing check -- ' + name + ' -- ' + exchange)
239
- exchanges[exchange].publish(check_request.to_json)
235
+ @amq.fanout(exchange).publish(check_request.to_json)
240
236
  end
241
237
  end
242
238
  end
@@ -264,17 +260,17 @@ module Sensu
264
260
  when time_since_last_keepalive >= 180
265
261
  result.check.status = 2
266
262
  result.check.output = 'No keep-alive sent from host in over 180 seconds'
267
- @result_queue.publish(result.to_json)
263
+ @amq.queue('results').publish(result.to_json)
268
264
  when time_since_last_keepalive >= 120
269
265
  result.check.status = 1
270
266
  result.check.output = 'No keep-alive sent from host in over 120 seconds'
271
- @result_queue.publish(result.to_json)
267
+ @amq.queue('results').publish(result.to_json)
272
268
  else
273
269
  @redis.hexists('events:' + client_id, 'keepalive').callback do |exists|
274
270
  if exists
275
271
  result.check.status = 0
276
272
  result.check.output = 'Keep-alive sent from host'
277
- @result_queue.publish(result.to_json)
273
+ @amq.queue('results').publish(result.to_json)
278
274
  end
279
275
  end
280
276
  end
@@ -284,15 +280,57 @@ module Sensu
284
280
  end
285
281
  end
286
282
 
283
+ def master_duties
284
+ setup_publisher
285
+ setup_keepalive_monitor
286
+ end
287
+
288
+ def request_master_election
289
+ @is_master ||= false
290
+ @redis.setnx('lock:master', Time.now.to_i).callback do |created|
291
+ if created
292
+ @logger.info('[master] -- i am the master')
293
+ @is_master = true
294
+ master_duties
295
+ else
296
+ @redis.get('lock:master') do |timestamp|
297
+ if Time.now.to_i - timestamp.to_i >= 60
298
+ @redis.getset('lock:master', Time.now.to_i).callback do |previous|
299
+ if previous == timestamp
300
+ @logger.info('[master] -- i am now the master')
301
+ @is_master = true
302
+ master_duties
303
+ end
304
+ end
305
+ end
306
+ end
307
+ end
308
+ end
309
+ end
310
+
311
+ def setup_master_monitor
312
+ request_master_election
313
+ EM.add_periodic_timer(20) do
314
+ if @is_master
315
+ timestamp = Time.now.to_i
316
+ @redis.set('lock:master', timestamp).callback do
317
+ @logger.debug('[master] -- updated master lock timestamp -- ' + timestamp.to_s)
318
+ end
319
+ else
320
+ request_master_election
321
+ end
322
+ end
323
+ end
324
+
287
325
  def setup_queue_monitor
288
326
  @logger.debug('[monitor] -- setup queue monitor')
289
327
  EM.add_periodic_timer(5) do
290
328
  unless @keepalive_queue.subscribed?
291
- @logger.warn('[monitor] -- reconnecting to rabbitmq')
329
+ @logger.warn('[monitor] -- reconnecting to rabbitmq -- keepalives')
292
330
  setup_keepalives
293
331
  end
294
332
  unless @result_queue.subscribed?
295
- @logger.warn('[monitor] -- reconnecting to rabbitmq')
333
+ @logger.warn('[monitor] -- reconnecting to rabbitmq -- results')
296
334
  setup_results
297
335
  end
298
336
  end
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sensu
3
3
  version: !ruby/object:Gem::Version
4
- hash: 25
5
- prerelease: false
4
+ hash: 31098121
5
+ prerelease: true
6
6
  segments:
7
7
  - 0
8
- - 8
9
- - 19
10
- version: 0.8.19
8
+ - 9
9
+ - 0
10
+ - beta
11
+ version: 0.9.0.beta
11
12
  platform: ruby
12
13
  authors:
13
14
  - Sean Porter
@@ -16,7 +17,7 @@ autorequire:
16
17
  bindir: bin
17
18
  cert_chain: []
18
19
 
19
- date: 2011-12-16 00:00:00 -08:00
20
+ date: 2011-12-21 00:00:00 -08:00
20
21
  default_executable:
21
22
  dependencies:
22
23
  - !ruby/object:Gem::Dependency
@@ -263,12 +264,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
263
264
  required_rubygems_version: !ruby/object:Gem::Requirement
264
265
  none: false
265
266
  requirements:
266
- - - ">="
267
+ - - ">"
267
268
  - !ruby/object:Gem::Version
268
- hash: 3
269
+ hash: 25
269
270
  segments:
270
- - 0
271
- version: "0"
271
+ - 1
272
+ - 3
273
+ - 1
274
+ version: 1.3.1
272
275
  requirements: []
273
276
 
274
277
  rubyforge_project: