sensu 0.25.7 → 0.26.0.beta

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: 3e1bd8a6afeb2016ff1c3b95dfad080d1f6e13f9
4
- data.tar.gz: e2a506b7e5d9868b61dbc546637e6be31829986a
3
+ metadata.gz: 43dd37a6c04af17e71c851c56ca704a7a074748b
4
+ data.tar.gz: f5bc129d97ce20cc38aa2011263b5c06378ddd9d
5
5
  SHA512:
6
- metadata.gz: 64b81f62ec58d7360495020a9180040a3116f88e9f879f48d3b35480a23af0f8b3af47e8f1e0d78b74f53d247aa5ad972a2b0694cd66af78f573c8c76b6baad4
7
- data.tar.gz: 362603f315346825d2e1a65f84e509b46c97f2bb0bf6223fcd6cb6c9a860092a4b69bd426a19c3e5fc0275e6dc0365a1bfadde27188156136dfc6a2d7ff900ab
6
+ metadata.gz: ba486e8272baf979179579d9c4c9f8a66ec88bad4c3c0fd298ed9210aa45e37941429d7cf57a3b4b7a362604ff8d0abb6dbf05c246fa77b51a38070f3b9fba56
7
+ data.tar.gz: 24cd248e6322595ee191aee1a16bd5903b9d8eff1d0ad9479d337c085470c628752f77260659823d9a602bd0d377914ebcc67ecace581b52c2f6502959dee068
data/CHANGELOG.md CHANGED
@@ -1,3 +1,50 @@
1
+ ## 0.26.0 - TBD
2
+
3
+ ### Fixes
4
+
5
+ Increased the maximum number of EventMachine timers from 100k to 200k, to
6
+ accommodate very large Sensu installations that execute over 100k checks.
7
+
8
+ Only attempt to schedule standalone checks that have an interval.
9
+
10
+ Standalone checks are now long provided by the Sensu API /checks endpoint.
11
+
12
+ Check TTL events are no longer created if the associated Sensu client has
13
+ a current keepalive event.
14
+
15
+ Fixed a Sensu API /results endpoint race condition that caused incomplete
16
+ response content.
17
+
18
+ ### Features
19
+
20
+ Event silencing is now built into Sensu Core! The Sensu API now provides a
21
+ set of /silencing endpoints, for silencing one or more subscriptions
22
+ and/or checks. Silencing applies to all event handlers by default, the new
23
+ handler definition attribute `handle_silenced` can be used to disable it
24
+ for a handler. Metric check events (OK) bypass event silencing.
25
+
26
+ Subdue now ONLY applies to check scheduling via check definitions, it has
27
+ been removed from handlers (no more `"at": "handler"`). The Sensu client
28
+ standalone check execution scheduler now supports subdue. The subdue
29
+ configuration syntax has changed, please refer to the 0.26 documentation.
30
+
31
+ Event filters now support time windows, via the filter definition
32
+ attribute `"when": {}`. The configuration syntax is the same as check
33
+ subdue.
34
+
35
+ Sensu Extensions are now loaded from Rubygems! The Sensu installer,
36
+ `sensu-install`, can now be used to install Sensu Extensions, e.g.
37
+ `sensu-install -e system-profile`. Extensions gems must be enabled via
38
+ Sensu configuration, please refer to the 0.26 documentation.
39
+
40
+ A check can now be a member of more than one aggregate, via the check
41
+ definition attribute `"aggregates": []`.
42
+
43
+ Every Sensu client now creates/subscribes to its own unique client
44
+ subscription named after it, e.g. `client:i-424242`. This unique client
45
+ subscription allows Sensu checks to target a single client (host) and
46
+ enables silencing events for a single client.
47
+
1
48
  ## 0.25.7 - 2016-08-09
2
49
 
3
50
  ### Fixes
data/exe/sensu-install CHANGED
@@ -8,7 +8,8 @@ module Sensu
8
8
  def cli_options(arguments=ARGV)
9
9
  options = {
10
10
  :verbose => false,
11
- :plugins => []
11
+ :plugins => [],
12
+ :extensions => []
12
13
  }
13
14
  optparse = OptionParser.new do |opts|
14
15
  opts.on("-h", "--help", "Display this message") do
@@ -24,6 +25,12 @@ module Sensu
24
25
  opts.on("-P", "--plugins PLUGIN[,PLUGIN]", "PLUGIN or comma-delimited list of Sensu plugins to install") do |plugins|
25
26
  options[:plugins].concat(plugins.split(","))
26
27
  end
28
+ opts.on("-e", "--extension EXTENSION", "Install a Sensu EXTENSION") do |extension|
29
+ options[:extensions] << extension
30
+ end
31
+ opts.on("-E", "--extensions EXTENSION[,EXT]", "EXTENSION or comma-delimited list of Sensu extensions to install") do |extensions|
32
+ options[:extensions].concat(extensions.split(","))
33
+ end
27
34
  opts.on("-s", "--source SOURCE", "Install Sensu plugins from a custom SOURCE") do |source|
28
35
  options[:source] = source
29
36
  end
@@ -36,23 +43,23 @@ module Sensu
36
43
  puts "[SENSU-INSTALL] #{message}"
37
44
  end
38
45
 
39
- def plugin_gem_installed?(raw_gem, options={})
40
- log "determining if Sensu plugin gem '#{raw_gem}' is already installed ..."
46
+ def gem_installed?(raw_gem, options={})
47
+ log "determining if Sensu gem '#{raw_gem}' is already installed ..."
41
48
  gem_name, gem_version = raw_gem.split(":")
42
49
  gem_command = "gem list -i #{gem_name}"
43
50
  gem_command << " --version '#{gem_version}'" if gem_version
44
51
  log gem_command if options[:verbose]
45
52
  if system(gem_command)
46
- log "Sensu plugin gem '#{gem_name}' has already been installed"
53
+ log "Sensu gem '#{gem_name}' has already been installed"
47
54
  true
48
55
  else
