sensu 0.8.19 → 0.9.0.beta

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/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: