sensu 0.9.5 → 0.9.6.beta

Sign up to get free protection for your applications and to get access to all the features.
data/lib/sensu/base.rb ADDED
@@ -0,0 +1,57 @@
1
+ require 'rubygems'
2
+
3
+ gem 'eventmachine', '~> 1.0.0.beta.4'
4
+
5
+ require 'optparse'
6
+ require 'json'
7
+ require 'cabin'
8
+ require 'amqp'
9
+
10
+ require File.join(File.dirname(__FILE__), 'patches', 'ruby')
11
+ require File.join(File.dirname(__FILE__), 'patches', 'amqp')
12
+
13
+ require File.join(File.dirname(__FILE__), 'constants')
14
+ require File.join(File.dirname(__FILE__), 'cli')
15
+ require File.join(File.dirname(__FILE__), 'logger')
16
+ require File.join(File.dirname(__FILE__), 'settings')
17
+ require File.join(File.dirname(__FILE__), 'process')
18
+
19
+ module Sensu
20
+ class Base
21
+ attr_reader :options, :logger, :settings
22
+
23
+ def initialize(options={})
24
+ @options = DEFAULT_OPTIONS.merge(options)
25
+ @logger = Cabin::Channel.get
26
+ setup_logging
27
+ setup_settings
28
+ setup_process
29
+ end
30
+
31
+ def setup_logging
32
+ Sensu::Logger.new(@options)
33
+ end
34
+
35
+ def setup_settings
36
+ @settings = Sensu::Settings.new
37
+ @settings.load_env
38
+ @settings.load_file(@options[:config_file])
39
+ Dir[@options[:config_dir] + '/**/*.json'].each do |file|
40
+ @settings.load_file(file)
41
+ end
42
+ begin
43
+ @settings.validate
44
+ rescue => error
45
+ @logger.fatal('CONFIG INVALID', {
46
+ :error => error.to_s
47
+ })
48
+ @logger.fatal('SENSU NOT RUNNING!')
49
+ exit 2
50
+ end
51
+ end
52
+
53
+ def setup_process
54
+ Sensu::Process.new(@options)
55
+ end
56
+ end
57
+ end
data/lib/sensu/cli.rb ADDED
@@ -0,0 +1,37 @@
1
+ module Sensu
2
+ class CLI
3
+ def self.read(arguments=ARGV)
4
+ options = Hash.new
5
+ optparse = OptionParser.new do |opts|
6
+ opts.on('-h', '--help', 'Display this message') do
7
+ puts opts
8
+ exit
9
+ end
10
+ opts.on('-V', '--version', 'Display version') do
11
+ puts VERSION
12
+ exit
13
+ end
14
+ opts.on('-c', '--config FILE', 'Sensu JSON config FILE. Default is /etc/sensu/config.json') do |file|
15
+ options[:config_file] = file
16
+ end
17
+ opts.on('-d', '--config_dir DIR', 'DIR for supplemental Sensu JSON config files. Default is /etc/sensu/conf.d/') do |dir|
18
+ options[:config_dir] = dir
19
+ end
20
+ opts.on('-l', '--log FILE', 'Log to a given FILE. Default is to log to stdout') do |file|
21
+ options[:log_file] = file
22
+ end
23
+ opts.on('-v', '--verbose', 'Enable verbose logging') do
24
+ options[:verbose] = true
25
+ end
26
+ opts.on('-b', '--background', 'Fork into the background') do
27
+ options[:daemonize] = true
28
+ end
29
+ opts.on('-p', '--pid_file FILE', 'Write the PID to a given FILE') do |file|
30
+ options[:pid_file] = file
31
+ end
32
+ end
33
+ optparse.parse!(arguments)
34
+ DEFAULT_OPTIONS.merge(options)
35
+ end
36
+ end
37
+ end
data/lib/sensu/client.rb CHANGED
@@ -1,23 +1,17 @@
1
- require File.join(File.dirname(__FILE__), 'config')
1
+ require File.join(File.dirname(__FILE__), 'base')
2
+ require File.join(File.dirname(__FILE__), 'socket')
2
3
 