49
- log "Sensu plugin gem '#{gem_name}' has not been installed" if options[:verbose]
56
+ log "Sensu gem '#{gem_name}' has not been installed" if options[:verbose]
50
57
  false
51
58
  end
52
59
  end
53
60
 
54
- def install_plugin_gem(raw_gem, options={})
55
- log "installing Sensu plugin gem '#{raw_gem}'"
61
+ def install_gem(raw_gem, options={})
62
+ log "installing Sensu gem '#{raw_gem}'"
56
63
  gem_name, gem_version = raw_gem.split(":")
57
64
  gem_command = "gem install #{gem_name}"
58
65
  gem_command << " --version '#{gem_version}'" if gem_version
@@ -61,7 +68,7 @@ module Sensu
61
68
  gem_command << " --source #{options[:source]}" if options[:source]
62
69
  log gem_command if options[:verbose]
63
70
  unless system(gem_command)
64
- log "failed to install Sensu plugin gem '#{gem_name}'"
71
+ log "failed to install Sensu gem '#{gem_name}'"
65
72
  log "you can run the sensu-install command again with --verbose for more info" unless options[:verbose]
66
73
  log "please take note of any failure messages above"
67
74
  log "make sure you have build tools installed (e.g. gcc)"
@@ -83,21 +90,43 @@ module Sensu
83
90
  end
84
91
  log "compiled Sensu plugin gems: #{plugin_gems}" if options[:verbose]
85
92
  plugin_gems.reject! do |raw_gem|
86
- plugin_gem_installed?(raw_gem, options)
93
+ gem_installed?(raw_gem, options)
87
94
  end
88
95
  log "Sensu plugin gems to be installed: #{plugin_gems}"
89
96
  plugin_gems.each do |raw_gem|
90
- install_plugin_gem(raw_gem, options)
97
+ install_gem(raw_gem, options)
91
98
  end
92
99
  log "successfully installed Sensu plugins: #{plugins}"
93
100
  end
94
101
 
102
+ def install_extensions(extensions, options={})
103
+ log "installing Sensu extensions ..."
104
+ log "provided Sensu extensions: #{extensions}" if options[:verbose]
105
+ extension_gems = extensions.map do |extension|
106
+ if extension.start_with?("sensu-extensions-")
107
+ extension
108
+ else
109
+ "sensu-extensions-#{extension}"
110
+ end
111
+ end
112
+ log "compiled Sensu extension gems: #{extension_gems}" if options[:verbose]
113
+ extension_gems.reject! do |raw_gem|
114
+ gem_installed?(raw_gem, options)
115
+ end
116
+ log "Sensu extension gems to be installed: #{extension_gems}"
117
+ extension_gems.each do |raw_gem|
118
+ install_gem(raw_gem, options)
119
+ end
120
+ log "successfully installed Sensu extensions: #{extensions}"
121
+ end
122
+
95
123
  def run
96
124
  options = cli_options
97
125
  unless options[:plugins].empty?
98
126
  install_plugins(options[:plugins], options)
99
- else
100
- log "nothing to do"
127
+ end
128
+ unless options[:extensions].empty?
129
+ install_extensions(options[:extensions], options)
101
130
  end
102
131
  end
103
132
  end
@@ -95,8 +95,8 @@ module Sensu
95
95
  data = Sensu::JSON.load(@http_content)
96
96
  valid = data.is_a?(Hash) && rules.all? do |key, rule|
97
97
  value = data[key]
98
- (value.is_a?(rule[:type]) || (rule[:nil_ok] && value.nil?)) &&
99
- (value.nil? || rule[:regex].nil?) ||
98
+ (Array(rule[:type]).any? {|type| value.is_a?(type)} ||
99
+ (rule[:nil_ok] && value.nil?)) && (value.nil? || rule[:regex].nil?) ||
100
100
  (rule[:regex] && (value =~ rule[:regex]) == 0)
101
101
  end
102
102
  if valid
@@ -8,6 +8,7 @@ require "sensu/api/routes/resolve"
8
8
  require "sensu/api/routes/aggregates"
9
9
  require "sensu/api/routes/stashes"
10
10
  require "sensu/api/routes/results"
11
+ require "sensu/api/routes/silenced"
11
12
 
12
13
  module Sensu
13
14
  module API
@@ -22,6 +23,7 @@ module Sensu
22
23
  include Aggregates
23
24
  include Stashes
24
25
  include Results
26
+ include Silenced
25
27
 
26
28
  GET_METHOD = "GET".freeze
27
29
  HEAD_METHOD = "HEAD".freeze
@@ -49,7 +51,10 @@ module Sensu
49
51
  [STASH_URI, :get_stash],
50
52
  [RESULTS_URI, :get_results],
51
53
  [RESULTS_CLIENT_URI, :get_results_client],
52
- [RESULT_URI, :get_result]
54
+ [RESULT_URI, :get_result],
55
+ [SILENCED_URI, :get_silenced],
56
+ [SILENCED_SUBSCRIPTION_URI, :get_silenced_subscription],
57
+ [SILENCED_CHECK_URI, :get_silenced_check]
53
58
  ]
54
59
 
55
60
  ROUTES = {
@@ -61,7 +66,9 @@ module Sensu
61
66
  [RESOLVE_URI, :post_resolve],
62
67
  [STASHES_URI, :post_stashes],
63
68
  [STASH_URI, :post_stash],
64
- [RESULTS_URI, :post_results]
69
+ [RESULTS_URI, :post_results],
70
+ [SILENCED_URI, :post_silenced],
71
+ [SILENCED_CLEAR_URI, :post_silenced_clear]
65
72
  ],
