sensu 0.27.1 → 0.28.0

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.
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)