3
4
  module Sensu
4
5
  class Client
5
6
  def self.run(options={})
6
7
  client = self.new(options)
7
- if options[:daemonize]
8
- Process.daemonize
9
- end
10
- if options[:pid_file]
11
- Process.write_pid(options[:pid_file])
12
- end
13
- EM::threadpool_size = 14
14
8
  EM::run do
15
9
  client.setup_rabbitmq
16
10
  client.setup_keepalives
17
11
  client.setup_subscriptions
18
12
  client.setup_rabbitmq_monitor
19
13
  client.setup_standalone
20
- client.setup_socket
14
+ client.setup_sockets
21
15
 
22
16
  %w[INT TERM].each do |signal|
23
17
  Signal.trap(signal) do
@@ -28,27 +22,31 @@ module Sensu
28
22
  end
29
23
 
30
24
  def initialize(options={})
31
- config = Sensu::Config.new(options)
32
- @logger = config.logger
33
- @settings = config.settings
25
+ base = Sensu::Base.new(options)
26
+ @logger = base.logger
27
+ @settings = base.settings
34
28
  @timers = Array.new
35
29
  @checks_in_progress = Array.new
36
30
  end
37
31
 
38
32
  def setup_rabbitmq
39
- @logger.debug('[rabbitmq] -- connecting to rabbitmq')
40
- @rabbitmq = AMQP.connect(@settings.rabbitmq.to_hash.symbolize_keys)
33
+ @logger.debug('connecting to rabbitmq', {
34
+ :settings => @settings[:rabbitmq]
35
+ })
36
+ @rabbitmq = AMQP.connect(@settings[:rabbitmq])
41
37
  @amq = AMQP::Channel.new(@rabbitmq)
42
38
  end
43
39
 
44
40
  def publish_keepalive
45
- @settings.client.timestamp = Time.now.to_i
46
- @logger.debug('[keepalive] -- publishing keepalive -- ' + @settings.client.timestamp.to_s)
47
- @amq.queue('keepalives').publish(@settings.client.to_json)
41
+ payload = @settings[:client].merge(:timestamp => Time.now.to_i)
42
+ @logger.debug('publishing keepalive', {
43
+ :payload => payload
44
+ })
45
+ @amq.queue('keepalives').publish(payload.to_json)
48
46
  end
49
47
 
50
48
  def setup_keepalives
51
- @logger.debug('[keepalive] -- setup keepalives')
49
+ @logger.debug('scheduling keepalives')
52
50
  publish_keepalive
53
51
  @timers << EM::PeriodicTimer.new(30) do
54
52
  unless @rabbitmq.reconnecting?
@@ -58,111 +56,133 @@ module Sensu
58
56
  end
59
57
 
60
58
  def publish_result(check)
61
- @logger.info('[result] -- publishing check result -- ' + [check.name, check.status, check.output.gsub(/\n/, '\n')].join(' -- '))
62
- @amq.queue('results').publish({
63
- :client => @settings.client.name,
64
- :check => check.to_hash
65
- }.to_json)
59
+ payload = {
60
+ :client => @settings[:client][:name],
61
+ :check => check
62
+ }
63
+ @logger.info('publishing check result', {
64
+ :payload => payload
65
+ })
66
+ @amq.queue('results').publish(payload.to_json)
66
67
  end
67
68
 
68
69
  def execute_check(check)
69
- @logger.debug('[execute] -- attempting to execute check -- ' + check.name)
70
- if @settings.checks.key?(check.name)
71
- unless @checks_in_progress.include?(check.name)
72
- @logger.debug('[execute] -- executing check -- ' + check.name)
73
- @checks_in_progress.push(check.name)
70
+ @logger.debug('attempting to execute check', {
71
+ :check => check
72
+ })
73
+ if @settings.check_exists?(check[:name])
74
+ unless @checks_in_progress.include?(check[:name])
75
+ @logger.debug('executing check', {
76
+ :check => check
77
+ })
78
+ @checks_in_progress.push(check[:name])
74
79
  unmatched_tokens = Array.new
