sensu 0.13.1 → 0.14.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: b97c7d173f1349f87c0770d57f03560f6ca33496
4
- data.tar.gz: 97e6ab5a073fb228dd6607074b28fe15f9708434
3
+ metadata.gz: 03f7b351c7b3313b052fc9101104a507e222a72a
4
+ data.tar.gz: 9d383b007b24f9ffd36199fda48d93301e78512e
5
5
  SHA512:
6
- metadata.gz: 3ed616f47828abc82ec5b886257a2012ead0c74f0f175d476163825ba5f593214852f9d150a3df4d2fe05a486f3ce98bbf23f115743129d9f9c6685dbdaf5831
7
- data.tar.gz: fb72effe5bbe18f177b1a3471cb6b06c33839b4a6820a548bbcc6debb736071e881a2afe77508fb1d7912808df1ba117723b5fd64926930bbcc9b4c70e9c97a4
6
+ metadata.gz: 2a97f6ffac42dd09e68eea5baaf9248c9dec3f20c5495c560651c28ea2900f1b6b86ac2dfbdb202c9460e6965c18595eae76ce85bc3715028d39d5548eaa0568
7
+ data.tar.gz: ea7357550ec3663bad511a880d6db95b2a4454efe34af55cd21e892f6baf1a5f0f8529896f1c67e6c6488aa1c5db3e323aeba901ce9eefc0defd1c9122e9c16b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,20 @@
1
+ ## 0.14.0 - TBD
2
+
3
+ ### Features
4
+
5
+ Client socket now supports sending a result via a TCP stream. This feature
6
+ allows check results to have larger output (metrics, backtraces, etc).
7
+
8
+ API now supports CORS (configurable).
9
+
10
+ ### Other
11
+
12
+ Child process manager now supports check output larger than the max OS
13
+ buffer size. The parent process was waiting on the child to exit before
14
+ closing its write end of the pipe.
15
+
16
+ Client & server are now guarding against invalid JSON transport payloads.
17
+
1
18
  ## 0.13.1 - 2014-07-28
2
19
 
3
20
  ### Other
data/lib/sensu/api.rb CHANGED
@@ -39,13 +39,15 @@ module Sensu
39
39
  setup_logger(options)
40
40
  set :logger, @logger
41
41
  load_settings(options)
42
+ set :api, @settings[:api]
42
43
  set :checks, @settings[:checks]
43
44
  set :all_checks, @settings.checks
44
- if @settings[:api][:user] && @settings[:api][:password]
45
- use Rack::Auth::Basic do |user, password|
46
- user == @settings[:api][:user] && password == @settings[:api][:password]
47
- end
48
- end
45
+ set :cors, @settings[:cors] || {
46
+ 'Origin' => '*',
47
+ 'Methods' => 'GET, POST, PUT, DELETE, OPTIONS',
48
+ 'Credentials' => 'true',
49
+ 'Headers' => 'Origin, X-Requested-With, Content-Type, Accept, Authorization'
50
+ }
49
51
  on_reactor_run
50
52
  self
51
53
  end
@@ -112,10 +114,30 @@ module Sensu
112
114
  env['rack.input'].rewind
113
115
  end
114
116
 
117
+ def protected!
118
+ if settings.api[:user] && settings.api[:password]
119
+ return if !(settings.api[:user] && settings.api[:password]) || authorized?
120
+ headers['WWW-Authenticate'] = 'Basic realm="Restricted Area"'
121
+ unauthorized!
122
+ end
123
+ end
124
+
125
+ def authorized?
126
+ @auth ||= Rack::Auth::Basic::Request.new(request.env)
127
+ @auth.provided? &&
128
+ @auth.basic? &&
129
+ @auth.credentials &&
130
+ @auth.credentials == [settings.api[:user], settings.api[:password]]
131
+ end
132
+
115
133
  def bad_request!
116
134
  ahalt 400
117
135
  end
118
136
 