66
73
  DELETE_METHOD => [
67
74
  [CLIENT_URI, :delete_client],
@@ -7,14 +7,14 @@ module Sensu
7
7
 
8
8
  # GET /checks
9
9
  def get_checks
10
- @response_content = @settings.checks
10
+ @response_content = @settings.checks.reject { |check| check[:standalone] }
11
11
  respond
12
12
  end
13
13
 
14
14
  # GET /checks/:check_name
15
15
  def get_check
16
16
  check_name = parse_uri(CHECK_URI).first
17
- if @settings[:checks][check_name]
17
+ if @settings[:checks][check_name] && !@settings[:checks][check_name][:standalone]
18
18
  @response_content = @settings[:checks][check_name].merge(:name => check_name)
19
19
  respond
20
20
  else
@@ -46,7 +46,9 @@ module Sensu
46
46
  end
47
47
  end
48
48
  elsif client_index == clients.length - 1
49
- respond
49
+ @redis.ping do
50
+ respond
51
+ end
50
52
  end
51
53
  end
52
54
  end
@@ -0,0 +1,153 @@
1
+ module Sensu
2
+ module API
3
+ module Routes
4
+ module Silenced
5
+ SILENCED_URI = /^\/silenced$/
6
+ SILENCED_SUBSCRIPTION_URI = /^\/silenced\/subscriptions\/([\w\.-]+)$/
7
+ SILENCED_CHECK_URI = /^\/silenced\/checks\/([\w\.-]+)$/
8
+ SILENCED_CLEAR_URI = /^\/silenced\/clear$/
9
+
10
+ # Fetch silenced registry entries for the provided silenced
11
+ # entry keys.
12
+ #
13
+ # @param silenced_keys [Array]
14
+ # @yield callback [entries] callback/block called after the
15
+ # silenced registry entries have been fetched.
16
+ def fetch_silenced(silenced_keys=[])
17
+ entries = []
18
+ unless silenced_keys.empty?
19
+ @redis.mget(*silenced_keys) do |silenced|
20
+ silenced_keys.each_with_index do |silenced_key, silenced_index|
21
+ if silenced[silenced_index]
22
+ silenced_info = Sensu::JSON.load(silenced[silenced_index])
23
+ @redis.ttl(silenced_key) do |ttl|
24
+ silenced_info[:expire] = ttl
25
+ entries << silenced_info
26
+ if silenced_index == silenced_keys.length - 1
27
+ yield(entries)
28
+ end
29
+ end
30
+ else
31
+ @redis.srem("silenced", silenced_key)
32
+ if silenced_index == silenced_keys.length - 1
33
+ @redis.ping do
34
+ yield(entries)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ else
41
+ yield(entries)
42
+ end
43
+ end
44
+
45
+ # POST /silenced
46
+ def post_silenced
47
+ rules = {
48
+ :subscription => {:type => String, :nil_ok => true},
49
+ :check => {:type => String, :nil_ok => true, :regex => /\A[\w\.-]+\z/},
50
+ :expire => {:type => Integer, :nil_ok => true},
51
+ :reason => {:type => String, :nil_ok => true},
52
+ :creator => {:type => String, :nil_ok => true},
53
+ :expire_on_resolve => {:type => [TrueClass, FalseClass], :nil_ok => true}
54
+ }
55
+ read_data(rules) do |data|
56
+ if data[:subscription] || data[:check]
57
+ subscription = data.fetch(:subscription, "*")
58
+ check = data.fetch(:check, "*")
59
+ silenced_id = "#{subscription}:#{check}"
60
+ silenced_info = {
61
+ :id => silenced_id,
62
+ :subscription => data[:subscription],
63
+ :check => data[:check],
64
+ :reason => data[:reason],
65
+ :creator => data[:creator],
66
+ :expire_on_resolve => data.fetch(:expire_on_resolve, false)
67
+ }
68
+ silenced_key = "silence:#{silenced_id}"
69
+ @redis.set(silenced_key, Sensu::JSON.dump(silenced_info)) do
70
+ @redis.sadd("silenced", silenced_key) do
71
+ if data[:expire]
72
+ @redis.expire(silenced_key, data[:expire]) do
73
+ created!
74
+ end
75
+ else
76
+ created!
77
+ end
78
+ end
79
+ end
80
+ else
81
+ bad_request!
82
+ end
83
+ end
84
+ end
85
+
86
+ # GET /silenced
87
+ def get_silenced
88
+ @redis.smembers("silenced") do |silenced_keys|
89
+ silenced_keys = pagination(silenced_keys)
90
+ fetch_silenced(silenced_keys) do |silenced|
91
+ @response_content = silenced
92
+ respond
93
+ end
94
+ end
95
+ end
96
+
97
+ # GET /silenced/subscriptions/:subscription
98
+ def get_silenced_subscription
99
+ subscription = parse_uri(SILENCED_SUBSCRIPTION_URI).first
100
+ @redis.smembers("silenced") do |silenced_keys|
101
+ silenced_keys.select! do |key|
102
+ key =~ /^silence:#{subscription}:/
103
+ end
104
+ silenced_keys = pagination(silenced_keys)
105
+ fetch_silenced(silenced_keys) do |silenced|
106
+ @response_content = silenced
107
+ respond
108
+ end
109
+ end
110
+ end
111
+
112
+ # GET /silenced/checks/:check
113
+ def get_silenced_check
114
+ check_name = parse_uri(SILENCED_CHECK_URI).first
115
+ @redis.smembers("silenced") do |silenced_keys|
116
+ silenced_keys.select! do |key|
117
+ key =~ /.*:#{check_name}$/
118
+ end
119
+ silenced_keys = pagination(silenced_keys)
120
+ fetch_silenced(silenced_keys) do |silenced|
121
+ @response_content = silenced
122
+ respond
123
+ end
124
+ end
125
+ end
126
+
127
+ # POST /silenced/clear
128
+ def post_silenced_clear
129
+ rules = {
130
+ :id => {:type => String, :nil_ok => true},
131
+ :subscription => {:type => String, :nil_ok => true},
132
+ :check => {:type => String, :nil_ok => true, :regex => /\A[\w\.-]+\z/}
133
+ }
134
+ read_data(rules) do |data|
135
+ if !data[:id].nil? || (data[:subscription] || data[:check])
136
+ subscription = data.fetch(:subscription, "*")
137
+ check = data.fetch(:check, "*")
138
+ silenced_id = data[:id] || "#{subscription}:#{check}"
139
+ silenced_key = "silence:#{silenced_id}"
140
+ @redis.srem("silenced", silenced_key) do
141
+ @redis.del(silenced_key) do |deleted|
142
+ deleted ? no_content! : not_found!
143
+ end
144
+ end
145
+ else
146
+ bad_request!
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
@@ -297,15 +297,20 @@ module Sensu
297
297
  # using a calculated execution splay. The timers are stored in
298
298
  # the timers hash under `:run`, so they can be cancelled etc.
299
299
  # Check definitions are duplicated before processing them, in
300
- # case they are mutated. The check `:issued` timestamp is set
301
- # here, to mimic check requests issued by a Sensu server.
300
+ # case they are mutated. A check will not be executed if it is
301
+ # subdued. The check `:issued` timestamp is set here, to mimic
302
+ # check requests issued by a Sensu server.
302
303
  #
303
304
  # @param checks [Array] of definitions.
304
305
  def schedule_checks(checks)
305
306
  checks.each do |check|
306
307
  execute_check = Proc.new do
307
- check[:issued] = Time.now.to_i
308
- process_check_request(check.dup)
308
+ unless check_subdued?(check)
309
+ check[:issued] = Time.now.to_i
310
+ process_check_request(check.dup)
311
+ else
312
+ @logger.info("check execution was subdued", :check => check)
313
+ end
309
314
  end
310
315
  execution_splay = testing? ? 0 : calculate_execution_splay(check)
311
316
  interval = testing? ? 0.5 : check[:interval]
@@ -318,15 +323,16 @@ module Sensu
318
323
 
319
324
  # Setup standalone check executions, scheduling standard check
320
325
  # definition and check extension executions. Check definitions
321
- # and extensions with `:standalone` set to `true` will be
322
- # scheduled by the Sensu client for execution.
326
+ # and extensions with `:standalone` set to `true`, have a
327
+ # integer `:interval`, and do not have `:publish` set to `false`
328
+ # will be scheduled by the Sensu client for execution.
323
329
  def setup_standalone
324
330
  @logger.debug("scheduling standalone checks")
325
331
  standard_checks = @settings.checks.select do |check|
326
- check[:standalone]
332
+ check[:standalone] && check[:interval].is_a?(Integer) && check[:publish] != false
327
333
  end
328
334
  extension_checks = @extensions.checks.select do |check|
329
- check[:standalone] && check[:interval].is_a?(Integer)
335
+ check[:standalone] && check[:interval].is_a?(Integer) && check[:publish] != false
330
336
  end
331
337
  schedule_checks(standard_checks + extension_checks)
332
338
  end
@@ -1,7 +1,7 @@
1
1
  module Sensu
2
2
  unless defined?(Sensu::VERSION)
3
3
  # Sensu release version.
4
- VERSION = "0.25.7".freeze
4
+ VERSION = "0.26.0.beta".freeze
5
5
 
6
6
  # Sensu check severities.
7
7
  SEVERITIES = %w[ok warning critical unknown].freeze
data/lib/sensu/daemon.rb CHANGED
@@ -4,12 +4,12 @@ gem "eventmachine", "1.2.0.1"
4
4
 
5
5
  gem "sensu-json", "2.0.0"
6
6
  gem "sensu-logger", "1.2.0"
7
- gem "sensu-settings", "5.2.0"
7
+ gem "sensu-settings", "9.1.0"
8
8
  gem "sensu-extension", "1.5.0"
9
- gem "sensu-extensions", "1.5.0"
9
+ gem "sensu-extensions", "1.7.0"
10
10
  gem "sensu-transport", "6.0.0"
11
11
  gem "sensu-spawn", "2.2.0"
12
- gem "sensu-redis", "1.4.0"
12
+ gem "sensu-redis", "1.6.0"
13
13
 
14
14
  require "time"
15
15
  require "uri"
@@ -34,19 +34,21 @@ module Sensu
34
34
  module Daemon
35
35
  include Utilities
36
36
 
37
- attr_reader :start_time
37
+ attr_reader :start_time, :settings
38
38
 
39
39
  # Initialize the Sensu process. Set the start time, initial
40
- # service state, set up the logger, and load settings. This method
41
- # will load extensions and setup Sensu Spawn if the Sensu process
42
- # is not the Sensu API. This method can and optionally daemonize
43
- # the process and/or create a PID file.
40
+ # service state, double the maximum number of EventMachine timers,
41
+ # set up the logger, and load settings. This method will load
42
+ # extensions and setup Sensu Spawn if the Sensu process is not the
43
+ # Sensu API. This method can and optionally daemonize the process
44
+ # and/or create a PID file.
44
45
  #
45
46
  # @param options [Hash]
46
47
  def initialize(options={})
47
48
  @start_time = Time.now.to_i
48
49
  @state = :initializing
49
50
  @timers = {:run => []}
51
+ EM::set_max_timers(200000) unless EM::reactor_running?
50
52
  setup_logger(options)
51
53
  load_settings(options)
52
54
  unless sensu_service_name == "api"
@@ -148,7 +150,8 @@ module Sensu
148
150
  #
149
151
  # @param options [Hash]
150
152
  def load_extensions(options={})
151
- @extensions = Extensions.get(options)
153
+ extensions_options = options.merge(:extensions => @settings[:extensions])
154
+ @extensions = Extensions.get(extensions_options)
152
155
  log_notices(@extensions.warnings)
153
156
  extension_settings = @settings.to_hash.dup
154
157
  @extensions.all.each do |extension|
@@ -5,112 +5,13 @@ module Sensu
5
5
  module Filter
6
6
  EVAL_PREFIX = "eval:".freeze
7
7
 
8
- # Determine if a period of time (window) is subdued. The
9
- # provided condition must have a `:begin` and `:end` time, eg.
10
- # "11:30:00 PM", or `false` will be returned.
11
- #
12
- # @param condition [Hash]
13
- # @option condition [String] :begin time.
14
- # @option condition [String] :end time.
15
- # @return [TrueClass, FalseClass]
16
- def subdue_time?(condition)
17
- if condition.has_key?(:begin) && condition.has_key?(:end)
18
- begin_time = Time.parse(condition[:begin])
19
- end_time = Time.parse(condition[:end])
20
- if end_time < begin_time
21
- if Time.now < end_time
22
- begin_time = Time.parse("12:00:00 AM")
23
- else
24
- end_time = Time.parse("11:59:59 PM")
25
- end
26
- end
27
- Time.now >= begin_time && Time.now <= end_time
28
- else
29
- false
30
- end
31
- end
32
-
33
- # Determine if the current day is subdued. The provided
34
- # condition must have a list of `:days`, or false will be
35
- # returned.
36
- #
37
- # @param condition [Hash]
38
- # @option condition [Array] :days of the week to subdue.
39
- # @return [TrueClass, FalseClass]
40
- def subdue_days?(condition)
41
- if condition.has_key?(:days)
42
- days = condition[:days].map(&:downcase)
43
- days.include?(Time.now.strftime("%A").downcase)
44
- else
45
- false
46
- end
47
- end
48
-
49
- # Determine if there is an exception a period of time (window)
50
- # that is subdued. The provided condition must have an
51
- # `:exception`, containing one or more `:begin` and `:end`
52
- # times, eg. "11:30:00 PM", or `false` will be returned. If
53
- # there are any exceptions to a subdued period of time, `true`
54
- # will be returned.
55
- #
56
- # @param condition [Hash]
57
- # @option condition [Hash] :exceptions array of `:begin` and
58
- # `:end` times.
59
- # @return [TrueClass, FalseClass]
60
- def subdue_exception?(condition)
61
- if condition.has_key?(:exceptions)
62
- condition[:exceptions].any? do |exception|
63
- Time.now >= Time.parse(exception[:begin]) && Time.now <= Time.parse(exception[:end])
64
- end
65
- else
66
- false
67
- end
68
- end
69
-
70
- # Determine if an action is subdued and if there is an
71
- # exception. This method makes use of `subdue_time?()`,
72
- # `subdue_days?()`, and subdue_exception?().
73
- #
74
- # @param condition [Hash]
75
- # @return [TrueClass, FalseClass]
76
- def action_subdued?(condition)
77
- subdued = subdue_time?(condition) || subdue_days?(condition)
78
- subdued && !subdue_exception?(condition)
79
- end
80
-
81
- # Determine if an event handler is subdued, by conditions set in
82
- # the check and/or the handler definition. If any of the
83
- # conditions are true, without an exception, the handler is
84
- # subdued.
8
+ # Determine if an event handler is silenced.
85
9
  #
86
10
  # @param handler [Hash] definition.
87
- # @param event [Hash] data possibly containing subdue
88
- # conditions.
89
- # @return [TrueClass, FalseClass]
90
- def handler_subdued?(handler, event)
91
- subdued = []
92
- if handler[:subdue]
93
- subdued << action_subdued?(handler[:subdue])
94
- end
95
- check = event[:check]
96
- if check[:subdue] && check[:subdue][:at] != "publisher"
97
- subdued << action_subdued?(check[:subdue])
98
- end
99
- subdued.any?
100
- end
101
-
102
- # Determine if a check request is subdued, by conditions set in
103
- # the check definition. If any of the conditions are true,
104
- # without an exception, the check request is subdued.
105
- #
106
- # @param check [Hash] definition.
11
+ # @param event [Hash]
107
12
  # @return [TrueClass, FalseClass]
108
- def check_request_subdued?(check)
109
- if check[:subdue] && check[:subdue][:at] == "publisher"
110
- action_subdued?(check[:subdue])
111
- else
112
- false
113
- end
13
+ def handler_silenced?(handler, event)
14
+ event[:silenced] && !handler[:handle_silenced]
114
15
  end
115
16
 
116
17
  # Determine if handling is disabled for an event. Check
@@ -257,13 +158,64 @@ module Sensu
257
158
  end
258
159
  end
259
160
 
260
- # Determine if an event is filtered by an event filter, standard
161
+ # Determine if a filter is to be evoked for the current time. A
162
+ # filter can be configured with a time window defining when it
163
+ # is to be evoked, e.g. Monday through Friday, 9-5.
164
+ #
165
+ # @param filter [Hash] definition.
166
+ # @return [TrueClass, FalseClass]
167
+ def in_filter_time_windows?(filter)
168
+ if filter[:when]
169
+ in_time_windows?(filter[:when])
170
+ else
171
+ true
172
+ end
173
+ end
174
+
175
+ # Determine if an event is filtered by a native filter.
176
+ #
177
+ # @param filter_name [String]
178
+ # @param event [Hash]
179
+ # @yield [filtered] callback/block called with a single
180
+ # parameter to indicate if the event was filtered.
181
+ # @yieldparam filtered [TrueClass,FalseClass] indicating if the
182
+ # event was filtered.
183
+ def native_filter(filter_name, event)
184
+ filter = @settings[:filters][filter_name]
185
+ if in_filter_time_windows?(filter)
186
+ matched = filter_attributes_match?(event, filter[:attributes])
187
+ yield(filter[:negate] ? matched : !matched)
188
+ else
189
+ yield(false)
190
+ end
191
+ end
192
+
193
+ # Determine if an event is filtered by a filter extension.
194
+ #
195
+ # @param filter_name [String]
196
+ # @param event [Hash]
197
+ # @yield [filtered] callback/block called with a single
198
+ # parameter to indicate if the event was filtered.
199
+ # @yieldparam filtered [TrueClass,FalseClass] indicating if the
200
+ # event was filtered.
201
+ def extension_filter(filter_name, event)
202
+ extension = @extensions[:filters][filter_name]
203
+ if in_filter_time_windows?(extension.definition)
204
+ extension.safe_run(event) do |output, status|
205
+ yield(status == 0)
206
+ end
207
+ else
208
+ yield(false)
209
+ end
210
+ end
211
+
212
+ # Determine if an event is filtered by an event filter, native
261
213
  # or extension. This method first checks for the existence of a
262
- # standard filter, then checks for an extension if a standard
263
- # filter is not defined. The provided callback is called with a
264
- # single parameter, indicating if the event was filtered by a
265
- # filter. If a filter does not exist for the provided name, the
266
- # event is not filtered.
214
+ # native filter, then checks for an extension if a native filter
215
+ # is not defined. The provided callback is called with a single
216
+ # parameter, indicating if the event was filtered by a filter.
217
+ # If a filter does not exist for the provided name, the event is
218
+ # not filtered.
267
219
  #
268
220
  # @param filter_name [String]
269
221
  # @param event [Hash]
@@ -274,13 +226,12 @@ module Sensu
274
226
  def event_filter(filter_name, event)
275
227
  case
276
228
  when @settings.filter_exists?(filter_name)
277
- filter = @settings[:filters][filter_name]
278
- matched = filter_attributes_match?(event, filter[:attributes])
279
- yield(filter[:negate] ? matched : !matched)
229
+ native_filter(filter_name, event) do |filtered|
230
+ yield(filtered)
231
+ end
280
232
  when @extensions.filter_exists?(filter_name)
281
- extension = @extensions[:filters][filter_name]
282
- extension.safe_run(event) do |output, status|
283
- yield(status == 0)
233
+ extension_filter(filter_name, event) do |filtered|
234
+ yield(filtered)
284
235
  end
285
236
  else
286
237
  @logger.error("unknown filter", :filter_name => filter_name)
@@ -340,8 +291,8 @@ module Sensu
340
291
  "handler does not handle action"
341
292
  when !handle_severity?(handler, event)
342
293
  "handler does not handle event severity"
343
- when handler_subdued?(handler, event)
344
- "handler is subdued"
294
+ when handler_silenced?(handler, event)
295
+ "handler is silenced"
345
296
  end
346
297
  if filter_message
347
298
  @logger.info(filter_message, details)
@@ -275,26 +275,37 @@ module Sensu
275
275
  end
276
276
  end
277
277
 
278
- # Add a check result to an aggregate. The aggregate name is
279
- # determined by the value of check `:aggregate`. If check
280
- # `:aggregate` is `true` (legacy), the check `:name` is used as
281
- # the aggregate name. If check `:aggregate` is a string, it is
282
- # used as the aggregate name. This method will add the client
283
- # name to the aggregate, all other processing (e.g. counters) is
284
- # done by the Sensu API on request.
278
+ # Add a check result to one or more aggregates. The aggregate name is
279
+ # determined by the value of check `:aggregates` array, if present,
280
+ # and falling back to `:aggregate` otherwise.
281
+ #
282
+ # When one or more aggregates are specified as `:aggregates`, the
283
+ # client name and check are updated on each aggregate.
284
+ #
285
+ # When no aggregates are specified as `:aggregates`, and `:aggregate`
286
+ # is `true` (legacy), the check `:name` is used as the aggregate name.
287
+ #
288
+ # When no aggregates are specified as `:aggregates` and check `:aggregate`
289
+ # is a string, it used as the aggregate name.
290
+ #
291
+ # This method will add the client name to configured aggregates, all
292
+ # other processing (e.g. counters) is done by the Sensu API on request.
285
293
  #
286
294
  # @param client [Hash]
287
295
  # @param check [Hash]
288
296
  def aggregate_check_result(client, check)
289
- aggregate = (check[:aggregate].is_a?(String) ? check[:aggregate] : check[:name])
290
- @logger.debug("adding check result to aggregate", {
291
- :aggregate => aggregate,
292
- :client => client,
293
- :check => check
294
- })
295
- aggregate_member = "#{client[:name]}:#{check[:name]}"
296
- @redis.sadd("aggregates:#{aggregate}", aggregate_member) do
297
- @redis.sadd("aggregates", aggregate)
297
+ check_aggregate = (check[:aggregate].is_a?(String) ? check[:aggregate] : check[:name])
298
+ aggregate_list = Array(check[:aggregates] || check_aggregate)
299
+ aggregate_list.each do |aggregate|
300
+ @logger.debug("adding check result to aggregate", {
301
+ :aggregate => aggregate,
302
+ :client => client,
303
+ :check => check
304
+ })
305
+ aggregate_member = "#{client[:name]}:#{check[:name]}"
306
+ @redis.sadd("aggregates:#{aggregate}", aggregate_member) do
307
+ @redis.sadd("aggregates", aggregate)
308
+ end
298
309
  end
299
310
  end
300
311
 
@@ -414,6 +425,50 @@ module Sensu
414
425
  end
415
426
  end
416
427
 
428
+ # Determine if an event has been silenced. This method compiles
429
+ # an array of possible silenced registry entry keys for the
430
+ # event. An attempt is made to fetch one or more of the silenced
431
+ # registry entries to determine if the event has been silenced.
432
+ # The event data is updated to indicate if the event has been
433
+ # silenced. If the event is silenced and the event action is
434
+ # `:resolve`, silenced registry entries with
435
+ # `:expire_on_resolve` set to true will be deleted. Silencing is
436
+ # disabled for events with a check status of `0` (OK), unless
437
+ # the event action is `:resolve` or `:flapping`.
438
+ #
439
+ # @param event [Hash]
440
+ # @yield callback [event] callback/block called after the event
441
+ # data has been updated to indicate if it has been silenced.
442
+ def event_silenced?(event)
443
+ event[:silenced] = false
444
+ event[:silenced_by] = []
445
+ if event[:check][:status] != 0 || event[:action] != :create
446
+ check_name = event[:check][:name]
447
+ silenced_keys = event[:client][:subscriptions].map { |subscription|
448
+ ["silence:#{subscription}:*", "silence:#{subscription}:#{check_name}"]
449
+ }.flatten
450
+ silenced_keys << "silence:*:#{check_name}"
451
+ @redis.mget(*silenced_keys) do |silenced|
452
+ silenced.compact!
453
+ event[:silenced] = !silenced.empty?
454
+ if event[:silenced]
455
+ silenced.each do |silenced_json|
456
+ silenced_info = Sensu::JSON.load(silenced_json)
457
+ event[:silenced_by] << silenced_info[:id]
458
+ silenced_key = "silence:#{silenced_info[:id]}"
459
+ if silenced_info[:expire_on_resolve] && event[:action] == :resolve
460
+ @redis.srem("silenced", silenced_key)
461
+ @redis.del(silenced_key)
462
+ end
463
+ end
464
+ end
465
+ yield(event)
466
+ end
467
+ else
468
+ yield(event)
469
+ end
470
+ end
471
+
417
472
  # Update the event registry, stored in Redis. This method
418
473
  # determines if event data warrants in the creation or update of
419
474
  # event data in the registry. If a check `:status` is not
@@ -451,7 +506,8 @@ module Sensu
451
506
  # Create an event, using the provided client and check result
452
507
  # data. Existing event data for the client/check pair is fetched
453
508
  # from the event registry to be used in the composition of the
454
- # new event.
509
+ # new event. The silenced registry is used to determine if the
510
+ # event has been silenced.
455
511
  #
456
512
  # @param client [Hash]
457
513
  # @param check [Hash]
@@ -469,6 +525,7 @@ module Sensu
469
525
  :client => client,
470
526
  :check => check,
471
527
  :occurrences => 1,
528
+ :occurrences_watermark => 1,
472
529
  :action => (flapping ? :flapping : :create),
473
530
  :timestamp => Time.now.to_i
474
531
  }