75
- command = @settings.checks[check.name].command.gsub(/:::(.*?):::/) do
80
+ command = @settings[:checks][check[:name]][:command].gsub(/:::(.*?):::/) do
76
81
  token = $1.to_s
77
82
  begin
78
- value = @settings.client.instance_eval(token)
79
- if value.nil?
80
- unmatched_tokens.push(token)
81
- end
82
- rescue NoMethodError
83
- value = nil
83
+ substitute = @settings[:client].instance_eval(token)
84
+ rescue NameError, NoMethodError
85
+ substitute = nil
86
+ end
87
+ if substitute.nil?
84
88
  unmatched_tokens.push(token)
85
89
  end
86
- value
90
+ substitute
87
91
  end
88
92
  if unmatched_tokens.empty?
89
93
  execute = proc do
90
- Bundler.with_clean_env do
91
- started = Time.now.to_f
92
- begin
93
- IO.popen(command + ' 2>&1') do |io|
94
- check.output = io.read
95
- end
96
- check.status = $?.exitstatus
97
- rescue => error
98
- @logger.warn('[execute] -- unexpected error: ' + error.to_s)
99
- check.output = 'Unexpected error: ' + error.to_s
100
- check.status = 2
94
+ started = Time.now.to_f
95
+ begin
96
+ IO.popen(command + ' 2>&1') do |io|
97
+ check[:output] = io.read
101
98
  end
102
- check.duration = ('%.3f' % (Time.now.to_f - started)).to_f
99
+ check[:status] = $?.exitstatus
100
+ rescue => error
101
+ @logger.warn('unexpected error', {
102
+ :error => error.to_s
103
+ })
104
+ check[:output] = 'Unexpected error: ' + error.to_s
105
+ check[:status] = 2
103
106
  end
107
+ check[:duration] = ('%.3f' % (Time.now.to_f - started)).to_f
104
108
  end
105
109
  publish = proc do
106
- unless check.status.nil?
110
+ unless check[:status].nil?
107
111
  publish_result(check)
108
112
  else
109
- @logger.warn('[execute] -- nil exit status code -- ' + check.name)
113
+ @logger.warn('nil exit status code', {
114
+ :check => check
115
+ })
110
116
  end
111
- @checks_in_progress.delete(check.name)
117
+ @checks_in_progress.delete(check[:name])
112
118
  end
113
119
  EM::defer(execute, publish)
114
120
  else
115
- @logger.warn('[execute] -- missing client attributes -- ' + unmatched_tokens.join(', ') + ' -- ' + check.name)
116
- check.output = 'Missing client attributes: ' + unmatched_tokens.join(', ')
117
- check.status = 3
118
- check.handle = false
121
+ @logger.warn('missing client attributes', {
122
+ :check => check,
123
+ :unmatched_tokens => unmatched_tokens
124
+ })
125
+ check[:output] = 'Missing client attributes: ' + unmatched_tokens.join(', ')
126
+ check[:status] = 3
127
+ check[:handle] = false
119
128
  publish_result(check)
120
- @checks_in_progress.delete(check.name)
129
+ @checks_in_progress.delete(check[:name])
121
130
  end
122
131
  else
123
- @logger.warn('[execute] -- previous check execution still in progress -- ' + check.name)
132
+ @logger.warn('previous check execution in progress', {
133
+ :check => check
134
+ })
124
135
  end
125
136
  else
126
- @logger.warn('[execute] -- unkown check -- ' + check.name)
127
- check.output = 'Unknown check'
128
- check.status = 3
129
- check.handle = false
137
+ @logger.warn('unknown check', {
138
+ :check => check
139
+ })
140
+ check[:output] = 'Unknown check'
141
+ check[:status] = 3
142
+ check[:handle] = false
130
143
  publish_result(check)