137
+ def unauthorized!
138
+ ahalt 401
139
+ end
140
+
119
141
  def not_found!
120
142
  ahalt 404
121
143
  end
@@ -235,6 +257,14 @@ module Sensu
235
257
  before do
236
258
  request_log_line
237
259
  content_type 'application/json'
260
+ settings.cors.each do |header, value|
261
+ headers['Access-Control-Allow-' + header] = value
262
+ end
263
+ protected! unless env['REQUEST_METHOD'] == 'OPTIONS'
264
+ end
265
+
266
+ aoptions '/*' do
267
+ body ''
238
268
  end
239
269
 
240
270
  aget '/info/?' do
data/lib/sensu/client.rb CHANGED
@@ -170,11 +170,18 @@ module Sensu
170
170
  })
171
171
  funnel = [@settings[:client][:name], VERSION, Time.now.to_i].join('-')
172
172
  @transport.subscribe(:fanout, subscription, funnel) do |message_info, message|
173
- check = MultiJson.load(message)
174
- @logger.info('received check request', {
175
- :check => check
176
- })
177
- process_check(check)
173
+ begin
174
+ check = MultiJson.load(message)
175
+ @logger.info('received check request', {
176
+ :check => check
177
+ })
178
+ process_check(check)
179
+ rescue MultiJson::ParseError => error
180
+ @logger.error('failed to parse the check request payload', {
181
+ :message => message,
182
+ :error => error.to_s
183
+ })
184
+ end
178
185
  end
179
186
  end
180
187
  end
@@ -224,7 +231,7 @@ module Sensu
224
231
  socket.logger = @logger
225
232
  socket.settings = @settings
226
233
  socket.transport = @transport
227
- socket.reply = false
234
+ socket.protocol = :udp
228
235
  end
229
236
  end
230
237
 
@@ -1,6 +1,6 @@
1
1
  module Sensu
2
2
  unless defined?(Sensu::VERSION)
3
- VERSION = '0.13.1'
3
+ VERSION = '0.14.0.beta'
4
4
 
5
5
  SEVERITIES = %w[ok warning critical unknown]
6
6
 
data/lib/sensu/daemon.rb CHANGED
@@ -8,7 +8,7 @@ gem 'sensu-settings', '1.0.0'
8
8
  gem 'sensu-extension', '1.0.0'
9
9
  gem 'sensu-extensions', '1.0.0'
10
10
  gem 'sensu-transport', '1.0.0'
11
- gem 'sensu-spawn', '1.0.0'
11
+ gem 'sensu-spawn', '1.1.0'
12
12
 
13
13
  require 'time'
14
14
  require 'uri'
data/lib/sensu/server.rb CHANGED
@@ -26,14 +26,21 @@ module Sensu
26
26
  def setup_keepalives
27
27
  @logger.debug('subscribing to keepalives')
28
28
  @transport.subscribe(:direct, 'keepalives', 'keepalives', :ack => true) do |message_info, message|
29
- client = MultiJson.load(message)
30
- @logger.debug('received keepalive', {
31
- :client => client
32
- })
33
- @redis.set('client:' + client[:name], MultiJson.dump(client)) do
34
- @redis.sadd('clients', client[:name]) do
35
- @transport.ack(message_info)
29
+ begin
30
+ client = MultiJson.load(message)
31
+ @logger.debug('received keepalive', {
32
+ :client => client
33
+ })
34
+ @redis.set('client:' + client[:name], MultiJson.dump(client)) do
35
+ @redis.sadd('clients', client[:name]) do
36
+ @transport.ack(message_info)
37
+ end
36
38
  end
39
+ rescue MultiJson::ParseError => error
40
+ @logger.error('failed to parse keepalive payload', {
41
+ :message => message,
42
+ :error => error.to_s
43
+ })
37
44
  end
38
45
  end
39
46
  end
@@ -447,13 +454,20 @@ module Sensu
447
454
  def setup_results
448
455
  @logger.debug('subscribing to results')
