sensu 0.23.3 → 0.24.0.beta

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: 2a163224e80319ffc8f609c822c4243991a86585
4
- data.tar.gz: f5f24edd6a1f730516ba87a3911b8da6fc0c2a31
3
+ metadata.gz: cf29524e586ad648f83f891749f04ed9bb41d1f5
4
+ data.tar.gz: 651104b216bbc218bebfc51ec19e46563a32c093
5
5
  SHA512:
6
- metadata.gz: 99e3e414e8b34e1b271c846c484c28433eedbc9aad76830c11eed1df07595d3715704216475edf3307d87f1b30105271e74a51d45671027f23efdf6e9d0f2457
7
- data.tar.gz: b48247feeedbcc6cfd60e9fd719484ef2f08bd0bf7ad79e4533d08e634722f2d159ebc1558fae477f3c13ee238c145da9115068ebf1fff79d75d2ac25fba06b9
6
+ metadata.gz: ee318b81fcda3b1b714052000cad8c7fede926803f31f96dd52f33a461e42b4b840addce01b950139feca58f6583cc65ee9dd1e3e42dea257a94182ec0df2f50
7
+ data.tar.gz: cfeee1f970ae412bd090004b6f977026382f7806c2e520363c11731885fd3a8f218c2aa4c19214d4035fec22fd6b54de6c97d0f6f9c4eaaafe6c2e0cd99b29a2
data/CHANGELOG.md CHANGED
@@ -1,3 +1,61 @@
1
+ ## 0.24.0 - TBD
2
+
3
+ ### Important
4
+
5
+ Sensu check ["Aggregates 2.0"](https://github.com/sensu/sensu/issues/1218)
6
+ breaks the existing Sensu API aggregate endpoints.
7
+
8
+ Sensu API GET /health endpoint, failed health checks now respond with a
9
+ `412` (preconditions failed) instead of a `503`.
10
+
11
+ ### Features
12
+
13
+ Persistent Sensu event IDs, event occurrences for a client/check pair will
14
+ now have the same event ID until the event is resolved.
15
+
16
+ Added a CLI option/argument to cause the Sensu service to validate its
17
+ compiled configuration settings and exit with the appropriate exit
18
+ code, e.g. `2` for invalid. The CLI option is `--validate_config`. This
19
+ feature is now used when restarting a Sensu service to first validate the
20
+ new configuration before stopping the running service.
21
+
22
+ Improved tracking of in progress check result processing, no longer
23
+ potentially losing check results when restarting the Sensu server service.
24
+
25
+ Event data check type now explicitly defaults to "standard".
26
+
27
+ Check results for proxy clients (a.k.a JIT clients) will now have a check
28
+ "origin" set to the client name of the result producer.
29
+
30
+ Configurable Sensu Spawn concurrent child process limit (checks, mutators,
31
+ & pipe handlers). The default limit is still `12` and the EventMachine
32
+ threadpool size is automatically adjusted to accommodate a larger limit.
33
+
34
+ Sensu check ["Aggregates 2.0"](https://github.com/sensu/sensu/issues/1218).
35
+
36
+ Sensu client token substitution is now supported in every check definition
37
+ attribute value, no longer just the check command.
38
+
39
+ ### Other
40
+
41
+ Sensu API GET /health endpoint, failed health check now responds with a
42
+ `412` (preconditions failed) instead of a `503`.
43
+
44
+ Sensu API POST /clients endpoint can now create clients in the registry
45
+ that are expected to produce keepalives, and validates clients with the
46
+ Sensu Settings client definition validator.
47
+
48
+ Updated Thin (used by Sensu API) to the latest version, 1.6.4.
49
+
50
+ JrJackson is now used to parse JSON when Sensu is running on JRuby.
51
+
52
+ The Sensu API now listens immediately on service start, even before it has
53
+ successfully connected to Redis and the Sensu Transport. It will now
54
+ respond with a `500` response, with a descriptive error message, when it
55
+ has not yet initialized its connections or it is reconnecting to either
56
+ Redis or the Sensu Transport. The API /info and /health endpoints will
57
+ still respond normally while reconnecting.
58
+
1
59
  ## 0.23.3 - 2016-05-26
2
60
 
3
61
  ### Fixes
@@ -1,10 +1,11 @@
1
1
  require "sensu/daemon"
2
+ require "sensu/api/validators"
2
3
 
3
4
  gem "sinatra", "1.4.6"
4
5
  gem "async_sinatra", "1.2.0"
5
6
 
6
7
  unless RUBY_PLATFORM =~ /java/
7
- gem "thin", "1.6.3"
8
+ gem "thin", "1.6.4"
8
9
  require "thin"
9
10
  end
10
11
 
@@ -22,10 +23,9 @@ module Sensu
22
23
  bootstrap(options)
23
24
  setup_process(options)
24
25
  EM::run do
25
- setup_connections do
26
- start
27
- setup_signal_traps
28
- end
26
+ setup_connections
27
+ start
28
+ setup_signal_traps
29
29
  end
30
30
  end
31
31
 
@@ -84,8 +84,8 @@ module Sensu
84
84
  def stop
85
85
  @logger.warn("stopping")
86
86
  stop_server do
87
- settings.redis.close if settings.redis
88
- settings.transport.close if settings.transport
87
+ settings.redis.close if settings.respond_to?(:redis)
88
+ settings.transport.close if settings.respond_to?(:transport)
89
89
  super
90
90
  end
91
91
  end
@@ -124,11 +124,18 @@ module Sensu
124
124
  env["rack.input"].rewind
125
125
  end
126
126
 
127
- def protected!
128
- if settings.api[:user] && settings.api[:password]
129
- return if !(settings.api[:user] && settings.api[:password]) || authorized?
130
- headers["WWW-Authenticate"] = 'Basic realm="Restricted Area"'
131
- unauthorized!
127
+ def connected?
128
+ if settings.respond_to?(:redis) && settings.respond_to?(:transport)
129
+ unless ["/info", "/health"].include?(env["REQUEST_PATH"])
130
+ unless settings.redis.connected?
131
+ not_connected!("not connected to redis")
132
+ end
133
+ unless settings.transport.connected?
134
+ not_connected!("not connected to transport")
135
+ end
136
+ end
137
+ else
138
+ not_connected!("redis and transport connections not initialized")
132
139
  end
133
140
  end
134
141
 
@@ -140,6 +147,22 @@ module Sensu
140
147
  @auth.credentials == [settings.api[:user], settings.api[:password]]
141
148
  end
142
149
 
150
+ def protected!
151
+ if settings.api[:user] && settings.api[:password]
152
+ return if authorized?
153
+ headers["WWW-Authenticate"] = 'Basic realm="Restricted Area"'
154
+ unauthorized!
155
+ end
156
+ end
157
+
158
+ def error!(body="")
159
+ throw(:halt, [500, body])
160
+ end
161
+
162
+ def not_connected!(message)
163
+ error!(Sensu::JSON.dump(:error => message))
164
+ end
165
+
143
166
  def bad_request!
144
167
  ahalt 400
145
168
  end
@@ -152,8 +175,8 @@ module Sensu
152
175
  ahalt 404
153
176
  end
154
177
 
155
- def unavailable!
156
- ahalt 503
178
+ def precondition_failed!
179
+ ahalt 412
157
180
  end
158
181
 
159
182
  def created!(response)
@@ -306,6 +329,7 @@ module Sensu
306
329
  settings.cors.each do |header, value|
307
330
  headers["Access-Control-Allow-#{header}"] = value
308
331
  end
332
+ connected?
309
333
  protected! unless env["REQUEST_METHOD"] == "OPTIONS"
310
334
  end
311
335
 
@@ -342,27 +366,27 @@ module Sensu
342
366
  healthy << (info[:keepalives][:messages] <= max_messages)
343
367
  healthy << (info[:results][:messages] <= max_messages)
344
368
  end
345
- healthy.all? ? no_content! : unavailable!
369
+ healthy.all? ? no_content! : precondition_failed!
346
370
  end
347
371
  else
348
- unavailable!
372
+ precondition_failed!
349
373
  end
350
374
  end
351
375
 
352
376
  apost "/clients/?" do
353
- rules = {
354
- :name => {:type => String, :nil_ok => false, :regex => /\A[\w\.-]+\z/},
355
- :address => {:type => String, :nil_ok => false},
356
- :subscriptions => {:type => Array, :nil_ok => false}
357
- }
358
- read_data(rules) do |data|
359
- data[:keepalives] = false
377
+ read_data do |data|
378
+ data[:keepalives] = data.fetch(:keepalives, false)
360
379
  data[:version] = VERSION
361
380
  data[:timestamp] = Time.now.to_i
362
- settings.redis.set("client:#{data[:name]}", Sensu::JSON.dump(data)) do
363
- settings.redis.sadd("clients", data[:name]) do
364
- created!(Sensu::JSON.dump(:name => data[:name]))
381
+ validator = Validators::Client.new
382
+ if validator.valid?(data)
383
+ settings.redis.set("client:#{data[:name]}", Sensu::JSON.dump(data)) do
384
+ settings.redis.sadd("clients", data[:name]) do
385
+ created!(Sensu::JSON.dump(:name => data[:name]))
386
+ end
365
387
  end
388
+ else
389
+ bad_request!
366
390
  end
367
391
  end
368
392
  end
@@ -582,102 +606,172 @@ module Sensu
582
606
  end
583
607
 
584
608
  aget "/aggregates/?" do
585
- response = Array.new
586
- settings.redis.smembers("aggregates") do |checks|
587
- unless checks.empty?
588
- checks.each_with_index do |check_name, index|
589
- settings.redis.smembers("aggregates:#{check_name}") do |aggregates|
590
- aggregates.reverse!
591
- aggregates.map! do |issued|
592
- issued.to_i
609
+ settings.redis.smembers("aggregates") do |aggregates|
610
+ aggregates.map! do |aggregate|
611
+ {:name => aggregate}
612
+ end
613
+ body Sensu::JSON.dump(aggregates)
614
+ end
615
+ end
616
+
617
+ aget %r{^/aggregates/([\w\.-]+)/?$} do |aggregate|
618
+ response = {
619
+ :clients => 0,
620
+ :checks => 0,
621
+ :results => {
622
+ :ok => 0,
623
+ :warning => 0,
624
+ :critical => 0,
625
+ :unknown => 0,
626
+ :total => 0,
627
+ :stale => 0
628
+ }
629
+ }
630
+ settings.redis.smembers("aggregates:#{aggregate}") do |aggregate_members|
631
+ unless aggregate_members.empty?
632
+ clients = []
633
+ checks = []
634
+ results = []
635
+ aggregate_members.each_with_index do |member, index|
636
+ client_name, check_name = member.split(":")
637
+ clients << client_name
638
+ checks << check_name
639
+ result_key = "result:#{client_name}:#{check_name}"
640
+ settings.redis.get(result_key) do |result_json|
641
+ unless result_json.nil?
642
+ results << Sensu::JSON.load(result_json)
643
+ else
644
+ settings.redis.srem("aggregates:#{aggregate}", member)
593
645
  end
594
- item = {
595
- :check => check_name,
596
- :issued => aggregates
597
- }
598
- response << item
599
- if index == checks.length - 1
646
+ if index == aggregate_members.length - 1
647
+ response[:clients] = clients.uniq.length
648
+ response[:checks] = checks.uniq.length
649
+ max_age = integer_parameter(params[:max_age])
650
+ if max_age
651
+ result_count = results.length
652
+ timestamp = Time.now.to_i - max_age
653
+ results.reject! do |result|
654
+ result[:executed] < timestamp
655
+ end
656
+ response[:results][:stale] = result_count - results.length
657
+ end
658
+ response[:results][:total] = results.length
659
+ results.each do |result|
660
+ severity = (SEVERITIES[result[:status]] || "unknown")
661
+ response[:results][severity.to_sym] += 1
662
+ end
600
663
  body Sensu::JSON.dump(response)
601
664
  end
602
665
  end
603
666
  end
604
667
  else
605
- body Sensu::JSON.dump(response)
668
+ not_found!
606
669
  end
607
670
  end
608
671
  end
609
672
 
610
- aget %r{^/aggregates/([\w\.-]+)/?$} do |check_name|
611
- settings.redis.smembers("aggregates:#{check_name}") do |aggregates|
612
- unless aggregates.empty?
613
- aggregates.reverse!
614
- aggregates.map! do |issued|
615
- issued.to_i
616
- end
617
- age = integer_parameter(params[:age])
618
- if age
619
- timestamp = Time.now.to_i - age
620
- aggregates.reject! do |issued|
621
- issued > timestamp
673
+ adelete %r{^/aggregates/([\w\.-]+)/?$} do |aggregate|
674
+ settings.redis.smembers("aggregates") do |aggregates|
675
+ if aggregates.include?(aggregate)
676
+ settings.redis.srem("aggregates", aggregate) do
677
+ settings.redis.del("aggregates:#{aggregate}") do
678
+ no_content!
622
679
  end
623
680
  end
624
- body Sensu::JSON.dump(pagination(aggregates))
625
681
  else
626
682
  not_found!
627
683
  end
628
684
  end
629
685
  end
630
686
 
631
- adelete %r{^/aggregates/([\w\.-]+)/?$} do |check_name|
632
- settings.redis.smembers("aggregates:#{check_name}") do |aggregates|
633
- unless aggregates.empty?
634
- aggregates.each do |check_issued|
635
- result_set = "#{check_name}:#{check_issued}"
636
- settings.redis.del("aggregation:#{result_set}")
637
- settings.redis.del("aggregate:#{result_set}")
687
+ aget %r{^/aggregates/([\w\.-]+)/clients/?$} do |aggregate|
688
+ response = Array.new
689
+ settings.redis.smembers("aggregates:#{aggregate}") do |aggregate_members|
690
+ unless aggregate_members.empty?
691
+ clients = Hash.new
692
+ aggregate_members.each do |member|
693
+ client_name, check_name = member.split(":")
694
+ clients[client_name] ||= []
695
+ clients[client_name] << check_name
638
696
  end
639
- settings.redis.del("aggregates:#{check_name}") do
640
- settings.redis.srem("aggregates", check_name) do
641
- no_content!
642
- end
697
+ clients.each do |client_name, checks|
698
+ response << {
699
+ :name => client_name,
700
+ :checks => checks
701
+ }
643
702
  end
703
+ body Sensu::JSON.dump(response)
644
704
  else
645
705
  not_found!
646
706
  end
647
707
  end
648
708
  end
649
709
 
650
- aget %r{^/aggregates?/([\w\.-]+)/([\w\.-]+)/?$} do |check_name, check_issued|
651
- result_set = "#{check_name}:#{check_issued}"
652
- settings.redis.hgetall("aggregate:#{result_set}") do |aggregate|
653
- unless aggregate.empty?
654
- response = aggregate.inject(Hash.new) do |totals, (status, count)|
655
- totals[status] = Integer(count)
656
- totals
710
+ aget %r{^/aggregates/([\w\.-]+)/checks/?$} do |aggregate|
711
+ response = Array.new
712
+ settings.redis.smembers("aggregates:#{aggregate}") do |aggregate_members|
713
+ unless aggregate_members.empty?
714
+ checks = Hash.new
715
+ aggregate_members.each do |member|
716
+ client_name, check_name = member.split(":")
717
+ checks[check_name] ||= []
718
+ checks[check_name] << client_name
657
719
  end
658
- settings.redis.hgetall("aggregation:#{result_set}") do |results|
659
- parsed_results = results.inject(Array.new) do |parsed, (client_name, check_json)|
660
- check = Sensu::JSON.load(check_json)
661
- parsed << check.merge(:client => client_name)
662
- end
663
- if params[:summarize]
664
- options = params[:summarize].split(",")
665
- if options.include?("output")
666
- outputs = Hash.new(0)
667
- parsed_results.each do |result|
668
- outputs[result[:output]] += 1
720
+ checks.each do |check_name, clients|
721
+ response << {
722
+ :name => check_name,
723
+ :clients => clients
724
+ }
725
+ end
726
+ body Sensu::JSON.dump(response)
727
+ else
728
+ not_found!
729
+ end
730
+ end
731
+ end
732
+
733
+ aget %r{^/aggregates/([\w\.-]+)/results/([\w\.-]+)/?$} do |aggregate, severity|
734
+ response = Array.new
735
+ if SEVERITIES.include?(severity)
736
+ settings.redis.smembers("aggregates:#{aggregate}") do |aggregate_members|
737
+ unless aggregate_members.empty?
738
+ summaries = Hash.new
739
+ max_age = integer_parameter(params[:max_age])
740
+ current_timestamp = Time.now.to_i
741
+ aggregate_members.each_with_index do |member, index|
742
+ client_name, check_name = member.split(":")
743
+ result_key = "result:#{client_name}:#{check_name}"
744
+ settings.redis.get(result_key) do |result_json|
745
+ unless result_json.nil?
746
+ result = Sensu::JSON.load(result_json)
747
+ if SEVERITIES[result[:status]] == severity &&
748
+ (max_age.nil? || result[:executed] >= (current_timestamp - max_age))
749
+ summaries[check_name] ||= {}
750
+ summaries[check_name][result[:output]] ||= {:total => 0, :clients => []}
751
+ summaries[check_name][result[:output]][:total] += 1
752
+ summaries[check_name][result[:output]][:clients] << client_name
753
+ end
754
+ end
755
+ if index == aggregate_members.length - 1
756
+ summaries.each do |check_name, outputs|
757
+ summary = outputs.map do |output, output_summary|
758
+ {:output => output}.merge(output_summary)
759
+ end
760
+ response << {
761
+ :check => check_name,
762
+ :summary => summary
763
+ }
764
+ end
765
+ body Sensu::JSON.dump(response)
669
766
  end
670
- response[:outputs] = outputs
671
767
  end
672
768
  end
673
- if params[:results]
674
- response[:results] = parsed_results
675
- end
676
- body Sensu::JSON.dump(response)
769
+ else
770
+ not_found!
677
771
  end
678
- else
679
- not_found!
680
772
  end
773
+ else
774
+ bad_request!
681
775
  end
682
776
  end
683
777
 
@@ -0,0 +1,37 @@
1
+ require "sensu/settings/rules"
2
+ require "sensu/settings/validators/client"
3
+
4
+ module Sensu
5
+ module API
6
+ module Validators
7
+ # The error class for validation.
8
+ class Invalid < RuntimeError; end
9
+
10
+ class Client
11
+ # Include Sensu Settings rules and client validator.
12
+ include Sensu::Settings::Rules
13
+ include Sensu::Settings::Validators::Client
14
+
15
+ # Determine if a client definition is valid.
16
+ #
17
+ # @param client [Hash]
18
+ # @return [TrueClass, FalseClass]
19
+ def valid?(client)
20
+ validate_client(client)
21
+ true
22
+ rescue Invalid
23
+ false
24
+ end
25
+
26
+ private
27
+
28
+ # This method is called when `validate_client()` encounters an
29
+ # invalid definition object. This method raises an exception
30
+ # to be caught by `valid?()`.
31
+ def invalid(*arguments)
32
+ raise Invalid
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
data/lib/sensu/cli.rb CHANGED
@@ -26,6 +26,9 @@ module Sensu
26
26
  opts.on("-d", "--config_dir DIR[,DIR]", "DIR or comma-delimited DIR list for Sensu JSON config files") do |dir|
27
27
  options[:config_dirs] = dir.split(",")
28
28
  end
29
+ opts.on("--validate_config", "Validate the compiled configuration and exit") do
30
+ options[:validate_config] = true
31
+ end
29
32
  opts.on("-P", "--print_config", "Print the compiled configuration and exit") do
30
33
  options[:print_config] = true
31
34
  end
@@ -99,12 +99,40 @@ module Sensu
99
99
  end
100
100
  end
101
101
 
102
+ # Perform token substitution for an object. String values are
103
+ # passed to `substitute_tokens()`, arrays and sub-hashes are
104
+ # processed recursively. Numeric values are ignored.
105
+ #
106
+ # @param object [Object]
107
+ # @return [Array] containing the updated object with substituted
108
+ # values and an array of unmatched tokens.
109
+ def object_substitute_tokens(object)
110
+ unmatched_tokens = []
111
+ case object
112
+ when Hash
113
+ object.each do |key, value|
114
+ object[key], unmatched = object_substitute_tokens(value)
115
+ unmatched_tokens.push(*unmatched)
116
+ end
117
+ when Array
118
+ object.map! do |value|
119
+ value, unmatched = object_substitute_tokens(value)
120
+ unmatched_tokens.push(*unmatched)
121
+ value
122
+ end
123
+ when String
124
+ object, unmatched_tokens = substitute_tokens(object, @settings[:client])
125
+ end
126
+ [object, unmatched_tokens.uniq]
127
+ end
128
+
102
129
  # Execute a check command, capturing its output (STDOUT/ERR),
103
130
  # exit status code, execution duration, timestamp, and publish
104
131
  # the result. This method guards against multiple executions for
105
- # the same check. Check command tokens are substituted with the
106
- # associated client attribute values. If there are unmatched
107
- # check command tokens, the check command will not be executed,
132
+ # the same check. Check attribute value tokens are substituted
133
+ # with the associated client attribute values, via
134
+ # `object_substitute_tokens()`. If there are unmatched check
135
+ # attribute value tokens, the check will not be executed,
108
136
  # instead a check result will be published reporting the
109
137
  # unmatched tokens.
110
138
  #
@@ -113,11 +141,11 @@ module Sensu
113
141
  @logger.debug("attempting to execute check command", :check => check)
114
142
  unless @checks_in_progress.include?(check[:name])
115
143
  @checks_in_progress << check[:name]
116
- command, unmatched_tokens = substitute_tokens(check[:command], @settings[:client])
144
+ check, unmatched_tokens = object_substitute_tokens(check)
117
145
  if unmatched_tokens.empty?
118
- check[:executed] = Time.now.to_i
119
146
  started = Time.now.to_f
120
- Spawn.process(command, :timeout => check[:timeout]) do |output, status|
147
+ check[:executed] = started.to_i
148
+ Spawn.process(check[:command], :timeout => check[:timeout]) do |output, status|
121
149
  check[:duration] = ("%.3f" % (Time.now.to_f - started)).to_f
122
150
  check[:output] = output
123
151
  check[:status] = status
@@ -1,7 +1,7 @@
1
1
  module Sensu
2
2
  unless defined?(Sensu::VERSION)
3
3
  # Sensu release version.
4
- VERSION = "0.23.3".freeze
4
+ VERSION = "0.24.0.beta".freeze
5
5
 
6
6
  # Sensu check severities.
7
7
  SEVERITIES = %w[ok warning critical unknown].freeze