131
- @checks_in_progress.delete(check.name)
144
+ @checks_in_progress.delete(check[:name])
132
145
  end
133
146
  end
134
147
 
135
148
  def setup_subscriptions
136
- @logger.debug('[subscribe] -- setup subscriptions')
137
- @uniq_queue_name ||= rand(36**32).to_s(36)
149
+ @logger.debug('subscribing to client subscriptions')
150
+ @uniq_queue_name ||= rand(36 ** 32).to_s(36)
138
151
  @check_request_queue = @amq.queue(@uniq_queue_name, :auto_delete => true)
139
- @settings.client.subscriptions.uniq.each do |exchange|
140
- @logger.debug('[subscribe] -- queue binding to exchange -- ' + exchange)
141
- @check_request_queue.bind(@amq.fanout(exchange))
152
+ @settings[:client][:subscriptions].uniq.each do |exchange_name|
153
+ @logger.debug('binding queue to exchange', {
154
+ :queue => @uniq_queue_name,
155
+ :exchange => {
156
+ :name => exchange_name,
157
+ :type => 'fanout'
158
+ }
159
+ })
160
+ @check_request_queue.bind(@amq.fanout(exchange_name))
142
161
  end
143
- @check_request_queue.subscribe do |check_request_json|
162
+ @check_request_queue.subscribe do |payload|
144
163
  begin
145
- check = Hashie::Mash.new(JSON.parse(check_request_json))
146
- if check.name.is_a?(String) && check.issued.is_a?(Integer)
147
- @logger.info('[subscribe] -- received check request -- ' + check.name)
148
- execute_check(check)
149
- else
150
- @logger.warn('[subscribe] -- invalid check request: ' + check_request_json)
151
- end
164
+ check = JSON.parse(payload, :symbolize_names => true)
165
+ @logger.info('received check request', {
166
+ :check => check
167
+ })
168
+ execute_check(check)
152
169
  rescue JSON::ParserError => error
153
- @logger.warn('[subscribe] -- check request must be valid json: ' + error.to_s)
170
+ @logger.warn('check request payload must be valid json', {
171
+ :payload => payload,
172
+ :error => error.to_s
173
+ })
154
174
  end
155
175
  end
156
176
  end
157
177
 
158
178
  def setup_rabbitmq_monitor
159
- @logger.debug('[monitor] -- setup rabbitmq monitor')
179
+ @logger.debug('monitoring rabbitmq connection')
160
180
  @timers << EM::PeriodicTimer.new(5) do
161
181
  if @rabbitmq.reconnecting?
162
- @logger.warn('[monitor] -- reconnecting to rabbitmq')
182
+ @logger.warn('reconnecting to rabbitmq')
163
183
  else
164
184
  unless @check_request_queue.subscribed?
165
- @logger.warn('[monitor] -- re-subscribing to client subscriptions')
185
+ @logger.warn('re-subscribing to client subscriptions')
166
186
  setup_subscriptions
167
187
  end
168
188
  end
@@ -170,18 +190,17 @@ module Sensu
170
190
  end
171
191
 
172
192
  def setup_standalone(options={})
173
- @logger.debug('[standalone] -- setup standalone checks')
193
+ @logger.debug('scheduling standalone checks')
174
194
  standalone_check_count = 0
175
- @settings.checks.each do |name, details|
176
- if details.standalone
195
+ stagger = options[:test] ? 0 : 7
196
+ @settings.checks.each do |check|
197
+ if check[:standalone]
177
198
  standalone_check_count += 1
178
- check = Hashie::Mash.new(details.merge(:name => name))
179
- stagger = options[:test] ? 0 : 7
180
- @timers << EM::Timer.new(stagger*standalone_check_count) do
181
- interval = options[:test] ? 0.5 : details.interval
199
+ @timers << EM::Timer.new(stagger * standalone_check_count) do
200
+ interval = options[:test] ? 0.5 : check[:interval]
182
201
  @timers << EM::PeriodicTimer.new(interval) do
183
202
  unless @rabbitmq.reconnecting?