449
456
  @transport.subscribe(:direct, 'results', 'results', :ack => true) do |message_info, message|
450
- result = MultiJson.load(message)
451
- @logger.debug('received result', {
452
- :result => result
453
- })
454
- process_result(result)
455
- EM::next_tick do
456
- @transport.ack(message_info)
457
+ begin
458
+ result = MultiJson.load(message)
459
+ @logger.debug('received result', {
460
+ :result => result
461
+ })
462
+ process_result(result)
463
+ EM::next_tick do
464
+ @transport.ack(message_info)
465
+ end
466
+ rescue MultiJson::ParseError => error
467
+ @logger.error('failed to parse result payload', {
468
+ :message => message,
469
+ :error => error.to_s
470
+ })
457
471
  end
458
472
  end
459
473
  end
data/lib/sensu/socket.rb CHANGED
@@ -1,15 +1,184 @@
1
+ require 'multi_json'
2
+
1
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.
2
48
  class Socket < EM::Connection
3
- attr_accessor :logger, :settings, :transport, :reply
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
4
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.
5
86
  def respond(data)
6
- unless @reply == false
87
+ if @protocol == :tcp
7
88
  send_data(data)
89
+ close_connection_after_writing
8
90
  end
91
+ post_init
9
92
  end
10
93
 
11
- def receive_data(data)
12
- if data =~ /[\x80-\xff]/n
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 }
13
182
  @logger.warn('socket received non-ascii characters')
14
183
  respond('invalid')
15
184
  elsif data.strip == 'ping'
@@ -20,32 +189,9 @@ module Sensu
20
189
  :data => data
21
190
  })
22
191
  begin
