portertech-sensu 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +961 -0
  3. data/MIT-LICENSE.txt +20 -0
  4. data/README.md +65 -0
  5. data/exe/sensu-api +10 -0
  6. data/exe/sensu-client +10 -0
  7. data/exe/sensu-install +195 -0
  8. data/exe/sensu-server +10 -0
  9. data/lib/sensu/api/http_handler.rb +434 -0
  10. data/lib/sensu/api/process.rb +79 -0
  11. data/lib/sensu/api/routes/aggregates.rb +196 -0
  12. data/lib/sensu/api/routes/checks.rb +44 -0
  13. data/lib/sensu/api/routes/clients.rb +171 -0
  14. data/lib/sensu/api/routes/events.rb +86 -0
  15. data/lib/sensu/api/routes/health.rb +45 -0
  16. data/lib/sensu/api/routes/info.rb +37 -0
  17. data/lib/sensu/api/routes/request.rb +44 -0
  18. data/lib/sensu/api/routes/resolve.rb +32 -0
  19. data/lib/sensu/api/routes/results.rb +153 -0
  20. data/lib/sensu/api/routes/settings.rb +23 -0
  21. data/lib/sensu/api/routes/silenced.rb +182 -0
  22. data/lib/sensu/api/routes/stashes.rb +107 -0
  23. data/lib/sensu/api/routes.rb +88 -0
  24. data/lib/sensu/api/utilities/filter_response_content.rb +44 -0
  25. data/lib/sensu/api/utilities/publish_check_request.rb +107 -0
  26. data/lib/sensu/api/utilities/publish_check_result.rb +39 -0
  27. data/lib/sensu/api/utilities/resolve_event.rb +29 -0
  28. data/lib/sensu/api/utilities/servers_info.rb +43 -0
  29. data/lib/sensu/api/utilities/transport_info.rb +43 -0
  30. data/lib/sensu/api/validators/check.rb +55 -0
  31. data/lib/sensu/api/validators/client.rb +35 -0
  32. data/lib/sensu/api/validators/invalid.rb +8 -0
  33. data/lib/sensu/cli.rb +69 -0
  34. data/lib/sensu/client/http_socket.rb +217 -0
  35. data/lib/sensu/client/process.rb +655 -0
  36. data/lib/sensu/client/socket.rb +207 -0
  37. data/lib/sensu/client/utils.rb +53 -0
  38. data/lib/sensu/client/validators/check.rb +53 -0
  39. data/lib/sensu/constants.rb +17 -0
  40. data/lib/sensu/daemon.rb +396 -0
  41. data/lib/sensu/sandbox.rb +19 -0
  42. data/lib/sensu/server/filter.rb +227 -0
  43. data/lib/sensu/server/handle.rb +201 -0
  44. data/lib/sensu/server/mutate.rb +92 -0
  45. data/lib/sensu/server/process.rb +1646 -0
  46. data/lib/sensu/server/socket.rb +54 -0
  47. data/lib/sensu/server/tessen.rb +170 -0
  48. data/lib/sensu/utilities.rb +398 -0
  49. data/lib/sensu.rb +3 -0
  50. data/sensu.gemspec +36 -0
  51. metadata +322 -0