184
- check.issued = Time.now.to_i
203
+ check[:issued] = Time.now.to_i
185
204
  execute_check(check)
186
205
  end
187
206
  end
@@ -190,24 +209,34 @@ module Sensu
190
209
  end
191
210
  end
192
211
 
193
- def setup_socket
194
- @logger.debug('[socket] -- starting up client tcp socket')
195
- EM::start_server('127.0.0.1', 3030, ClientSocket) do |socket|
212
+ def setup_sockets
213
+ @logger.debug('binding client tcp socket')
214
+ EM::start_server('127.0.0.1', 3030, Sensu::Socket) do |socket|
215
+ socket.protocol = :tcp
216
+ socket.logger = @logger
196
217
  socket.settings = @settings
218
+ socket.amq = @amq
219
+ end
220
+ @logger.debug('binding client udp socket')
221
+ EM::open_datagram_socket('127.0.0.1', 3030, Sensu::Socket) do |socket|
222
+ socket.protocol = :udp
197
223
  socket.logger = @logger
224
+ socket.settings = @settings
198
225
  socket.amq = @amq
199
226
  end
200
227
  end
201
228
 
202
229
  def stop_reactor
203
- @logger.info('[stop] -- completing checks in progress')
230
+ @logger.info('completing checks in progress', {
231
+ :checks_in_progress => @checks_in_progress
232
+ })
204
233
  complete_in_progress = EM::tick_loop do
205
234
  if @checks_in_progress.empty?
206
235
  :stop
207
236
  end
208
237
  end
209
238
  complete_in_progress.on_stop do
210
- @logger.warn('[stop] -- stopping reactor')
239
+ @logger.warn('stopping reactor')
211
240
  EM::PeriodicTimer.new(0.25) do
212
241
  EM::stop_event_loop
213
242
  end
@@ -215,12 +244,15 @@ module Sensu
215
244
  end
216
245
 
217
246
  def stop(signal)
218
- @logger.warn('[stop] -- stopping sensu client -- ' + signal)
247
+ @logger.warn('received signal', {
248
+ :signal => signal
249
+ })
250
+ @logger.warn('stopping')
219
251
  @timers.each do |timer|
220
252
  timer.cancel
221
253
  end
222
254
  unless @rabbitmq.reconnecting?
223
- @logger.warn('[stop] -- unsubscribing from client subscriptions')
255
+ @logger.warn('unsubscribing from client subscriptions')
224
256
  @check_request_queue.unsubscribe do
225
257
  stop_reactor
226
258
  end
@@ -229,39 +261,4 @@ module Sensu
229
261
  end
230
262
  end
231
263
  end
232
-
233
- class ClientSocket < EM::Connection
234
- attr_accessor :settings, :logger, :amq
235
-
236
- def receive_data(data)
237
- if data == 'ping'
238
- @logger.debug('[socket] -- received ping')
239
- send_data('pong')
240
- else
241
- @logger.debug('[socket] -- received data -- ' + data)
242
- begin
243
- check = Hashie::Mash.new(JSON.parse(data))
244
- validates = %w[name output].all? do |key|
245
- check[key].is_a?(String)
246
- end
247
- check.issued = Time.now.to_i
248
- check.status ||= 0
249
- if validates && check.status.is_a?(Integer)
250
- @logger.info('[socket] -- publishing check result -- ' + [check.name, check.status, check.output.gsub(/\n/, '\n')].join(' -- '))
251
- @amq.queue('results').publish({
252
- :client => @settings.client.name,
253
- :check => check.to_hash
254
- }.to_json)
255
- send_data('ok')
256
- else
257
- @logger.warn('[socket] -- check name and output must be strings, status defaults to 0 -- e.g. {"name": "x", "output": "y"}')
258
- send_data('invalid')
259
- end
260
- rescue JSON::ParserError => error
261
- @logger.warn('[socket] -- check result must be valid json: ' + error.to_s)
262
- send_data('invalid')
263
- end
264
- end
265
- end
266
- end
267
264
  end