sensu 1.0.4 → 1.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7340072ff38575eed6bc61c3ebdf267e6256b43e
4
- data.tar.gz: 3abac29f261e891ddc35c8999dc3bd44c7bdc4dd
3
+ metadata.gz: ed1314dc2d888635865717c37c4e71d77da17b28
4
+ data.tar.gz: 5f90de786a5691adf798879beca63a4d11732f38
5
5
  SHA512:
6
- metadata.gz: 98d1abcbd269002e00bcc0e78753ecdcca406771f945aa589d330fcea5acfabde81ce87eea3f359839c3b7e4a140b157c03d2b37a19716257e53f387e0d0983d
7
- data.tar.gz: 3d70a5834d1e8d8fadd6893f53bebad799a54a235a24c5d77dff62899957a74334bc8d65dbecac854cd8cf8b26e195ecf470b5c2fa2dfc44bc81132ab7b4aa50
6
+ metadata.gz: 6ad6541d3bba0982e9b0f397349f29889fc597a294ffdaad6e7dba49aa8af8830745d30a45061adbb67385a21ec2248ca8dc32640b73700f8278671280edb021
7
+ data.tar.gz: 60f3e3a064bcdcc8255ba3f0a52d4798ddbbf2a67b9588048b43e1b648ed1ebfe4d0cd747947df4980ea9616003e006937b9c6d99647e27db1ae3970667722e7
data/CHANGELOG.md CHANGED
@@ -1,3 +1,61 @@
1
+ ## 1.1.0 - 2017-09-27
2
+
3
+ ### Features
4
+
5
+ Check hooks, commands run by the Sensu client in response to the result of
6
+ the check command execution. The Sensu client will execute the appropriate
7
+ configured hook command, depending on the check execution status (e.g. 1).
8
+ Valid hook names include (in order of precedence): "1"-"255", "ok",
9
+ "warning", "critical", "unknown", and "non-zero". The check hook command
10
+ output, status, executed timestamp, and duration are captured and
11
+ published in the check result. Check hook commands can optionally receive
12
+ JSON serialized Sensu client and check definition data via STDIN.
13
+
14
+ Check STDIN. A boolean check definition attribute, `"stdin"`, when set to
15
+ `true` instructs the Sensu client to write JSON serialized Sensu client
16
+ and check definition data to the check command process STDIN. This
17
+ attribute cannot be used with existing Sensu check plugins, nor Nagios
18
+ plugins etc, as the Sensu client will wait indefinitely for the check
19
+ process to read and close STDIN.
20
+
21
+ Splayed proxy check request publishing. Users can now splay proxy check
22
+ requests (optional), evenly, over a window of time, determined by the
23
+ check interval and a configurable splay coverage percentage. For
24
+ example, if a check has an interval of 60s and a configured splay
25
+ coverage of 90%, its proxy check requests would be splayed evenly over a
26
+ time window of 60s * 90%, 54s, leaving 6s for the last proxy check
27
+ execution before the the next round of proxy check requests for the same
28
+ check. Proxy check request splayed publishing can be configured with two
29
+ new check definition attributes, within the `proxy_requests` scope,
30
+ `splay` (boolean) to enable it and `splay_coverage` (integer percentage,
31
+ defaults to `90`).
32
+
33
+ Configurable check output truncation (for storage in Redis). Check output
34
+ truncation can be manually enabled/disabled with the check definition
35
+ attribute "truncate_output", e.g.`"truncate_output": false`. The output
36
+ truncation length can be configured with the check definition attribute
37
+ "truncate_output_length", e.g. `"truncate_output_length": 1024`. Check
38
+ output truncation is still enabled by default for metric checks, with
39
+ `"type": "metric"`.
40
+
41
+ Sensu client HTTP socket basic authentication can how be applied to all
42
+ endpoints (not just `/settings`), via the client definition http_socket
43
+ attribute "protect_all_endpoints", e.g. `"protect_all_endpoints": true`.
44
+
45
+ ### Other
46
+
47
+ Improved check TTL monitoring performance.
48
+
49
+ The Sensu extension run log event log level is now set to debug (instead
50
+ of info) when the run output is empty and the status is 0.
51
+
52
+ ### Fixes
53
+
54
+ Added initial timestamp to proxy client definitions. The Uchiwa and Sensu
55
+ dashboards will no longer display "Invalid Date".
56
+
57
+ Deleting check history when deleting an associated check result.
58
+
1
59
  ## 1.0.3 - 2017-08-25
