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/README.org +8 -42
- data/bin/sensu-api +5 -2
- data/bin/sensu-client +5 -2
- data/bin/sensu-server +5 -2
- data/lib/sensu/api.rb +125 -95
- data/lib/sensu/base.rb +57 -0
- data/lib/sensu/cli.rb +37 -0
- data/lib/sensu/client.rb +126 -129
- data/lib/sensu/constants.rb +8 -0
- data/lib/sensu/logger.rb +39 -0
- data/lib/sensu/patches/ruby.rb +4 -80
- data/lib/sensu/process.rb +57 -0
- data/lib/sensu/server.rb +229 -163
- data/lib/sensu/settings.rb +280 -0
- data/lib/sensu/socket.rb +52 -0
- data/sensu.gemspec +22 -23
- metadata +171 -185
- data/lib/sensu/config.rb +0 -266
- data/lib/sensu/version.rb +0 -3
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__), '
|
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.
|
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
|
-
|
32
|
-
@logger =
|
33
|
-
@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('
|
40
|
-
|
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
|
46
|
-
@logger.debug('
|
47
|
-
|
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('
|
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
|
-
|
62
|
-
|
63
|
-
:
|
64
|
-
|
65
|
-
|
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('
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
80
|
+
command = @settings[:checks][check[:name]][:command].gsub(/:::(.*?):::/) do
|
76
81
|
token = $1.to_s
|
77
82
|
begin
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
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
|
-
|
90
|
+
substitute
|
87
91
|
end
|
88
92
|
if unmatched_tokens.empty?
|
89
93
|
execute = proc do
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
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
|
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
|
110
|
+
unless check[:status].nil?
|
107
111
|
publish_result(check)
|
108
112
|
else
|
109
|
-
@logger.warn('
|
113
|
+
@logger.warn('nil exit status code', {
|
114
|
+
:check => check
|
115
|
+
})
|
110
116
|
end
|
111
|
-
@checks_in_progress.delete(check
|
117
|
+
@checks_in_progress.delete(check[:name])
|
112
118
|
end
|
113
119
|
EM::defer(execute, publish)
|
114
120
|
else
|
115
|
-
@logger.warn('
|
116
|
-
|
117
|
-
|
118
|
-
|
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
|
129
|
+
@checks_in_progress.delete(check[:name])
|
121
130
|
end
|
122
131
|
else
|
123
|
-
@logger.warn('
|
132
|
+
@logger.warn('previous check execution in progress', {
|
133
|
+
:check => check
|
134
|
+
})
|
124
135
|
end
|
125
136
|
else
|
126
|
-
@logger.warn('
|
127
|
-
|
128
|
-
|
129
|
-
check
|
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
|
144
|
+
@checks_in_progress.delete(check[:name])
|
132
145
|
end
|
133
146
|
end
|
134
147
|
|
135
148
|
def setup_subscriptions
|
136
|
-
@logger.debug('
|
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
|
140
|
-
@logger.debug('
|
141
|
-
|
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 |
|
162
|
+
@check_request_queue.subscribe do |payload|
|
144
163
|
begin
|
145
|
-
check =
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
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('
|
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('
|
179
|
+
@logger.debug('monitoring rabbitmq connection')
|
160
180
|
@timers << EM::PeriodicTimer.new(5) do
|
161
181
|
if @rabbitmq.reconnecting?
|
162
|
-
@logger.warn('
|
182
|
+
@logger.warn('reconnecting to rabbitmq')
|
163
183
|
else
|
164
184
|
unless @check_request_queue.subscribed?
|
165
|
-
@logger.warn('
|
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('
|
193
|
+
@logger.debug('scheduling standalone checks')
|
174
194
|
standalone_check_count = 0
|
175
|
-
|
176
|
-
|
195
|
+
stagger = options[:test] ? 0 : 7
|
196
|
+
@settings.checks.each do |check|
|
197
|
+
if check[:standalone]
|
177
198
|
standalone_check_count += 1
|
178
|
-
|
179
|
-
|
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
|
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
|
194
|
-
@logger.debug('
|
195
|
-
EM::start_server('127.0.0.1', 3030,
|
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('
|
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('
|
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('
|
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('
|
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
|