sensu 0.16.0-java → 0.17.0.beta.1-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 +4 -4
- data/CHANGELOG.md +21 -0
- data/bin/sensu-api +4 -4
- data/bin/sensu-client +4 -4
- data/bin/sensu-server +4 -4
- data/lib/sensu/api/process.rb +704 -0
- data/lib/sensu/cli.rb +21 -15
- data/lib/sensu/client/process.rb +414 -0
- data/lib/sensu/client/socket.rb +226 -0
- data/lib/sensu/constants.rb +4 -1
- data/lib/sensu/daemon.rb +125 -73
- data/lib/sensu/redis.rb +10 -5
- data/lib/sensu/server/filter.rb +309 -0
- data/lib/sensu/server/handle.rb +168 -0
- data/lib/sensu/server/mutate.rb +92 -0
- data/lib/sensu/server/process.rb +811 -0
- data/lib/sensu/server/sandbox.rb +21 -0
- data/lib/sensu/server/socket.rb +42 -0
- data/lib/sensu/utilities.rb +29 -3
- data/sensu.gemspec +29 -28
- metadata +34 -16
- data/lib/sensu/api.rb +0 -704
- data/lib/sensu/client.rb +0 -292
- data/lib/sensu/sandbox.rb +0 -11
- data/lib/sensu/server.rb +0 -767
- data/lib/sensu/socket.rb +0 -246
data/lib/sensu/cli.rb
CHANGED
@@ -1,46 +1,52 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "optparse"
|
2
|
+
require "sensu/logger/constants"
|
3
3
|
|
4
4
|
module Sensu
|
5
5
|
class CLI
|
6
|
+
# Parse CLI arguments using Ruby stdlib `optparse`. This method
|
7
|
+
# provides Sensu with process options (eg. log file), and can
|
8
|
+
# provide users with information, such as the Sensu version.
|
9
|
+
#
|
10
|
+
# @param arguments [Array] to parse.
|
11
|
+
# @return [Hash] options
|
6
12
|
def self.read(arguments=ARGV)
|
7
|
-
options =
|
13
|
+
options = {}
|
8
14
|
optparse = OptionParser.new do |opts|
|
9
|
-
opts.on(
|
15
|
+
opts.on("-h", "--help", "Display this message") do
|
10
16
|
puts opts
|
11
17
|
exit
|
12
18
|
end
|
13
|
-
opts.on(
|
19
|
+
opts.on("-V", "--version", "Display version") do
|
14
20
|
puts VERSION
|
15
21
|
exit
|
16
22
|
end
|
17
|
-
opts.on(
|
23
|
+
opts.on("-c", "--config FILE", "Sensu JSON config FILE") do |file|
|
18
24
|
options[:config_file] = file
|
19
25
|
end
|
20
|
-
opts.on(
|
21
|
-
options[:config_dirs] = dir.split(
|
26
|
+
opts.on("-d", "--config_dir DIR[,DIR]", "DIR or comma-delimited DIR list for Sensu JSON config files") do |dir|
|
27
|
+
options[:config_dirs] = dir.split(",")
|
22
28
|
end
|
23
|
-
opts.on(
|
29
|
+
opts.on("-e", "--extension_dir DIR", "DIR for Sensu extensions") do |dir|
|
24
30
|
options[:extension_dir] = dir
|
25
31
|
end
|
26
|
-
opts.on(
|
32
|
+
opts.on("-l", "--log FILE", "Log to a given FILE. Default: STDOUT") do |file|
|
27
33
|
options[:log_file] = file
|
28
34
|
end
|
29
|
-
opts.on(
|
35
|
+
opts.on("-L", "--log_level LEVEL", "Log severity LEVEL") do |level|
|
30
36
|
log_level = level.to_s.downcase.to_sym
|
31
37
|
unless Logger::LEVELS.include?(log_level)
|
32
|
-
puts
|
38
|
+
puts "Unknown log level: #{level}"
|
33
39
|
exit 1
|
34
40
|
end
|
35
41
|
options[:log_level] = log_level
|
36
42
|
end
|
37
|
-
opts.on(
|
43
|
+
opts.on("-v", "--verbose", "Enable verbose logging") do
|
38
44
|
options[:log_level] = :debug
|
39
45
|
end
|
40
|
-
opts.on(
|
46
|
+
opts.on("-b", "--background", "Fork into the background") do
|
41
47
|
options[:daemonize] = true
|
42
48
|
end
|
43
|
-
opts.on(
|
49
|
+
opts.on("-p", "--pid_file FILE", "Write the PID to a given FILE") do |file|
|
44
50
|
options[:pid_file] = file
|
45
51
|
end
|
46
52
|
end
|
@@ -0,0 +1,414 @@
|
|
1
|
+
require "sensu/daemon"
|
2
|
+
require "sensu/client/socket"
|
3
|
+
|
4
|
+
module Sensu
|
5
|
+
module Client
|
6
|
+
class Process
|
7
|
+
include Daemon
|
8
|
+
|
9
|
+
attr_accessor :safe_mode
|
10
|
+
|
11
|
+
# Create an instance of the Sensu client process, start the
|
12
|
+
# client within the EventMachine event loop, and set up client
|
13
|
+
# process signal traps (for stopping).
|
14
|
+
#
|
15
|
+
# @param options [Hash]
|
16
|
+
def self.run(options={})
|
17
|
+
client = self.new(options)
|
18
|
+
EM::run do
|
19
|
+
client.start
|
20
|
+
client.setup_signal_traps
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Override Daemon initialize() to support Sensu client check
|
25
|
+
# execution safe mode and checks in progress.
|
26
|
+
#
|
27
|
+
# @param options [Hash]
|
28
|
+
def initialize(options={})
|
29
|
+
super
|
30
|
+
@safe_mode = @settings[:client][:safe_mode] || false
|
31
|
+
@checks_in_progress = []
|
32
|
+
end
|
33
|
+
|
34
|
+
# Create a Sensu client keepalive payload, to be sent over the
|
35
|
+
# transport for processing. A client keepalive is composed of
|
36
|
+
# its settings definition, the Sensu version, and a timestamp.
|
37
|
+
# Sensitive information is redacted from the keepalive payload.
|
38
|
+
#
|
39
|
+
# @return [Hash] keepalive payload
|
40
|
+
def keepalive_payload
|
41
|
+
payload = @settings[:client].merge({
|
42
|
+
:version => VERSION,
|
43
|
+
:timestamp => Time.now.to_i
|
44
|
+
})
|
45
|
+
redact_sensitive(payload, @settings[:client][:redact])
|
46
|
+
end
|
47
|
+
|
48
|
+
# Publish a Sensu client keepalive to the transport for
|
49
|
+
# processing. JSON serialization is used for transport messages.
|
50
|
+
def publish_keepalive
|
51
|
+
payload = keepalive_payload
|
52
|
+
@logger.debug("publishing keepalive", :payload => payload)
|
53
|
+
@transport.publish(:direct, "keepalives", MultiJson.dump(payload)) do |info|
|
54
|
+
if info[:error]
|
55
|
+
@logger.error("failed to publish keepalive", {
|
56
|
+
:payload => payload,
|
57
|
+
:error => info[:error].to_s
|
58
|
+
})
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Schedule Sensu client keepalives. Immediately publish a
|
64
|
+
# keepalive to register the client, then publish a keepalive
|
65
|
+
# every 20 seconds. Sensu client keepalives are used to
|
66
|
+
# determine client (& machine) health.
|
67
|
+
def setup_keepalives
|
68
|
+
@logger.debug("scheduling keepalives")
|
69
|
+
publish_keepalive
|
70
|
+
@timers[:run] << EM::PeriodicTimer.new(20) do
|
71
|
+
publish_keepalive
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Publish a check result to the transport for processing. A
|
76
|
+
# check result is composed of a client (name) and a check
|
77
|
+
# definition, containing check `:output` and `:status`. JSON
|
78
|
+
# serialization is used when publishing the check result payload
|
79
|
+
# to the transport pipe. Transport errors are logged.
|
80
|
+
#
|
81
|
+
# @param check [Hash]
|
82
|
+
def publish_check_result(check)
|
83
|
+
payload = {
|
84
|
+
:client => @settings[:client][:name],
|
85
|
+
:check => check
|
86
|
+
}
|
87
|
+
@logger.info("publishing check result", :payload => payload)
|
88
|
+
@transport.publish(:direct, "results", MultiJson.dump(payload)) do |info|
|
89
|
+
if info[:error]
|
90
|
+
@logger.error("failed to publish check result", {
|
91
|
+
:payload => payload,
|
92
|
+
:error => info[:error].to_s
|
93
|
+
})
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Traverse the Sensu client definition (hash) for an attribute
|
99
|
+
# value, with a fallback default value if nil.
|
100
|
+
#
|
101
|
+
# @param tree [Hash] to traverse.
|
102
|
+
# @param path [Array] of attribute keys.
|
103
|
+
# @param default [Object] value if attribute value is nil.
|
104
|
+
# @return [Object] attribute or fallback default value.
|
105
|
+
def find_client_attribute(tree, path, default)
|
106
|
+
attribute = tree[path.shift]
|
107
|
+
if attribute.is_a?(Hash)
|
108
|
+
find_client_attribute(attribute, path, default)
|
109
|
+
else
|
110
|
+
attribute.nil? ? default : attribute
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Substitue check command tokens (eg. :::db.name|production:::)
|
115
|
+
# with the associated client definition attribute value. Command
|
116
|
+
# tokens can provide a fallback default value, following a pipe.
|
117
|
+
#
|
118
|
+
# @param check [Hash]
|
119
|
+
# @return [Array] containing the check command string with
|
120
|
+
# tokens substituted and an array of unmatched command tokens.
|
121
|
+
def substitute_check_command_tokens(check)
|
122
|
+
unmatched_tokens = []
|
123
|
+
substituted = check[:command].gsub(/:::([^:].*?):::/) do
|
124
|
+
token, default = $1.to_s.split("|", -1)
|
125
|
+
matched = find_client_attribute(@settings[:client], token.split("."), default)
|
126
|
+
if matched.nil?
|
127
|
+
unmatched_tokens << token
|
128
|
+
end
|
129
|
+
matched
|
130
|
+
end
|
131
|
+
[substituted, unmatched_tokens]
|
132
|
+
end
|
133
|
+
|
134
|
+
# Execute a check command, capturing its output (STDOUT/ERR),
|
135
|
+
# exit status code, execution duration, timestamp, and publish
|
136
|
+
# the result. This method guards against multiple executions for
|
137
|
+
# the same check. Check command tokens are substituted with the
|
138
|
+
# associated client attribute values. If there are unmatched
|
139
|
+
# check command tokens, the check command will not be executed,
|
140
|
+
# instead a check result will be published reporting the
|
141
|
+
# unmatched tokens.
|
142
|
+
#
|
143
|
+
# @param check [Hash]
|
144
|
+
def execute_check_command(check)
|
145
|
+
@logger.debug("attempting to execute check command", :check => check)
|
146
|
+
unless @checks_in_progress.include?(check[:name])
|
147
|
+
@checks_in_progress << check[:name]
|
148
|
+
command, unmatched_tokens = substitute_check_command_tokens(check)
|
149
|
+
if unmatched_tokens.empty?
|
150
|
+
check[:executed] = Time.now.to_i
|
151
|
+
started = Time.now.to_f
|
152
|
+
Spawn.process(command, :timeout => check[:timeout]) do |output, status|
|
153
|
+
check[:duration] = ("%.3f" % (Time.now.to_f - started)).to_f
|
154
|
+
check[:output] = output
|
155
|
+
check[:status] = status
|
156
|
+
publish_check_result(check)
|
157
|
+
@checks_in_progress.delete(check[:name])
|
158
|
+
end
|
159
|
+
else
|
160
|
+
check[:output] = "Unmatched command tokens: " + unmatched_tokens.join(", ")
|
161
|
+
check[:status] = 3
|
162
|
+
check[:handle] = false
|
163
|
+
publish_check_result(check)
|
164
|
+
@checks_in_progress.delete(check[:name])
|
165
|
+
end
|
166
|
+
else
|
167
|
+
@logger.warn("previous check command execution in progress", :check => check)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# Run a check extension and publish the result. The Sensu client
|
172
|
+
# loads check extensions, checks that run within the Sensu Ruby
|
173
|
+
# VM and the EventMachine event loop, using the Sensu Extension
|
174
|
+
# API.
|
175
|
+
#
|
176
|
+
# https://github.com/sensu/sensu-extension
|
177
|
+
#
|
178
|
+
# @param check [Hash]
|
179
|
+
def run_check_extension(check)
|
180
|
+
@logger.debug("attempting to run check extension", :check => check)
|
181
|
+
check[:executed] = Time.now.to_i
|
182
|
+
extension = @extensions[:checks][check[:name]]
|
183
|
+
extension.safe_run do |output, status|
|
184
|
+
check[:output] = output
|
185
|
+
check[:status] = status
|
186
|
+
publish_check_result(check)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# Process a check request. If a check request has a check
|
191
|
+
# command, it will be executed. A check request without a check
|
192
|
+
# command indicates a check extension run. A check request will
|
193
|
+
# be merged with a local check definition, if present. Client
|
194
|
+
# safe mode is enforced in this method, requiring a local check
|
195
|
+
# definition in order to execute the check command. If a local
|
196
|
+
# check definition does not exist when operating with client
|
197
|
+
# safe mode, a check result will be published to report the
|
198
|
+
# missing check definition.
|
199
|
+
#
|
200
|
+
# @param check [Hash]
|
201
|
+
def process_check_request(check)
|
202
|
+
@logger.debug("processing check", :check => check)
|
203
|
+
if check.has_key?(:command)
|
204
|
+
if @settings.check_exists?(check[:name])
|
205
|
+
check.merge!(@settings[:checks][check[:name]])
|
206
|
+
execute_check_command(check)
|
207
|
+
elsif @safe_mode
|
208
|
+
check[:output] = "Check is not locally defined (safe mode)"
|
209
|
+
check[:status] = 3
|
210
|
+
check[:handle] = false
|
211
|
+
check[:executed] = Time.now.to_i
|
212
|
+
publish_check_result(check)
|
213
|
+
else
|
214
|
+
execute_check_command(check)
|
215
|
+
end
|
216
|
+
else
|
217
|
+
if @extensions.check_exists?(check[:name])
|
218
|
+
run_check_extension(check)
|
219
|
+
else
|
220
|
+
@logger.warn("unknown check extension", :check => check)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# Set up Sensu client subscriptions. Subscriptions determine the
|
226
|
+
# kinds of check requests the client will receive. A unique
|
227
|
+
# transport funnel is created for the Sensu client, using a
|
228
|
+
# combination of it's name, the Sensu version, and the current
|
229
|
+
# timestamp (epoch). The unique funnel is bound to each
|
230
|
+
# transport pipe, named after the client subscription. The Sensu
|
231
|
+
# client will receive JSON serialized check requests from its
|
232
|
+
# funnel, that get parsed and processed.
|
233
|
+
def setup_subscriptions
|
234
|
+
@logger.debug("subscribing to client subscriptions")
|
235
|
+
@settings[:client][:subscriptions].each do |subscription|
|
236
|
+
@logger.debug("subscribing to a subscription", :subscription => subscription)
|
237
|
+
funnel = [@settings[:client][:name], VERSION, Time.now.to_i].join("-")
|
238
|
+
@transport.subscribe(:fanout, subscription, funnel) do |message_info, message|
|
239
|
+
begin
|
240
|
+
check = MultiJson.load(message)
|
241
|
+
@logger.info("received check request", :check => check)
|
242
|
+
process_check_request(check)
|
243
|
+
rescue MultiJson::ParseError => error
|
244
|
+
@logger.error("failed to parse the check request payload", {
|
245
|
+
:message => message,
|
246
|
+
:error => error.to_s
|
247
|
+
})
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# Calculate a check execution splay, taking into account the
|
254
|
+
# current time and the execution interval to ensure it's
|
255
|
+
# consistent between process restarts.
|
256
|
+
#
|
257
|
+
# @param check [Hash] definition.
|
258
|
+
def calculate_execution_splay(check)
|
259
|
+
key = [@settings[:client][:name], check[:name]].join(":")
|
260
|
+
splay_hash = Digest::MD5.digest(key).unpack("Q<").first
|
261
|
+
current_time = (Time.now.to_f * 1000).to_i
|
262
|
+
(splay_hash - current_time) % (check[:interval] * 1000) / 1000.0
|
263
|
+
end
|
264
|
+
|
265
|
+
# Schedule check executions, using EventMachine periodic timers,
|
266
|
+
# using a calculated execution splay. The timers are stored in
|
267
|
+
# the timers hash under `:run`, so they can be cancelled etc.
|
268
|
+
# Check definitions are duplicated before processing them, in
|
269
|
+
# case they are mutated. The check `:issued` timestamp is set
|
270
|
+
# here, to mimic check requests issued by a Sensu server.
|
271
|
+
#
|
272
|
+
# @param checks [Array] of definitions.
|
273
|
+
def schedule_checks(checks)
|
274
|
+
checks.each do |check|
|
275
|
+
execute_check = Proc.new do
|
276
|
+
check[:issued] = Time.now.to_i
|
277
|
+
process_check_request(check.dup)
|
278
|
+
end
|
279
|
+
execution_splay = testing? ? 0 : calculate_execution_splay(check)
|
280
|
+
interval = testing? ? 0.5 : check[:interval]
|
281
|
+
@timers[:run] << EM::Timer.new(execution_splay) do
|
282
|
+
execute_check.call
|
283
|
+
@timers[:run] << EM::PeriodicTimer.new(interval, &execute_check)
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
# Setup standalone check executions, scheduling standard check
|
289
|
+
# definition and check extension executions. Check definitions
|
290
|
+
# and extensions with `:standalone` set to `true` will be
|
291
|
+
# scheduled by the Sensu client for execution.
|
292
|
+
def setup_standalone
|
293
|
+
@logger.debug("scheduling standalone checks")
|
294
|
+
standard_checks = @settings.checks.select do |check|
|
295
|
+
check[:standalone]
|
296
|
+
end
|
297
|
+
extension_checks = @extensions.checks.select do |check|
|
298
|
+
check[:standalone] && check[:interval].is_a?(Integer)
|
299
|
+
end
|
300
|
+
schedule_checks(standard_checks + extension_checks)
|
301
|
+
end
|
302
|
+
|
303
|
+
# Setup the Sensu client socket, for external check result
|
304
|
+
# input. By default, the client socket is bound to localhost on
|
305
|
+
# TCP & UDP port 3030. The socket can be configured via the
|
306
|
+
# client definition, `:socket` with `:bind` and `:port`. The
|
307
|
+
# current instance of the Sensu logger, settings, and transport
|
308
|
+
# are passed to the socket handler, `Sensu::Client::Socket`.
|
309
|
+
def setup_sockets
|
310
|
+
options = @settings[:client][:socket] || Hash.new
|
311
|
+
options[:bind] ||= "127.0.0.1"
|
312
|
+
options[:port] ||= 3030
|
313
|
+
@logger.debug("binding client tcp and udp sockets", :options => options)
|
314
|
+
EM::start_server(options[:bind], options[:port], Socket) do |socket|
|
315
|
+
socket.logger = @logger
|
316
|
+
socket.settings = @settings
|
317
|
+
socket.transport = @transport
|
318
|
+
end
|
319
|
+
EM::open_datagram_socket(options[:bind], options[:port], Socket) do |socket|
|
320
|
+
socket.logger = @logger
|
321
|
+
socket.settings = @settings
|
322
|
+
socket.transport = @transport
|
323
|
+
socket.protocol = :udp
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
# Call a callback (Ruby block) when there are no longer check
|
328
|
+
# executions in progress. This method is used when stopping the
|
329
|
+
# Sensu client. The `retry_until_true` helper method is used to
|
330
|
+
# check the condition every 0.5 seconds until `true` is
|
331
|
+
# returned.
|
332
|
+
#
|
333
|
+
# @param callback [Proc] called when there are no check
|
334
|
+
# executions in progress.
|
335
|
+
def complete_checks_in_progress(&callback)
|
336
|
+
@logger.info("completing checks in progress", :checks_in_progress => @checks_in_progress)
|
337
|
+
retry_until_true do
|
338
|
+
if @checks_in_progress.empty?
|
339
|
+
callback.call
|
340
|
+
true
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
# Bootstrap the Sensu client, setting up client keepalives,
|
346
|
+
# subscriptions, and standalone check executions. This method
|
347
|
+
# sets the process/daemon `@state` to `:running`.
|
348
|
+
def bootstrap
|
349
|
+
setup_keepalives
|
350
|
+
setup_subscriptions
|
351
|
+
setup_standalone
|
352
|
+
@state = :running
|
353
|
+
end
|
354
|
+
|
355
|
+
# Start the Sensu client process, setting up the client
|
356
|
+
# transport connection, the sockets, and calling the
|
357
|
+
# `bootstrap()` method.
|
358
|
+
def start
|
359
|
+
setup_transport
|
360
|
+
setup_sockets
|
361
|
+
bootstrap
|
362
|
+
end
|
363
|
+
|
364
|
+
# Pause the Sensu client process, unless it is being paused or
|
365
|
+
# has already been paused. The process/daemon `@state` is first
|
366
|
+
# set to `:pausing`, to indicate that it's in progress. All run
|
367
|
+
# timers are cancelled, and the references are cleared. The
|
368
|
+
# Sensu client will unsubscribe from all transport
|
369
|
+
# subscriptions, then set the process/daemon `@state` to
|
370
|
+
# `:paused`.
|
371
|
+
def pause
|
372
|
+
unless @state == :pausing || @state == :paused
|
373
|
+
@state = :pausing
|
374
|
+
@timers[:run].each do |timer|
|
375
|
+
timer.cancel
|
376
|
+
end
|
377
|
+
@timers[:run].clear
|
378
|
+
@transport.unsubscribe
|
379
|
+
@state = :paused
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
# Resume the Sensu client process if it is currently or will
|
384
|
+
# soon be paused. The `retry_until_true` helper method is used
|
385
|
+
# to determine if the process is paused and if the transport is
|
386
|
+
# connected. If the conditions are met, `bootstrap()` will be
|
387
|
+
# called and true is returned to stop `retry_until_true`.
|
388
|
+
def resume
|
389
|
+
retry_until_true(1) do
|
390
|
+
if @state == :paused
|
391
|
+
if @transport.connected?
|
392
|
+
bootstrap
|
393
|
+
true
|
394
|
+
end
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
# Stop the Sensu client process, pausing it, completing check
|
400
|
+
# executions in progress, closing the transport connection, and
|
401
|
+
# exiting the process (exit 0). After pausing the process, the
|
402
|
+
# process/daemon `@state` is set to `:stopping`.
|
403
|
+
def stop
|
404
|
+
@logger.warn("stopping")
|
405
|
+
pause
|
406
|
+
@state = :stopping
|
407
|
+
complete_checks_in_progress do
|
408
|
+
@transport.close
|
409
|
+
super
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|
414
|
+
end
|