@@ -477,12 +534,17 @@ module Sensu
477
534
  event[:last_state_change] = stored_event[:last_state_change]
478
535
  event[:last_ok] = stored_event[:last_ok]
479
536
  event[:occurrences] = stored_event[:occurrences]
537
+ event[:occurrences_watermark] = stored_event[:occurrences_watermark] || event[:occurrences]
480
538
  else
481
539
  event[:id] = random_uuid
540
+ event[:last_ok] = event[:timestamp]
482
541
  end
483
542
  if check[:status] != 0 || flapping
484
543
  if history[-1] == history[-2]
485
544
  event[:occurrences] += 1
545
+ if event[:occurrences] > event[:occurrences_watermark]
546
+ event[:occurrences_watermark] = event[:occurrences]
547
+ end
486
548
  else
487
549
  event[:occurrences] = 1
488
550
  event[:last_state_change] = event[:timestamp]
@@ -494,7 +556,9 @@ module Sensu
494
556
  if check[:status] == 0
495
557
  event[:last_ok] = event[:timestamp]
496
558
  end
497
- yield(event)
559
+ event_silenced?(event) do |event|
560
+ yield(event)
561
+ end
498
562
  end
499
563
  end
500
564
  end
@@ -555,6 +619,16 @@ module Sensu
555
619
  end
