sensu 0.16.0-java → 0.17.0.beta.1-java

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.
@@ -0,0 +1,704 @@
1
+ require "sensu/daemon"
2
+
3
+ gem "sinatra", "1.3.5"
4
+ gem "async_sinatra", "1.0.0"
5
+
6
+ unless RUBY_PLATFORM =~ /java/
7
+ gem "thin", "1.5.0"
8
+ require "thin"
9
+ end
10
+
11
+ require "sinatra/async"
12
+
13
+ module Sensu
14
+ module API
15
+ class Process < Sinatra::Base
16
+ register Sinatra::Async
17
+
18
+ class << self
19
+ include Daemon
20
+
21
+ def run(options={})
22
+ bootstrap(options)
23
+ setup_process(options)
24
+ EM::run do
25
+ start
26
+ setup_signal_traps
27
+ end
28
+ end
29
+
30
+ def on_reactor_run
31
+ EM::next_tick do
32
+ setup_redis
33
+ set :redis, @redis
34
+ setup_transport
35
+ set :transport, @transport
36
+ end
37
+ end
38
+
39
+ def bootstrap(options)
40
+ setup_logger(options)
41
+ set :logger, @logger
42
+ load_settings(options)
43
+ set :api, @settings[:api]
44
+ set :checks, @settings[:checks]
45
+ set :all_checks, @settings.checks
46
+ set :cors, @settings[:cors] || {
47
+ "Origin" => "*",
48
+ "Methods" => "GET, POST, PUT, DELETE, OPTIONS",
49
+ "Credentials" => "true",
50
+ "Headers" => "Origin, X-Requested-With, Content-Type, Accept, Authorization"
51
+ }
52
+ on_reactor_run
53
+ self
54
+ end
55
+
56
+ def start_server
57
+ Thin::Logging.silent = true
58
+ bind = @settings[:api][:bind] || "0.0.0.0"
59
+ @thin = Thin::Server.new(bind, @settings[:api][:port], self)
60
+ @thin.start
61
+ end
62
+
63
+ def stop_server(&callback)
64
+ @thin.stop
65
+ retry_until_true do
66
+ unless @thin.running?
67
+ callback.call
68
+ true
69
+ end
70
+ end
71
+ end
72
+
73
+ def start
74
+ start_server
75
+ super
76
+ end
77
+
78
+ def stop
79
+ @logger.warn("stopping")
80
+ stop_server do
81
+ @redis.close
82
+ @transport.close
83
+ super
84
+ end
85
+ end
86
+
87
+ def test(options={})
88
+ bootstrap(options)
89
+ start
90
+ end
91
+ end
92
+
93
+ configure do
94
+ disable :protection
95
+ disable :show_exceptions
96
+ end
97
+
98
+ not_found do
99
+ ""
100
+ end
101
+
102
+ error do
103
+ ""
104
+ end
105
+
106
+ helpers do
107
+ def request_log_line
108
+ settings.logger.info([env["REQUEST_METHOD"], env["REQUEST_PATH"]].join(" "), {
109
+ :remote_address => env["REMOTE_ADDR"],
110
+ :user_agent => env["HTTP_USER_AGENT"],
111
+ :request_method => env["REQUEST_METHOD"],
112
+ :request_uri => env["REQUEST_URI"],
113
+ :request_body => env["rack.input"].read
114
+ })
115
+ env["rack.input"].rewind
116
+ end
117
+
118
+ def protected!
119
+ if settings.api[:user] && settings.api[:password]
120
+ return if !(settings.api[:user] && settings.api[:password]) || authorized?
121
+ headers["WWW-Authenticate"] = 'Basic realm="Restricted Area"'
122
+ unauthorized!
123
+ end
124
+ end
125
+
126
+ def authorized?
127
+ @auth ||= Rack::Auth::Basic::Request.new(request.env)
128
+ @auth.provided? &&
129
+ @auth.basic? &&
130
+ @auth.credentials &&
131
+ @auth.credentials == [settings.api[:user], settings.api[:password]]
132
+ end
133
+
134
+ def bad_request!
135
+ ahalt 400
136
+ end
137
+
138
+ def unauthorized!
139
+ throw(:halt, [401, ""])
140
+ end
141
+
142
+ def not_found!
143
+ ahalt 404
144
+ end
145
+
146
+ def unavailable!
147
+ ahalt 503
148
+ end
149
+
150
+ def created!(response)
151
+ status 201
152
+ body response
153
+ end
154
+
155
+ def accepted!(response)
156
+ status 202
157
+ body response
158
+ end
159
+
160
+ def issued!
161
+ accepted!(MultiJson.dump(:issued => Time.now.to_i))
162
+ end
163
+
164
+ def no_content!
165
+ status 204
166
+ body ""
167
+ end
168
+
169
+ def read_data(rules={}, &callback)
170
+ begin
171
+ data = MultiJson.load(env["rack.input"].read)
172
+ valid = rules.all? do |key, rule|
173
+ data[key].is_a?(rule[:type]) || (rule[:nil_ok] && data[key].nil?)
174
+ end
175
+ if valid
176
+ callback.call(data)
177
+ else
178
+ bad_request!
179
+ end
180
+ rescue MultiJson::ParseError
181
+ bad_request!
182
+ end
183
+ end
184
+
185
+ def integer_parameter(parameter)
186
+ parameter =~ /^[0-9]+$/ ? parameter.to_i : nil
187
+ end
188
+
189
+ def pagination(items)
190
+ limit = integer_parameter(params[:limit])
191
+ offset = integer_parameter(params[:offset]) || 0
192
+ unless limit.nil?
193
+ headers["X-Pagination"] = MultiJson.dump(
194
+ :limit => limit,
195
+ :offset => offset,
196
+ :total => items.size
197
+ )
198
+ paginated = items.slice(offset, limit)
199
+ Array(paginated)
200
+ else
201
+ items
202
+ end
203
+ end
204
+
205
+ def transport_info(&callback)
206
+ info = {
207
+ :keepalives => {
208
+ :messages => nil,
209
+ :consumers => nil
210
+ },
211
+ :results => {
212
+ :messages => nil,
213
+ :consumers => nil
214
+ },
215
+ :connected => settings.transport.connected?
216
+ }
217
+ if settings.transport.connected?
218
+ settings.transport.stats("keepalives") do |stats|
219
+ info[:keepalives] = stats
220
+ settings.transport.stats("results") do |stats|
221
+ info[:results] = stats
222
+ callback.call(info)
223
+ end
224
+ end
225
+ else
226
+ callback.call(info)
227
+ end
228
+ end
229
+
230
+ def resolve_event(event_json)
231
+ event = MultiJson.load(event_json)
232
+ check = event[:check].merge(
233
+ :output => "Resolving on request of the API",
234
+ :status => 0,
235
+ :issued => Time.now.to_i,
236
+ :executed => Time.now.to_i,
237
+ :force_resolve => true
238
+ )
239
+ check.delete(:history)
240
+ payload = {
241
+ :client => event[:client][:name],
242
+ :check => check
243
+ }
244
+ settings.logger.info("publishing check result", :payload => payload)
245
+ settings.transport.publish(:direct, "results", MultiJson.dump(payload)) do |info|
246
+ if info[:error]
247
+ settings.logger.error("failed to publish check result", {
248
+ :payload => payload,
249
+ :error => info[:error].to_s
250
+ })
251
+ end
252
+ end
253
+ end
254
+ end
255
+
256
+ before do
257
+ request_log_line
258
+ content_type "application/json"
259
+ settings.cors.each do |header, value|
260
+ headers["Access-Control-Allow-#{header}"] = value
261
+ end
262
+ protected! unless env["REQUEST_METHOD"] == "OPTIONS"
263
+ end
264
+
265
+ aoptions "/*" do
266
+ body ""
267
+ end
268
+
269
+ aget "/info/?" do
270
+ transport_info do |info|
271
+ response = {
272
+ :sensu => {
273
+ :version => VERSION
274
+ },
275
+ :transport => info,
276
+ :redis => {
277
+ :connected => settings.redis.connected?
278
+ }
279
+ }
280
+ body MultiJson.dump(response)
281
+ end
282
+ end
283
+
284
+ aget "/health/?" do
285
+ if settings.redis.connected? && settings.transport.connected?
286
+ healthy = Array.new
287
+ min_consumers = integer_parameter(params[:consumers])
288
+ max_messages = integer_parameter(params[:messages])
289
+ transport_info do |info|
290
+ if min_consumers
291
+ healthy << (info[:keepalives][:consumers] >= min_consumers)
292
+ healthy << (info[:results][:consumers] >= min_consumers)
293
+ end
294
+ if max_messages
295
+ healthy << (info[:keepalives][:messages] <= max_messages)
296
+ healthy << (info[:results][:messages] <= max_messages)
297
+ end
298
+ healthy.all? ? no_content! : unavailable!
299
+ end
300
+ else
301
+ unavailable!
302
+ end
303
+ end
304
+
305
+ aget "/clients/?" do
306
+ response = Array.new
307
+ settings.redis.smembers("clients") do |clients|
308
+ clients = pagination(clients)
309
+ unless clients.empty?
310
+ clients.each_with_index do |client_name, index|
311
+ settings.redis.get("client:#{client_name}") do |client_json|
312
+ response << MultiJson.load(client_json)
313
+ if index == clients.size - 1
314
+ body MultiJson.dump(response)
315
+ end
316
+ end
317
+ end
318
+ else
319
+ body MultiJson.dump(response)
320
+ end
321
+ end
322
+ end
323
+
324
+ aget %r{/clients?/([\w\.-]+)/?$} do |client_name|
325
+ settings.redis.get("client:#{client_name}") do |client_json|
326
+ unless client_json.nil?
327
+ body client_json
328
+ else
329
+ not_found!
330
+ end
331
+ end
332
+ end
333
+
334
+ aget %r{/clients/([\w\.-]+)/history/?$} do |client_name|
335
+ response = Array.new
336
+ settings.redis.smembers("history:#{client_name}") do |checks|
337
+ unless checks.empty?
338
+ checks.each_with_index do |check_name, index|
339
+ history_key = "history:#{client_name}:#{check_name}"
340
+ settings.redis.lrange(history_key, -21, -1) do |history|
341
+ history.map! do |status|
342
+ status.to_i
343
+ end
344
+ execution_key = "execution:#{client_name}:#{check_name}"
345
+ settings.redis.get(execution_key) do |last_execution|
346
+ unless history.empty? || last_execution.nil?
347
+ item = {
348
+ :check => check_name,
349
+ :history => history,
350
+ :last_execution => last_execution.to_i,
351
+ :last_status => history.last
352
+ }
353
+ response << item
354
+ end
355
+ if index == checks.size - 1
356
+ body MultiJson.dump(response)
357
+ end
358
+ end
359
+ end
360
+ end
361
+ else
362
+ body MultiJson.dump(response)
363
+ end
364
+ end
365
+ end
366
+
367
+ adelete %r{/clients?/([\w\.-]+)/?$} do |client_name|
368
+ settings.redis.get("client:#{client_name}") do |client_json|
369
+ unless client_json.nil?
370
+ settings.redis.hgetall("events:#{client_name}") do |events|
371
+ events.each do |check_name, event_json|
372
+ resolve_event(event_json)
373
+ end
374
+ EM::Timer.new(5) do
375
+ client = MultiJson.load(client_json)
376
+ settings.logger.info("deleting client", {
377
+ :client => client
378
+ })
379
+ settings.redis.srem("clients", client_name) do
380
+ settings.redis.del("client:#{client_name}")
381
+ settings.redis.del("events:#{client_name}")
382
+ settings.redis.smembers("history:#{client_name}") do |checks|
383
+ checks.each do |check_name|
384
+ settings.redis.del("history:#{client_name}:#{check_name}")
385
+ settings.redis.del("execution:#{client_name}:#{check_name}")
386
+ end
387
+ settings.redis.del("history:#{client_name}")
388
+ end
389
+ end
390
+ end
391
+ issued!
392
+ end
393
+ else
394
+ not_found!
395
+ end
396
+ end
397
+ end
398
+
399
+ aget "/checks/?" do
400
+ body MultiJson.dump(settings.all_checks)
401
+ end
402
+
403
+ aget %r{/checks?/([\w\.-]+)/?$} do |check_name|
404
+ if settings.checks[check_name]
405
+ response = settings.checks[check_name].merge(:name => check_name)
406
+ body MultiJson.dump(response)
407
+ else
408
+ not_found!
409
+ end
410
+ end
411
+
412
+ apost "/request/?" do
413
+ rules = {
414
+ :check => {:type => String, :nil_ok => false},
415
+ :subscribers => {:type => Array, :nil_ok => true}
416
+ }
417
+ read_data(rules) do |data|
418
+ if settings.checks[data[:check]]
419
+ check = settings.checks[data[:check]]
420
+ subscribers = data[:subscribers] || check[:subscribers] || Array.new
421
+ payload = {
422
+ :name => data[:check],
423
+ :command => check[:command],
424
+ :issued => Time.now.to_i
425
+ }
426
+ settings.logger.info("publishing check request", {
427
+ :payload => payload,
428
+ :subscribers => subscribers
429
+ })
430
+ subscribers.uniq.each do |exchange_name|
431
+ settings.transport.publish(:fanout, exchange_name, MultiJson.dump(payload)) do |info|
432
+ if info[:error]
433
+ settings.logger.error("failed to publish check request", {
434
+ :exchange_name => exchange_name,
435
+ :payload => payload,
436
+ :error => info[:error].to_s
437
+ })
438
+ end
439
+ end
440
+ end
441
+ issued!
442
+ else
443
+ not_found!
444
+ end
445
+ end
446
+ end
447
+
448
+ aget "/events/?" do
449
+ response = Array.new
450
+ settings.redis.smembers("clients") do |clients|
451
+ unless clients.empty?
452
+ clients.each_with_index do |client_name, index|
453
+ settings.redis.hgetall("events:#{client_name}") do |events|
454
+ events.each do |check_name, event_json|
455
+ response << MultiJson.load(event_json)
456
+ end
457
+ if index == clients.size - 1
458
+ body MultiJson.dump(response)
459
+ end
460
+ end
461
+ end
462
+ else
463
+ body MultiJson.dump(response)
464
+ end
465
+ end
466
+ end
467
+
468
+ aget %r{/events/([\w\.-]+)/?$} do |client_name|
469
+ response = Array.new
470
+ settings.redis.hgetall("events:#{client_name}") do |events|
471
+ events.each do |check_name, event_json|
472
+ response << MultiJson.load(event_json)
473
+ end
474
+ body MultiJson.dump(response)
475
+ end
476
+ end
477
+
478
+ aget %r{/events?/([\w\.-]+)/([\w\.-]+)/?$} do |client_name, check_name|
479
+ settings.redis.hgetall("events:#{client_name}") do |events|
480
+ event_json = events[check_name]
481
+ unless event_json.nil?
482
+ body event_json
483
+ else
484
+ not_found!
485
+ end
486
+ end
487
+ end
488
+
489
+ adelete %r{/events?/([\w\.-]+)/([\w\.-]+)/?$} do |client_name, check_name|
490
+ settings.redis.hgetall("events:#{client_name}") do |events|
491
+ if events.include?(check_name)
492
+ resolve_event(events[check_name])
493
+ issued!
494
+ else
495
+ not_found!
496
+ end
497
+ end
498
+ end
499
+
500
+ apost "/resolve/?" do
501
+ rules = {
502
+ :client => {:type => String, :nil_ok => false},
503
+ :check => {:type => String, :nil_ok => false}
504
+ }
505
+ read_data(rules) do |data|
506
+ settings.redis.hgetall("events:#{data[:client]}") do |events|
507
+ if events.include?(data[:check])
508
+ resolve_event(events[data[:check]])
509
+ issued!
510
+ else
511
+ not_found!
512
+ end
513
+ end
514
+ end
515
+ end
516
+
517
+ aget "/aggregates/?" do
518
+ response = Array.new
519
+ settings.redis.smembers("aggregates") do |checks|
520
+ unless checks.empty?
521
+ checks.each_with_index do |check_name, index|
522
+ settings.redis.smembers("aggregates:#{check_name}") do |aggregates|
523
+ aggregates.reverse!
524
+ aggregates.map! do |issued|
525
+ issued.to_i
526
+ end
527
+ item = {
528
+ :check => check_name,
529
+ :issued => aggregates
530
+ }
531
+ response << item
532
+ if index == checks.size - 1
533
+ body MultiJson.dump(response)
534
+ end
535
+ end
536
+ end
537
+ else
538
+ body MultiJson.dump(response)
539
+ end
540
+ end
541
+ end
542
+
543
+ aget %r{/aggregates/([\w\.-]+)/?$} do |check_name|
544
+ settings.redis.smembers("aggregates:#{check_name}") do |aggregates|
545
+ unless aggregates.empty?
546
+ aggregates.reverse!
547
+ aggregates.map! do |issued|
548
+ issued.to_i
549
+ end
550
+ age = integer_parameter(params[:age])
551
+ if age
552
+ timestamp = Time.now.to_i - age
553
+ aggregates.reject! do |issued|
554
+ issued > timestamp
555
+ end
556
+ end
557
+ body MultiJson.dump(pagination(aggregates))
558
+ else
559
+ not_found!
560
+ end
561
+ end
562
+ end
563
+
564
+ adelete %r{/aggregates/([\w\.-]+)/?$} do |check_name|
565
+ settings.redis.smembers("aggregates:#{check_name}") do |aggregates|
566
+ unless aggregates.empty?
567
+ aggregates.each do |check_issued|
568
+ result_set = "#{check_name}:#{check_issued}"
569
+ settings.redis.del("aggregation:#{result_set}")
570
+ settings.redis.del("aggregate:#{result_set}")
571
+ end
572
+ settings.redis.del("aggregates:#{check_name}") do
573
+ settings.redis.srem("aggregates", check_name) do
574
+ no_content!
575
+ end
576
+ end
577
+ else
578
+ not_found!
579
+ end
580
+ end
581
+ end
582
+
583
+ aget %r{/aggregates?/([\w\.-]+)/([\w\.-]+)/?$} do |check_name, check_issued|
584
+ result_set = "#{check_name}:#{check_issued}"
585
+ settings.redis.hgetall("aggregate:#{result_set}") do |aggregate|
586
+ unless aggregate.empty?
587
+ response = aggregate.inject(Hash.new) do |totals, (status, count)|
588
+ totals[status] = Integer(count)
589
+ totals
590
+ end
591
+ settings.redis.hgetall("aggregation:#{result_set}") do |results|
592
+ parsed_results = results.inject(Array.new) do |parsed, (client_name, check_json)|
593
+ check = MultiJson.load(check_json)
594
+ parsed << check.merge(:client => client_name)
595
+ end
596
+ if params[:summarize]
597
+ options = params[:summarize].split(",")
598
+ if options.include?("output")
599
+ outputs = Hash.new(0)
600
+ parsed_results.each do |result|
601
+ outputs[result[:output]] += 1
602
+ end
603
+ response[:outputs] = outputs
604
+ end
605
+ end
606
+ if params[:results]
607
+ response[:results] = parsed_results
608
+ end
609
+ body MultiJson.dump(response)
610
+ end
611
+ else
612
+ not_found!
613
+ end
614
+ end
615
+ end
616
+
617
+ apost %r{/stash(?:es)?/(.*)/?} do |path|
618
+ read_data do |data|
619
+ settings.redis.set("stash:#{path}", MultiJson.dump(data)) do
620
+ settings.redis.sadd("stashes", path) do
621
+ created!(MultiJson.dump(:path => path))
622
+ end
623
+ end
624
+ end
625
+ end
626
+
627
+ aget %r{/stash(?:es)?/(.*)/?} do |path|
628
+ settings.redis.get("stash:#{path}") do |stash_json|
629
+ unless stash_json.nil?
630
+ body stash_json
631
+ else
632
+ not_found!
633
+ end
634
+ end
635
+ end
636
+
637
+ adelete %r{/stash(?:es)?/(.*)/?} do |path|
638
+ settings.redis.exists("stash:#{path}") do |stash_exists|
639
+ if stash_exists
640
+ settings.redis.srem("stashes", path) do
641
+ settings.redis.del("stash:#{path}") do
642
+ no_content!
643
+ end
644
+ end
645
+ else
646
+ not_found!
647
+ end
648
+ end
649
+ end
650
+
651
+ aget "/stashes/?" do
652
+ response = Array.new
653
+ settings.redis.smembers("stashes") do |stashes|
654
+ unless stashes.empty?
655
+ stashes.each_with_index do |path, index|
656
+ settings.redis.get("stash:#{path}") do |stash_json|
657
+ settings.redis.ttl("stash:#{path}") do |ttl|
658
+ unless stash_json.nil?
659
+ item = {
660
+ :path => path,
661
+ :content => MultiJson.load(stash_json),
662
+ :expire => ttl
663
+ }
664
+ response << item
665
+ else
666
+ settings.redis.srem("stashes", path)
667
+ end
668
+ if index == stashes.size - 1
669
+ body MultiJson.dump(pagination(response))
670
+ end
671
+ end
672
+ end
673
+ end
674
+ else
675
+ body MultiJson.dump(response)
676
+ end
677
+ end
678
+ end
679
+
680
+ apost "/stashes/?" do
681
+ rules = {
682
+ :path => {:type => String, :nil_ok => false},
683
+ :content => {:type => Hash, :nil_ok => false},
684
+ :expire => {:type => Integer, :nil_ok => true}
685
+ }
686
+ read_data(rules) do |data|
687
+ stash_key = "stash:#{data[:path]}"
688
+ settings.redis.set(stash_key, MultiJson.dump(data[:content])) do
689
+ settings.redis.sadd("stashes", data[:path]) do
690
+ response = MultiJson.dump(:path => data[:path])
691
+ if data[:expire]
692
+ settings.redis.expire(stash_key, data[:expire]) do
693
+ created!(response)
694
+ end
695
+ else
696
+ created!(response)
697
+ end
698
+ end
699
+ end
700
+ end
701
+ end
702
+ end
703
+ end
704
+ end