sensu 0.27.1 → 0.28.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: 848a4443459327c245760df5a5845eb525c0f764
4
- data.tar.gz: 747c00aa13b824942c83c1d9970ffdc2d922aae3
3
+ metadata.gz: 36cc218210482434f3c8fd4e27e662d109f336f6
4
+ data.tar.gz: 25fd707fa69a6228cd9979e80d726facb8d4325b
5
5
  SHA512:
6
- metadata.gz: 15d626cff0c140b0cad61db37d810c7d4a535c85e938c73b0e12baf31c01bb7850246867c5886e63099308f776754962f6ba03843a6aae19c8580197dc1e4c96
7
- data.tar.gz: da0a760f6bb3ebf0ba1832387e6ff2ea84202692f10dab35e03ce06fbdcddf31bd8a8e5230b22b93f9b002303f5b16589f8a3de5ed5aa85050ed5bb01cccb70c
6
+ metadata.gz: d16c1695e58f06722efb401f28759ce0b187bd66893e371eaf9e6e6f3df1d4d55b2c1c943de0e53334f09e8a678677b8ec7b5d94ba083bd3c288c7c46cd68dc3
7
+ data.tar.gz: fe9d61057a7d5914b8c8dd901d0bb4630783fcf21b0bd48befa1f07abe8b1e38bc1c23672321baa870bdd46260968c798104970bbd7eb0b45236be5d29bfb947
data/CHANGELOG.md CHANGED
@@ -1,3 +1,40 @@
1
+ ## 0.28.0 - 2017-02-23
2
+
3
+ ### Features
4
+
5
+ Added proxy check requests to improve Sensu's ability to monitor external
6
+ resources that have an associated Sensu proxy client. Publish a check
7
+ request to the configured `subscribers` (e.g.
8
+ `["round-robin:snmp_pollers"]`) for every Sensu client in the registry
9
+ that matches the configured client attributes in `client_attributes` on
10
+ the configured `interval` (e.g. `60`). Client tokens in the check
11
+ definition (e.g. `"check-snmp-if.rb -h :::address::: -i eth0"`) are
12
+ substituted prior to publishing the check request. The check request check
13
+ `source` is set to the client `name`.
14
+
15
+ Schedule check requests and standalone executions with the Cron syntax.
16
+
17
+ Added the Sensu server registry, containing information about the running
18
+ Sensu servers. Information about the Sensu servers is now accessible via
19
+ the Sensu API `/info` endpoint.
20
+
21
+ Added two optional attributes to Sensu API POST `/request`, `"reason"` and
22
+ `"creator"`, for additional context. The check request reason and creator
23
+ are added to the check request payload under `"api_requested"` and become
24
+ part of the check result.
25
+
26
+ Added event IDs to event handler log events for additional context, making
27
+ it easier to trace an event through the Sensu pipeline.
28
+
29
+ ### Fixes
30
+
31
+ The Sensu interval timers, used for scheduling tasks, now account for
32
+ drift. The check request and standalone execution scheduler timers are now
33
+ more accurate.
34
+
35
+ Fixed a bug in the Sensu `deep_merge()` method that was responsible for
36
+ mutating arrays of the original provided objects.
37
+
1
38
  ## 0.27.1 - 2017-02-17
2
39
 
3
40
  ### Other
@@ -137,7 +137,7 @@ module Sensu
137
137
  end
138
138
  end
139
139
  else
140
- EM::Timer.new(1) do
140
+ Timer.new(1) do
141
141
  delete_client.call(attempts)
142
142
  end
143
143
  end
@@ -1,26 +1,31 @@
1
1
  require "sensu/api/utilities/transport_info"
2
+ require "sensu/api/utilities/servers_info"
2
3
 
3
4
  module Sensu
4
5
  module API
5
6
  module Routes
6
7
  module Info
7
8
  include Utilities::TransportInfo
9
+ include Utilities::ServersInfo
8
10
 
