sensu 0.9.5 → 0.9.6.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 +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
|