portertech-sensu 1.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +961 -0
- data/MIT-LICENSE.txt +20 -0
- data/README.md +65 -0
- data/exe/sensu-api +10 -0
- data/exe/sensu-client +10 -0
- data/exe/sensu-install +195 -0
- data/exe/sensu-server +10 -0
- data/lib/sensu/api/http_handler.rb +434 -0
- data/lib/sensu/api/process.rb +79 -0
- data/lib/sensu/api/routes/aggregates.rb +196 -0
- data/lib/sensu/api/routes/checks.rb +44 -0
- data/lib/sensu/api/routes/clients.rb +171 -0
- data/lib/sensu/api/routes/events.rb +86 -0
- data/lib/sensu/api/routes/health.rb +45 -0
- data/lib/sensu/api/routes/info.rb +37 -0
- data/lib/sensu/api/routes/request.rb +44 -0
- data/lib/sensu/api/routes/resolve.rb +32 -0
- data/lib/sensu/api/routes/results.rb +153 -0
- data/lib/sensu/api/routes/settings.rb +23 -0
- data/lib/sensu/api/routes/silenced.rb +182 -0
- data/lib/sensu/api/routes/stashes.rb +107 -0
- data/lib/sensu/api/routes.rb +88 -0
- data/lib/sensu/api/utilities/filter_response_content.rb +44 -0
- data/lib/sensu/api/utilities/publish_check_request.rb +107 -0
- data/lib/sensu/api/utilities/publish_check_result.rb +39 -0
- data/lib/sensu/api/utilities/resolve_event.rb +29 -0
- data/lib/sensu/api/utilities/servers_info.rb +43 -0
- data/lib/sensu/api/utilities/transport_info.rb +43 -0
- data/lib/sensu/api/validators/check.rb +55 -0
- data/lib/sensu/api/validators/client.rb +35 -0
- data/lib/sensu/api/validators/invalid.rb +8 -0
- data/lib/sensu/cli.rb +69 -0
- data/lib/sensu/client/http_socket.rb +217 -0
- data/lib/sensu/client/process.rb +655 -0
- data/lib/sensu/client/socket.rb +207 -0
- data/lib/sensu/client/utils.rb +53 -0
- data/lib/sensu/client/validators/check.rb +53 -0
- data/lib/sensu/constants.rb +17 -0
- data/lib/sensu/daemon.rb +396 -0
- data/lib/sensu/sandbox.rb +19 -0
- data/lib/sensu/server/filter.rb +227 -0
- data/lib/sensu/server/handle.rb +201 -0
- data/lib/sensu/server/mutate.rb +92 -0
- data/lib/sensu/server/process.rb +1646 -0
- data/lib/sensu/server/socket.rb +54 -0
- data/lib/sensu/server/tessen.rb +170 -0
- data/lib/sensu/utilities.rb +398 -0
- data/lib/sensu.rb +3 -0
- data/sensu.gemspec +36 -0
- metadata +322 -0
@@ -0,0 +1,655 @@
|
|
1
|
+
require "sensu/daemon"
|
2
|
+
require "sensu/client/socket"
|
3
|
+
require "sensu/client/http_socket"
|
4
|
+
|
5
|
+
module Sensu
|
6
|
+
module Client
|
7
|
+
class Process
|
8
|
+
include Daemon
|
9
|
+
|
10
|
+
attr_accessor :safe_mode
|
11
|
+
|
12
|
+
# Create an instance of the Sensu client process, start the
|
13
|
+
# client within the EventMachine event loop, and set up client
|
14
|
+
# process signal traps (for stopping).
|
15
|
+
#
|
16
|
+
# @param options [Hash]
|
17
|
+
def self.run(options={})
|
18
|
+
client = self.new(options)
|
19
|
+
EM::run do
|
20
|
+
client.start
|
21
|
+
client.setup_signal_traps
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Override Daemon initialize() to support Sensu client check
|
26
|
+
# execution safe mode, checks in progress, and open sockets.
|
27
|
+
#
|
28
|
+
# @param options [Hash]
|
29
|
+
def initialize(options={})
|
30
|
+
super
|
31
|
+
@safe_mode = @settings[:client][:safe_mode] || false
|
32
|
+
@checks_in_progress = []
|
33
|
+
@sockets = []
|
34
|
+
end
|
35
|
+
|
36
|
+
# Create a Sensu client keepalive payload, to be sent over the
|
37
|
+
# transport for processing. A client keepalive is composed of
|
38
|
+
# its settings definition, the Sensu version, and a timestamp.
|
39
|
+
# Sensitive information is redacted from the keepalive payload.
|
40
|
+
#
|
41
|
+
# @return [Hash] keepalive payload
|
42
|
+
def keepalive_payload
|
43
|
+
payload = @settings[:client].merge({
|
44
|
+
:version => VERSION,
|
45
|
+
:timestamp => Time.now.to_i
|
46
|
+
})
|
47
|
+
redact_sensitive(payload, @settings[:client][:redact])
|
48
|
+
end
|
49
|
+
|
50
|
+
# Publish a Sensu client keepalive to the transport for
|
51
|
+
# processing. JSON serialization is used for transport messages.
|
52
|
+
def publish_keepalive
|
53
|
+
payload = keepalive_payload
|
54
|
+
@logger.debug("publishing keepalive", :payload => payload)
|
55
|
+
@transport.publish(:direct, "keepalives", Sensu::JSON.dump(payload)) do |info|
|
56
|
+
if info[:error]
|
57
|
+
@logger.error("failed to publish keepalive", {
|
58
|
+
:payload => payload,
|
59
|
+
:error => info[:error].to_s
|
60
|
+
})
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Schedule Sensu client keepalives. Immediately publish a
|
66
|
+
# keepalive to register the client, then publish a keepalive
|
67
|
+
# every 20 seconds. Sensu client keepalives are used to
|
68
|
+
# determine client (& machine) health.
|
69
|
+
def setup_keepalives
|
70
|
+
@logger.debug("scheduling keepalives")
|
71
|
+
publish_keepalive
|
72
|
+
@timers[:run] << EM::PeriodicTimer.new(20) do
|
73
|
+
publish_keepalive
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Publish a check result to the transport for processing. A
|
78
|
+
# check result is composed of a client (name) and a check
|
79
|
+
# definition, containing check `:output` and `:status`. JSON
|
80
|
+
# serialization is used when publishing the check result payload
|
81
|
+
# to the transport pipe. The check result is signed with the
|
82
|
+
# client signature if configured, for source validation.
|
83
|
+
# Transport errors are logged.
|
84
|
+
#
|
85
|
+
# @param check [Hash]
|
86
|
+
def publish_check_result(check)
|
87
|
+
check.delete(:source) if check[:source] == ""
|
88
|
+
payload = {
|
89
|
+
:client => @settings[:client][:name],
|
90
|
+
:check => check
|
91
|
+
}
|
92
|
+
payload[:signature] = @settings[:client][:signature] if @settings[:client][:signature]
|
93
|
+
@logger.info("publishing check result", :payload => payload)
|
94
|
+
@transport.publish(:direct, "results", Sensu::JSON.dump(payload)) do |info|
|
95
|
+
if info[:error]
|
96
|
+
@logger.error("failed to publish check result", {
|
97
|
+
:payload => payload,
|
98
|
+
:error => info[:error].to_s
|
99
|
+
})
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Create an in progress key for a check, used to determine if an
|
105
|
+
# execution is still in progress. The key is composed of check
|
106
|
+
# `source` (if set) and `name`, joined by a colon.
|
107
|
+
#
|
108
|
+
# @param check [Hash]
|
109
|
+
# @return [String]
|
110
|
+
def check_in_progress_key(check)
|
111
|
+
[check[:source], check[:name]].compact.join(":")
|
112
|
+
end
|
113
|
+
|
114
|
+
# Execute a check hook, capturing its output (STDOUT/ERR),
|
115
|
+
# exit status code, executed timestamp, and duration. This
|
116
|
+
# method determines which hook command to run by inspecting the
|
117
|
+
# check execution status. Check hook command tokens are
|
118
|
+
# substituted with the associated client attribute values, via
|
119
|
+
# `substitute_tokens()`. If there are unmatched check attribute
|
120
|
+
# value tokens, the check hook will not be executed, instead the
|
121
|
+
# hook command output will be set to report the unmatched
|
122
|
+
# tokens. Hook commands may expect/read and utilize JSON
|
123
|
+
# serialized Sensu client and check data via STDIN, if the hook
|
124
|
+
# definition includes `"stdin": true` (default is `false`). A
|
125
|
+
# hook may have a configured execution timeout, e.g. `"timeout":
|
126
|
+
# 30`, if one is not specified, the timeout defaults to 60
|
127
|
+
# seconds.
|
128
|
+
#
|
129
|
+
# @param check [Hash]
|
130
|
+
# @yield [check] callback/block called after executing the check
|
131
|
+
# hook (if any).
|
132
|
+
def execute_check_hook(check)
|
133
|
+
@logger.debug("attempting to execute check hook", :check => check)
|
134
|
+
severity = SEVERITIES[check[:status]] || "unknown"
|
135
|
+
hook = check[:hooks][check[:status].to_s.to_sym] || check[:hooks][severity.to_sym]
|
136
|
+
if hook.nil? && check[:status] != 0
|
137
|
+
hook = check[:hooks]["non-zero".to_sym]
|
138
|
+
end
|
139
|
+
if hook
|
140
|
+
command, unmatched_tokens = substitute_tokens(hook[:command].dup, @settings[:client])
|
141
|
+
started = Time.now.to_f
|
142
|
+
hook[:executed] = started.to_i
|
143
|
+
if unmatched_tokens.empty?
|
144
|
+
options = {:timeout => hook.fetch(:timeout, 60)}
|
145
|
+
if hook[:stdin]
|
146
|
+
options[:data] = Sensu::JSON.dump({
|
147
|
+
:client => @settings[:client],
|
148
|
+
:check => check
|
149
|
+
})
|
150
|
+
end
|
151
|
+
Spawn.process(command, options) do |output, status|
|
152
|
+
hook[:duration] = ("%.3f" % (Time.now.to_f - started)).to_f
|
153
|
+
hook[:output] = output
|
154
|
+
hook[:status] = status
|
155
|
+
yield(check)
|
156
|
+
end
|
157
|
+
else
|
158
|
+
hook[:output] = "Unmatched client token(s): " + unmatched_tokens.join(", ")
|
159
|
+
hook[:status] = 3
|
160
|
+
yield(check)
|
161
|
+
end
|
162
|
+
else
|
163
|
+
yield(check)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Execute a check command, capturing its output (STDOUT/ERR),
|
168
|
+
# exit status code, execution duration, timestamp, and publish
|
169
|
+
# the result. This method guards against multiple executions for
|
170
|
+
# the same check. Check attribute value tokens are substituted
|
171
|
+
# with the associated client attribute values, via
|
172
|
+
# `object_substitute_tokens()`. The original check command and
|
173
|
+
# hooks are always published, to guard against publishing
|
174
|
+
# sensitive/redacted client attribute values. If there are
|
175
|
+
# unmatched check attribute value tokens, the check will not be
|
176
|
+
# executed, instead a check result will be published reporting
|
177
|
+
# the unmatched tokens.
|
178
|
+
#
|
179
|
+
# @param check [Hash]
|
180
|
+
def execute_check_command(check)
|
181
|
+
@logger.debug("attempting to execute check command", :check => check)
|
182
|
+
in_progress_key = check_in_progress_key(check)
|
183
|
+
unless @checks_in_progress.include?(in_progress_key)
|
184
|
+
@checks_in_progress << in_progress_key
|
185
|
+
substituted, unmatched_tokens = object_substitute_tokens(check.dup, @settings[:client])
|
186
|
+
check = substituted.merge(:command => check[:command], :hooks => check[:hooks])
|
187
|
+
check.delete(:hooks) if check[:hooks].nil?
|
188
|
+
started = Time.now.to_f
|
189
|
+
check[:executed] = started.to_i
|
190
|
+
if unmatched_tokens.empty?
|
191
|
+
options = {:timeout => check[:timeout]}
|
192
|
+
if check[:stdin]
|
193
|
+
options[:data] = Sensu::JSON.dump({
|
194
|
+
:client => @settings[:client],
|
195
|
+
:check => check
|
196
|
+
})
|
197
|
+
end
|
198
|
+
Spawn.process(substituted[:command], options) do |output, status|
|
199
|
+
check[:duration] = ("%.3f" % (Time.now.to_f - started)).to_f
|
200
|
+
check[:output] = output
|
201
|
+
check[:status] = status
|
202
|
+
if check[:hooks] && !check[:hooks].empty?
|
203
|
+
execute_check_hook(check) do |check|
|
204
|
+
publish_check_result(check)
|
205
|
+
@checks_in_progress.delete(in_progress_key)
|
206
|
+
end
|
207
|
+
else
|
208
|
+
publish_check_result(check)
|
209
|
+
@checks_in_progress.delete(in_progress_key)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
else
|
213
|
+
check[:output] = "Unmatched client token(s): " + unmatched_tokens.join(", ")
|
214
|
+
check[:status] = 3
|
215
|
+
check[:handle] = false
|
216
|
+
publish_check_result(check)
|
217
|
+
@checks_in_progress.delete(in_progress_key)
|
218
|
+
end
|
219
|
+
else
|
220
|
+
@logger.warn("previous check command execution in progress", :check => check)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# Run a check extension and publish the result. The Sensu client
|
225
|
+
# loads check extensions, checks that run within the Sensu Ruby
|
226
|
+
# VM and the EventMachine event loop, using the Sensu Extension
|
227
|
+
# API. If a check definition includes `:extension`, use it's
|
228
|
+
# value for the extension name, otherwise use the check name.
|
229
|
+
# The check definition is passed to the extension `safe_run()`
|
230
|
+
# method as a parameter, the extension may utilize it. This
|
231
|
+
# method guards against multiple executions for the same check
|
232
|
+
# extension.
|
233
|
+
#
|
234
|
+
# https://github.com/sensu/sensu-extension
|
235
|
+
#
|
236
|
+
# @param check [Hash]
|
237
|
+
def run_check_extension(check)
|
238
|
+
@logger.debug("attempting to run check extension", :check => check)
|
239
|
+
in_progress_key = check_in_progress_key(check)
|
240
|
+
unless @checks_in_progress.include?(in_progress_key)
|
241
|
+
@checks_in_progress << in_progress_key
|
242
|
+
started = Time.now.to_f
|
243
|
+
check[:executed] = started.to_i
|
244
|
+
extension_name = check[:extension] || check[:name]
|
245
|
+
extension = @extensions[:checks][extension_name]
|
246
|
+
extension.safe_run(check) do |output, status|
|
247
|
+
check[:duration] = ("%.3f" % (Time.now.to_f - started)).to_f
|
248
|
+
check[:output] = output
|
249
|
+
check[:status] = status
|
250
|
+
if check[:hooks] && !check[:hooks].empty?
|
251
|
+
execute_check_hook(check) do |check|
|
252
|
+
publish_check_result(check)
|
253
|
+
@checks_in_progress.delete(in_progress_key)
|
254
|
+
end
|
255
|
+
else
|
256
|
+
publish_check_result(check)
|
257
|
+
@checks_in_progress.delete(in_progress_key)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
else
|
261
|
+
@logger.warn("previous check extension execution in progress", :check => check)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
# Process a check request. If a check request has a check
|
266
|
+
# command, it will be executed. A standard check request will be
|
267
|
+
# merged with a local check definition, if present. Client safe
|
268
|
+
# mode is enforced in this method, requiring a local check
|
269
|
+
# definition in order to execute the check command. If a local
|
270
|
+
# check definition does not exist when operating with client
|
271
|
+
# safe mode, a check result will be published to report the
|
272
|
+
# missing check definition. A check request without a
|
273
|
+
# command indicates a check extension run. The check request may
|
274
|
+
# contain `:extension`, the name of the extension to run. If
|
275
|
+
# `:extension` is not present, the check name is used for the
|
276
|
+
# extension name. If a check extension does not exist for a
|
277
|
+
# name, a check result will be published to report the unknown
|
278
|
+
# check extension.
|
279
|
+
#
|
280
|
+
# @param check [Hash]
|
281
|
+
def process_check_request(check)
|
282
|
+
@logger.debug("processing check", :check => check)
|
283
|
+
if @settings.check_exists?(check[:name]) && !check.has_key?(:proxy_requests)
|
284
|
+
check.merge!(@settings[:checks][check[:name]])
|
285
|
+
end
|
286
|
+
if check.has_key?(:command)
|
287
|
+
if @safe_mode && !@settings.check_exists?(check[:name])
|
288
|
+
check[:output] = "Check is not locally defined (safe mode)"
|
289
|
+
check[:status] = 3
|
290
|
+
check[:handle] = false
|
291
|
+
check[:executed] = Time.now.to_i
|
292
|
+
publish_check_result(check)
|
293
|
+
else
|
294
|
+
execute_check_command(check)
|
295
|
+
end
|
296
|
+
else
|
297
|
+
extension_name = check[:extension] || check[:name]
|
298
|
+
if @extensions.check_exists?(extension_name)
|
299
|
+
run_check_extension(check)
|
300
|
+
else
|
301
|
+
@logger.warn("unknown check extension", :check => check)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
# Determine the Sensu transport subscribe options for a
|
307
|
+
# subscription. If a subscription begins with a transport pipe
|
308
|
+
# type, either "direct:" or "roundrobin:", the subscription uses
|
309
|
+
# a direct transport pipe, and the subscription name is used for
|
310
|
+
# both the pipe and the funnel names. If a subscription does not
|
311
|
+
# specify a transport pipe type, a fanout transport pipe is
|
312
|
+
# used, the subscription name is used for the pipe, and a unique
|
313
|
+
# funnel is created for the Sensu client. The unique funnel name
|
314
|
+
# for the Sensu client is created using a combination of the
|
315
|
+
# client name, the Sensu version, and the process start time
|
316
|
+
# (epoch).
|
317
|
+
#
|
318
|
+
# @param subscription [String]
|
319
|
+
# @return [Array] containing the transport subscribe options:
|
320
|
+
# the transport pipe type, pipe, and funnel.
|
321
|
+
def transport_subscribe_options(subscription)
|
322
|
+
_, raw_type = subscription.split(":", 2).reverse
|
323
|
+
case raw_type
|
324
|
+
when "direct", "roundrobin"
|
325
|
+
[:direct, subscription, subscription]
|
326
|
+
else
|
327
|
+
funnel = [@settings[:client][:name], VERSION, start_time].join("-")
|
328
|
+
[:fanout, subscription, funnel]
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
# Set up Sensu client subscriptions. Subscriptions determine the
|
333
|
+
# kinds of check requests the client will receive. The Sensu
|
334
|
+
# client will receive JSON serialized check requests from its
|
335
|
+
# subscriptions, that get parsed and processed.
|
336
|
+
def setup_subscriptions
|
337
|
+
@logger.debug("subscribing to client subscriptions")
|
338
|
+
@settings[:client][:subscriptions].each do |subscription|
|
339
|
+
@logger.debug("subscribing to a subscription", :subscription => subscription)
|
340
|
+
options = transport_subscribe_options(subscription)
|
341
|
+
@transport.subscribe(*options) do |message_info, message|
|
342
|
+
begin
|
343
|
+
check = Sensu::JSON.load(message)
|
344
|
+
@logger.info("received check request", :check => check)
|
345
|
+
process_check_request(check)
|
346
|
+
rescue Sensu::JSON::ParseError => error
|
347
|
+
@logger.error("failed to parse the check request payload", {
|
348
|
+
:message => message,
|
349
|
+
:error => error.to_s
|
350
|
+
})
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
# Create a check execution proc, used to execute standalone
|
357
|
+
# checks. Checks are not executed if subdued. The check
|
358
|
+
# `:issued` timestamp is set here, to mimic check requests
|
359
|
+
# issued by a Sensu server. Check definitions are duplicated
|
360
|
+
# before processing them, in case they are mutated.
|
361
|
+
#
|
362
|
+
# @param check [Hash] definition.
|
363
|
+
def create_check_execution_proc(check)
|
364
|
+
Proc.new do
|
365
|
+
unless check_subdued?(check)
|
366
|
+
check[:issued] = Time.now.to_i
|
367
|
+
process_check_request(check.dup)
|
368
|
+
else
|
369
|
+
@logger.info("check execution was subdued", :check => check)
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
# Schedule a check execution, using the check cron. This method
|
375
|
+
# determines the time until the next cron time (in seconds) and
|
376
|
+
# creats an EventMachine timer for the execution. This method
|
377
|
+
# will be called after every check cron execution for subsequent
|
378
|
+
# executions. The timer is stored in the timers hash under
|
379
|
+
# `:run`, so it can be cancelled etc. The check cron execution
|
380
|
+
# timer object is removed from the timer hash after the
|
381
|
+
# execution, to stop the timer hash from growing infinitely.
|
382
|
+
#
|
383
|
+
# @param check [Hash] definition.
|
384
|
+
def schedule_check_cron_execution(check)
|
385
|
+
cron_time = determine_check_cron_time(check)
|
386
|
+
@timers[:run] << EM::Timer.new(cron_time) do |timer|
|
387
|
+
create_check_execution_proc(check).call
|
388
|
+
@timers[:run].delete(timer)
|
389
|
+
schedule_check_cron_execution(check)
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
# Calculate a check execution splay, taking into account the
|
394
|
+
# current time and the execution interval to ensure it's
|
395
|
+
# consistent between process restarts.
|
396
|
+
#
|
397
|
+
# @param check [Hash] definition.
|
398
|
+
def calculate_check_execution_splay(check)
|
399
|
+
key = [@settings[:client][:name], check[:name]].join(":")
|
400
|
+
splay_hash = Digest::MD5.digest(key).unpack("Q<").first
|
401
|
+
current_time = (Time.now.to_f * 1000).to_i
|
402
|
+
(splay_hash - current_time) % (check[:interval] * 1000) / 1000.0
|
403
|
+
end
|
404
|
+
|
405
|
+
# Schedule check executions, using the check interval. This
|
406
|
+
# method using an intial calculated execution splay EventMachine
|
407
|
+
# timer and an EventMachine periodic timer for subsequent check
|
408
|
+
# executions. The timers are stored in the timers hash under
|
409
|
+
# `:run`, so they can be cancelled etc.
|
410
|
+
#
|
411
|
+
# @param check [Hash] definition.
|
412
|
+
def schedule_check_interval_executions(check)
|
413
|
+
execution_splay = testing? ? 0 : calculate_check_execution_splay(check)
|
414
|
+
interval = testing? ? 0.5 : check[:interval]
|
415
|
+
@timers[:run] << EM::Timer.new(execution_splay) do
|
416
|
+
execute_check = create_check_execution_proc(check)
|
417
|
+
execute_check.call
|
418
|
+
@timers[:run] << EM::PeriodicTimer.new(interval, &execute_check)
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
# Schedule check executions. This method iterates through defined
|
423
|
+
# checks and uses the appropriate method of check execution
|
424
|
+
# scheduling, either with the cron syntax or a numeric interval.
|
425
|
+
#
|
426
|
+
# @param checks [Array] of definitions.
|
427
|
+
def schedule_checks(checks)
|
428
|
+
checks.each do |check|
|
429
|
+
if check[:cron]
|
430
|
+
schedule_check_cron_execution(check)
|
431
|
+
else
|
432
|
+
schedule_check_interval_executions(check)
|
433
|
+
end
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
# Setup standalone check executions, scheduling standard check
|
438
|
+
# definition and check extension executions. Check definitions
|
439
|
+
# and extensions with `:standalone` set to `true`, do not have
|
440
|
+
# `:publish` set to `false`, and have a integer `:interval` or a
|
441
|
+
# string `cron` will be scheduled by the Sensu client for
|
442
|
+
# execution.
|
443
|
+
def setup_standalone
|
444
|
+
@logger.debug("scheduling standalone checks")
|
445
|
+
standard_checks = @settings.checks.select do |check|
|
446
|
+
check[:standalone] && check[:publish] != false &&
|
447
|
+
(check[:interval].is_a?(Integer) || check[:cron].is_a?(String))
|
448
|
+
end
|
449
|
+
extension_checks = @extensions.checks.select do |check|
|
450
|
+
check[:standalone] && check[:publish] != false &&
|
451
|
+
(check[:interval].is_a?(Integer) || check[:cron].is_a?(String))
|
452
|
+
end
|
453
|
+
schedule_checks(standard_checks + extension_checks)
|
454
|
+
end
|
455
|
+
|
456
|
+
# Setup the Sensu client JSON socket, for external check result
|
457
|
+
# input. By default, the client socket is bound to localhost on
|
458
|
+
# TCP & UDP port 3030. The socket can be configured via the
|
459
|
+
# client definition, `:socket` with `:bind` and `:port`. Users can opt-out
|
460
|
+
# of using the TCP and UDP socket by setting `:enabled` to `false`.
|
461
|
+
# The current instance of the Sensu logger, settings, and transport
|
462
|
+
# are passed to the socket handler, `Sensu::Client::Socket`. The
|
463
|
+
# TCP socket server signature (Fixnum) and UDP connection object
|
464
|
+
# are stored in `@sockets`, so that they can be managed
|
465
|
+
# elsewhere, eg. `close_sockets()`.
|
466
|
+
def setup_json_socket
|
467
|
+
options = @settings[:client][:socket] || Hash.new
|
468
|
+
options[:bind] ||= "127.0.0.1"
|
469
|
+
options[:port] ||= 3030
|
470
|
+
unless options[:enabled] == false
|
471
|
+
@logger.debug("binding client tcp and udp sockets", :options => options)
|
472
|
+
@sockets << EM::start_server(options[:bind], options[:port], Socket) do |socket|
|
473
|
+
socket.logger = @logger
|
474
|
+
socket.settings = @settings
|
475
|
+
socket.transport = @transport
|
476
|
+
end
|
477
|
+
@sockets << EM::open_datagram_socket(options[:bind], options[:port], Socket) do |socket|
|
478
|
+
socket.logger = @logger
|
479
|
+
socket.settings = @settings
|
480
|
+
socket.transport = @transport
|
481
|
+
socket.protocol = :udp
|
482
|
+
end
|
483
|
+
else
|
484
|
+
@logger.info("client tcp/udp socket disabled per configuration")
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
# Setup the Sensu client HTTP socket, for external check result
|
489
|
+
# input and informational queries. By default, the client HTTP
|
490
|
+
# socket is bound to localhost on TCP port 3031. The socket can
|
491
|
+
# be configured via the client definition, `:http_socket` with
|
492
|
+
# `:bind` and `:port`. Users can opt-out of using the HTTP
|
493
|
+
# socket by setting `:enabled` to `false. The current instance
|
494
|
+
# of the Sensu logger, settings, and transport are passed to the
|
495
|
+
# HTTP socket handler, `Sensu::Client::HTTPSocket`. The HTTP
|
496
|
+
# socket server signature (Fixnum) is stored in `@sockets`, so
|
497
|
+
# that it can be managed elsewhere, eg. `close_sockets()`.
|
498
|
+
def setup_http_socket
|
499
|
+
options = @settings[:client][:http_socket] || Hash.new
|
500
|
+
options[:bind] ||= "127.0.0.1"
|
501
|
+
options[:port] ||= 3031
|
502
|
+
unless options[:enabled] == false
|
503
|
+
@logger.debug("binding client http socket", :options => options)
|
504
|
+
@sockets << EM::start_server(options[:bind], options[:port], HTTPSocket) do |socket|
|
505
|
+
socket.logger = @logger
|
506
|
+
socket.settings = @settings
|
507
|
+
socket.transport = @transport
|
508
|
+
end
|
509
|
+
else
|
510
|
+
@logger.info("client http socket disabled per configuration")
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
# Setup the Sensu client sockets, JSON TCP & UDP, and HTTP.
|
515
|
+
# Users can opt-out of using the HTTP socket via configuration.
|
516
|
+
def setup_sockets
|
517
|
+
setup_json_socket
|
518
|
+
setup_http_socket
|
519
|
+
end
|
520
|
+
|
521
|
+
# Call a callback (Ruby block) when there are no longer check
|
522
|
+
# executions in progress. This method is used when stopping the
|
523
|
+
# Sensu client. The `retry_until_true` helper method is used to
|
524
|
+
# check the condition every 0.5 seconds until `true` is
|
525
|
+
# returned.
|
526
|
+
#
|
527
|
+
# @yield [] callback/block called when there are no check
|
528
|
+
# executions in progress.
|
529
|
+
def complete_checks_in_progress
|
530
|
+
@logger.info("completing checks in progress", :checks_in_progress => @checks_in_progress)
|
531
|
+
retry_until_true do
|
532
|
+
if @checks_in_progress.empty?
|
533
|
+
yield
|
534
|
+
true
|
535
|
+
end
|
536
|
+
end
|
537
|
+
end
|
538
|
+
|
539
|
+
# Create a check result intended for deregistering a client.
|
540
|
+
# Client definitions may contain `:deregistration` configuration,
|
541
|
+
# containing custom attributes and handler information. By
|
542
|
+
# default, the deregistration check result sets the `:handler` to
|
543
|
+
# `deregistration`. If the client provides its own `:deregistration`
|
544
|
+
# configuration, it's deep merged with the defaults. The
|
545
|
+
# check `:name`, `:output`, `:issued`, and `:executed` values
|
546
|
+
# are always overridden to guard against an invalid definition.
|
547
|
+
def deregister
|
548
|
+
check = {:handler => "deregistration", :status => 1}
|
549
|
+
if @settings[:client].has_key?(:deregistration)
|
550
|
+
check = deep_merge(check, @settings[:client][:deregistration])
|
551
|
+
end
|
552
|
+
timestamp = Time.now.to_i
|
553
|
+
overrides = {
|
554
|
+
:name => "deregistration",
|
555
|
+
:output => "client initiated deregistration",
|
556
|
+
:issued => timestamp,
|
557
|
+
:executed => timestamp
|
558
|
+
}
|
559
|
+
publish_check_result(check.merge(overrides))
|
560
|
+
end
|
561
|
+
|
562
|
+
# Close the Sensu client TCP and UDP sockets. This method
|
563
|
+
# iterates through `@sockets`, which contains socket server
|
564
|
+
# signatures (Fixnum) and connection objects. A signature
|
565
|
+
# indicates a TCP socket server that needs to be stopped. A
|
566
|
+
# connection object indicates a socket connection that needs to
|
567
|
+
# be closed, eg. a UDP datagram socket.
|
568
|
+
def close_sockets
|
569
|
+
@logger.info("closing client sockets")
|
570
|
+
@sockets.each do |socket|
|
571
|
+
if socket.is_a?(Numeric)
|
572
|
+
EM.stop_server(socket)
|
573
|
+
else
|
574
|
+
socket.close_connection
|
575
|
+
end
|
576
|
+
end
|
577
|
+
end
|
578
|
+
|
579
|
+
# Bootstrap the Sensu client, setting up client keepalives,
|
580
|
+
# subscriptions, and standalone check executions. This method
|
581
|
+
# sets the process/daemon `@state` to `:running`.
|
582
|
+
def bootstrap
|
583
|
+
setup_keepalives
|
584
|
+
setup_subscriptions
|
585
|
+
setup_standalone
|
586
|
+
@state = :running
|
587
|
+
end
|
588
|
+
|
589
|
+
# Start the Sensu client process, setting up the client
|
590
|
+
# transport connection, the sockets, and calling the
|
591
|
+
# `bootstrap()` method.
|
592
|
+
def start
|
593
|
+
setup_transport do
|
594
|
+
setup_sockets
|
595
|
+
bootstrap
|
596
|
+
end
|
597
|
+
end
|
598
|
+
|
599
|
+
# Pause the Sensu client process, unless it is being paused or
|
600
|
+
# has already been paused. The process/daemon `@state` is first
|
601
|
+
# set to `:pausing`, to indicate that it's in progress. All run
|
602
|
+
# timers are cancelled, and the references are cleared. The
|
603
|
+
# Sensu client will unsubscribe from all transport
|
604
|
+
# subscriptions, then set the process/daemon `@state` to
|
605
|
+
# `:paused`.
|
606
|
+
def pause
|
607
|
+
unless @state == :pausing || @state == :paused
|
608
|
+
@state = :pausing
|
609
|
+
@timers[:run].each do |timer|
|
610
|
+
timer.cancel
|
611
|
+
end
|
612
|
+
@timers[:run].clear
|
613
|
+
@transport.unsubscribe if @transport
|
614
|
+
@state = :paused
|
615
|
+
end
|
616
|
+
end
|
617
|
+
|
618
|
+
# Resume the Sensu client process if it is currently or will
|
619
|
+
# soon be paused. The `retry_until_true` helper method is used
|
620
|
+
# to determine if the process is paused and if the transport is
|
621
|
+
# connected. If the conditions are met, `bootstrap()` will be
|
622
|
+
# called and true is returned to stop `retry_until_true`.
|
623
|
+
def resume
|
624
|
+
retry_until_true(1) do
|
625
|
+
if @state == :paused
|
626
|
+
if @transport.connected?
|
627
|
+
bootstrap
|
628
|
+
true
|
629
|
+
end
|
630
|
+
end
|
631
|
+
end
|
632
|
+
end
|
633
|
+
|
634
|
+
# Stop the Sensu client process, pausing it, completing check
|
635
|
+
# executions in progress, closing the transport connection, and
|
636
|
+
# exiting the process (exit 0). After pausing the process, the
|
637
|
+
# process/daemon `@state` is set to `:stopping`. Also sends
|
638
|
+
# deregistration check result if configured to do so.
|
639
|
+
def stop
|
640
|
+
@logger.warn("stopping")
|
641
|
+
last_state = @state
|
642
|
+
pause
|
643
|
+
if @settings[:client][:deregister] == true && last_state != :initializing
|
644
|
+
deregister
|
645
|
+
end
|
646
|
+
@state = :stopping
|
647
|
+
complete_checks_in_progress do
|
648
|
+
close_sockets
|
649
|
+
@transport.close if @transport
|
650
|
+
super
|
651
|
+
end
|
652
|
+
end
|
653
|
+
end
|
654
|
+
end
|
655
|
+
end
|