@@ -0,0 +1,434 @@
1
+ require "sensu/utilities"
2
+ require "sensu/api/routes"
3
+ require "sensu/api/utilities/filter_response_content"
4
+
5
+ gem "em-http-server", "0.1.8"
6
+
7
+ require "em-http-server"
8
+ require "base64"
9
+
10
+ module Sensu
11
+ module API
12
+ class HTTPHandler < EM::HttpServer::Server
13
+ include Routes
14
+ include Utilities::FilterResponseContent
15
+
16
+ attr_accessor :logger, :settings, :redis, :transport
17
+
18
+ # Create a hash containing the HTTP request details. This method
19
+ # determines the remote address for the HTTP client (using
20
+ # EventMachine Connection `get_peername()`).
21
+ #
22
+ # @result [Hash]
23
+ def request_details
24
+ return @request_details if @request_details
25
+ @request_id = @http.fetch(:x_request_id, random_uuid)
26
+ @request_start_time = Time.now.to_f
27
+ _, remote_address = Socket.unpack_sockaddr_in(get_peername)
28
+ @request_details = {
29
+ :request_id => @request_id,
30
+ :remote_address => remote_address,
31
+ :user_agent => @http[:user_agent],
32
+ :method => @http_request_method,
33
+ :uri => @http_request_uri,
34
+ :query_string => @http_query_string,
35
+ :body => @http_content
36
+ }
37
+ if @http[:x_forwarded_for]
38
+ @request_details[:x_forwarded_for] = @http[:x_forwarded_for]
39
+ end
40
+ @request_details
41
+ end
42
+
43
+ # Log the HTTP request. The debug log level is used for requests
44
+ # as response logging includes the same information.
45
+ def log_request
46
+ @logger.debug("api request", request_details)
47
+ end
48
+
49
+ # 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.
53
+ def log_response
54
+ @logger.info("api response", {
55
+ :request => request_details,
56
+ :status => @response.status,
57
+ :content_length => @response.content.to_s.bytesize,
58
+ :time => (Time.now.to_f - @request_start_time).round(3)
59
+ })
60
+ end
61
+
62
+ # Parse the HTTP request URI using a regular expression,
63
+ # returning the URI unescaped match data values.
64
+ #
65
+ # @param regex [Regexp]
66
+ # @return [Array] URI unescaped match data values.
67
+ def parse_uri(regex)
68
+ uri_match = regex.match(@http_request_uri)[1..-1]
69
+ uri_match.map { |s| URI.decode_www_form_component(s) }
70
+ end
71
+
72
+ # Parse the HTTP request query string for parameters. This
73
+ # method creates `@params`, a hash of parsed query parameters,
74
+ # used by the API routes. This method also creates
75
+ # `@filter_params`, a hash of parsed response content filter
76
+ # parameters.
77
+ def parse_parameters
78
+ @params = {}
79
+ if @http_query_string
80
+ @http_query_string.split("&").each do |pair|
81
+ key, value = pair.split("=")
82
+ @params[key.to_sym] = value
83
+ if key.start_with?("filter.")
84
+ filter_param = key.sub(/^filter\./, "")
85
+ @filter_params ||= {}
86
+ @filter_params[filter_param] = value
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ # Determine if a parameter has an integer value and if so return
93
+ # it as one. This method will return `nil` if the parameter
94
+ # value is not an integer.
95
+ #
96
+ # @param value [String]
97
+ # @return [Integer, nil]
98
+ def integer_parameter(value)
99
+ value =~ /\A[0-9]+\z/ ? value.to_i : nil
100
+ end
101
+
102
+ # Read JSON data from the HTTP request content and validate it
103
+ # with the provided rules. If the HTTP request content does not
104
+ # contain valid JSON or it does not pass validation, this method
105
+ # returns a `400` (Bad Request) HTTP response.
106
+ #
107
+ # @param rules [Hash] containing the validation rules.
108
+ # @yield [Object] the callback/block called with the data after successfully
109
+ # parsing and validating the the HTTP request content.
110
+ def read_data(rules={})
111
+ begin
112
+ data = Sensu::JSON.load(@http_content)
113
+ valid = data.is_a?(Hash) && rules.all? do |key, rule|
114
+ value = data[key]
115
+ (Array(rule[:type]).any? {|type| value.is_a?(type)} ||
116
+ (rule[:nil_ok] && value.nil?)) && (value.nil? || rule[:regex].nil?) ||
117
+ (rule[:regex] && value.is_a?(String) && (value =~ rule[:regex]) == 0)
118
+ end
119
+ if valid
120
+ yield(data)
121
+ else
122
+ bad_request!
123
+ end
124
+ rescue Sensu::JSON::ParseError
125
+ bad_request!
126
+ end
127
+ end
128
+
129
+ # Create an EM HTTP Server HTTP response object, `@response`.
130
+ # The response object is use to build up the response status,
131
+ # status string, content type, and content. The response object
132
+ # is responsible for sending the HTTP response to the HTTP
133
+ # client and closing the connection afterwards.
134
+ #
135
+ # @return [Object]
136
+ def create_response
137
+ @response = EM::DelegatedHttpResponse.new(self)
138
+ end
139
+
140
+ # Set the cors (Cross-origin resource sharing) HTTP headers.
141
+ def set_cors_headers
142
+ api = @settings[:api] || {}
143
+ api[:cors] ||= {
144
+ "Origin" => "*",
145
+ "Methods" => "GET, POST, PUT, DELETE, OPTIONS",
146
+ "Credentials" => "true",
147
+ "Headers" => "Origin, X-Requested-With, Content-Type, Accept, Authorization"
148
+ }
149
+ if api[:cors].is_a?(Hash)
150
+ api[:cors].each do |header, value|
151
+ @response.headers["Access-Control-Allow-#{header}"] = value
152
+ end
153
+ end
154
+ end
155
+
156
+ # Set the HTTP response headers, including the request ID and
157
+ # cors headers (via `set_cores_headers()`).
158
+ def set_headers
159
+ @response.headers["X-Request-ID"] = @request_id
160
+ set_cors_headers
161
+ end
162
+
163
+ # Paginate the provided items. This method uses two HTTP query
164
+ # parameters to determine how to paginate the items, `limit` and
165
+ # `offset`. The parameter `limit` specifies how many items are
166
+ # to be returned in the response. The parameter `offset`
167
+ # specifies the items array index, skipping a number of items.
168
+ # This method sets the "X-Pagination" HTTP response header to a
169
+ # JSON object containing the `limit`, `offset` and `total`
170
+ # number of items that are being paginated.
171
+ #
172
+ # @param items [Array]
173
+ # @return [Array] paginated items.
174
+ def pagination(items)
175
+ limit = integer_parameter(@params[:limit])
176
+ offset = integer_parameter(@params[:offset]) || 0
177
+ unless limit.nil?
178
+ @response.headers["X-Pagination"] = Sensu::JSON.dump(
179
+ :limit => limit,
180
+ :offset => offset,
181
+ :total => items.length
182
+ )
183
+ paginated = items.slice(offset, limit)
184
+ Array(paginated)
185
+ else
186
+ items
187
+ end
188
+ end
189
+
190
+ # Respond to an HTTP request. The routes set `@response_status`,
191
+ # `@response_status_string`, and `@response_content`
192
+ # appropriately. The HTTP response status defaults to `200` with
193
+ # the status string `OK`. If filter params were provided,
194
+ # `@response_content` is filtered (mutated). The Sensu API only
195
+ # returns JSON response content, `@response_content` is assumed
196
+ # to be a Ruby object that can be serialized as JSON.
197
+ def respond
198
+ @response.status = @response_status || 200
199
+ @response.status_string = @response_status_string || "OK"
200
+ if @response_content && @http_request_method != HEAD_METHOD
201
+ if @http_request_method == GET_METHOD && @filter_params
202
+ filter_response_content!
203
+ end
204
+ @response.content_type "application/json"
205
+ @response.content = Sensu::JSON.dump(@response_content)
206
+ end
207
+ @response.headers["Connection"] = "close"
208
+ log_response
209
+ @response.send_response
210
+ end
211
+
212
+ # Determine if an HTTP request is authorized. This method
213
+ # compares the configured API user and password (if any) with
214
+ # the HTTP request basic authentication credentials. No
215
+ # authentication is done if the API user and password are not
216
+ # configured. OPTIONS HTTP requests bypass authentication.
217
+ #
218
+ # @return [TrueClass, FalseClass]
219
+ def authorized?
220
+ api = @settings[:api]
221
+ if api && api[:user] && api[:password]
222
+ if @http_request_method == OPTIONS_METHOD
223
+ true
224
+ elsif @http[:authorization]
225
+ scheme, base64 = @http[:authorization].split("\s")
226
+ if scheme == "Basic"
227
+ user, password = Base64.decode64(base64).split(":")
228
+ user == api[:user] && password == api[:password]
229
+ else
230
+ false
231
+ end
232
+ else
233
+ false
234
+ end
235
+ else
236
+ true
237
+ end
238
+ end
239
+
240
+ # Determine if the API is connected to Redis and the Transport.
241
+ # This method sets the `@response_content` if the API is not
242
+ # connected or it has not yet initialized the connection
243
+ # objects. The `/info` and `/health` routes are excluded from
244
+ # the connectivity checks.
245
+ def connected?
246
+ connected = true
247
+ if @redis && @transport
248
+ unless @http_request_uri =~ INFO_URI || @http_request_uri =~ HEALTH_URI
249
+ unless @redis.connected?
250
+ @response_content = {:error => "not connected to redis"}
251
+ connected = false
252
+ end
253
+ unless @transport.connected?
254
+ @response_content = {:error => "not connected to transport"}
255
+ connected = false
256
+ end
257
+ end
258
+ else
259
+ @response_content = {:error => "redis and transport connections not initialized"}
260
+ connected = false
261
+ end
262
+ connected
263
+ end
264
+
265
+ # Respond to the HTTP request with a `201` (Created) response.
266
+ def created!
267
+ @response_status = 201
268
+ @response_status_string = "Created"
269
+ respond
270
+ end
271
+
272
+ # Respond to the HTTP request with a `202` (Accepted) response.
273
+ def accepted!
274
+ @response_status = 202
275
+ @response_status_string = "Accepted"
276
+ respond
277
+ end
278
+
279
+ # Respond to the HTTP request with a `204` (No Content)
280
+ # response.
281
+ def no_content!
282
+ @response_status = 204
283
+ @response_status_string = "No Content"
284
+ @response_content = nil
285
+ respond
286
+ end
287
+
288
+ # Respond to the HTTP request with a `400` (Bad Request)
289
+ # response.
290
+ def bad_request!
291
+ @response_status = 400
292
+ @response_status_string = "Bad Request"
293
+ respond
294
+ end
295
+
296
+ # Respond to the HTTP request with a `401` (Unauthroized)
297
+ # response. This method sets the "WWW-Autenticate" HTTP response
298
+ # header.
299
+ def unauthorized!
300
+ @response.headers["WWW-Authenticate"] = 'Basic realm="Restricted Area"'
301
+ @response_status = 401
302
+ @response_status_string = "Unauthorized"
303
+ respond
304
+ end
305
+
306
+ # Respond to the HTTP request with a `404` (Not Found) response.
307
+ def not_found!
308
+ @response_status = 404
309
+ @response_status_string = "Not Found"
310
+ respond
311
+ end
312
+
313
+ # Respond to the HTTP request with a `405` (Method Not Allowed) response.
314
+ def method_not_allowed!(allowed_http_methods=[])
315
+ @response.headers["Allow"] = allowed_http_methods.join(", ")
316
+ @response_status = 405
317
+ @response_status_string = "Method Not Allowed"
318
+ respond
319
+ end
320
+
321
+ # Respond to the HTTP request with a `412` (Precondition Failed)
322
+ # response.
323
+ def precondition_failed!
324
+ @response_status = 412
325
+ @response_status_string = "Precondition Failed"
326
+ respond
327
+ end
328
+
329
+ # Respond to the HTTP request with a `500` (Internal Server
330
+ # Error) response.
331
+ def error!
332
+ @response_status = 500
333
+ @response_status_string = "Internal Server Error"
334
+ respond
335
+ end
336
+
337
+ # Determine the allowed HTTP methods for a route. The route
338
+ # regular expressions and associated route method calls are
339
+ # provided by `ROUTES`. This method returns an array of HTTP
340
+ # methods that have a route that matches the HTTP request URI.
341
+ #
342
+ # @return [Array]
343
+ def allowed_http_methods?
344
+ ROUTES.map { |http_method, routes|
345
+ match = routes.detect do |route|
346
+ @http_request_uri =~ route[0]
347
+ end
348
+ match ? http_method : nil
349
+ }.flatten.compact
350
+ end
351
+
352
+ # Determine the route method for the HTTP request method and
353
+ # URI. The route regular expressions and associated route method
354
+ # calls are provided by `ROUTES`. This method will return the
355
+ # first route method name (Ruby symbol) that has matching URI
356
+ # regular expression. If an HTTP method is not supported, or
357
+ # there is not a matching regular expression, `nil` will be
358
+ # returned.
359
+ #
360
+ # @return [Symbol]
361
+ def determine_route_method
362
+ if ROUTES.has_key?(@http_request_method)
363
+ route = ROUTES[@http_request_method].detect do |route|
364
+ @http_request_uri =~ route[0]
365
+ end
366
+ route ? route[1] : nil
367
+ else
368
+ nil
369
+ end
370
+ end
371
+
372
+ # Route the HTTP request. OPTIONS HTTP requests will always
373
+ # return a `200` with no response content. This method uses
374
+ # `determine_route_method()` to determine the symbolized route
375
+ # method to send/call. If a route method does not exist for the
376
+ # HTTP request method and URI, this method uses
377
+ # `allowed_http_methods?()` to determine if a 404 (Not Found) or
378
+ # 405 (Method Not Allowed) HTTP response should be used.
379
+ def route_request
380
+ if @http_request_method == OPTIONS_METHOD
381
+ respond
382
+ else
383
+ route_method = determine_route_method
384
+ if route_method
385
+ send(route_method)
386
+ else
387
+ allowed_http_methods = allowed_http_methods?
388
+ if allowed_http_methods.empty?
389
+ not_found!
390
+ else
391
+ method_not_allowed!(allowed_http_methods)
392
+ end
393
+ end
394
+ end
395
+ end
396
+
397
+ # Process a HTTP request. Log the request, parse the HTTP query
398
+ # parameters, create the HTTP response object, set the cors HTTP
399
+ # response headers, determine if the request is authorized,
400
+ # determine if the API is connected to Redis and the Transport,
401
+ # and then route the HTTP request (responding to the request).
402
+ # This method is called by EM HTTP Server when handling a new
403
+ # connection.
404
+ def process_http_request
405
+ log_request
406
+ parse_parameters
407
+ create_response
408
+ set_headers
409
+ if authorized?
410
+ if connected?
411
+ route_request
412
+ else
413
+ error!
414
+ end
415
+ else
416
+ unauthorized!
417
+ end
418
+ end
419
+
420
+ # Catch uncaught/unexpected errors, log them, and attempt to
421
+ # respond with a `500` (Internal Server Error) HTTP response.
422
+ # This method is called by EM HTTP Server.
423
+ #
424
+ # @param error [Object]
425
+ def http_request_errback(error)
426
+ @logger.error("unexpected api error", {
427
+ :error => error.to_s,
428
+ :backtrace => error.backtrace.join("\n")
429
+ })
430
+ error! rescue nil
431
+ end
432
+ end
433
+ end
434
+ end
@@ -0,0 +1,79 @@
1
+ require "sensu/daemon"
2
+ require "sensu/api/http_handler"
3
+
4
+ module Sensu
5
+ module API
6
+ class Process
7
+ include Daemon
8
+
9
+ # Create an instance of the Sensu API process, setup the Redis
10
+ # and Transport connections, start the API HTTP server, set up
11
+ # API process signal traps (for stopping), within the
12
+ # EventMachine event loop.
13
+ #
14
+ # @param options [Hash]
15
+ def self.run(options={})
16
+ api = self.new(options)
17
+ EM::run do
18
+ api.setup_redis
19
+ api.setup_transport
20
+ api.start
21
+ api.setup_signal_traps
22
+ end
23
+ end
24
+
25
+ # Start the API HTTP server. This method sets `@http_server`.
26
+ #
27
+ # @param bind [String] address to listen on.
28
+ # @param port [Integer] to listen on.
29
+ def start_http_server(bind, port)
30
+ @logger.info("api listening", {
31
+ :protocol => "http",
32
+ :bind => bind,
33
+ :port => port
34
+ })
35
+ @http_server = EM::start_server(bind, port, HTTPHandler) do |handler|
36
+ handler.logger = @logger
37
+ handler.settings = @settings
38
+ handler.redis = @redis
39
+ handler.transport = @transport
40
+ end
41
+ end
42
+
43
+ # Start the Sensu API HTTP server. This method sets the service
44
+ # state to `:running`.
45
+ def start
46
+ api = @settings[:api] || {}
47
+ bind = api[:bind] || "0.0.0.0"
48
+ port = api[:port] || 4567
49
+ start_http_server(bind, port)
50
+ super
51
+ end
52
+
53
+ # Stop the Sensu API process. This method stops the HTTP server,
54
+ # closes the Redis and transport connections, sets the service
55
+ # state to `:stopped`, and stops the EventMachine event loop.
56
+ def stop
57
+ @logger.warn("stopping")
58
+ EM::stop_server(@http_server)
59
+ @redis.close if @redis
60
+ @transport.close if @transport
61
+ super
62
+ end
63
+
64
+ # Create an instance of the Sensu API with initialized
65
+ # connections for running test specs.
66
+ #
67
+ # @param options [Hash]
68
+ def self.test(options={})
69
+ api = self.new(options)
70
+ api.setup_redis do
71
+ api.setup_transport do
72
+ api.start
73
+ yield
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,196 @@
1
+ module Sensu
2
+ module API
3
+ module Routes
4
+ module Aggregates
5
+ AGGREGATES_URI = /^\/aggregates$/
6
+ AGGREGATE_URI = /^\/aggregates\/([\w\.-]+)$/
7
+ AGGREGATE_CLIENTS_URI = /^\/aggregates\/([\w\.-]+)\/clients$/
8
+ AGGREGATE_CHECKS_URI = /^\/aggregates\/([\w\.-]+)\/checks$/
9
+ AGGREGATE_RESULTS_SEVERITY_URI = /^\/aggregates\/([\w\.-]+)\/results\/([\w\.-]+)$/
10
+
11
+ # GET /aggregates
12
+ def get_aggregates
13
+ @redis.smembers("aggregates") do |aggregates|
14
+ aggregates = pagination(aggregates)
15
+ aggregates.map! do |aggregate|
16
+ {:name => aggregate}
17
+ end
18
+ @response_content = aggregates
19
+ respond
20
+ end
21
+ end
22
+
23
+ # GET /aggregates/:aggregate
24
+ def get_aggregate
25
+ aggregate = parse_uri(AGGREGATE_URI).first
26
+ @redis.smembers("aggregates:#{aggregate}") do |aggregate_members|
27
+ unless aggregate_members.empty?
28
+ @response_content = {
29
+ :clients => 0,
30
+ :checks => 0,
31
+ :results => {
32
+ :ok => 0,
33
+ :warning => 0,
34
+ :critical => 0,
35
+ :unknown => 0,
36
+ :total => 0,
37
+ :stale => 0
38
+ }
39
+ }
40
+ clients = []
41
+ checks = []
42
+ results = []
43
+ aggregate_members.each_with_index do |member, index|
44
+ client_name, check_name = member.split(":")
45
+ clients << client_name
46
+ checks << check_name
47
+ result_key = "result:#{client_name}:#{check_name}"
48
+ @redis.get(result_key) do |result_json|
49
+ unless result_json.nil?
50
+ results << Sensu::JSON.load(result_json)
51
+ else
52
+ @redis.srem("aggregates:#{aggregate}", member)
53
+ end
54
+ if index == aggregate_members.length - 1
55
+ @response_content[:clients] = clients.uniq.length
56
+ @response_content[:checks] = checks.uniq.length
57
+ max_age = integer_parameter(@params[:max_age])
58
+ if max_age
59
+ result_count = results.length
60
+ timestamp = Time.now.to_i - max_age
61
+ results.reject! do |result|
62
+ result[:executed] && result[:executed] < timestamp
63
+ end
64
+ @response_content[:results][:stale] = result_count - results.length
65
+ end
66
+ @response_content[:results][:total] = results.length
67
+ results.each do |result|
68
+ severity = (SEVERITIES[result[:status]] || "unknown")
69
+ @response_content[:results][severity.to_sym] += 1
70
+ end
71
+ respond
72
+ end
73
+ end
74
+ end
75
+ else
76
+ not_found!
77
+ end
78
+ end
79
+ end
80
+
81
+ # DELETE /aggregates/:aggregate
82
+ def delete_aggregate
83
+ aggregate = parse_uri(AGGREGATE_URI).first
84
+ @redis.smembers("aggregates") do |aggregates|
85
+ if aggregates.include?(aggregate)
86
+ @redis.srem("aggregates", aggregate) do
87
+ @redis.del("aggregates:#{aggregate}") do
88
+ no_content!
89
+ end
90
+ end
91
+ else
92
+ not_found!
93
+ end
94
+ end
95
+ end
96
+
97
+ # GET /aggregates/:aggregate/clients
98
+ def get_aggregate_clients
99
+ aggregate = parse_uri(AGGREGATE_CLIENTS_URI).first
100
+ @response_content = []
101
+ @redis.smembers("aggregates:#{aggregate}") do |aggregate_members|
102
+ unless aggregate_members.empty?
103
+ clients = {}
104
+ aggregate_members.each do |member|
105
+ client_name, check_name = member.split(":")
106
+ clients[client_name] ||= []
107
+ clients[client_name] << check_name
108
+ end
109
+ clients.each do |client_name, checks|
110
+ @response_content << {
111
+ :name => client_name,
112
+ :checks => checks
113
+ }
114
+ end
115
+ respond
116
+ else
117
+ not_found!
118
+ end
119
+ end
120
+ end
121
+
122
+ # GET /aggregates/:aggregate/checks
123
+ def get_aggregate_checks
124
+ aggregate = parse_uri(AGGREGATE_CHECKS_URI).first
125
+ @response_content = []
126
+ @redis.smembers("aggregates:#{aggregate}") do |aggregate_members|
127
+ unless aggregate_members.empty?
128
+ checks = {}
129
+ aggregate_members.each do |member|
130
+ client_name, check_name = member.split(":")
131
+ checks[check_name] ||= []
132
+ checks[check_name] << client_name
133
+ end
134
+ checks.each do |check_name, clients|
135
+ @response_content << {
136
+ :name => check_name,
137
+ :clients => clients
138
+ }
139
+ end
140
+ respond
141
+ else
142
+ not_found!
143
+ end
144
+ end
145
+ end
146
+
147
+ # GET /aggregates/:aggregate/results/:severity
148
+ def get_aggregate_results_severity
149
+ aggregate, severity = parse_uri(AGGREGATE_RESULTS_SEVERITY_URI)
150
+ if SEVERITIES.include?(severity)
151
+ @redis.smembers("aggregates:#{aggregate}") do |aggregate_members|
152
+ unless aggregate_members.empty?
153
+ @response_content = []
154
+ summaries = Hash.new
155
+ max_age = integer_parameter(@params[:max_age])
156
+ current_timestamp = Time.now.to_i
157
+ aggregate_members.each_with_index do |member, index|
158
+ client_name, check_name = member.split(":")
159
+ result_key = "result:#{client_name}:#{check_name}"
160
+ @redis.get(result_key) do |result_json|
161
+ unless result_json.nil?
162
+ result = Sensu::JSON.load(result_json)
163
+ if SEVERITIES[result[:status]] == severity &&
164
+ (max_age.nil? || result[:executed].nil? || result[:executed] >= (current_timestamp - max_age))
165
+ summaries[check_name] ||= {}
166
+ summaries[check_name][result[:output]] ||= {:total => 0, :clients => []}
167
+ summaries[check_name][result[:output]][:total] += 1
168
+ summaries[check_name][result[:output]][:clients] << client_name
169
+ end
170
+ end
171
+ if index == aggregate_members.length - 1
172
+ summaries.each do |check_name, outputs|
173
+ summary = outputs.map do |output, output_summary|
174
+ {:output => output}.merge(output_summary)
175
+ end
176
+ @response_content << {
177
+ :check => check_name,
178
+ :summary => summary
179
+ }
180
+ end
181
+ respond
182
+ end
183
+ end
184
+ end
185
+ else
186
+ not_found!
187
+ end
188
+ end
189
+ else
190
+ bad_request!
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end