sensu 0.25.7 → 0.26.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: 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