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.
data/lib/sensu/socket.rb DELETED
@@ -1,246 +0,0 @@
1
- require 'multi_json'
2
-
3
- module Sensu
4
- # EventMachine connection handler for the Sensu client's socket.
5
- #
6
- # The Sensu client listens on localhost, port 3030 (by default), for
7
- # UDP and TCP traffic. This allows software running on the host to
8
- # push check results (that may contain metrics) into Sensu, without
9
- # needing to know anything about Sensu's internal implementation.
10
- #
11
- # The socket only accepts 7-bit ASCII-encoded data.
12
- #
13
- # Although the Sensu client accepts UDP and TCP traffic, you must be
14
- # aware of the UDP protocol limitations. Any data you send over UDP
15
- # must fit in a single datagram and you will not receive a response
16
- # (no confirmation).
17
- #
18
- # == UDP Protocol ==
19
- #
20
- # If the socket receives a message containing whitespace and the
21
- # string +'ping'+, it will ignore it.
22
- #
23
- # The socket assumes all other messages will contain a single,
24
- # complete, JSON hash. The hash must be a valid JSON check result.
25
- # Deserialization failures will be logged at the ERROR level by the
26
- # Sensu client, but the sender of the invalid data will not be
27
- # notified.
28
- #
29
- # == TCP Protocol ==
30
- #
31
- # If the socket receives a message containing whitespace and the
32
- # string +'ping'+, it will respond with the message +'pong'+.
33
- #
34
- # The socket assumes any other stream will be a single, complete,
35
- # JSON hash. A deserialization failure will be logged at the WARN
36
- # level by the Sensu client and respond with the message
37
- # +'invalid'+. An +'ok'+ response indicates the Sensu client
38
- # successfully received the JSON hash and will publish the check
39
- # result.
40
- #
41
- # Streams can be of any length. The socket protocol does not require
42
- # any headers, instead the socket tries to parse everything it has
43
- # been sent each time a chunk of data arrives. Once the JSON parses
44
- # successfully, the Sensu client publishes the result. After
45
- # +WATCHDOG_DELAY+ (default is 500 msec) since the most recent chunk
46
- # of data was received, the agent will give up on the sender, and
47
- # instead respond +'invalid'+ and close the connection.
48
- class Socket < EM::Connection
49
- class DataError < StandardError; end
50
-
51
- attr_accessor :logger, :settings, :transport, :protocol
52
-
53
- # The number of seconds that may elapse between chunks of data
54
- # from a sender before it is considered dead, and the connection
55
- # is close.
56
- WATCHDOG_DELAY = 0.5
57
-
58
- #
59
- # Sensu::Socket operating mode enum.
60
- #
61
-
62
- # ACCEPT mode. Append chunks of data to a buffer and test to see
63
- # whether the buffer contents are valid JSON.
64
- MODE_ACCEPT = :ACCEPT
65
-
66
- # REJECT mode. No longer receiving data from sender. Discard
67
- # chunks of data in this mode, the connection is being closed.
68
- MODE_REJECT = :REJECT
69
-
70
- # Initialize instance variables that will be used throughout the
71
- # lifetime of the connection. This method is called when the
72
- # network connection has been established, and immediately after
73
- # responding to a sender.
74
- def post_init
75
- @protocol ||= :tcp
76
- @data_buffer = ''
77
- @parse_error = nil
78
- @watchdog = nil
79
- @mode = MODE_ACCEPT
80
- end
81
-
82
- # Send a response to the sender, close the
83
- # connection, and call post_init().
84
- #
85
- # @param [String] data to send as a response.
86
- def respond(data)
87
- if @protocol == :tcp
88
- send_data(data)
89
- close_connection_after_writing
90
- end
91
- post_init
92
- end
93
-
94
- # Cancel the current connection watchdog.
95
- def cancel_watchdog
96
- if @watchdog
97
- @watchdog.cancel
98
- end
99
- end
100
-
101
- # Reset (or start) the connection watchdog.
102
- def reset_watchdog
103
- cancel_watchdog
104
- @watchdog = EM::Timer.new(WATCHDOG_DELAY) do
105
- @mode = MODE_REJECT
106
- @logger.warn('discarding data buffer for sender and closing connection', {
107
- :data => @data_buffer,
108
- :parse_error => @parse_error
109
- })
110
- respond('invalid')
111
- end
112
- end
113
-
114
- # Validate check result attributes.
115
- #
116
- # @param [Hash] check result to validate.
117
- def validate_check_result(check)
118
- unless check[:name] =~ /^[\w\.-]+$/
119
- raise DataError, 'check name must be a string and cannot contain spaces or special characters'
120
- end
121
- unless check[:output].is_a?(String)
122
- raise DataError, 'check output must be a string'
123
- end
124
- unless check[:status].is_a?(Integer)
125
- raise DataError, 'check status must be an integer'
126
- end
127
- end
128
-
129
- # Publish a check result to the Sensu transport.
130
- #
131
- # @param [Hash] check result.
132
- def publish_check_result(check)
133
- payload = {
134
- :client => @settings[:client][:name],
135
- :check => check.merge(:issued => Time.now.to_i)
136
- }
137
- @logger.info('publishing check result', {
138
- :payload => payload
139
- })
140
- @transport.publish(:direct, 'results', MultiJson.dump(payload))
141
- end
142
-
143
- # Process a check result. Set check result attribute defaults,
144
- # validate the attributes, publish the check result to the Sensu
145
- # transport, and respond to the sender with the message +'ok'+.
146
- #
147
- # @param [Hash] check result to be validated and published.
148
- # @raise [DataError] if +check+ is invalid.
149
- def process_check_result(check)
150
- check[:status] ||= 0
151
- validate_check_result(check)
152
- publish_check_result(check)
153
- respond('ok')
154
- end
155
-
156
- # Parse a JSON check result. For UDP, immediately raise a parser
157
- # error. For TCP, record parser errors, so the connection
158
- # +watchdog+ can report them.
159
- #
160
- # @param [String] data to parse for a check result.
161
- def parse_check_result(data)
162
- begin
163
- check = MultiJson.load(data)
164
- cancel_watchdog
165
- process_check_result(check)
166
- rescue MultiJson::ParseError, ArgumentError => error
167
- if @protocol == :tcp
168
- @parse_error = error.to_s
169
- else
170
- raise error
171
- end
172
- end
173
- end
174
-
175
- # Process the data received. This method validates the data
176
- # encoding, provides ping/pong functionality, and passes potential
177
- # check results on for further processing.
178
- #
179
- # @param [String] data to be processed.
180
- def process_data(data)
181
- if data.bytes.find { |char| char > 0x80 }
182
- @logger.warn('socket received non-ascii characters')
183
- respond('invalid')
184
- elsif data.strip == 'ping'
185
- @logger.debug('socket received ping')
186
- respond('pong')
187
- else
188
- @logger.debug('socket received data', {
189
- :data => data
190
- })
191
- begin
192
- parse_check_result(data)
193
- rescue => error
194
- @logger.error('failed to process check result from socket', {
195
- :data => data,
196
- :error => error.to_s
197
- })
198
- respond('invalid')
199
- end
200
- end
201
- end
202
-
203
- # This method is called whenever data is received. For UDP, it
204
- # will only be called once, the original data length can be
205
- # expected. For TCP, this method may be called several times, data
206
- # received is buffered. TCP connections require a +watchdog+.
207
- #
208
- # @param [String] data received from the sender.
209
- def receive_data(data)
210
- unless @mode == MODE_REJECT
211
- case @protocol
212
- when :udp
213
- process_data(data)
214
- when :tcp
215
- if EM.reactor_running?
216
- reset_watchdog
217
- end
218
- @data_buffer << data
219
- process_data(@data_buffer)
220
- end
221
- end
222
- end
223
- end
224
-
225
- class SocketHandler < EM::Connection
226
- attr_accessor :on_success, :on_error
227
-
228
- def connection_completed
229
- @connected_at = Time.now.to_f
230
- @inactivity_timeout = comm_inactivity_timeout
231
- end
232
-
233
- def unbind
234
- if @connected_at
235
- elapsed_time = Time.now.to_f - @connected_at
236
- if elapsed_time >= @inactivity_timeout
237
- @on_error.call('socket inactivity timeout')
238
- else
239
- @on_success.call('wrote to socket')
240
- end
241
- else
242
- @on_error.call('failed to connect to socket')
243
- end
244
- end
245
- end
246
- end