556
620
  end
557
621
 
622
+ # Determine if a keepalive event exists for a client.
623
+ #
624
+ # @param client_name [String] name of client to look up in event registry.
625
+ # @return [TrueClass, FalseClass]
626
+ def keepalive_event_exists?(client_name)
627
+ @redis.hexists("events:#{client_name}", "keepalive") do |event_exists|
628
+ yield(event_exists)
629
+ end
630
+ end
631
+
558
632
  # Process a check result, storing its data, inspecting its
559
633
  # contents, and taking the appropriate actions (eg. update the
560
634
  # event registry). The `@in_progress[:check_results]` counter is
@@ -579,7 +653,7 @@ module Sensu
579
653
  end
580
654
  check[:type] ||= STANDARD_CHECK_TYPE
581
655
  check[:origin] = result[:client] if check[:source]
582
- aggregate_check_result(client, check) if check[:aggregate]
656
+ aggregate_check_result(client, check) if check[:aggregates] || check[:aggregate]
583
657
  store_check_result(client, check) do
584
658
  create_event(client, check) do |event|
585
659
  event_bridges(event)
@@ -693,7 +767,7 @@ module Sensu
693
767
  def schedule_check_executions(checks)
694
768
  checks.each do |check|
695
769
  create_check_request = Proc.new do
