sensu 0.17.0.beta → 0.17.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,309 @@
1
+ require "sensu/server/sandbox"
2
+
3
+ module Sensu
4
+ module Server
5
+ module Filter
6
+ # Determine if a period of time (window) is subdued. The
7
+ # provided condition must have a `:begin` and `:end` time, eg.
8
+ # "11:30:00 PM", or `false` will be returned.
9
+ #
10
+ # @param condition [Hash]
11
+ # @option condition [String] :begin time.
12
+ # @option condition [String] :end time.
13
+ # @return [TrueClass, FalseClass]
14
+ def subdue_time?(condition)
15
+ if condition.has_key?(:begin) && condition.has_key?(:end)
16
+ begin_time = Time.parse(condition[:begin])
17
+ end_time = Time.parse(condition[:end])
18
+ if end_time < begin_time
19
+ if Time.now < end_time
20
+ begin_time = Time.parse("12:00:00 AM")
21
+ else
22
+ end_time = Time.parse("11:59:59 PM")
23
+ end
24
+ end
25
+ Time.now >= begin_time && Time.now <= end_time
26
+ else
27
+ false
28
+ end
29
+ end
30
+
31
+ # Determine if the current day is subdued. The provided
32
+ # condition must have a list of `:days`, or false will be
33
+ # returned.
34
+ #
35
+ # @param condition [Hash]
36
+ # @option condition [Array] :days of the week to subdue.
37
+ # @return [TrueClass, FalseClass]
38
+ def subdue_days?(condition)
39
+ if condition.has_key?(:days)
40
+ days = condition[:days].map(&:downcase)
41
+ days.include?(Time.now.strftime("%A").downcase)
42
+ else
43
+ false
44
+ end
45
+ end
46
+
47
+ # Determine if there is an exception a period of time (window)
48
+ # that is subdued. The provided condition must have an
49
+ # `:exception`, containing one or more `:begin` and `:end`
50
+ # times, eg. "11:30:00 PM", or `false` will be returned. If
51
+ # there are any exceptions to a subdued period of time, `true`
52
+ # will be returned.
53
+ #
54
+ # @param condition [Hash]
55
+ # @option condition [Hash] :exceptions array of `:begin` and
56
+ # `:end` times.
57
+ # @return [TrueClass, FalseClass]
58
+ def subdue_exception?(condition)
59
+ if condition.has_key?(:exceptions)
60
+ condition[:exceptions].any? do |exception|
61
+ Time.now >= Time.parse(exception[:begin]) && Time.now <= Time.parse(exception[:end])
62
+ end
63
+ else
64
+ false
65
+ end
66
+ end
67
+
68
+ # Determine if an action is subdued and if there is an
69
+ # exception. This method makes use of `subdue_time?()`,
70
+ # `subdue_days?()`, and subdue_exception?().
71
+ #
72
+ # @param condition [Hash]
73
+ # @return [TrueClass, FalseClass]
74
+ def action_subdued?(condition)
75
+ subdued = subdue_time?(condition) || subdue_days?(condition)
76
+ subdued && !subdue_exception?(condition)
77
+ end
78
+
79
+ # Determine if an event handler is subdued, by conditions set in
80
+ # the check and/or the handler definition. If any of the
81
+ # conditions are true, without an exception, the handler is
82
+ # subdued.
83
+ #
84
+ # @param handler [Hash] definition.
85
+ # @param event [Hash] data possibly containing subdue
86
+ # conditions.
87
+ # @return [TrueClass, FalseClass]
88
+ def handler_subdued?(handler, event)
89
+ subdued = []
90
+ if handler[:subdue]
91
+ subdued << action_subdued?(handler[:subdue])
92
+ end
93
+ check = event[:check]
94
+ if check[:subdue] && check[:subdue][:at] != "publisher"
95
+ subdued << action_subdued?(check[:subdue])
96
+ end
97
+ subdued.any?
98
+ end
99
+
100
+ # Determine if a check request is subdued, by conditions set in
101
+ # the check definition. If any of the conditions are true,
102
+ # without an exception, the check request is subdued.
103
+ #
104
+ # @param check [Hash] definition.
105
+ # @return [TrueClass, FalseClass]
106
+ def check_request_subdued?(check)
107
+ if check[:subdue] && check[:subdue][:at] == "publisher"
108
+ action_subdued?(check[:subdue])
109
+ else
110
+ false
111
+ end
112
+ end
113
+
114
+ # Determine if handling is disabled for an event. Check
115
+ # definitions can disable event handling with an attribute,
116
+ # `:handle`, by setting it to `false`.
117
+ #
118
+ # @param event [Hash]
119
+ # @return [TrueClass, FalseClass]
120
+ def handling_disabled?(event)
121
+ event[:check][:handle] == false
122
+ end
123
+
124
+ # Determine if an event with an action should be handled. An
125
+ # event action of `:flapping` indicates that the event state is
126
+ # flapping, and the event should not be handled unless its
127
+ # handler has `:handle_flapping` set to `true`.
128
+ #
129
+ # @param handler [Hash] definition.
130
+ # @param event [Hash]
131
+ # @return [TrueClass, FalseClass]
132
+ def handle_action?(handler, event)
133
+ event[:action] != :flapping ||
134
+ (event[:action] == :flapping && !!handler[:handle_flapping])
135
+ end
136
+
137
+ # Determine if an event with a check severity will be handled.
138
+ # Event handlers can specify the check severities they will
139
+ # handle, using the definition attribute `:severities`. The
140
+ # possible severities are "ok", "warning", "critical", and
141
+ # "unknown". Handler severity filtering is bypassed when the
142
+ # event `:action` is `:resolve`, if the check history contains
143
+ # one of the specified severities since the last OK result.
144
+ #
145
+ # @param handler [Hash] definition.
146
+ # @param event [Hash]
147
+ # @return [TrueClass, FalseClass]
148
+ def handle_severity?(handler, event)
149
+ if handler.has_key?(:severities)
150
+ case event[:action]
151
+ when :resolve
152
+ event[:check][:history].reverse[1..-1].any? do |status|
153
+ if status.to_i == 0
154
+ break false
155
+ end
156
+ severity = SEVERITIES[status.to_i] || "unknown"
157
+ handler[:severities].include?(severity)
158
+ end
159
+ else
160
+ severity = SEVERITIES[event[:check][:status]] || "unknown"
161
+ handler[:severities].include?(severity)
162
+ end
163
+ else
164
+ true
165
+ end
166
+ end
167
+
168
+ # Ruby eval() a string containing an expression, within the
169
+ # scope/context of a sandbox. This method is for filter
170
+ # attribute values starting with "eval:", with the Ruby
171
+ # expression following the colon. A single variable is provided
172
+ # to the expression, `value`, equal to the corresponding event
173
+ # attribute value. The expression is expected to return a
174
+ # boolean value.
175
+ #
176
+ # @param raw_eval_string [String] containing the Ruby
177
+ # expression to be evaluated.
178
+ # @param value [Object] of the corresponding event attribute.
179
+ # @return [TrueClass, FalseClass]
180
+ def eval_attribute_value(raw_eval_string, value)
181
+ begin
182
+ eval_string = raw_eval_string.gsub(/^eval:(\s+)?/, "")
183
+ !!Sandbox.eval(eval_string, value)
184
+ rescue => error
185
+ @logger.error("filter attribute eval error", {
186
+ :raw_eval_string => raw_eval_string,
187
+ :value => value,
188
+ :error => error.to_s
189
+ })
190
+ false
191
+ end
192
+ end
193
+
194
+ # Determine if all filter attribute values match those of the
195
+ # corresponding event attributes. Attributes match if the value
196
+ # objects are equivalent, are both hashes with matching
197
+ # key/value pairs (recursive), have equal string values, or
198
+ # evaluate to true (Ruby eval).
199
+ #
200
+ # @param hash_one [Hash]
201
+ # @param hash_two [Hash]
202
+ # @return [TrueClass, FalseClass]
203
+ def filter_attributes_match?(hash_one, hash_two)
204
+ hash_one.all? do |key, value_one|
205
+ value_two = hash_two[key]
206
+ case
207
+ when value_one == value_two
208
+ true
209
+ when value_one.is_a?(Hash) && value_two.is_a?(Hash)
210
+ filter_attributes_match?(value_one, value_two)
211
+ when hash_one[key].to_s == hash_two[key].to_s
212
+ true
213
+ when value_one.is_a?(String) && value_one.start_with?("eval:")
214
+ eval_attribute_value(value_one, value_two)
215
+ else
216
+ false
217
+ end
218
+ end
219
+ end
220
+
221
+ # Determine if an event is filtered by an event filter, standard
222
+ # or extension. This method first checks for the existence of a
223
+ # standard filter, then checks for an extension if a standard
224
+ # filter is not defined. The provided callback is called with a
225
+ # single parameter, indicating if the event was filtered by a
226
+ # filter. If a filter does not exist for the provided name, the
227
+ # event is not filtered.
228
+ #
229
+ # @param filter_name [String]
230
+ # @param event [Hash]
231
+ # @param callback [Proc]
232
+ def event_filter(filter_name, event, &callback)
233
+ case
234
+ when @settings.filter_exists?(filter_name)
235
+ filter = @settings[:filters][filter_name]
236
+ matched = filter_attributes_match?(filter[:attributes], event)
237
+ callback.call(filter[:negate] ? matched : !matched)
238
+ when @extensions.filter_exists?(filter_name)
239
+ extension = @extensions[:filters][filter_name]
240
+ extension.safe_run(event) do |output, status|
241
+ callback.call(status == 0)
242
+ end
243
+ else
244
+ @logger.error("unknown filter", :filter_name => filter_name)
245
+ callback.call(false)
246
+ end
247
+ end
248
+
249
+ # Determine if an event is filtered for a handler. If a handler
250
+ # specifies one or more filters, via `:filters` or `:filter`,
251
+ # the `event_filter()` method is called for each of them. If any
252
+ # of the filters return `true`, the event is filtered for the
253
+ # handler. If no filters are defined in the handler definition,
254
+ # the event is not filtered.
255
+ #
256
+ # @param handler [Hash] definition.
257
+ # @param event [Hash]
258
+ # @param callback [Proc]
259
+ def event_filtered?(handler, event, &callback)
260
+ if handler.has_key?(:filters) || handler.has_key?(:filter)
261
+ filter_list = Array(handler[:filters] || handler[:filter])
262
+ filter_results = EM::Iterator.new(filter_list)
263
+ run_filters = Proc.new do |filter_name, iterator|
264
+ event_filter(filter_name, event) do |filtered|
265
+ iterator.return(filtered)
266
+ end
267
+ end
268
+ filtered = Proc.new do |results|
269
+ callback.call(results.any?)
270
+ end
271
+ filter_results.map(run_filters, filtered)
272
+ else
273
+ callback.call(false)
274
+ end
275
+ end
276
+
277
+ # Attempt to filter an event for a handler. This method will
278
+ # check to see if handling is disabled, if the event action is
279
+ # handled, if the event check severity is handled, if the
280
+ # handler is sbuded, and if the event is filtered by any of the
281
+ # filters specified in the handler definition.
282
+ #
283
+ # @param handler [Hash] definition.
284
+ # @param event [Hash]
285
+ # @param callback [Proc]
286
+ def filter_event(handler, event, &callback)
287
+ details = {:handler => handler, :event => event}
288
+ if handling_disabled?(event)
289
+ @logger.info("event handling disabled for event", details)
290
+ elsif !handle_action?(handler, event)
291
+ @logger.info("handler does not handle action", details)
292
+ elsif !handle_severity?(handler, event)
293
+ @logger.info("handler does not handle event severity", details)
294
+ elsif handler_subdued?(handler, event)
295
+ @logger.info("handler is subdued", details)
296
+ else
297
+ event_filtered?(handler, event) do |filtered|
298
+ unless filtered
299
+ callback.call(event)
300
+ else
301
+ @logger.info("event was filtered", details)
302
+ @handling_event_count -= 1 if @handling_event_count
303
+ end
304
+ end
305
+ end
306
+ end
307
+ end
308
+ end
309
+ end
@@ -0,0 +1,168 @@
1
+ require "sensu/server/socket"
2
+
3
+ module Sensu
4
+ module Server
5
+ module Handle
6
+ # Create a handler error callback, for logging the error and
7
+ # decrementing the `@handling_event_count` by `1`.
8
+ #
9
+ # @param handler [Object]
10
+ # @param event_data [Object]
11
+ # @return [Proc] error callback.
12
+ def handler_error(handler, event_data)
13
+ Proc.new do |error|
14
+ @logger.error("handler error", {
15
+ :handler => handler,
16
+ :event_data => event_data,
17
+ :error => error.to_s
18
+ })
19
+ @handling_event_count -= 1 if @handling_event_count
20
+ end
21
+ end
22
+
23
+ # Execute a pipe event handler, using the defined handler
24
+ # command to spawn a process, passing it event data via STDIN.
25
+ # Log the handler output lines and decrement the
26
+ # `@handling_event_count` by `1` when the handler executes
27
+ # successfully.
28
+ #
29
+ # @param handler [Hash] definition.
30
+ # @param event_data [Object] provided to the spawned handler
31
+ # process via STDIN.
32
+ def pipe_handler(handler, event_data)
33
+ options = {:data => event_data, :timeout => handler[:timeout]}
34
+ Spawn.process(handler[:command], options) do |output, status|
35
+ @logger.info("handler output", {
36
+ :handler => handler,
37
+ :output => output.lines
38
+ })
39
+ @handling_event_count -= 1 if @handling_event_count
40
+ end
41
+ end
42
+
43
+ # Connect to a TCP socket and transmit event data to it, then
44
+ # close the connection. The `Sensu::Server::Socket` connection
45
+ # handler is used for the socket. The socket timeouts are
46
+ # configurable via the handler definition, `:timeout`. The
47
+ # `handler_error()` method is used to create the `on_error`
48
+ # callback for the connection handler. The `on_error` callback
49
+ # is call in the event of any error(s). The
50
+ # `@handling_event_count` is decremented by `1` when the data is
51
+ # transmitted successfully, `on_success`.
52
+ #
53
+ # @param handler [Hash] definition.
54
+ # @param event_data [Object] to transmit to the TCP socket.
55
+ def tcp_handler(handler, event_data)
56
+ on_error = handler_error(handler, event_data)
57
+ begin
58
+ EM::connect(handler[:socket][:host], handler[:socket][:port], Socket) do |socket|
59
+ socket.on_success = Proc.new do
60
+ @handling_event_count -= 1 if @handling_event_count
61
+ end
62
+ socket.on_error = on_error
63
+ timeout = handler[:timeout] || 10
64
+ socket.pending_connect_timeout = timeout
65
+ socket.comm_inactivity_timeout = timeout
66
+ socket.send_data(event_data.to_s)
67
+ socket.close_connection_after_writing
68
+ end
69
+ rescue => error
70
+ on_error.call(error)
71
+ end
72
+ end
73
+
74
+ # Transmit event data to a UDP socket, then close the
75
+ # connection. The `@handling_event_count` is decremented by `1`
76
+ # when the data is assumed to have been transmitted.
77
+ #
78
+ # @param handler [Hash] definition.
79
+ # @param event_data [Object] to transmit to the UDP socket.
80
+ def udp_handler(handler, event_data)
81
+ begin
82
+ EM::open_datagram_socket("0.0.0.0", 0, nil) do |socket|
83
+ socket.send_datagram(event_data.to_s, handler[:socket][:host], handler[:socket][:port])
84
+ socket.close_connection_after_writing
85
+ @handling_event_count -= 1 if @handling_event_count
86
+ end
87
+ rescue => error
88
+ handler_error(handler, event_data).call(error)
89
+ end
90
+ end
91
+
92
+ # Publish event data to a Sensu transport pipe. Event data that
93
+ # is `nil` or empty will not be published, to prevent transport
94
+ # errors. The `@handling_event_count` is decremented by `1`,
95
+ # even if the event data is not published.
96
+ #
97
+ # @param handler [Hash] definition.
98
+ # @param event_data [Object] to publish to the transport pipe.
99
+ def transport_handler(handler, event_data)
100
+ unless event_data.nil? || event_data.empty?
101
+ pipe = handler[:pipe]
102
+ pipe_options = pipe[:options] || {}
103
+ @transport.publish(pipe[:type].to_sym, pipe[:name], event_data, pipe_options) do |info|
104
+ if info[:error]
105
+ handler_error(handler, event_data).call(info[:error])
106
+ end
107
+ end
108
+ end
109
+ @handling_event_count -= 1 if @handling_event_count
110
+ end
111
+
112
+ # Run a handler extension, within the Sensu EventMachine reactor
113
+ # (event loop). The extension API `safe_run()` method is used to
114
+ # guard against most errors. The `safe_run()` callback is always
115
+ # called, logging the extension run output and status, and
116
+ # decrementing the `@handling_event_count` by `1`.
117
+ #
118
+ # @param handler [Hash] definition.
119
+ # @param event_data [Object] to pass to the handler extension.
120
+ def handler_extension(handler, event_data)
121
+ handler.safe_run(event_data) do |output, status|
122
+ @logger.info("handler extension output", {
123
+ :extension => handler.definition,
124
+ :output => output,
125
+ :status => status
126
+ })
127
+ @handling_event_count -= 1 if @handling_event_count
128
+ end
129
+ end
130
+
131
+ # Route the event data to the appropriate handler type method.
132
+ # Routing is done using the handler definition, `:type`.
133
+ #
134
+ # @param handler [Hash] definition.
135
+ # @param event_data [Object] to pass to the handler type method.
136
+ def handler_type_router(handler, event_data)
137
+ case handler[:type]
138
+ when "pipe"
139
+ pipe_handler(handler, event_data)
140
+ when "tcp"
141
+ tcp_handler(handler, event_data)
142
+ when "udp"
143
+ udp_handler(handler, event_data)
144
+ when "transport"
145
+ transport_handler(handler, event_data)
146
+ when "extension"
147
+ handler_extension(handler, event_data)
148
+ end
149
+ end
150
+
151
+ # Handle an event, providing event data to an event handler.
152
+ # This method logs event data and the handler definition at the
153
+ # debug log level, then calls the `handler_type_router()`
154
+ # method.
155
+ #
156
+ # @param handler [Hash] definition.
157
+ # @param event_data [Object] to pass to an event handler.
158
+ def handle_event(handler, event_data)
159
+ definition = handler.is_a?(Hash) ? handler : handler.definition
160
+ @logger.debug("handling event", {
161
+ :event_data => event_data,
162
+ :handler => definition
163
+ })
164
+ handler_type_router(handler, event_data)
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,92 @@
1
+ module Sensu
2
+ module Server
3
+ module Mutate
4
+ # Create a mutator callback (Proc). A mutator callback takes two
5
+ # parameters, for the mutator output and status code. The
6
+ # created callback can be used for standard mutators and mutator
7
+ # extensions. The provided callback will only be called when the
8
+ # mutator status is `0` (OK). If the status is not `0`, an error
9
+ # is logged, and the `@handling_event_count` is decremented by
10
+ # `1`.
11
+ #
12
+ # @param mutator [Object] definition or extension.
13
+ # @param event [Hash] data.
14
+ # @param callback [Proc] to call when the mutator status is `0`.
15
+ # @return [Proc] mutator callback.
16
+ def mutator_callback(mutator, event, &callback)
17
+ Proc.new do |output, status|
18
+ if status == 0
19
+ callback.call(output)
20
+ else
21
+ definition = mutator.is_a?(Hash) ? mutator : mutator.definition
22
+ @logger.error("mutator error", {
23
+ :mutator => definition,
24
+ :event => event,
25
+ :output => output,
26
+ :status => status
27
+ })
28
+ @handling_event_count -= 1 if @handling_event_count
29
+ end
30
+ end
31
+ end
32
+
33
+ # Execute a standard mutator (pipe), spawn a process using the
34
+ # mutator command and pipe the event data to it via STDIN. The
35
+ # `mutator_callback()` method is used to create the mutator
36
+ # callback, wrapping the provided callback (event handler).
37
+ #
38
+ # @param mutator [Hash] definition.
39
+ # @param event [Hash] data.
40
+ # @param callback [Proc] to call when the mutator executes
41
+ # successfully.
42
+ def pipe_mutator(mutator, event, &callback)
43
+ options = {:data => MultiJson.dump(event), :timeout => mutator[:timeout]}
44
+ block = mutator_callback(mutator, event, &callback)
45
+ Spawn.process(mutator[:command], options, &block)
46
+ end
47
+
48
+ # Run a mutator extension, within the Sensu EventMachine reactor
49
+ # (event loop). The `mutator_callback()` method is used to
50
+ # create the mutator callback, wrapping the provided callback
51
+ # (event handler).
52
+ #
53
+ # @param mutator [Object] extension.
54
+ # @param event [Hash] data.
55
+ # @param callback [Proc] to call when the mutator runs
56
+ # successfully.
57
+ def mutator_extension(mutator, event, &callback)
58
+ block = mutator_callback(mutator, event, &callback)
59
+ mutator.safe_run(event, &block)
60
+ end
61
+
62
+ # Mutate event data for a handler. By default, the "json"
63
+ # mutator is used, unless the handler specifies another mutator.
64
+ # If a mutator does not exist, not defined or a missing
65
+ # extension, an error will be logged and the
66
+ # `@handling_event_count` is decremented by `1`. This method
67
+ # first checks for the existence of a standard mutator, then
68
+ # checks for an extension if a standard mutator is not defined.
69
+ #
70
+ # @param handler [Hash] definition.
71
+ # @param event [Hash] data.
72
+ # @param callback [Proc] to call when the mutator executes/runs
73
+ # successfully (event handler).
74
+ def mutate_event(handler, event, &callback)
75
+ mutator_name = handler[:mutator] || "json"
76
+ case
77
+ when @settings.mutator_exists?(mutator_name)
78
+ mutator = @settings[:mutators][mutator_name]
79
+ pipe_mutator(mutator, event, &callback)
80
+ when @extensions.mutator_exists?(mutator_name)
81
+ mutator = @extensions[:mutators][mutator_name]
82
+ mutator_extension(mutator, event, &callback)
83
+ else
84
+ @logger.error("unknown mutator", {
85
+ :mutator_name => mutator_name
86
+ })
87
+ @handling_event_count -= 1 if @handling_event_count
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end