sensu 0.13.0.alpha.2-java
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.
- 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
|