696
- unless check_request_subdued?(check)
770
+ unless check_subdued?(check)
697
771
  publish_check_request(check)
698
772
  else
699
773
  @logger.info("check request was subdued", :check => check)
@@ -877,10 +951,14 @@ module Sensu
877
951
  time_since_last_execution = Time.now.to_i - check[:executed]
878
952
  if time_since_last_execution >= check[:ttl]
879
953
  client_name = result_key.split(":").first
880
- check[:output] = "Last check execution was "
881
- check[:output] << "#{time_since_last_execution} seconds ago"
882
- check[:status] = 1
883
- publish_check_result(client_name, check)
954
+ keepalive_event_exists?(client_name) do |event_exists|
955
+ unless event_exists
956
+ check[:output] = "Last check execution was "
957
+ check[:output] << "#{time_since_last_execution} seconds ago"
958
+ check[:status] = 1
959
+ publish_check_result(client_name, check)
960
+ end
961
+ end
884
962
  end
885
963
  else
886
964
  @redis.srem("ttl", result_key)
@@ -118,5 +118,69 @@ module Sensu
118
118
  end
119
119
  [substituted, unmatched_tokens]
120
120
  end
121
+
122
+ # Determine if the current time falls within a time window. The
123
+ # provided condition must have a `:begin` and `:end` time, eg.
124
+ # "11:30:00 PM", or `false` will be returned.
125
+ #
126
+ # @param condition [Hash]
127
+ # @option condition [String] :begin time.
128
+ # @option condition [String] :end time.
129
+ # @return [TrueClass, FalseClass]
130
+ def in_time_window?(condition)
131
+ if condition.has_key?(:begin) && condition.has_key?(:end)
132
+ begin_time = Time.parse(condition[:begin])
133
+ end_time = Time.parse(condition[:end])
134
+ if end_time < begin_time
135
+ if Time.now < end_time
136
+ begin_time = Time.parse("12:00:00 AM")
137
+ else
138
+ end_time = Time.parse("11:59:59 PM")
139
+ end
140
+ end
141
+ Time.now >= begin_time && Time.now <= end_time
142
+ else
143
+ false
144
+ end
145
+ end
146
+
147
+ # Determine if time window conditions for one or more days of the
148
+ # week are met. If a day of the week is provided, it can provide
149
+ # one or more conditions, each with a `:begin` and `:end` time,
150
+ # eg. "11:30:00 PM", or `false` will be returned.
151
+ #
152
+ # @param conditions [Hash]
153
+ # @option conditions [String] :days of the week.
154
+ # @return [TrueClass, FalseClass]
155
+ def in_time_windows?(conditions)
156
+ in_window = false
157
+ window_days = conditions[:days] || {}
158
+ if window_days[:all]
159
+ in_window = window_days[:all].any? do |condition|
160
+ in_time_window?(condition)
161
+ end
162
+ end
163
+ current_day = Time.now.strftime("%A").downcase.to_sym
164
+ if !in_window && window_days[current_day]
165
+ in_window = window_days[current_day].any? do |condition|
166
+ in_time_window?(condition)
167
+ end
168
+ end
169
+ in_window
170
+ end
171
+
172
+ # Determine if a check is subdued, by conditions set in the check
173
+ # definition. If any of the conditions are true, without an
174
+ # exception, the check is subdued.
175
+ #
176
+ # @param check [Hash] definition.
177
+ # @return [TrueClass, FalseClass]
178
+ def check_subdued?(check)
179
+ if check[:subdue]
180
+ in_time_windows?(check[:subdue])
181
+ else
182
+ false
183
+ end
184
+ end
121
185
  end