2
60
 
3
61
  ### Fixes
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  ![sensu](https://raw.github.com/sensu/sensu/master/sensu-logo.png)
2
2
 
3
- [![Build Status](https://img.shields.io/travis/sensu/sensu.png)](https://travis-ci.org/sensu/sensu)
3
+ [![Build Status](https://img.shields.io/travis/sensu/sensu.svg)](https://travis-ci.org/sensu/sensu)
4
4
  [![Gem Version](https://img.shields.io/gem/v/sensu.svg)](https://github.com/sensu/sensu/blob/master/CHANGELOG.md)
5
- ![MIT Licensed](https://img.shields.io/github/license/sensu/sensu.svg)
5
+ [![MIT Licensed](https://img.shields.io/github/license/sensu/sensu.svg)](https://raw.githubusercontent.com/sensu/sensu/master/MIT-LICENSE.txt)
6
6
 
7
7
  # Sensu
8
8
 
@@ -27,7 +27,7 @@ on configuring and operating Sensu.
27
27
  ## Getting Help
28
28
 
29
29
  If you have questions not covered by the documentation, the Sensu community
30
- is here to help. Please check out our [chat][4] on Freenode IRC, or the
30
+ is here to help. Please check out our [chat][4] on Slack, or the
31
31
  [sensu-users][5] discussion list.
32
32
 
33
33
  Commercial support is also available. See the [support section of our website][6] for more detail.
@@ -45,7 +45,7 @@ Sensu Core is released under the [MIT license][8].
45
45
  [1]: https://sensuapp.org/enterprise
46
46
  [2]: https://sensuapp.org/docs/latest/platforms/
47
47
  [3]: http://sensuapp.org/docs/latest/overview
48
- [4]: http://webchat.freenode.net/?channels=%23sensu
48
+ [4]: https://slack.sensu.io/
49
49
  [5]: http://groups.google.com/group/sensu-users
50
50
  [6]: https://sensuapp.org/support
51
51
  [7]: https://github.com/sensu/sensu/blob/master/CONTRIBUTING.md
@@ -1,4 +1,3 @@
1
- require "sensu/utilities"
2
1
  require "sensu/api/routes"
3
2
  require "sensu/api/utilities/filter_response_content"
4
3
 
@@ -22,11 +21,9 @@ module Sensu
22
21
  # @result [Hash]
23
22
  def request_details
24
23
  return @request_details if @request_details
25
- @request_id = @http.fetch(:x_request_id, random_uuid)
26
24
  @request_start_time = Time.now.to_f
27
25
  _, remote_address = Socket.unpack_sockaddr_in(get_peername)
28
26
  @request_details = {
29
- :request_id => @request_id,
30
27
  :remote_address => remote_address,
31
28
  :user_agent => @http[:user_agent],
32
29
  :method => @http_request_method,
@@ -47,9 +44,7 @@ module Sensu
47
44
  end
48
45
 
49
46
  # Log the HTTP response. This method calculates the
50
- # request/response time. The debug log level is used for the
51
- # response body log event, as it is generally very verbose and
52
- # unnecessary in most cases.
47
+ # request/response time.
53
48
  def log_response
54
49
  @logger.info("api response", {
55
50
  :request => request_details,
@@ -57,12 +52,6 @@ module Sensu
57
52
  :content_length => @response.content.to_s.bytesize,
58
53
  :time => (Time.now.to_f - @request_start_time).round(3)
59
54
  })
60
- @logger.debug("api response body", {
61
- :request => {
62
- :request_id => @request_id
63
- },
64
- :content => @response.content
65
- })
66
55
  end
67
56
 
68
57
  # Parse the HTTP request URI using a regular expression,
@@ -159,13 +148,6 @@ module Sensu
159
148
  end
160
149
  end
161
150
 
162
- # Set the HTTP response headers, including the request ID and
163
- # cors headers (via `set_cores_headers()`).
164
- def set_headers
165
- @response.headers["X-Request-ID"] = @request_id
166
- set_cors_headers
167
- end
168
-
169
151
  # Paginate the provided items. This method uses two HTTP query
170
152
  # parameters to determine how to paginate the items, `limit` and
171
153
  # `offset`. The parameter `limit` specifies how many items are
@@ -410,7 +392,7 @@ module Sensu
410
392
  log_request
411
393
  parse_parameters
412
394
  create_response
413
- set_headers
395
+ set_cors_headers
414
396
  if authorized?
415
397
  if connected?
416
398
  route_request
@@ -105,7 +105,9 @@ module Sensu
105
105
  if result_exists
106
106
  @redis.srem("result:#{client_name}", check_name) do
107
107
  @redis.del(result_key) do
108
- no_content!
108
+ @redis.del("history:#{client_name}:#{check_name}") do
109
+ no_content!
110
+ end
109
111
  end
110
112
  end
111
113
  else
@@ -87,6 +87,14 @@ module Sensu
87
87
  false
88
88
  end
89
89
 
90
+ def unauthorized_response
91
+ @logger.warn("http socket refusing to serve unauthorized request")
92
+ @response.headers["WWW-Authenticate"] = 'Basic realm="Sensu Client Restricted Area"'
93
+ send_response(401, "Unauthorized", {
94
+ :response => "You must be authenticated using your http_options user and password settings"
95
+ })
96
+ end
97
+
90
98
  def send_response(status, status_string, content)
91
99
  @logger.debug("http socket sending response", {
92
100
  :status => status,
@@ -100,6 +108,9 @@ module Sensu
100
108
  end
101
109
 
102
110
  def process_request_info
111
+ if @settings[:client][:http_socket][:protect_all_endpoints]
112
+ return unauthorized_response unless authorized?
113
+ end
103
114
  transport_info do |info|
104
115
  send_response(200, "OK", {
105
116
  :sensu => {
@@ -111,6 +122,9 @@ module Sensu
111
122
  end
112
123
 
113
124
  def process_request_results
125
+ if @settings[:client][:http_socket][:protect_all_endpoints]
126
+ return unauthorized_response unless authorized?
127
+ end
114
128
  if @http[:content_type] and @http[:content_type] == "application/json" and @http_content
115
129
  begin
116
130
  check = Sensu::JSON::load(@http_content)
@@ -125,19 +139,12 @@ module Sensu
125
139
  end
126
140
 
127
141
  def process_request_settings
128
- if authorized?
129
- @logger.info("http socket responding to request for configuration settings")
130
- if @http_query_string and @http_query_string.downcase.include?("redacted=false")
131
- send_response(200, "OK", @settings.to_hash)
132
- else
133
- send_response(200, "OK", redact_sensitive(@settings.to_hash))
134
- end
142
+ return unauthorized_response unless authorized?
143
+ @logger.info("http socket responding to request for configuration settings")
144
+ if @http_query_string and @http_query_string.downcase.include?("redacted=false")
145
+ send_response(200, "OK", @settings.to_hash)
135
146
  else
136
- @logger.warn("http socket refusing to serve unauthorized settings request")
137
- @response.headers["WWW-Authenticate"] = 'Basic realm="Sensu Client Restricted Area"'
138
- send_response(401, "Unauthorized", {
139
- :response => "You must be authenticated using your http_options user and password settings"
140
- })
147
+ send_response(200, "OK", redact_sensitive(@settings.to_hash))
141
148
  end
142
149
  end
143
150
 
@@ -181,7 +188,7 @@ module Sensu
181
188
  :http_request_uri => @http_request_uri
182
189
  })
183
190
  send_response(405, "Method Not Allowed", {
184
- :response => "Valid methods for this endpoint: #{reqdef['methods'].keys}"
191
+ :response => "Valid methods for this endpoint: #{endpoint['methods'].keys}"
185
192
  })
186
193
  end
187
194
  else
@@ -110,13 +110,66 @@ module Sensu
110
110
  [check[:source], check[:name]].compact.join(":")
111
111
  end
112
112
 
113
+ # Execute a check hook, capturing its output (STDOUT/ERR),
114
+ # exit status code, executed timestamp, and duration. This
115
+ # method determines which hook command to run by inspecting the
116
+ # check execution status. Check hook command tokens are
117
+ # substituted with the associated client attribute values, via
118
+ # `substitute_tokens()`. If there are unmatched check attribute
119
+ # value tokens, the check hook will not be executed, instead the
120
+ # hook command output will be set to report the unmatched
121
+ # tokens. Hook commands may expect/read and utilize JSON
122
+ # serialized Sensu client and check data via STDIN, if the hook
123
+ # definition includes `"stdin": true` (default is `false`). A
124
+ # hook may have a configured execution timeout, e.g. `"timeout":
125
+ # 30`, if one is not specified, the timeout defaults to 60
126
+ # seconds.
127
+ #
128
+ # @param check [Hash]
129
+ # @yield [check] callback/block called after executing the check
130
+ # hook (if any).
131
+ def execute_check_hook(check)
132
+ @logger.debug("attempting to execute check hook", :check => check)
133
+ severity = SEVERITIES[check[:status]] || "unknown"
134
+ hook = check[:hooks][check[:status].to_s] || check[:hooks][severity.to_sym]
135
+ if hook.nil? && check[:status] != 0
136
+ hook = check[:hooks]["non-zero"]
137
+ end
138
+ if hook
139
+ command, unmatched_tokens = substitute_tokens(hook[:command].dup, @settings[:client])
140
+ started = Time.now.to_f
141
+ hook[:executed] = started.to_i
142
+ if unmatched_tokens.empty?
143
+ options = {:timeout => hook.fetch(:timeout, 60)}
144
+ if hook[:stdin]
145
+ options[:data] = Sensu::JSON.dump({
146
+ :client => @settings[:client],
147
+ :check => check
148
+ })
149
+ end
150
+ Spawn.process(command, options) do |output, status|
151
+ hook[:duration] = ("%.3f" % (Time.now.to_f - started)).to_f
152
+ hook[:output] = output
153
+ hook[:status] = status
154
+ yield(check)
155
+ end
156
+ else
157
+ hook[:output] = "Unmatched client token(s): " + unmatched_tokens.join(", ")
158
+ hook[:status] = 3
159
+ yield(check)
160
+ end
161
+ else
162
+ yield(check)
163
+ end
164
+ end
165
+
113
166
  # Execute a check command, capturing its output (STDOUT/ERR),
114
167
  # exit status code, execution duration, timestamp, and publish
115
168
  # the result. This method guards against multiple executions for
116
169
  # the same check. Check attribute value tokens are substituted
117
170
  # with the associated client attribute values, via
118
- # `object_substitute_tokens()`. The original check command is
119
- # always published, to guard against publishing
171
+ # `object_substitute_tokens()`. The original check command and
172
+ # hooks are always published, to guard against publishing
120
173
  # sensitive/redacted client attribute values. If there are
121
174
  # unmatched check attribute value tokens, the check will not be
122
175
  # executed, instead a check result will be published reporting
@@ -129,16 +182,31 @@ module Sensu
129
182
  unless @checks_in_progress.include?(in_progress_key)
130
183
  @checks_in_progress << in_progress_key
131
184
  substituted, unmatched_tokens = object_substitute_tokens(check.dup, @settings[:client])
132
- check = substituted.merge(:command => check[:command])
185
+ check = substituted.merge(:command => check[:command], :hooks => check[:hooks])
186
+ check.delete(:hooks) if check[:hooks].nil?
133
187
  started = Time.now.to_f
134
188
  check[:executed] = started.to_i
135
189
  if unmatched_tokens.empty?
136
- Spawn.process(substituted[:command], :timeout => check[:timeout]) do |output, status|
190
+ options = {:timeout => check[:timeout]}
191
+ if check[:stdin]
192
+ options[:data] = Sensu::JSON.dump({
193
+ :client => @settings[:client],
194
+ :check => check
195
+ })
196
+ end
197
+ Spawn.process(substituted[:command], options) do |output, status|
137
198
  check[:duration] = ("%.3f" % (Time.now.to_f - started)).to_f
138
199
  check[:output] = output
139
200
  check[:status] = status
140
- publish_check_result(check)
141
- @checks_in_progress.delete(in_progress_key)
201
+ if check[:hooks] && !check[:hooks].empty?
202
+ execute_check_hook(check) do |check|
203
+ publish_check_result(check)
204
+ @checks_in_progress.delete(in_progress_key)
205
+ end
206
+ else
207
+ publish_check_result(check)
208
+ @checks_in_progress.delete(in_progress_key)
209
+ end
142
210
  end
143
211
  else
144
212
  check[:output] = "Unmatched client token(s): " + unmatched_tokens.join(", ")
@@ -178,8 +246,15 @@ module Sensu
178
246
  check[:duration] = ("%.3f" % (Time.now.to_f - started)).to_f
179
247
  check[:output] = output
180
248
  check[:status] = status
181
- publish_check_result(check)
182
- @checks_in_progress.delete(in_progress_key)
249
+ if check[:hooks] && !check[:hooks].empty?
250
+ execute_check_hook(check) do |check|
251
+ publish_check_result(check)
252
+ @checks_in_progress.delete(in_progress_key)
253
+ end
254
+ else
255
+ publish_check_result(check)
256
+ @checks_in_progress.delete(in_progress_key)
257
+ end
183
258
  end
184
259
  else
185
260
  @logger.warn("previous check extension execution in progress", :check => check)
@@ -1,7 +1,7 @@
1
1
  module Sensu
2
2
  unless defined?(Sensu::VERSION)
3
3
  # Sensu release version.
4
- VERSION = "1.0.4".freeze
4
+ VERSION = "1.1.0".freeze
5
5
 
6
6
  # Sensu release information.
7
7
  RELEASE_INFO = {
data/lib/sensu/daemon.rb CHANGED
@@ -4,7 +4,7 @@ gem "eventmachine", "1.2.5"
4
4
 
5
5
  gem "sensu-json", "2.1.0"
6
6
  gem "sensu-logger", "1.2.1"
7
- gem "sensu-settings", "10.3.0"
7
+ gem "sensu-settings", "10.9.0"
8
8
  gem "sensu-extension", "1.5.1"
9
9
  gem "sensu-extensions", "1.9.0"
10
10
  gem "sensu-transport", "7.0.2"
@@ -126,7 +126,8 @@ module Sensu
126
126
  # @param event_id [String] event UUID
127
127
  def handler_extension(handler, event_data, event_id)
128
128
  handler.safe_run(event_data) do |output, status|
129
- @logger.info("handler extension output", {
129
+ log_level = (output.empty? && status.zero?) ? :debug : :info
130
+ @logger.send(log_level, "handler extension output", {
130
131
  :extension => handler.definition,
131
132
  :event => { :id => event_id },
132
133
  :output => output,
@@ -320,16 +320,17 @@ module Sensu
320
320
  end
321
321
  end
322
322
 
323
- # Truncate check output. For metric checks, (`"type":
324
- # "metric"`), check output is truncated to a single line and a
325
- # maximum of 255 characters. Check output is currently left
326
- # unmodified for standard checks.
323
+ # Truncate check output. Metric checks (`"type": "metric"`), or
324
+ # checks with `"truncate_output": true`, have their output
325
+ # truncated to a single line and a maximum character length of
326
+ # 255 by default. The maximum character length can be change by
327
+ # the `"truncate_output_length"` check definition attribute.
327
328
  #
328
329
  # @param check [Hash]
329
330
  # @return [Hash] check with truncated output.
330
331
  def truncate_check_output(check)
331
- case check[:type]
332
- when METRIC_CHECK_TYPE
332
+ if check[:truncate_output] ||
333
+ (check[:type] == METRIC_CHECK_TYPE && check[:truncate_output] != false)
333
334
  begin
334
335
  output_lines = check[:output].split("\n")
335
336
  rescue ArgumentError
@@ -341,8 +342,9 @@ module Sensu
341
342
  output_lines = utf8_output.split("\n")
342
343
  end
343
344
  output = output_lines.first || check[:output]
344
- if output_lines.length > 1 || output.length > 255
345
- output = output[0..255] + "\n..."
345
+ truncate_output_length = check.fetch(:truncate_output_length, 255)
346
+ if output_lines.length > 1 || output.length > truncate_output_length
347
+ output = output[0..truncate_output_length] + "\n..."
346
348
  end
347
349
  check.merge(:output => output)
348
350
  else
@@ -603,7 +605,8 @@ module Sensu
603
605
  :address => "unknown",
604
606
  :subscriptions => ["client:#{name}"],
605
607
  :keepalives => false,
606
- :version => VERSION
608
+ :version => VERSION,
609
+ :timestamp => Time.now.to_i
607
610
  }
608
611
  end
609
612
 
@@ -792,45 +795,129 @@ module Sensu
792
795
  end
793
796
  end
794
797
 
798
+ # Determine and return clients from the registry that match a
799
+ # set of attributes.
800
+ #
801
+ # @param clients [Array] of client names.
802
+ # @param attributes [Hash]
803
+ # @yield [Array] callback/block called after determining the
804
+ # matching clients, returning them as a block parameter.
805
+ def determine_matching_clients(clients, attributes)
806
+ client_keys = clients.map { |client_name| "client:#{client_name}" }
807
+ @redis.mget(*client_keys) do |client_json_objects|
808
+ matching_clients = []
809
+ client_json_objects.each do |client_json|
810
+ unless client_json.nil?
811
+ client = Sensu::JSON.load(client_json)
812
+ if attributes_match?(client, attributes)
813
+ matching_clients << client
814
+ end
815
+ end
816
+ end
817
+ yield(matching_clients)
818
+ end
819
+ end
820
+
821
+ # Publish a proxy check request for a client. This method
822
+ # substitutes client tokens in the check definition prior to
823
+ # publish the check request. If there are unmatched client
824
+ # tokens, a warning is logged, and a check request is not
825
+ # published.
826
+ #
827
+ # @param client [Hash] definition.
828
+ # @param check [Hash] definition.
829
+ def publish_proxy_check_request(client, check)
830
+ @logger.debug("creating a proxy check request", {
831
+ :client => client,
832
+ :check => check
833
+ })
834
+ proxy_check, unmatched_tokens = object_substitute_tokens(check.dup, client)
835
+ if unmatched_tokens.empty?
836
+ proxy_check[:source] ||= client[:name]
837
+ publish_check_request(proxy_check)
838
+ else
839
+ @logger.warn("failed to publish a proxy check request", {
840
+ :reason => "unmatched client tokens",
841
+ :unmatched_tokens => unmatched_tokens,
842
+ :client => client,
843
+ :check => check
844
+ })
845
+ end
846
+ end
847
+
848
+ # Publish proxy check requests for one or more clients. This
849
+ # method can optionally splay proxy check requests, evenly, over
850
+ # a period of time, determined by the check interval and a
851
+ # configurable splay coverage percentage. For example, splay
852
+ # proxy check requests over 60s * 90%, 54s, leaving 6s for the
853
+ # last proxy check execution before the the next round of proxy
854
+ # check requests for the same check. The
855
+ # `publish_proxy_check_request() method is used to publish the
856
+ # proxy check requests.
857
+ #
858
+ # @param clients [Array] of client definitions.
859
+ # @param check [Hash] definition.
860
+ def publish_proxy_check_requests(clients, check)
861
+ client_count = clients.length
862
+ splay = 0
863
+ if check[:proxy_requests][:splay]
864
+ interval = check[:interval]
865
+ if check[:cron]
866
+ interval = determine_check_cron_time(check)
867
+ end
868
+ unless interval.nil?
869
+ splay_coverage = check[:proxy_requests].fetch(:splay_coverage, 90)
870
+ splay = interval * (splay_coverage / 100.0) / client_count
871
+ end
872
+ end
873
+ splay_timer = 0
874
+ clients.each do |client|
875
+ unless splay == 0
876
+ EM::Timer.new(splay_timer) do
877
+ publish_proxy_check_request(client, check)
878
+ end
879
+ splay_timer += splay
880
+ else
881
+ publish_proxy_check_request(client, check)
882
+ end
883
+ end
884
+ end
885
+
795
886
  # Create and publish one or more proxy check requests. This
796
887
  # method iterates through the Sensu client registry for clients
797
888
  # that matched provided proxy request client attributes. A proxy
798
889
  # check request is created for each client in the registry that
799
890
  # matches the proxy request client attributes. Proxy check
800
891
  # requests have their client tokens subsituted by the associated
801
- # client attributes values. The check requests are published to
802
- # the Transport via `publish_check_request()`.
892
+ # client attributes values. The `determine_matching_clients()`
893
+ # method is used to fetch and inspect each slide of clients from
894
+ # the registry, returning those that match the configured proxy
895
+ # request client attributes. A relatively small clients slice
896
+ # size (20) is used to reduce the number of clients inspected
897
+ # within a single tick of the EM reactor. The
898
+ # `publish_proxy_check_requests()` method is used to iterate
899
+ # through the matching Sensu clients, creating their own unique
900
+ # proxy check request, substituting client tokens, and then
901
+ # publishing them to the targetted subscriptions.
803
902
  #
804
903
  # @param check [Hash] definition.
805
- def publish_proxy_check_requests(check)
904
+ def create_proxy_check_requests(check)
806
905
  client_attributes = check[:proxy_requests][:client_attributes]
807
906
  unless client_attributes.empty?
808
907
  @redis.smembers("clients") do |clients|
809
- clients.each do |client_name|
810
- @redis.get("client:#{client_name}") do |client_json|
811
- unless client_json.nil?
812
- client = Sensu::JSON.load(client_json)
813
- if attributes_match?(client, client_attributes)
814
- @logger.debug("creating a proxy check request", {
815
- :client => client,
816
- :check => check
817
- })
818
- proxy_check, unmatched_tokens = object_substitute_tokens(check.dup, client)
819
- if unmatched_tokens.empty?
820
- proxy_check[:source] ||= client[:name]
821
- publish_check_request(proxy_check)
822
- else
823
- @logger.warn("failed to publish a proxy check request", {
824
- :reason => "unmatched client tokens",
825
- :unmatched_tokens => unmatched_tokens,
826
- :client => client,
827
- :check => check
828
- })
829
- end
830
- end
908
+ client_count = clients.length
909
+ proxy_check_requests = Proc.new do |matching_clients, slice_start, slice_size|
910
+ unless slice_start > client_count - 1
911
+ clients_slice = clients.slice(slice_start..slice_size)
912
+ determine_matching_clients(clients_slice, client_attributes) do |additional_clients|
913
+ matching_clients += additional_clients
914
+ proxy_check_requests.call(matching_clients, slice_start + 20, slice_size + 20)
831
915
  end
916
+ else
917
+ publish_proxy_check_requests(matching_clients, check)
832
918
  end
833
919
  end
920
+ proxy_check_requests.call([], 0, 19)
834
921
  end
835
922
  end
836
923
  end
@@ -846,7 +933,7 @@ module Sensu
846
933
  Proc.new do
847
934
  unless check_subdued?(check)
848
935
  if check[:proxy_requests]
849
- publish_proxy_check_requests(check)
936
+ create_proxy_check_requests(check)
850
937
  else
851
938
  publish_check_request(check)
852
939
  end
@@ -1064,46 +1151,83 @@ module Sensu
1064
1151
  end
1065
1152
  end
1066
1153
 
1154
+ # Create check TTL results. This method will retrieve check
1155
+ # results from the registry and determine the time since their
1156
+ # last check execution (in seconds). If the time since last
1157
+ # execution is equal to or greater than the defined check TTL, a
1158
+ # warning check result is published with the appropriate check
1159
+ # output.
1160
+ #
1161
+ # @param ttl_keys [Array] of TTL keys.
1162
+ # @param interval [Integer] to use for the check TTL result
1163
+ # interval.
1164
+ # @yield [] callback/block called after the check TTL results
1165
+ # have been created.
1166
+ def create_check_ttl_results(ttl_keys, interval=30)
1167
+ result_keys = ttl_keys.map { |ttl_key| "result:#{ttl_key}" }
1168
+ @redis.mget(*result_keys) do |result_json_objects|
1169
+ result_json_objects.each_with_index do |result_json, index|
1170
+ unless result_json.nil?
1171
+ check = Sensu::JSON.load(result_json)
1172
+ next unless check[:ttl] && check[:executed] && !check[:force_resolve]
1173
+ time_since_last_execution = Time.now.to_i - check[:executed]
1174
+ if time_since_last_execution >= check[:ttl]
1175
+ client_name = ttl_keys[index].split(":").first
1176
+ keepalive_event_exists?(client_name) do |event_exists|
1177
+ unless event_exists
1178
+ check[:output] = "Last check execution was "
1179
+ check[:output] << "#{time_since_last_execution} seconds ago"
1180
+ check[:status] = check[:ttl_status] || 1
1181
+ check[:interval] = interval
1182
+ publish_check_result(client_name, check)
1183
+ end
1184
+ end
1185
+ end
1186
+ else
1187
+ @redis.srem("ttl", result_key)
1188
+ end
1189
+ end
1190
+ yield
1191
+ end
1192
+ end
1193
+
1067
1194
  # Determine stale check results, those that have not executed in
1068
1195
  # a specified amount of time (check TTL). This method iterates
1069
1196
  # through stored check results that have a defined TTL value (in
1070
- # seconds). The time since last check execution (in seconds) is
1071
- # calculated for each check result. If the time since last
1072
- # execution is equal to or greater than the check TTL, a warning
1073
- # check result is published with the appropriate check output.
1074
- def determine_stale_check_results(interval = 30)
1075
- @logger.info("determining stale check results")
1076
- @redis.smembers("ttl") do |result_keys|
1077
- result_keys.each do |result_key|
1078
- @redis.get("result:#{result_key}") do |result_json|
1079
- unless result_json.nil?
1080
- check = Sensu::JSON.load(result_json)
1081
- next unless check[:ttl] && check[:executed] && !check[:force_resolve]
1082
- time_since_last_execution = Time.now.to_i - check[:executed]
1083
- if time_since_last_execution >= check[:ttl]
1084
- client_name = result_key.split(":").first
1085
- keepalive_event_exists?(client_name) do |event_exists|
1086
- unless event_exists
1087
- check[:output] = "Last check execution was "
1088
- check[:output] << "#{time_since_last_execution} seconds ago"
1089
- check[:status] = check[:ttl_status] || 1
1090
- check[:interval] = interval
1091
- publish_check_result(client_name, check)
1092
- end
1093
- end
1094
- end
1095
- else
1096
- @redis.srem("ttl", result_key)
1197
+ # seconds). The `create_check_ttl_results()` method is used to
1198
+ # inspect each check result, calculating their time since last
1199
+ # check execution (in seconds). If the time since last execution
1200
+ # is equal to or greater than the check TTL, a warning check
1201
+ # result is published with the appropriate check output. A
1202
+ # relatively small check results slice size (20) is used to
1203
+ # reduce the number of check results inspected within a single
1204
+ # tick of the EM reactor.
1205
+ #
1206
+ # @param interval [Integer] to use for the check TTL result
1207
+ # interval.
1208
+ def determine_stale_check_results(interval=30)
1209
+ @logger.info("determining stale check results (ttl)")
1210
+ @redis.smembers("ttl") do |ttl_keys|
1211
+ ttl_key_count = ttl_keys.length
1212
+ ttl_check_results = Proc.new do |slice_start, slice_size|
1213
+ unless slice_start > ttl_key_count - 1
1214
+ ttl_keys_slice = ttl_keys.slice(slice_start..slice_size)
1215
+ create_check_ttl_results(ttl_keys_slice, interval) do
1216
+ ttl_check_results.call(slice_start + 20, slice_size + 20)
1097
1217
  end
1098
1218
  end
1099
1219
  end
1220
+ ttl_check_results.call(0, 19)
1100
1221
  end
1101
1222
  end
1102
1223
 
1103
1224
  # Set up the check result monitor, a periodic timer to run
1104
1225
  # `determine_stale_check_results()` every 30 seconds. The timer
1105
1226
  # is stored in the timers hash under `:tasks`.
1106
- def setup_check_result_monitor(interval = 30)
1227
+ #
1228
+ # @param interval [Integer] to use for the check TTL result
1229
+ # interval.
1230
+ def setup_check_result_monitor(interval=30)
1107
1231
  @logger.debug("monitoring check results")
1108
1232
  @timers[:tasks][:check_result_monitor] << EM::PeriodicTimer.new(interval) do
1109
1233
  determine_stale_check_results(interval)
data/sensu.gemspec CHANGED
@@ -15,7 +15,7 @@ Gem::Specification.new do |s|
15
15
  s.add_dependency "eventmachine", "1.2.5"
16
16
  s.add_dependency "sensu-json", "2.1.0"
17
17
  s.add_dependency "sensu-logger", "1.2.1"
18
- s.add_dependency "sensu-settings", "10.3.0"
18
+ s.add_dependency "sensu-settings", "10.9.0"
19
19
  s.add_dependency "sensu-extension", "1.5.1"
20
20
  s.add_dependency "sensu-extensions", "1.9.0"
21
21
  s.add_dependency "sensu-transport", "7.0.2"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sensu
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Porter
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2017-10-18 00:00:00.000000000 Z
12
+ date: 2017-09-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: eventmachine
@@ -59,14 +59,14 @@ dependencies:
59
59
  requirements:
60
60
  - - '='
61
61
  - !ruby/object:Gem::Version
62
- version: 10.3.0
62
+ version: 10.9.0
63
63
  type: :runtime
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
66
66
  requirements:
67
67
  - - '='
68
68
  - !ruby/object:Gem::Version
69
- version: 10.3.0
69
+ version: 10.9.0
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: sensu-extension
72
72
  requirement: !ruby/object:Gem::Requirement