portertech-sensu 1.10.0

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.
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