portertech-sensu 1.10.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 +7 -0
- data/CHANGELOG.md +961 -0
- data/MIT-LICENSE.txt +20 -0
- data/README.md +65 -0
- data/exe/sensu-api +10 -0
- data/exe/sensu-client +10 -0
- data/exe/sensu-install +195 -0
- data/exe/sensu-server +10 -0
- data/lib/sensu/api/http_handler.rb +434 -0
- data/lib/sensu/api/process.rb +79 -0
- data/lib/sensu/api/routes/aggregates.rb +196 -0
- data/lib/sensu/api/routes/checks.rb +44 -0
- data/lib/sensu/api/routes/clients.rb +171 -0
- data/lib/sensu/api/routes/events.rb +86 -0
- data/lib/sensu/api/routes/health.rb +45 -0
- data/lib/sensu/api/routes/info.rb +37 -0
- data/lib/sensu/api/routes/request.rb +44 -0
- data/lib/sensu/api/routes/resolve.rb +32 -0
- data/lib/sensu/api/routes/results.rb +153 -0
- data/lib/sensu/api/routes/settings.rb +23 -0
- data/lib/sensu/api/routes/silenced.rb +182 -0
- data/lib/sensu/api/routes/stashes.rb +107 -0
- data/lib/sensu/api/routes.rb +88 -0
- data/lib/sensu/api/utilities/filter_response_content.rb +44 -0
- data/lib/sensu/api/utilities/publish_check_request.rb +107 -0
- data/lib/sensu/api/utilities/publish_check_result.rb +39 -0
- data/lib/sensu/api/utilities/resolve_event.rb +29 -0
- data/lib/sensu/api/utilities/servers_info.rb +43 -0
- data/lib/sensu/api/utilities/transport_info.rb +43 -0
- data/lib/sensu/api/validators/check.rb +55 -0
- data/lib/sensu/api/validators/client.rb +35 -0
- data/lib/sensu/api/validators/invalid.rb +8 -0
- data/lib/sensu/cli.rb +69 -0
- data/lib/sensu/client/http_socket.rb +217 -0
- data/lib/sensu/client/process.rb +655 -0
- data/lib/sensu/client/socket.rb +207 -0
- data/lib/sensu/client/utils.rb +53 -0
- data/lib/sensu/client/validators/check.rb +53 -0
- data/lib/sensu/constants.rb +17 -0
- data/lib/sensu/daemon.rb +396 -0
- data/lib/sensu/sandbox.rb +19 -0
- data/lib/sensu/server/filter.rb +227 -0
- data/lib/sensu/server/handle.rb +201 -0
- data/lib/sensu/server/mutate.rb +92 -0
- data/lib/sensu/server/process.rb +1646 -0
- data/lib/sensu/server/socket.rb +54 -0
- data/lib/sensu/server/tessen.rb +170 -0
- data/lib/sensu/utilities.rb +398 -0
- data/lib/sensu.rb +3 -0
- data/sensu.gemspec +36 -0
- metadata +322 -0
data/lib/sensu/daemon.rb
ADDED
@@ -0,0 +1,396 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
|
3
|
+
gem "eventmachine", "1.2.7"
|
4
|
+
|
5
|
+
gem "portertech-sensu-json", "2.2.1"
|
6
|
+
gem "portertech-sensu-logger", "1.4.0"
|
7
|
+
gem "portertech-sensu-settings", "10.18.0"
|
8
|
+
gem "sensu-extension", "1.5.2"
|
9
|
+
gem "portertech-sensu-extensions", "1.12.0"
|
10
|
+
gem "sensu-transport", "8.3.0"
|
11
|
+
gem "portertech-sensu-spawn", "2.6.1"
|
12
|
+
gem "sensu-redis", "2.4.0"
|
13
|
+
|
14
|
+
require "time"
|
15
|
+
require "uri"
|
16
|
+
|
17
|
+
if RUBY_PLATFORM =~ /aix/ || RUBY_PLATFORM =~ /solaris/
|
18
|
+
require "em/pure_ruby"
|
19
|
+
end
|
20
|
+
|
21
|
+
require "sensu/json"
|
22
|
+
require "sensu/logger"
|
23
|
+
require "sensu/settings"
|
24
|
+
require "sensu/extensions"
|
25
|
+
require "sensu/transport"
|
26
|
+
require "sensu/spawn"
|
27
|
+
require "sensu/redis"
|
28
|
+
|
29
|
+
require "sensu/constants"
|
30
|
+
require "sensu/utilities"
|
31
|
+
require "sensu/cli"
|
32
|
+
|
33
|
+
module Sensu
|
34
|
+
module Daemon
|
35
|
+
include Utilities
|
36
|
+
|
37
|
+
attr_reader :start_time, :settings
|
38
|
+
|
39
|
+
# Initialize the Sensu process. Set the start time, initial
|
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.
|
45
|
+
#
|
46
|
+
# @param options [Hash]
|
47
|
+
def initialize(options={})
|
48
|
+
@start_time = Time.now.to_i
|
49
|
+
@state = :initializing
|
50
|
+
@timers = {:run => []}
|
51
|
+
unless EM::reactor_running?
|
52
|
+
EM::epoll
|
53
|
+
EM::set_max_timers(200000)
|
54
|
+
EM::error_handler do |error|
|
55
|
+
unexpected_error(error)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
setup_logger(options)
|
59
|
+
load_settings(options)
|
60
|
+
unless sensu_service_name == "api"
|
61
|
+
load_extensions(options)
|
62
|
+
setup_spawn
|
63
|
+
end
|
64
|
+
setup_process(options)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Handle an unexpected error. This method is used for EM global
|
68
|
+
# catch-all error handling, accepting an error object. Error
|
69
|
+
# handling is opt-in via a configuration option, e.g. `"sensu":
|
70
|
+
# {"global_error_handler": true}`. If a user does not opt-in, the
|
71
|
+
# provided error will be raised (uncaught). If a user opts-in via
|
72
|
+
# configuration, the error will be logged and ignored :itsfine:.
|
73
|
+
#
|
74
|
+
# @param error [Object]
|
75
|
+
def unexpected_error(error)
|
76
|
+
if @settings && @settings[:sensu][:global_error_handler]
|
77
|
+
backtrace = error.backtrace.join("\n")
|
78
|
+
if @logger
|
79
|
+
@logger.warn("global catch-all error handling enabled")
|
80
|
+
@logger.fatal("unexpected error - please address this immediately", {
|
81
|
+
:error => error.to_s,
|
82
|
+
:error_class => error.class,
|
83
|
+
:backtrace => backtrace
|
84
|
+
})
|
85
|
+
else
|
86
|
+
puts "global catch-all error handling enabled"
|
87
|
+
puts "unexpected error - please address this immediately: #{error.to_s}\n#{error.class}\n#{backtrace}"
|
88
|
+
end
|
89
|
+
else
|
90
|
+
raise error
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Set up the Sensu logger and its process signal traps for log
|
95
|
+
# rotation and debug log level toggling. This method creates the
|
96
|
+
# logger instance variable: `@logger`.
|
97
|
+
#
|
98
|
+
# https://github.com/sensu/sensu-logger
|
99
|
+
#
|
100
|
+
# @param options [Hash]
|
101
|
+
def setup_logger(options={})
|
102
|
+
@logger = Logger.get(options)
|
103
|
+
@logger.setup_signal_traps
|
104
|
+
end
|
105
|
+
|
106
|
+
# Log setting or extension loading notices, sensitive information
|
107
|
+
# is redacted.
|
108
|
+
#
|
109
|
+
# @param notices [Array] to be logged.
|
110
|
+
# @param level [Symbol] to log the notices at.
|
111
|
+
def log_notices(notices=[], level=:warn)
|
112
|
+
notices.each do |concern|
|
113
|
+
message = concern.delete(:message)
|
114
|
+
@logger.send(level, message, redact_sensitive(concern))
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Determine if the Sensu settings are valid, if there are load or
|
119
|
+
# validation errors, and immediately exit the process with the
|
120
|
+
# appropriate exit status code. This method is used to determine
|
121
|
+
# if the latest configuration changes are valid prior to
|
122
|
+
# restarting the Sensu service, triggered by a CLI argument, e.g.
|
123
|
+
# `--validate_config`.
|
124
|
+
#
|
125
|
+
# @param settings [Object]
|
126
|
+
def validate_settings!(settings)
|
127
|
+
if settings.errors.empty?
|
128
|
+
puts "configuration is valid"
|
129
|
+
exit
|
130
|
+
else
|
131
|
+
puts "configuration is invalid"
|
132
|
+
puts Sensu::JSON.dump({:errors => @settings.errors}, :pretty => true)
|
133
|
+
exit 2
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Print the Sensu settings (JSON) to STDOUT and immediately exit
|
138
|
+
# the process with the appropriate exit status code. This method
|
139
|
+
# is used while troubleshooting configuration issues, triggered by
|
140
|
+
# a CLI argument, e.g. `--print_config`. Sensu settings with
|
141
|
+
# sensitive values (e.g. passwords) are first redacted.
|
142
|
+
#
|
143
|
+
# @param settings [Object]
|
144
|
+
def print_settings!(settings)
|
145
|
+
redacted_settings = redact_sensitive(settings.to_hash)
|
146
|
+
@logger.warn("outputting compiled configuration and exiting")
|
147
|
+
puts Sensu::JSON.dump(redacted_settings, :pretty => true)
|
148
|
+
exit(settings.errors.empty? ? 0 : 2)
|
149
|
+
end
|
150
|
+
|
151
|
+
# Load Sensu settings. This method creates the settings instance
|
152
|
+
# variable: `@settings`. If the `validate_config` option is true,
|
153
|
+
# this method calls `validate_settings!()` to validate the latest
|
154
|
+
# compiled configuration settings and will then exit the process.
|
155
|
+
# If the `print_config` option is true, this method calls
|
156
|
+
# `print_settings!()` to output the compiled configuration
|
157
|
+
# settings and will then exit the process. If there are loading or
|
158
|
+
# validation errors, they will be logged (notices), and this
|
159
|
+
# method will exit(2) the process.
|
160
|
+
#
|
161
|
+
#
|
162
|
+
# https://github.com/sensu/sensu-settings
|
163
|
+
#
|
164
|
+
# @param options [Hash]
|
165
|
+
def load_settings(options={})
|
166
|
+
@settings = Settings.get(options)
|
167
|
+
validate_settings!(@settings) if options[:validate_config]
|
168
|
+
log_notices(@settings.warnings)
|
169
|
+
log_notices(@settings.errors, :fatal)
|
170
|
+
print_settings!(@settings) if options[:print_config]
|
171
|
+
unless @settings.errors.empty?
|
172
|
+
@logger.fatal("SENSU NOT RUNNING!")
|
173
|
+
exit 2
|
174
|
+
end
|
175
|
+
@settings.set_env!
|
176
|
+
end
|
177
|
+
|
178
|
+
# Load Sensu extensions and log any notices. Set the logger and
|
179
|
+
# settings for each extension instance. This method creates the
|
180
|
+
# extensions instance variable: `@extensions`.
|
181
|
+
#
|
182
|
+
# https://github.com/sensu/sensu-extensions
|
183
|
+
# https://github.com/sensu/sensu-extension
|
184
|
+
#
|
185
|
+
# @param options [Hash]
|
186
|
+
def load_extensions(options={})
|
187
|
+
extensions_options = options.merge(:extensions => @settings[:extensions])
|
188
|
+
@extensions = Extensions.get(extensions_options)
|
189
|
+
log_notices(@extensions.warnings)
|
190
|
+
extension_settings = @settings.to_hash.dup
|
191
|
+
@extensions.all.each do |extension|
|
192
|
+
extension.logger = @logger
|
193
|
+
extension.settings = extension_settings
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# Set up Sensu spawn, creating a worker to create, control, and
|
198
|
+
# limit spawned child processes. This method adjusts the
|
199
|
+
# EventMachine thread pool size to accommodate the concurrent
|
200
|
+
# process spawn limit and other Sensu process operations.
|
201
|
+
#
|
202
|
+
# https://github.com/sensu/sensu-spawn
|
203
|
+
def setup_spawn
|
204
|
+
@logger.info("configuring sensu spawn", :settings => @settings[:sensu][:spawn])
|
205
|
+
threadpool_size = @settings[:sensu][:spawn][:limit] + 10
|
206
|
+
@logger.debug("setting eventmachine threadpool size", :size => threadpool_size)
|
207
|
+
EM::threadpool_size = threadpool_size
|
208
|
+
Spawn.setup(@settings[:sensu][:spawn])
|
209
|
+
end
|
210
|
+
|
211
|
+
# Manage the current process, optionally daemonize and/or write
|
212
|
+
# the current process ID to a PID file.
|
213
|
+
#
|
214
|
+
# @param options [Hash]
|
215
|
+
def setup_process(options)
|
216
|
+
daemonize if options[:daemonize]
|
217
|
+
write_pid(options[:pid_file]) if options[:pid_file]
|
218
|
+
end
|
219
|
+
|
220
|
+
# Start the Sensu service and set the service state to `:running`.
|
221
|
+
# This method will likely be overridden by a subclass. Yield if a
|
222
|
+
# block is provided.
|
223
|
+
def start
|
224
|
+
@state = :running
|
225
|
+
yield if block_given?
|
226
|
+
end
|
227
|
+
|
228
|
+
# Pause the Sensu service and set the service state to `:paused`.
|
229
|
+
# This method will likely be overridden by a subclass.
|
230
|
+
def pause
|
231
|
+
@state = :paused
|
232
|
+
end
|
233
|
+
|
234
|
+
# Resume the paused Sensu service and set the service state to
|
235
|
+
# `:running`. This method will likely be overridden by a subclass.
|
236
|
+
def resume
|
237
|
+
@state = :running
|
238
|
+
end
|
239
|
+
|
240
|
+
# Stop the Sensu service and set the service state to `:stopped`.
|
241
|
+
# This method will likely be overridden by a subclass. This method
|
242
|
+
# should stop the EventMachine event loop.
|
243
|
+
def stop
|
244
|
+
@state = :stopped
|
245
|
+
@logger.warn("stopping reactor")
|
246
|
+
EM::stop_event_loop
|
247
|
+
end
|
248
|
+
|
249
|
+
# Set up process signal traps. This method uses the `STOP_SIGNALS`
|
250
|
+
# constant to determine which process signals will result in a
|
251
|
+
# graceful service stop. A periodic timer must be used to poll for
|
252
|
+
# received signals, as Mutex#lock cannot be used within the
|
253
|
+
# context of `trap()`.
|
254
|
+
def setup_signal_traps
|
255
|
+
@signals = []
|
256
|
+
STOP_SIGNALS.each do |signal|
|
257
|
+
Signal.trap(signal) do
|
258
|
+
@signals << signal
|
259
|
+
end
|
260
|
+
end
|
261
|
+
EM::PeriodicTimer.new(1) do
|
262
|
+
signal = @signals.shift
|
263
|
+
if STOP_SIGNALS.include?(signal)
|
264
|
+
@logger.warn("received signal", :signal => signal)
|
265
|
+
stop
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
# Set up the Sensu transport connection. Sensu uses a transport
|
271
|
+
# API, allowing it to use various message brokers. By default,
|
272
|
+
# Sensu will use the built-in "rabbitmq" transport. The Sensu
|
273
|
+
# service will stop gracefully in the event of a transport error,
|
274
|
+
# and pause/resume in the event of connectivity issues. This
|
275
|
+
# method creates the transport instance variable: `@transport`.
|
276
|
+
#
|
277
|
+
# https://github.com/sensu/sensu-transport
|
278
|
+
#
|
279
|
+
# @yield [Object] passes initialized and connected Transport
|
280
|
+
# connection object to the callback/block.
|
281
|
+
def setup_transport
|
282
|
+
transport_name = @settings[:transport][:name]
|
283
|
+
transport_settings = @settings[transport_name]
|
284
|
+
@logger.debug("connecting to transport", {
|
285
|
+
:name => transport_name,
|
286
|
+
:settings => transport_settings
|
287
|
+
})
|
288
|
+
Transport.logger = @logger
|
289
|
+
Transport.connect(transport_name, transport_settings) do |connection|
|
290
|
+
@transport = connection
|
291
|
+
@transport.on_error do |error|
|
292
|
+
@logger.error("transport connection error", :error => error.to_s)
|
293
|
+
if @settings[:transport][:reconnect_on_error]
|
294
|
+
@transport.reconnect
|
295
|
+
else
|
296
|
+
stop
|
297
|
+
end
|
298
|
+
end
|
299
|
+
@transport.before_reconnect do
|
300
|
+
unless testing?
|
301
|
+
@logger.warn("reconnecting to transport")
|
302
|
+
pause
|
303
|
+
end
|
304
|
+
end
|
305
|
+
@transport.after_reconnect do
|
306
|
+
@logger.info("reconnected to transport")
|
307
|
+
resume
|
308
|
+
end
|
309
|
+
yield(@transport) if block_given?
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
# Set up the Redis connection. Sensu uses Redis as a data store,
|
314
|
+
# to store the client registry, current events, etc. The Sensu
|
315
|
+
# service will stop gracefully in the event of a Redis error, and
|
316
|
+
# pause/resume in the event of connectivity issues. This method
|
317
|
+
# creates the Redis instance variable: `@redis`.
|
318
|
+
#
|
319
|
+
# https://github.com/sensu/sensu-redis
|
320
|
+
#
|
321
|
+
# @yield [Object] passes initialized and connected Redis
|
322
|
+
# connection object to the callback/block.
|
323
|
+
def setup_redis
|
324
|
+
@logger.debug("connecting to redis", :settings => @settings[:redis])
|
325
|
+
Redis.logger = @logger
|
326
|
+
Redis.connect(@settings[:redis]) do |connection|
|
327
|
+
@redis = connection
|
328
|
+
@redis.on_error do |error|
|
329
|
+
@logger.error("redis connection error", :error => error.to_s)
|
330
|
+
end
|
331
|
+
@redis.before_reconnect do
|
332
|
+
unless testing?
|
333
|
+
@logger.warn("reconnecting to redis")
|
334
|
+
pause
|
335
|
+
end
|
336
|
+
end
|
337
|
+
@redis.after_reconnect do
|
338
|
+
@logger.info("reconnected to redis")
|
339
|
+
resume
|
340
|
+
end
|
341
|
+
yield(@redis) if block_given?
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
private
|
346
|
+
|
347
|
+
# Get the Sensu service name.
|
348
|
+
#
|
349
|
+
# @return [String] Sensu service name.
|
350
|
+
def sensu_service_name
|
351
|
+
File.basename($0).split("-").last
|
352
|
+
end
|
353
|
+
|
354
|
+
# Write the current process ID (PID) to a file (PID file). This
|
355
|
+
# method will cause the Sensu service to exit (2) if the PID file
|
356
|
+
# cannot be written to.
|
357
|
+
#
|
358
|
+
# @param file [String] to write the current PID to.
|
359
|
+
def write_pid(file)
|
360
|
+
begin
|
361
|
+
File.open(file, "w") do |pid_file|
|
362
|
+
pid_file.puts(Process.pid)
|
363
|
+
end
|
364
|
+
rescue
|
365
|
+
@logger.fatal("could not write to pid file", :pid_file => file)
|
366
|
+
@logger.fatal("SENSU NOT RUNNING!")
|
367
|
+
exit 2
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
# Daemonize the current process. Seed the random number generator,
|
372
|
+
# fork (& exit), detach from controlling terminal, ignore SIGHUP,
|
373
|
+
# fork (& exit), use root '/' as the current working directory,
|
374
|
+
# and close STDIN/OUT/ERR since the process is no longer attached
|
375
|
+
# to a terminal.
|
376
|
+
def daemonize
|
377
|
+
Kernel.srand
|
378
|
+
exit if Kernel.fork
|
379
|
+
unless Process.setsid
|
380
|
+
@logger.fatal("cannot detach from controlling terminal")
|
381
|
+
@logger.fatal("SENSU NOT RUNNING!")
|
382
|
+
exit 2
|
383
|
+
end
|
384
|
+
Signal.trap("SIGHUP", "IGNORE")
|
385
|
+
exit if Kernel.fork
|
386
|
+
Dir.chdir("/")
|
387
|
+
ObjectSpace.each_object(IO) do |io|
|
388
|
+
unless [STDIN, STDOUT, STDERR].include?(io)
|
389
|
+
begin
|
390
|
+
io.close unless io.closed?
|
391
|
+
rescue; end
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|
395
|
+
end
|
396
|
+
end
|
@@ -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
|
@@ -0,0 +1,227 @@
|
|
1
|
+
module Sensu
|
2
|
+
module Server
|
3
|
+
module Filter
|
4
|
+
# Determine if an event handler is silenced.
|
5
|
+
#
|
6
|
+
# @param handler [Hash] definition.
|
7
|
+
# @param event [Hash]
|
8
|
+
# @return [TrueClass, FalseClass]
|
9
|
+
def handler_silenced?(handler, event)
|
10
|
+
event[:silenced] && !handler[:handle_silenced]
|
11
|
+
end
|
12
|
+
|
13
|
+
# Determine if handling is disabled for an event. Check
|
14
|
+
# definitions can disable event handling with an attribute,
|
15
|
+
# `:handle`, by setting it to `false`.
|
16
|
+
#
|
17
|
+
# @param event [Hash]
|
18
|
+
# @return [TrueClass, FalseClass]
|
19
|
+
def handling_disabled?(event)
|
20
|
+
event[:check][:handle] == false
|
21
|
+
end
|
22
|
+
|
23
|
+
# Determine if an event with an action should be handled. An
|
24
|
+
# event action of `:flapping` indicates that the event state is
|
25
|
+
# flapping, and the event should not be handled unless its
|
26
|
+
# handler has `:handle_flapping` set to `true`.
|
27
|
+
#
|
28
|
+
# @param handler [Hash] definition.
|
29
|
+
# @param event [Hash]
|
30
|
+
# @return [TrueClass, FalseClass]
|
31
|
+
def handle_action?(handler, event)
|
32
|
+
event[:action] != :flapping ||
|
33
|
+
(event[:action] == :flapping && !!handler[:handle_flapping])
|
34
|
+
end
|
35
|
+
|
36
|
+
# Determine if an event with a check severity will be handled.
|
37
|
+
# Event handlers can specify the check severities they will
|
38
|
+
# handle, using the definition attribute `:severities`. The
|
39
|
+
# possible severities are "ok", "warning", "critical", and
|
40
|
+
# "unknown". Handler severity filtering is bypassed when the
|
41
|
+
# event `:action` is `:resolve` and a previous check history
|
42
|
+
# status identifies a severity specified in the handler
|
43
|
+
# definition. It's possible for a check history status of 0 to
|
44
|
+
# have had the flapping action, so we are unable to consider
|
45
|
+
# every past 0 to indicate a resolution.
|
46
|
+
#
|
47
|
+
# @param handler [Hash] definition.
|
48
|
+
# @param event [Hash]
|
49
|
+
# @return [TrueClass, FalseClass]
|
50
|
+
def handle_severity?(handler, event)
|
51
|
+
if handler.has_key?(:severities)
|
52
|
+
case event[:action]
|
53
|
+
when :resolve
|
54
|
+
event[:check][:history].any? do |status|
|
55
|
+
severity = SEVERITIES[status.to_i] || "unknown"
|
56
|
+
handler[:severities].include?(severity)
|
57
|
+
end
|
58
|
+
else
|
59
|
+
severity = SEVERITIES[event[:check][:status]] || "unknown"
|
60
|
+
handler[:severities].include?(severity)
|
61
|
+
end
|
62
|
+
else
|
63
|
+
true
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Determine if a filter is to be evoked for the current time. A
|
68
|
+
# filter can be configured with a time window defining when it
|
69
|
+
# is to be evoked, e.g. Monday through Friday, 9-5.
|
70
|
+
#
|
71
|
+
# @param filter [Hash] definition.
|
72
|
+
# @return [TrueClass, FalseClass]
|
73
|
+
def in_filter_time_windows?(filter)
|
74
|
+
if filter[:when]
|
75
|
+
in_time_windows?(filter[:when])
|
76
|
+
else
|
77
|
+
true
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Determine if an event is filtered by a native filter.
|
82
|
+
#
|
83
|
+
# @param filter_name [String]
|
84
|
+
# @param event [Hash]
|
85
|
+
# @yield [filtered] callback/block called with a single
|
86
|
+
# parameter to indicate if the event was filtered.
|
87
|
+
# @yieldparam filtered [TrueClass,FalseClass] indicating if the
|
88
|
+
# event was filtered.
|
89
|
+
# @yieldparam filter_name [String] name of the filter being evaluated
|
90
|
+
def native_filter(filter_name, event)
|
91
|
+
filter = @settings[:filters][filter_name]
|
92
|
+
if in_filter_time_windows?(filter)
|
93
|
+
matched = attributes_match?(event, filter[:attributes])
|
94
|
+
yield(filter[:negate] ? matched : !matched, filter_name)
|
95
|
+
else
|
96
|
+
yield(false, filter_name)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Determine if an event is filtered by a filter extension.
|
101
|
+
#
|
102
|
+
# @param filter_name [String]
|
103
|
+
# @param event [Hash]
|
104
|
+
# @yield [filtered] callback/block called with a single
|
105
|
+
# parameter to indicate if the event was filtered.
|
106
|
+
# @yieldparam filtered [TrueClass,FalseClass] indicating if the
|
107
|
+
# event was filtered.
|
108
|
+
# @yieldparam filter_name [String] name of the filter being evaluated
|
109
|
+
def extension_filter(filter_name, event)
|
110
|
+
extension = @extensions[:filters][filter_name]
|
111
|
+
if in_filter_time_windows?(extension.definition)
|
112
|
+
extension.safe_run(event) do |output, status|
|
113
|
+
yield(status == 0, filter_name)
|
114
|
+
end
|
115
|
+
else
|
116
|
+
yield(false, filter_name)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Determine if an event is filtered by an event filter, native
|
121
|
+
# or extension. This method first checks for the existence of a
|
122
|
+
# native filter, then checks for an extension if a native filter
|
123
|
+
# is not defined. The provided callback is called with a single
|
124
|
+
# parameter, indicating if the event was filtered by a filter.
|
125
|
+
# If a filter does not exist for the provided name, the event is
|
126
|
+
# not filtered.
|
127
|
+
#
|
128
|
+
# @param filter_name [String]
|
129
|
+
# @param event [Hash]
|
130
|
+
# @yield [filtered] callback/block called with a single
|
131
|
+
# parameter to indicate if the event was filtered.
|
132
|
+
# @yieldparam filtered [TrueClass,FalseClass] indicating if the
|
133
|
+
# event was filtered.
|
134
|
+
# @yieldparam filter_name [String] name of the filter being evaluated
|
135
|
+
def event_filter(filter_name, event)
|
136
|
+
case
|
137
|
+
when @settings.filter_exists?(filter_name)
|
138
|
+
native_filter(filter_name, event) do |filtered|
|
139
|
+
yield(filtered, filter_name)
|
140
|
+
end
|
141
|
+
when @extensions.filter_exists?(filter_name)
|
142
|
+
extension_filter(filter_name, event) do |filtered|
|
143
|
+
yield(filtered, filter_name)
|
144
|
+
end
|
145
|
+
else
|
146
|
+
@logger.error("unknown filter", :filter_name => filter_name)
|
147
|
+
yield(false, filter_name)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Determine if an event is filtered for a handler. If a handler
|
152
|
+
# specifies one or more filters, via `:filters` or `:filter`,
|
153
|
+
# the `event_filter()` method is called for each of them. If any
|
154
|
+
# of the filters return `true`, the event is filtered for the
|
155
|
+
# handler. If no filters are defined in the handler definition,
|
156
|
+
# the event is not filtered.
|
157
|
+
#
|
158
|
+
# @param handler [Hash] definition.
|
159
|
+
# @param event [Hash]
|
160
|
+
# @yield [filtered] callback/block called with a single
|
161
|
+
# parameter to indicate if the event was filtered.
|
162
|
+
# @yieldparam filtered [TrueClass,FalseClass] indicating if the
|
163
|
+
# event was filtered.
|
164
|
+
def event_filtered?(handler, event)
|
165
|
+
if handler.has_key?(:filters) || handler.has_key?(:filter)
|
166
|
+
filter_list = Array(handler[:filters] || handler[:filter]).dup
|
167
|
+
filter = Proc.new do |filter_list|
|
168
|
+
filter_name = filter_list.shift
|
169
|
+
if filter_name.nil?
|
170
|
+
yield(false)
|
171
|
+
else
|
172
|
+
event_filter(filter_name, event) do |filtered|
|
173
|
+
filtered ? yield(true, filter_name) : EM.next_tick { filter.call(filter_list) }
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
filter.call(filter_list)
|
178
|
+
else
|
179
|
+
yield(false)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Attempt to filter an event for a handler. This method will
|
184
|
+
# check to see if handling is disabled, if the event action is
|
185
|
+
# handled, if the event check severity is handled, if the
|
186
|
+
# handler is subdued, and if the event is filtered by any of the
|
187
|
+
# filters specified in the handler definition.
|
188
|
+
#
|
189
|
+
# @param handler [Hash] definition.
|
190
|
+
# @param event [Hash]
|
191
|
+
# @yield [event] callback/block called if the event has not been
|
192
|
+
# filtered.
|
193
|
+
# @yieldparam event [Hash]
|
194
|
+
def filter_event(handler, event)
|
195
|
+
handler_info = case handler[:type]
|
196
|
+
when "extension" then handler.definition
|
197
|
+
else handler
|
198
|
+
end
|
199
|
+
details = {:handler => handler_info, :event => event}
|
200
|
+
filter_message = case
|
201
|
+
when handling_disabled?(event)
|
202
|
+
"event handling disabled for event"
|
203
|
+
when !handle_action?(handler, event)
|
204
|
+
"handler does not handle action"
|
205
|
+
when !handle_severity?(handler, event)
|
206
|
+
"handler does not handle event severity"
|
207
|
+
when handler_silenced?(handler, event)
|
208
|
+
"handler is silenced"
|
209
|
+
end
|
210
|
+
if filter_message
|
211
|
+
@logger.info(filter_message, details)
|
212
|
+
@in_progress[:events] -= 1 if @in_progress
|
213
|
+
else
|
214
|
+
event_filtered?(handler, event) do |filtered, filter_name|
|
215
|
+
unless filtered
|
216
|
+
yield(event)
|
217
|
+
else
|
218
|
+
details[:filter] = filter_name
|
219
|
+
@logger.info("event was filtered", details)
|
220
|
+
@in_progress[:events] -= 1 if @in_progress
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|