9
11
  INFO_URI = /^\/info$/
10
12
 
11
13
  # GET /info
12
14
  def get_info
13
- transport_info do |info|
14
- @response_content = {
15
- :sensu => {
16
- :version => VERSION
17
- },
18
- :transport => info,
19
- :redis => {
20
- :connected => @redis.connected?
15
+ transport_info do |transport|
16
+ servers_info do |servers|
17
+ @response_content = {
18
+ :sensu => {
19
+ :version => VERSION
20
+ },
21
+ :transport => transport,
22
+ :redis => {
23
+ :connected => @redis.connected?
24
+ },
25
+ :servers => servers
21
26
  }
22
- }
23
- respond
27
+ respond
28
+ end
24
29
  end
25
30
  end
26
31
  end
@@ -12,7 +12,9 @@ module Sensu
12
12
  def post_request
13
13
  rules = {
14
14
  :check => {:type => String, :nil_ok => false},
15
- :subscribers => {:type => Array, :nil_ok => true}
15
+ :subscribers => {:type => Array, :nil_ok => true},
16
+ :reason => {:type => String, :nil_ok => true},
17
+ :creator => {:type => String, :nil_ok => true}
16
18
  }
17
19
  read_data(rules) do |data|
18
20
  if @settings[:checks][data[:check]]
@@ -20,6 +22,10 @@ module Sensu
20
22
  check[:name] = data[:check]
21
23
  check[:subscribers] ||= Array.new
22
24
  check[:subscribers] = data[:subscribers] if data[:subscribers]
25
+ check[:api_requested] = {
26
+ :reason => data[:reason],
27
+ :creator => data[:creator]
28
+ }
23
29
  publish_check_request(check)
24
30
  @response_content = {:issued => Time.now.to_i}
25
31
  accepted!
@@ -64,7 +64,8 @@ module Sensu
64
64
  :check => data[:check],
65
65
  :reason => data[:reason],
66
66
  :creator => data[:creator],
67
- :expire_on_resolve => data.fetch(:expire_on_resolve, false)
67
+ :expire_on_resolve => data.fetch(:expire_on_resolve, false),
68
+ :timestamp => Time.now.to_i
68
69
  }
69
70
  silenced_key = "silence:#{silenced_id}"
70
71
  @redis.set(silenced_key, Sensu::JSON.dump(silenced_info)) do
@@ -0,0 +1,36 @@
1
+ module Sensu
2
+ module API
3
+ module Utilities
4
+ module ServersInfo
5
+ # Retreive the Sensu servers info.
6
+ #
7
+ # @yield [Hash] passes servers info to the callback/block.
8
+ def servers_info
9
+ info = []
10
+ if @redis.connected?
11
+ @redis.smembers("servers") do |servers|
12
+ unless servers.empty?
13
+ servers.each_with_index do |server_id, index|
14
+ @redis.get("server:#{server_id}") do |server_json|
15
+ unless server_json.nil?
16
+ info << Sensu::JSON.load(server_json)
17
+ else
18
+ @redis.srem("servers", server_id)
19
+ end
20
+ if index == servers.length - 1
21
+ yield(info)
22
+ end
23
+ end
24
+ end
25
+ else
26
+ yield(info)
27
+ end
28
+ end
29
+ else
30
+ yield(info)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -9,6 +9,7 @@ module Sensu
9
9
  # @yield [Hash] passes Transport info to the callback/block.
10
10
  def transport_info