23
- check = MultiJson.load(data)
24
- check[:issued] = Time.now.to_i
25
- check[:status] ||= 0
26
- validates = [
27
- check[:name] =~ /^[\w\.-]+$/,
28
- check[:output].is_a?(String),
29
- check[:status].is_a?(Integer)
30
- ].all?
31
- if validates
32
- payload = {
33
- :client => @settings[:client][:name],
34
- :check => check
35
- }
36
- @logger.info('publishing check result', {
37
- :payload => payload
38
- })
39
- @transport.publish(:direct, 'results', MultiJson.dump(payload))
40
- respond('ok')
41
- else
42
- @logger.warn('invalid check result', {
43
- :check => check
44
- })
45
- respond('invalid')
46
- end
47
- rescue MultiJson::ParseError => error
48
- @logger.warn('check result must be valid json', {
192
+ parse_check_result(data)
193
+ rescue => error
194
+ @logger.error('failed to process check result from socket', {
49
195
  :data => data,
50
196
  :error => error.to_s
51
197
  })
@@ -53,6 +199,27 @@ module Sensu
53
199
  end
54
200
  end
55
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
56
223
  end
57
224
 
58
225
  class SocketHandler < EM::Connection
data/sensu.gemspec CHANGED
@@ -22,15 +22,15 @@ Gem::Specification.new do |s|
22
22
  s.add_dependency('sensu-extension', '1.0.0')
23
23
  s.add_dependency('sensu-extensions', '1.0.0')
24
24
  s.add_dependency('sensu-transport', '1.0.0')
25
- s.add_dependency('sensu-spawn', '1.0.0')
25
+ s.add_dependency('sensu-spawn', '1.1.0')
26
26
  s.add_dependency('em-redis-unified', '0.5.0')
27
27
  s.add_dependency('sinatra', '1.3.5')
28
28
  s.add_dependency('async_sinatra', '1.0.0')
29
29
  s.add_dependency('thin', '1.5.0') unless RUBY_PLATFORM =~ /java/
30
30
 
31
- s.add_development_dependency('rake')
32
- s.add_development_dependency('rspec')
33
- s.add_development_dependency('em-http-request')
31
+ s.add_development_dependency('rake', '~> 10.3')
32
+ s.add_development_dependency('rspec', '~> 3.0.0')
33
+ s.add_development_dependency('em-http-request', '~> 1.1')
34
34
 
35
35
  s.files = Dir.glob('{bin,lib}/**/*') + %w[sensu.gemspec README.md CHANGELOG.md MIT-LICENSE.txt]
36
36
  s.executables = Dir.glob('bin/**/*').map { |file| File.basename(file) }
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.13.1
4
+ version: 0.14.0.beta
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Porter
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-07-28 00:00:00.000000000 Z
12
+ date: 2014-09-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: multi_json
@@ -129,14 +129,14 @@ dependencies:
129
129
  requirements:
130
130
  - - '='
131
131
  - !ruby/object:Gem::Version
132
- version: 1.0.0
132
+ version: 1.1.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.0.0
139
+ version: 1.1.0
140
140
  - !ruby/object:Gem::Dependency
141
141
  name: em-redis-unified
142
142
  requirement: !ruby/object:Gem::Requirement
@@ -197,52 +197,52 @@ dependencies:
197
197
  name: rake
198
198
  requirement: !ruby/object:Gem::Requirement
199
199
  requirements:
200
- - - ">="
200
+ - - "~>"
201
201
  - !ruby/object:Gem::Version
202
- version: '0'
202
+ version: '10.3'
203
203
  type: :development
204
204
  prerelease: false
205
205
  version_requirements: !ruby/object:Gem::Requirement
206
206
  requirements:
207
- - - ">="
207
+ - - "~>"
208
208
  - !ruby/object:Gem::Version
209
- version: '0'
209
+ version: '10.3'
210
210
  - !ruby/object:Gem::Dependency
211
211
  name: rspec
212
212
  requirement: !ruby/object:Gem::Requirement
213
213
  requirements:
214
- - - ">="
214
+ - - "~>"
215
215
  - !ruby/object:Gem::Version
216
- version: '0'
216
+ version: 3.0.0
217
217
  type: :development
218
218
  prerelease: false
219
219
  version_requirements: !ruby/object:Gem::Requirement
220
220
  requirements:
221
- - - ">="
221
+ - - "~>"
222
222
  - !ruby/object:Gem::Version
223
- version: '0'
223
+ version: 3.0.0
224
224
  - !ruby/object:Gem::Dependency
225
225
  name: em-http-request
226
226
  requirement: !ruby/object:Gem::Requirement
227
227
  requirements:
228
- - - ">="
228
+ - - "~>"
229
229
  - !ruby/object:Gem::Version
230
- version: '0'
230
+ version: '1.1'
231
231
  type: :development
232
232
  prerelease: false
233
233
  version_requirements: !ruby/object:Gem::Requirement
234
234
  requirements:
235
- - - ">="
235
+ - - "~>"
236
236
  - !ruby/object:Gem::Version
237
- version: '0'
237
+ version: '1.1'
238
238
  description: A monitoring framework that aims to be simple, malleable, and scalable.
239
239
  email:
240
240
  - portertech@gmail.com
241
241
  - justin.kolberg@sonian.net
242
242
  executables:
243
- - sensu-api
244
- - sensu-client
245
243
  - sensu-server
244
+ - sensu-client
245
+ - sensu-api
246
246
  extensions: []
247
247
  extra_rdoc_files: []
248
248
  files:
@@ -279,13 +279,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
279
279
  version: '0'
280
280
  required_rubygems_version: !ruby/object:Gem::Requirement
281
281
  requirements:
282
- - - ">="
282
+ - - ">"
283
283
  - !ruby/object:Gem::Version
284
- version: '0'
284
+ version: 1.3.1
285
285
  requirements: []
286
286
  rubyforge_project:
287
- rubygems_version: 2.2.0
287
+ rubygems_version: 2.2.2
288
288
  signing_key:
289
289
  specification_version: 4
290
290
  summary: A monitoring framework
291
291
  test_files: []
292
+ has_rdoc: false