122
186
  end
data/sensu.gemspec CHANGED
@@ -15,12 +15,12 @@ Gem::Specification.new do |s|
15
15
  s.add_dependency "eventmachine", "1.2.0.1"
16
16
  s.add_dependency "sensu-json", "2.0.0"
17
17
  s.add_dependency "sensu-logger", "1.2.0"
18
- s.add_dependency "sensu-settings", "5.2.0"
18
+ s.add_dependency "sensu-settings", "9.1.0"
19
19
  s.add_dependency "sensu-extension", "1.5.0"
20
- s.add_dependency "sensu-extensions", "1.5.0"
20
+ s.add_dependency "sensu-extensions", "1.7.0"
21
21
  s.add_dependency "sensu-transport", "6.0.0"
22
22
  s.add_dependency "sensu-spawn", "2.2.0"
23
- s.add_dependency "sensu-redis", "1.4.0"
23
+ s.add_dependency "sensu-redis", "1.6.0"
24
24
  s.add_dependency "em-http-server", "0.1.8"
25
25
 
26
26
  s.add_development_dependency "rake", "10.5.0"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sensu
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.25.7
4
+ version: 0.26.0.beta
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Porter
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2016-08-09 00:00:00.000000000 Z
12
+ date: 2016-08-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: eventmachine
@@ -59,14 +59,14 @@ dependencies:
59
59
  requirements:
60
60
  - - '='
61
61
  - !ruby/object:Gem::Version
62
- version: 5.2.0
62
+ version: 9.1.0
63
63
  type: :runtime
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
66
66
  requirements:
67
67
  - - '='
68
68
  - !ruby/object:Gem::Version
69
- version: 5.2.0
69
+ version: 9.1.0
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: sensu-extension
72
72
  requirement: !ruby/object:Gem::Requirement
@@ -87,14 +87,14 @@ dependencies:
87
87
  requirements:
88
88
  - - '='
89
89
  - !ruby/object:Gem::Version
90
- version: 1.5.0
90
+ version: 1.7.0
91
91
  type: :runtime
92
92
  prerelease: false
93
93
  version_requirements: !ruby/object:Gem::Requirement
94
94
  requirements:
95
95
  - - '='
96
96
  - !ruby/object:Gem::Version
97
- version: 1.5.0
97
+ version: 1.7.0
98
98
  - !ruby/object:Gem::Dependency
99
99
  name: sensu-transport
100
100
  requirement: !ruby/object:Gem::Requirement
@@ -129,14 +129,14 @@ dependencies:
129
129
  requirements:
130
130
  - - '='
131
131
  - !ruby/object:Gem::Version
132
- version: 1.4.0
132
+ version: 1.6.0
133
133
  type: :runtime
134
134
  prerelease: false
135
135
  version_requirements: !ruby/object:Gem::Requirement
136
136
  requirements:
137
137
  - - '='
138
138
  - !ruby/object:Gem::Version
139
- version: 1.4.0
139
+ version: 1.6.0
140
140
  - !ruby/object:Gem::Dependency
141
141
  name: em-http-server
142
142
  requirement: !ruby/object:Gem::Requirement
@@ -239,6 +239,7 @@ files:
239
239
  - lib/sensu/api/routes/request.rb
240
240
  - lib/sensu/api/routes/resolve.rb
241
241
  - lib/sensu/api/routes/results.rb
242
+ - lib/sensu/api/routes/silenced.rb
242
243
  - lib/sensu/api/routes/stashes.rb
243
244
  - lib/sensu/api/utilities/publish_check_request.rb
244
245
  - lib/sensu/api/utilities/publish_check_result.rb
@@ -274,9 +275,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
274
275
  version: '0'
275
276
  required_rubygems_version: !ruby/object:Gem::Requirement
276
277
  requirements:
277
- - - ">="
278
+ - - ">"
278
279
  - !ruby/object:Gem::Version
279
- version: '0'
280
+ version: 1.3.1
280
281
  requirements: []
281
282
  rubyforge_project:
282
283
  rubygems_version: 2.6.3