sensu 0.17.0.beta → 0.17.0.beta.1

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