11
11
  info = {
12
+ :name => @settings[:transport][:name],
12
13
  :keepalives => {
13
14
  :messages => nil,
14
15
  :consumers => nil
@@ -69,7 +69,7 @@ module Sensu
69
69
  def setup_keepalives
70
70
  @logger.debug("scheduling keepalives")
71
71
  publish_keepalive
72
- @timers[:run] << EM::PeriodicTimer.new(20) do
72
+ @timers[:run] << PeriodicTimer.new(20) do
73
73
  publish_keepalive
74
74
  end
75
75
  end
@@ -100,33 +100,6 @@ module Sensu
100
100
  end
101
101
  end
102
102
 
103
- # Perform token substitution for an object. String values are
104
- # passed to `substitute_tokens()`, arrays and sub-hashes are
105
- # processed recursively. Numeric values are ignored.
106
- #
107
- # @param object [Object]
108
- # @return [Array] containing the updated object with substituted
109
- # values and an array of unmatched tokens.
110
- def object_substitute_tokens(object)
111
- unmatched_tokens = []
112
- case object
113
- when Hash
114
- object.each do |key, value|
115
- object[key], unmatched = object_substitute_tokens(value)
116
- unmatched_tokens.push(*unmatched)
117
- end
118
- when Array
119
- object.map! do |value|
120
- value, unmatched = object_substitute_tokens(value)
121
- unmatched_tokens.push(*unmatched)
122
- value
123
- end
124
- when String
125
- object, unmatched_tokens = substitute_tokens(object, @settings[:client])
126
- end
127
- [object, unmatched_tokens.uniq]
128
- end
129
-
130
103
  # Execute a check command, capturing its output (STDOUT/ERR),
131
104
  # exit status code, execution duration, timestamp, and publish
132
105
  # the result. This method guards against multiple executions for
@@ -144,7 +117,7 @@ module Sensu
144
117
  @logger.debug("attempting to execute check command", :check => check)
145
118
  unless @checks_in_progress.include?(check[:name])
146
119
  @checks_in_progress << check[:name]
147
- substituted, unmatched_tokens = object_substitute_tokens(check.dup)
120
+ substituted, unmatched_tokens = object_substitute_tokens(check.dup, @settings[:client])
148
121
  check = substituted.merge(:command => check[:command])
149
122
  started = Time.now.to_f
150
123
  check[:executed] = started.to_i
@@ -292,58 +265,102 @@ module Sensu
292
265
  end
293
266
  end
294
267
 
268
+ # Create a check execution proc, used to execute standalone
269
+ # checks. Checks are not executed if subdued. The check
270
+ # `:issued` timestamp is set here, to mimic check requests
271
+ # issued by a Sensu server. Check definitions are duplicated
272
+ # before processing them, in case they are mutated.
273
+ #
274
+ # @param check [Hash] definition.
275
+ def create_check_execution_proc(check)
276
+ Proc.new do
277
+ unless check_subdued?(check)
278
+ check[:issued] = Time.now.to_i
279
+ process_check_request(check.dup)
280
+ else
281
+ @logger.info("check execution was subdued", :check => check)
282
+ end
283
+ end
284
+ end
285
+
286
+ # Schedule a check execution, using the check cron. This method
287
+ # determines the time until the next cron time (in seconds) and
288
+ # creats an EventMachine timer for the execution. This method
289
+ # will be called after every check cron execution for subsequent
290
+ # executions. The timer is stored in the timers hash under
291
+ # `:run`, so it can be cancelled etc. The check cron execution
292
+ # timer object is removed from the timer hash after the
293
+ # execution, to stop the timer hash from growing infinitely.
294
+ #
295
+ # @param check [Hash] definition.
296
+ def schedule_check_cron_execution(check)
297
+ cron_time = determine_check_cron_time(check)
298
+ @timers[:run] << Timer.new(cron_time) do |timer|
299
+ create_check_execution_proc(check).call
300
+ @timers[:run].delete(timer)
301
+ schedule_check_cron_execution(check)
302
+ end
303
+ end
304
+
295
305
  # Calculate a check execution splay, taking into account the
296
306
  # current time and the execution interval to ensure it's
297
307
  # consistent between process restarts.
298
308
  #
299
309
  # @param check [Hash] definition.
300
- def calculate_execution_splay(check)
310
+ def calculate_check_execution_splay(check)
301
311
  key = [@settings[:client][:name], check[:name]].join(":")
302
312
  splay_hash = Digest::MD5.digest(key).unpack("Q<").first
303
313
  current_time = (Time.now.to_f * 1000).to_i
304
314
  (splay_hash - current_time) % (check[:interval] * 1000) / 1000.0
305
315
  end
306
316
 
307
- # Schedule check executions, using EventMachine periodic timers,
308
- # using a calculated execution splay. The timers are stored in
309
- # the timers hash under `:run`, so they can be cancelled etc.
310
- # Check definitions are duplicated before processing them, in
311
- # case they are mutated. A check will not be executed if it is
312
- # subdued. The check `:issued` timestamp is set here, to mimic
313
- # check requests issued by a Sensu server.
317
+ # Schedule check executions, using the check interval. This
318
+ # method using an intial calculated execution splay EventMachine
319
+ # timer and an EventMachine periodic timer for subsequent check
320
+ # executions. The timers are stored in the timers hash under
321
+ # `:run`, so they can be cancelled etc.
322
+ #
323
+ # @param check [Hash] definition.
324
+ def schedule_check_interval_executions(check)
325
+ execution_splay = testing? ? 0 : calculate_check_execution_splay(check)
326
+ interval = testing? ? 0.5 : check[:interval]
327
+ @timers[:run] << Timer.new(execution_splay) do
328
+ execute_check = create_check_execution_proc(check)
329
+ execute_check.call
330
+ @timers[:run] << PeriodicTimer.new(interval, &execute_check)
331
+ end
332
+ end
333
+
334
+ # Schedule check executions. This method iterates through defined
335
+ # checks and uses the appropriate method of check execution
336
+ # scheduling, either with the cron syntax or a numeric interval.
314
337
  #
315
338
  # @param checks [Array] of definitions.
316
339
  def schedule_checks(checks)
317
340
  checks.each do |check|
318
- execute_check = Proc.new do
319
- unless check_subdued?(check)
320
- check[:issued] = Time.now.to_i
321
- process_check_request(check.dup)
322
- else
323
- @logger.info("check execution was subdued", :check => check)
324
- end
325
- end
326
- execution_splay = testing? ? 0 : calculate_execution_splay(check)
327
- interval = testing? ? 0.5 : check[:interval]
328
- @timers[:run] << EM::Timer.new(execution_splay) do
329
- execute_check.call
330
- @timers[:run] << EM::PeriodicTimer.new(interval, &execute_check)
341
+ if check[:cron]
342
+ schedule_check_cron_execution(check)
343
+ else
344
+ schedule_check_interval_executions(check)
331
345
  end
332
346
  end
333
347
  end
334
348
 
335
349
  # Setup standalone check executions, scheduling standard check
336
350
  # definition and check extension executions. Check definitions
337
- # and extensions with `:standalone` set to `true`, have a
338
- # integer `:interval`, and do not have `:publish` set to `false`
339
- # will be scheduled by the Sensu client for execution.
351
+ # and extensions with `:standalone` set to `true`, do not have
352
+ # `:publish` set to `false`, and have a integer `:interval` or a
353
+ # string `cron` will be scheduled by the Sensu client for
354
+ # execution.
340
355
  def setup_standalone
341
356
  @logger.debug("scheduling standalone checks")
342
357
  standard_checks = @settings.checks.select do |check|
343
- check[:standalone] && check[:interval].is_a?(Integer) && check[:publish] != false
358
+ check[:standalone] && check[:publish] != false &&
359
+ (check[:interval].is_a?(Integer) || check[:cron].is_a?(String))
344
360
  end
345
361
  extension_checks = @extensions.checks.select do |check|
346
- check[:standalone] && check[:interval].is_a?(Integer) && check[:publish] != false
362
+ check[:standalone] && check[:publish] != false &&
363
+ (check[:interval].is_a?(Integer) || check[:cron].is_a?(String))
347
364
  end
348
365
  schedule_checks(standard_checks + extension_checks)
349
366
  end
@@ -106,7 +106,7 @@ module Sensu
106
106
  # Reset (or start) the connection watchdog.
107
107
  def reset_watchdog
108
108
  cancel_watchdog
109
- @watchdog = EM::Timer.new(WATCHDOG_DELAY) do
109
+ @watchdog = Timer.new(WATCHDOG_DELAY) do
110
110
  @mode = MODE_REJECT
111
111
  @logger.warn("discarding data buffer for sender and closing connection", {
112
112
  :data => @data_buffer,
@@ -1,7 +1,7 @@
1
1
  module Sensu
2
2
  unless defined?(Sensu::VERSION)
3
3
  # Sensu release version.
4
- VERSION = "0.27.1".freeze
4
+ VERSION = "0.28.0".freeze
5
5
 
6
6
  # Sensu check severities.
7
7
  SEVERITIES = %w[ok warning critical unknown].freeze
data/lib/sensu/daemon.rb CHANGED
@@ -4,7 +4,7 @@ gem "eventmachine", "1.2.2"
4
4
 
5
5
  gem "sensu-json", "2.0.1"
6
6
  gem "sensu-logger", "1.2.1"
7
- gem "sensu-settings", "9.8.0"
7
+ gem "sensu-settings", "9.9.0"
8
8
  gem "sensu-extension", "1.5.1"
9
9
  gem "sensu-extensions", "1.7.1"
10
10
  gem "sensu-transport", "7.0.2"
@@ -18,6 +18,7 @@ if RUBY_PLATFORM =~ /aix/ || RUBY_PLATFORM =~ /solaris/
18
18
  require "em/pure_ruby"
19
19
  end
20
20
 
21
+ require "sensu/timers"
21
22
  require "sensu/json"
22
23
  require "sensu/logger"
23
24
  require "sensu/settings"
@@ -227,7 +228,7 @@ module Sensu
227
228
  @signals << signal
228
229
  end
229
230
  end
230
- EM::PeriodicTimer.new(1) do
231
+ PeriodicTimer.new(1) do
231
232
  signal = @signals.shift
232
233
  if STOP_SIGNALS.include?(signal)
233
234
  @logger.warn("received signal", :signal => signal)
@@ -0,0 +1,19 @@
1
+ module Sensu
2
+ module Sandbox
3
+ # Evaluate a Ruby expression within the context of a simple
4
+ # "sandbox", a Proc in a module method. As of Ruby 2.3.0,
5
+ # `$SAFE` no longer supports levels > 1, so its use has been
6
+ # removed from this method. A single value is provided to the
7
+ # "sandbox".
8
+ #
9
+ # @param expression [String] to be evaluated.
10
+ # @param value [Object] to provide the "sandbox" with.
11
+ # @return [Object]
12
+ def self.eval(expression, value=nil)
13
+ result = Proc.new do
14
+ Kernel.eval(expression)
15
+ end
16
+ result.call
17
+ end
18
+ end
19
+ end
@@ -1,10 +1,6 @@
1
- require "sensu/server/sandbox"
2
-
3
1
  module Sensu
4
2
  module Server
5
3
  module Filter
6
- EVAL_PREFIX = "eval:".freeze
7
-
8
4
  # Determine if an event handler is silenced.
9
5
  #
10
6
  # @param handler [Hash] definition.
@@ -68,96 +64,6 @@ module Sensu
68
64
  end
69
65
  end
70
66
 
71
- # Process a filter eval attribute, a Ruby `eval()` string
72
- # containing an expression to be evaluated within the
73
- # scope/context of a sandbox. This methods strips away the
74
- # expression prefix, `eval:`, and substitues any dot notation
75
- # tokens with the corresponding event data values. If there are
76
- # unmatched tokens, this method will return `nil`.
77
- #
78
- # @event [Hash]
79
- # @raw_eval_string [String]
80
- # @return [String] processed eval string.
81
- def process_eval_string(event, raw_eval_string)
82
- eval_string = raw_eval_string.slice(5..-1)
83
- eval_string, unmatched_tokens = substitute_tokens(eval_string, event)
84
- if unmatched_tokens.empty?
85
- eval_string
86
- else
87
- @logger.error("filter eval unmatched tokens", {
88
- :raw_eval_string => raw_eval_string,
89
- :unmatched_tokens => unmatched_tokens,
90
- :event => event
91
- })
92
- nil
93
- end
94
- end
95
-
96
- # Ruby `eval()` a string containing an expression, within the
97
- # scope/context of a sandbox. This method is for filter
98
- # attribute values starting with "eval:", with the Ruby
99
- # expression following the colon. A single variable is provided
100
- # to the expression, `value`, equal to the corresponding event
101
- # attribute value. Dot notation tokens in the expression, e.g.
102
- # `:::mysql.user:::`, are substituted with the corresponding
103
- # event data values prior to evaluation. The expression is
104
- # expected to return a boolean value.
105
- #
106
- # @param event [Hash]
107
- # @param raw_eval_string [String] containing the Ruby
108
- # expression to be evaluated.
109
- # @param raw_value [Object] of the corresponding event
110
- # attribute value.
111
- # @return [TrueClass, FalseClass]
112
- def eval_attribute_value(event, raw_eval_string, raw_value)
113
- eval_string = process_eval_string(event, raw_eval_string)
114
- unless eval_string.nil?
115
- begin
116
- value = Marshal.load(Marshal.dump(raw_value))
117
- !!Sandbox.eval(eval_string, value)
118
- rescue StandardError, SyntaxError => error
119
- @logger.error("filter attribute eval error", {
120
- :event => event,
121
- :raw_eval_string => raw_eval_string,
122
- :raw_value => raw_value,
123
- :error => error.to_s
124
- })
125
- false
126
- end
127
- else
128
- false
129
- end
130
- end
131
-
132
- # Determine if all filter attribute values match those of the
133
- # corresponding event attributes. Attributes match if the value
134
- # objects are equivalent, are both hashes with matching
135
- # key/value pairs (recursive), have equal string values, or
136
- # evaluate to true (Ruby eval).
137
- #
138
- # @param event [Hash]
139
- # @param filter_attributes [Object]
140
- # @param event_attributes [Object]
141
- # @return [TrueClass, FalseClass]
142
- def filter_attributes_match?(event, filter_attributes, event_attributes=nil)
143
- event_attributes ||= event
144
- filter_attributes.all? do |key, value_one|
145
- value_two = event_attributes[key]
146
- case
147
- when value_one == value_two
148
- true
149
- when value_one.is_a?(Hash) && value_two.is_a?(Hash)
150
- filter_attributes_match?(event, value_one, value_two)
151
- when value_one.to_s == value_two.to_s
152
- true
153
- when value_one.is_a?(String) && value_one.start_with?(EVAL_PREFIX)
154
- eval_attribute_value(event, value_one, value_two)
155
- else
156
- false
157
- end
158
- end
159
- end
160
-
161
67
  # Determine if a filter is to be evoked for the current time. A
162
68
  # filter can be configured with a time window defining when it
163
69
  # is to be evoked, e.g. Monday through Friday, 9-5.
@@ -184,7 +90,7 @@ module Sensu
184
90
  def native_filter(filter_name, event)
185
91
  filter = @settings[:filters][filter_name]
186
92
  if in_filter_time_windows?(filter)
187
- matched = filter_attributes_match?(event, filter[:attributes])
93
+ matched = attributes_match?(event, filter[:attributes])
188
94
  yield(filter[:negate] ? matched : !matched, filter_name)
189
95
  else
190
96
  yield(false, filter_name)