portertech-sensu 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +961 -0
  3. data/MIT-LICENSE.txt +20 -0
  4. data/README.md +65 -0
  5. data/exe/sensu-api +10 -0
  6. data/exe/sensu-client +10 -0
  7. data/exe/sensu-install +195 -0
  8. data/exe/sensu-server +10 -0
  9. data/lib/sensu/api/http_handler.rb +434 -0
  10. data/lib/sensu/api/process.rb +79 -0
  11. data/lib/sensu/api/routes/aggregates.rb +196 -0
  12. data/lib/sensu/api/routes/checks.rb +44 -0
  13. data/lib/sensu/api/routes/clients.rb +171 -0
  14. data/lib/sensu/api/routes/events.rb +86 -0
  15. data/lib/sensu/api/routes/health.rb +45 -0
  16. data/lib/sensu/api/routes/info.rb +37 -0
  17. data/lib/sensu/api/routes/request.rb +44 -0
  18. data/lib/sensu/api/routes/resolve.rb +32 -0
  19. data/lib/sensu/api/routes/results.rb +153 -0
  20. data/lib/sensu/api/routes/settings.rb +23 -0
  21. data/lib/sensu/api/routes/silenced.rb +182 -0
  22. data/lib/sensu/api/routes/stashes.rb +107 -0
  23. data/lib/sensu/api/routes.rb +88 -0
  24. data/lib/sensu/api/utilities/filter_response_content.rb +44 -0
  25. data/lib/sensu/api/utilities/publish_check_request.rb +107 -0
  26. data/lib/sensu/api/utilities/publish_check_result.rb +39 -0
  27. data/lib/sensu/api/utilities/resolve_event.rb +29 -0
  28. data/lib/sensu/api/utilities/servers_info.rb +43 -0
  29. data/lib/sensu/api/utilities/transport_info.rb +43 -0
  30. data/lib/sensu/api/validators/check.rb +55 -0
  31. data/lib/sensu/api/validators/client.rb +35 -0
  32. data/lib/sensu/api/validators/invalid.rb +8 -0
  33. data/lib/sensu/cli.rb +69 -0
  34. data/lib/sensu/client/http_socket.rb +217 -0
  35. data/lib/sensu/client/process.rb +655 -0
  36. data/lib/sensu/client/socket.rb +207 -0
  37. data/lib/sensu/client/utils.rb +53 -0
  38. data/lib/sensu/client/validators/check.rb +53 -0
  39. data/lib/sensu/constants.rb +17 -0
  40. data/lib/sensu/daemon.rb +396 -0
  41. data/lib/sensu/sandbox.rb +19 -0
  42. data/lib/sensu/server/filter.rb +227 -0
  43. data/lib/sensu/server/handle.rb +201 -0
  44. data/lib/sensu/server/mutate.rb +92 -0
  45. data/lib/sensu/server/process.rb +1646 -0
  46. data/lib/sensu/server/socket.rb +54 -0
  47. data/lib/sensu/server/tessen.rb +170 -0
  48. data/lib/sensu/utilities.rb +398 -0
  49. data/lib/sensu.rb +3 -0
  50. data/sensu.gemspec +36 -0
  51. 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