sensu 1.0.4 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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