sensu 0.13.0.alpha.2-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +455 -0
- data/MIT-LICENSE.txt +20 -0
- data/README.md +11 -0
- data/bin/sensu-api +10 -0
- data/bin/sensu-client +10 -0
- data/bin/sensu-server +10 -0
- data/lib/sensu.rb +3 -0
- data/lib/sensu/api.rb +674 -0
- data/lib/sensu/cli.rb +51 -0
- data/lib/sensu/client.rb +261 -0
- data/lib/sensu/constants.rb +9 -0
- data/lib/sensu/daemon.rb +221 -0
- data/lib/sensu/redis.rb +20 -0
- data/lib/sensu/sandbox.rb +11 -0
- data/lib/sensu/server.rb +764 -0
- data/lib/sensu/socket.rb +79 -0
- data/lib/sensu/utilities.rb +60 -0
- data/sensu.gemspec +38 -0
- metadata +277 -0
data/lib/sensu/cli.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'sensu/logger/constants'
|
3
|
+
|
4
|
+
module Sensu
|
5
|
+
class CLI
|
6
|
+
def self.read(arguments=ARGV)
|
7
|
+
options = Hash.new
|
8
|
+
optparse = OptionParser.new do |opts|
|
9
|
+
opts.on('-h', '--help', 'Display this message') do
|
10
|
+
puts opts
|
11
|
+
exit
|
12
|
+
end
|
13
|
+
opts.on('-V', '--version', 'Display version') do
|
14
|
+
puts VERSION
|
15
|
+
exit
|
16
|
+
end
|
17
|
+
opts.on('-c', '--config FILE', 'Sensu JSON config FILE') do |file|
|
18
|
+
options[:config_file] = file
|
19
|
+
end
|
20
|
+
opts.on('-d', '--config_dir DIR[,DIR]', 'DIR or comma-delimited DIR list for Sensu JSON config files') do |dir|
|
21
|
+
options[:config_dirs] = dir.split(',')
|
22
|
+
end
|
23
|
+
opts.on('-e', '--extension_dir DIR', 'DIR for Sensu extensions') do |dir|
|
24
|
+
options[:extension_dir] = dir
|
25
|
+
end
|
26
|
+
opts.on('-l', '--log FILE', 'Log to a given FILE. Default: STDOUT') do |file|
|
27
|
+
options[:log_file] = file
|
28
|
+
end
|
29
|
+
opts.on('-L', '--log_level LEVEL', 'Log severity LEVEL') do |level|
|
30
|
+
log_level = level.to_s.downcase.to_sym
|
31
|
+
unless Logger::LEVELS.include?(log_level)
|
32
|
+
puts 'Unknown log level: ' + level.to_s
|
33
|
+
exit 1
|
34
|
+
end
|
35
|
+
options[:log_level] = log_level
|
36
|
+
end
|
37
|
+
opts.on('-v', '--verbose', 'Enable verbose logging') do
|
38
|
+
options[:log_level] = :debug
|
39
|
+
end
|
40
|
+
opts.on('-b', '--background', 'Fork into the background') do
|
41
|
+
options[:daemonize] = true
|
42
|
+
end
|
43
|
+
opts.on('-p', '--pid_file FILE', 'Write the PID to a given FILE') do |file|
|
44
|
+
options[:pid_file] = file
|
45
|
+
end
|
46
|
+
end
|
47
|
+
optparse.parse!(arguments)
|
48
|
+
options
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/sensu/client.rb
ADDED
@@ -0,0 +1,261 @@
|
|
1
|
+
require 'sensu/daemon'
|
2
|
+
require 'sensu/socket'
|
3
|
+
|
4
|
+
module Sensu
|
5
|
+
class Client
|
6
|
+
include Daemon
|
7
|
+
|
8
|
+
attr_accessor :safe_mode
|
9
|
+
|
10
|
+
def self.run(options={})
|
11
|
+
client = self.new(options)
|
12
|
+
EM::run do
|
13
|
+
client.start
|
14
|
+
client.setup_signal_traps
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(options={})
|
19
|
+
super
|
20
|
+
@safe_mode = @settings[:client][:safe_mode] || false
|
21
|
+
@checks_in_progress = Array.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def publish_keepalive
|
25
|
+
keepalive = @settings[:client].merge({
|
26
|
+
:version => VERSION,
|
27
|
+
:timestamp => Time.now.to_i
|
28
|
+
})
|
29
|
+
payload = redact_sensitive(keepalive, @settings[:client][:redact])
|
30
|
+
@logger.debug('publishing keepalive', {
|
31
|
+
:payload => payload
|
32
|
+
})
|
33
|
+
@transport.publish(:direct, 'keepalives', MultiJson.dump(payload)) do |info|
|
34
|
+
if info[:error]
|
35
|
+
@logger.error('failed to publish keepalive', {
|
36
|
+
:payload => payload,
|
37
|
+
:error => info[:error].to_s
|
38
|
+
})
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def setup_keepalives
|
44
|
+
@logger.debug('scheduling keepalives')
|
45
|
+
publish_keepalive
|
46
|
+
@timers[:run] << EM::PeriodicTimer.new(20) do
|
47
|
+
unless @state == :paused
|
48
|
+
publish_keepalive
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def publish_result(check)
|
54
|
+
payload = {
|
55
|
+
:client => @settings[:client][:name],
|
56
|
+
:check => check
|
57
|
+
}
|
58
|
+
@logger.info('publishing check result', {
|
59
|
+
:payload => payload
|
60
|
+
})
|
61
|
+
@transport.publish(:direct, 'results', MultiJson.dump(payload)) do |info|
|
62
|
+
if info[:error]
|
63
|
+
@logger.error('failed to publish check result', {
|
64
|
+
:payload => payload,
|
65
|
+
:error => info[:error].to_s
|
66
|
+
})
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def substitute_command_tokens(check)
|
72
|
+
unmatched_tokens = Array.new
|
73
|
+
substituted = check[:command].gsub(/:::([^:]*?):::/) do
|
74
|
+
token, default = $1.to_s.split('|', -1)
|
75
|
+
matched = token.split('.').inject(@settings[:client]) do |client, attribute|
|
76
|
+
if client[attribute].nil?
|
77
|
+
default.nil? ? break : default
|
78
|
+
else
|
79
|
+
client[attribute]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
if matched.nil?
|
83
|
+
unmatched_tokens << token
|
84
|
+
end
|
85
|
+
matched
|
86
|
+
end
|
87
|
+
[substituted, unmatched_tokens]
|
88
|
+
end
|
89
|
+
|
90
|
+
def execute_check_command(check)
|
91
|
+
@logger.debug('attempting to execute check command', {
|
92
|
+
:check => check
|
93
|
+
})
|
94
|
+
unless @checks_in_progress.include?(check[:name])
|
95
|
+
@checks_in_progress << check[:name]
|
96
|
+
command, unmatched_tokens = substitute_command_tokens(check)
|
97
|
+
if unmatched_tokens.empty?
|
98
|
+
check[:executed] = Time.now.to_i
|
99
|
+
started = Time.now.to_f
|
100
|
+
Spawn.process(command, :timeout => check[:timeout]) do |output, status|
|
101
|
+
check[:duration] = ('%.3f' % (Time.now.to_f - started)).to_f
|
102
|
+
check[:output] = output
|
103
|
+
check[:status] = status
|
104
|
+
publish_result(check)
|
105
|
+
@checks_in_progress.delete(check[:name])
|
106
|
+
end
|
107
|
+
else
|
108
|
+
check[:output] = 'Unmatched command tokens: ' + unmatched_tokens.join(', ')
|
109
|
+
check[:status] = 3
|
110
|
+
check[:handle] = false
|
111
|
+
publish_result(check)
|
112
|
+
@checks_in_progress.delete(check[:name])
|
113
|
+
end
|
114
|
+
else
|
115
|
+
@logger.warn('previous check command execution in progress', {
|
116
|
+
:check => check
|
117
|
+
})
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def run_check_extension(check)
|
122
|
+
@logger.debug('attempting to run check extension', {
|
123
|
+
:check => check
|
124
|
+
})
|
125
|
+
check[:executed] = Time.now.to_i
|
126
|
+
extension = @extensions[:checks][check[:name]]
|
127
|
+
extension.safe_run do |output, status|
|
128
|
+
check[:output] = output
|
129
|
+
check[:status] = status
|
130
|
+
publish_result(check)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def process_check(check)
|
135
|
+
@logger.debug('processing check', {
|
136
|
+
:check => check
|
137
|
+
})
|
138
|
+
if check.has_key?(:command)
|
139
|
+
if @settings.check_exists?(check[:name])
|
140
|
+
check.merge!(@settings[:checks][check[:name]])
|
141
|
+
execute_check_command(check)
|
142
|
+
elsif @safe_mode
|
143
|
+
check[:output] = 'Check is not locally defined (safe mode)'
|
144
|
+
check[:status] = 3
|
145
|
+
check[:handle] = false
|
146
|
+
check[:executed] = Time.now.to_i
|
147
|
+
publish_result(check)
|
148
|
+
else
|
149
|
+
execute_check_command(check)
|
150
|
+
end
|
151
|
+
else
|
152
|
+
if @extensions.check_exists?(check[:name])
|
153
|
+
run_check_extension(check)
|
154
|
+
else
|
155
|
+
@logger.warn('unknown check extension', {
|
156
|
+
:check => check
|
157
|
+
})
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def setup_subscriptions
|
163
|
+
@logger.debug('subscribing to client subscriptions')
|
164
|
+
@settings[:client][:subscriptions].each do |subscription|
|
165
|
+
@logger.debug('subscribing to a subscription', {
|
166
|
+
:subscription => subscription
|
167
|
+
})
|
168
|
+
funnel = [@settings[:client][:name], VERSION, Time.now.to_i].join('-')
|
169
|
+
@transport.subscribe(:fanout, subscription, funnel) do |message_info, message|
|
170
|
+
check = MultiJson.load(message)
|
171
|
+
@logger.info('received check request', {
|
172
|
+
:check => check
|
173
|
+
})
|
174
|
+
process_check(check)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def schedule_checks(checks)
|
180
|
+
check_count = 0
|
181
|
+
stagger = testing? ? 0 : 2
|
182
|
+
checks.each do |check|
|
183
|
+
check_count += 1
|
184
|
+
scheduling_delay = stagger * check_count % 30
|
185
|
+
@timers[:run] << EM::Timer.new(scheduling_delay) do
|
186
|
+
interval = testing? ? 0.5 : check[:interval]
|
187
|
+
@timers[:run] << EM::PeriodicTimer.new(interval) do
|
188
|
+
unless @state == :paused
|
189
|
+
check[:issued] = Time.now.to_i
|
190
|
+
process_check(check.dup)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def setup_standalone
|
198
|
+
@logger.debug('scheduling standalone checks')
|
199
|
+
standard_checks = @settings.checks.select do |check|
|
200
|
+
check[:standalone]
|
201
|
+
end
|
202
|
+
extension_checks = @extensions.checks.select do |check|
|
203
|
+
check[:standalone] && check[:interval].is_a?(Integer)
|
204
|
+
end
|
205
|
+
schedule_checks(standard_checks + extension_checks)
|
206
|
+
end
|
207
|
+
|
208
|
+
def setup_sockets
|
209
|
+
options = @settings[:client][:socket] || Hash.new
|
210
|
+
options[:bind] ||= '127.0.0.1'
|
211
|
+
options[:port] ||= 3030
|
212
|
+
@logger.debug('binding client tcp and udp sockets', {
|
213
|
+
:options => options
|
214
|
+
})
|
215
|
+
EM::start_server(options[:bind], options[:port], Socket) do |socket|
|
216
|
+
socket.logger = @logger
|
217
|
+
socket.settings = @settings
|
218
|
+
socket.transport = @transport
|
219
|
+
end
|
220
|
+
EM::open_datagram_socket(options[:bind], options[:port], Socket) do |socket|
|
221
|
+
socket.logger = @logger
|
222
|
+
socket.settings = @settings
|
223
|
+
socket.transport = @transport
|
224
|
+
socket.reply = false
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def complete_checks_in_progress(&block)
|
229
|
+
@logger.info('completing checks in progress', {
|
230
|
+
:checks_in_progress => @checks_in_progress
|
231
|
+
})
|
232
|
+
retry_until_true do
|
233
|
+
if @checks_in_progress.empty?
|
234
|
+
block.call
|
235
|
+
true
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def start
|
241
|
+
setup_transport
|
242
|
+
setup_keepalives
|
243
|
+
setup_subscriptions
|
244
|
+
setup_standalone
|
245
|
+
setup_sockets
|
246
|
+
super
|
247
|
+
end
|
248
|
+
|
249
|
+
def stop
|
250
|
+
@logger.warn('stopping')
|
251
|
+
@timers[:run].each do |timer|
|
252
|
+
timer.cancel
|
253
|
+
end
|
254
|
+
@transport.unsubscribe
|
255
|
+
complete_checks_in_progress do
|
256
|
+
@transport.close
|
257
|
+
super
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
data/lib/sensu/daemon.rb
ADDED
@@ -0,0 +1,221 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
gem 'multi_json', '1.10.1'
|
4
|
+
|
5
|
+
gem 'sensu-em', '2.0.0'
|
6
|
+
gem 'sensu-logger', '0.0.1'
|
7
|
+
gem 'sensu-settings', '0.0.5'
|
8
|
+
gem 'sensu-extension', '0.0.3'
|
9
|
+
gem 'sensu-extensions', '0.0.5'
|
10
|
+
gem 'sensu-transport', '0.0.2'
|
11
|
+
gem 'sensu-spawn', '0.0.3'
|
12
|
+
|
13
|
+
require 'time'
|
14
|
+
require 'uri'
|
15
|
+
|
16
|
+
require 'sensu/logger'
|
17
|
+
require 'sensu/settings'
|
18
|
+
require 'sensu/extensions'
|
19
|
+
require 'sensu/transport'
|
20
|
+
require 'sensu/spawn'
|
21
|
+
|
22
|
+
require 'sensu/constants'
|
23
|
+
require 'sensu/utilities'
|
24
|
+
require 'sensu/cli'
|
25
|
+
require 'sensu/redis'
|
26
|
+
|
27
|
+
MultiJson.load_options = {:symbolize_keys => true}
|
28
|
+
|
29
|
+
module Sensu
|
30
|
+
module Daemon
|
31
|
+
include Utilities
|
32
|
+
|
33
|
+
attr_reader :state
|
34
|
+
|
35
|
+
def initialize(options={})
|
36
|
+
@state = :initializing
|
37
|
+
@timers = {
|
38
|
+
:run => Array.new
|
39
|
+
}
|
40
|
+
setup_logger(options)
|
41
|
+
load_settings(options)
|
42
|
+
load_extensions(options)
|
43
|
+
setup_process(options)
|
44
|
+
end
|
45
|
+
|
46
|
+
def setup_logger(options={})
|
47
|
+
@logger = Logger.get(options)
|
48
|
+
@logger.setup_signal_traps
|
49
|
+
end
|
50
|
+
|
51
|
+
def log_concerns(concerns=[], level=:warn)
|
52
|
+
concerns.each do |concern|
|
53
|
+
message = concern.delete(:message)
|
54
|
+
@logger.send(level, message, redact_sensitive(concern))
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def load_settings(options={})
|
59
|
+
@settings = Settings.get(options)
|
60
|
+
log_concerns(@settings.warnings)
|
61
|
+
failures = @settings.validate
|
62
|
+
unless failures.empty?
|
63
|
+
@logger.fatal('invalid settings')
|
64
|
+
log_concerns(failures, :fatal)
|
65
|
+
@logger.fatal('SENSU NOT RUNNING!')
|
66
|
+
exit 2
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def load_extensions(options={})
|
71
|
+
@extensions = Extensions.get(options)
|
72
|
+
log_concerns(@extensions.warnings)
|
73
|
+
extension_settings = @settings.to_hash.dup
|
74
|
+
@extensions.all.each do |extension|
|
75
|
+
extension.logger = @logger
|
76
|
+
extension.settings = extension_settings
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def setup_process(options)
|
81
|
+
if options[:daemonize]
|
82
|
+
daemonize
|
83
|
+
end
|
84
|
+
if options[:pid_file]
|
85
|
+
write_pid(options[:pid_file])
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def start
|
90
|
+
@state = :running
|
91
|
+
end
|
92
|
+
|
93
|
+
def pause
|
94
|
+
@state = :paused
|
95
|
+
end
|
96
|
+
|
97
|
+
def resume
|
98
|
+
@state = :running
|
99
|
+
end
|
100
|
+
|
101
|
+
def stop
|
102
|
+
@state = :stopped
|
103
|
+
@logger.warn('stopping reactor')
|
104
|
+
EM::stop_event_loop
|
105
|
+
end
|
106
|
+
|
107
|
+
def setup_signal_traps
|
108
|
+
@signals = Array.new
|
109
|
+
STOP_SIGNALS.each do |signal|
|
110
|
+
Signal.trap(signal) do
|
111
|
+
@signals << signal
|
112
|
+
end
|
113
|
+
end
|
114
|
+
EM::PeriodicTimer.new(1) do
|
115
|
+
signal = @signals.shift
|
116
|
+
if STOP_SIGNALS.include?(signal)
|
117
|
+
@logger.warn('received signal', {
|
118
|
+
:signal => signal
|
119
|
+
})
|
120
|
+
stop
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def setup_transport
|
126
|
+
if @settings[:transport].is_a?(Hash)
|
127
|
+
transport_name = @settings[:transport][:name]
|
128
|
+
end
|
129
|
+
transport_name ||= 'rabbitmq'
|
130
|
+
transport_settings = @settings[transport_name.to_sym]
|
131
|
+
@logger.debug('connecting to transport', {
|
132
|
+
:name => transport_name,
|
133
|
+
:settings => transport_settings
|
134
|
+
})
|
135
|
+
@transport = Transport.connect(transport_name, transport_settings)
|
136
|
+
@transport.logger = @logger
|
137
|
+
@transport.on_error do |error|
|
138
|
+
@logger.fatal('transport connection error', {
|
139
|
+
:error => error.to_s
|
140
|
+
})
|
141
|
+
stop
|
142
|
+
end
|
143
|
+
@transport.before_reconnect do
|
144
|
+
unless testing?
|
145
|
+
@logger.warn('reconnecting to transport')
|
146
|
+
pause
|
147
|
+
end
|
148
|
+
end
|
149
|
+
@transport.after_reconnect do
|
150
|
+
@logger.info('reconnected to transport')
|
151
|
+
resume
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def setup_redis
|
156
|
+
@logger.debug('connecting to redis', {
|
157
|
+
:settings => @settings[:redis]
|
158
|
+
})
|
159
|
+
@redis = Redis.connect(@settings[:redis])
|
160
|
+
@redis.on_error do |error|
|
161
|
+
@logger.fatal('redis connection error', {
|
162
|
+
:error => error.to_s
|
163
|
+
})
|
164
|
+
stop
|
165
|
+
end
|
166
|
+
@redis.before_reconnect do
|
167
|
+
unless testing?
|
168
|
+
@logger.warn('reconnecting to redis')
|
169
|
+
pause
|
170
|
+
end
|
171
|
+
end
|
172
|
+
@redis.after_reconnect do
|
173
|
+
@logger.info('reconnected to redis')
|
174
|
+
resume
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
private
|
179
|
+
|
180
|
+
def write_pid(file)
|
181
|
+
begin
|
182
|
+
File.open(file, 'w') do |pid_file|
|
183
|
+
pid_file.puts(Process.pid)
|
184
|
+
end
|
185
|
+
rescue
|
186
|
+
@logger.fatal('could not write to pid file', {
|
187
|
+
:pid_file => file
|
188
|
+
})
|
189
|
+
@logger.fatal('SENSU NOT RUNNING!')
|
190
|
+
exit 2
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def daemonize
|
195
|
+
Kernel.srand
|
196
|
+
if Kernel.fork
|
197
|
+
exit
|
198
|
+
end
|
199
|
+
unless Process.setsid
|
200
|
+
@logger.fatal('cannot detach from controlling terminal')
|
201
|
+
@logger.fatal('SENSU NOT RUNNING!')
|
202
|
+
exit 2
|
203
|
+
end
|
204
|
+
Signal.trap('SIGHUP', 'IGNORE')
|
205
|
+
if Kernel.fork
|
206
|
+
exit
|
207
|
+
end
|
208
|
+
Dir.chdir('/')
|
209
|
+
ObjectSpace.each_object(IO) do |io|
|
210
|
+
unless [STDIN, STDOUT, STDERR].include?(io)
|
211
|
+
begin
|
212
|
+
unless io.closed?
|
213
|
+
io.close
|
214
|
+
end
|
215
|
+
rescue
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|