sensu 0.13.1-java → 0.14.0.beta-java

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: 35af815a7ba0e87dc4d109ec68b610ff10bfa449
4
- data.tar.gz: 8f767185896d5f3a35545f131375dee6dc0a4043
3
+ metadata.gz: 4579594b99d018ca7ec1fe9c2d82d8c3a1796dc6
4
+ data.tar.gz: 95def4eb9ebce9a3851c3571dfb06a23a126a639
5
5
  SHA512:
6
- metadata.gz: 2af013e19ddd584d9f3754382f9de0641782a078fb6b8ea61079c48fdba51c27951ac73f8107530983dcb8f7bdef15eda20fb6047b521ad8d385239fea9c6d57
7
- data.tar.gz: b58c9d198ef4ea09d37ccca80f71144981e85f44ff3643ccdf4d5cad36db01665e52a8dcb291559c9b280217ff913f88146ad4398617082d3c0b43f8e0e999d7
6
+ metadata.gz: b185dc5176bb716b33537424da4cbfd81155dcee418911bc6beb980238781fd6b15e1080530294ade66d4666d368f3cd31d43dac7bc85c32178a28c8dabeb642
7
+ data.tar.gz: 594bddf91fb61f1e97a4ebf4867b967e0457c8eed61a5d24887a3ac2083d2fd9ddc6b03c1a180fbb69aed970546d370dcd51f6eb00587431afe31f73531aa5b5
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: java
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-08-07 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,12 +129,12 @@ dependencies:
129
129
  requirements:
130
130
  - - '='
131
131
  - !ruby/object:Gem::Version
132
- version: 1.0.0
132
+ version: 1.1.0
133
133
  requirement: !ruby/object:Gem::Requirement
134
134
  requirements:
135
135
  - - '='
136
136
  - !ruby/object:Gem::Version
137
- version: 1.0.0
137
+ version: 1.1.0
138
138
  prerelease: false
139
139
  type: :runtime
140
140
  - !ruby/object:Gem::Dependency
@@ -183,42 +183,42 @@ dependencies:
183
183
  name: rake
184
184
  version_requirements: !ruby/object:Gem::Requirement
185
185
  requirements:
186
- - - '>='
186
+ - - ~>
187
187
  - !ruby/object:Gem::Version
188
- version: '0'
188
+ version: '10.3'
189
189
  requirement: !ruby/object:Gem::Requirement
190
190
  requirements:
191
- - - '>='
191
+ - - ~>
192
192
  - !ruby/object:Gem::Version
193
- version: '0'
193
+ version: '10.3'
194
194
  prerelease: false
195
195
  type: :development
196
196
  - !ruby/object:Gem::Dependency
197
197
  name: rspec
198
198
  version_requirements: !ruby/object:Gem::Requirement
199
199
  requirements:
200
- - - '>='
200
+ - - ~>
201
201
  - !ruby/object:Gem::Version
202
- version: '0'
202
+ version: 3.0.0
203
203
  requirement: !ruby/object:Gem::Requirement
204
204
  requirements:
205
- - - '>='
205
+ - - ~>
206
206
  - !ruby/object:Gem::Version
207
- version: '0'
207
+ version: 3.0.0
208
208
  prerelease: false
209
209
  type: :development
210
210
  - !ruby/object:Gem::Dependency
211
211
  name: em-http-request
212
212
  version_requirements: !ruby/object:Gem::Requirement
213
213
  requirements:
214
- - - '>='
214
+ - - ~>
215
215
  - !ruby/object:Gem::Version
216
- version: '0'
216
+ version: '1.1'
217
217
  requirement: !ruby/object:Gem::Requirement
218
218
  requirements:
219
- - - '>='
219
+ - - ~>
220
220
  - !ruby/object:Gem::Version
221
- version: '0'
221
+ version: '1.1'
222
222
  prerelease: false
223
223
  type: :development
224
224
  description: A monitoring framework that aims to be simple, malleable, and scalable.
@@ -265,9 +265,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
265
265
  version: '0'
266
266
  required_rubygems_version: !ruby/object:Gem::Requirement
267
267
  requirements:
268
- - - '>='
268
+ - - '>'
269
269
  - !ruby/object:Gem::Version
270
- version: '0'
270
+ version: 1.3.1
271
271
  requirements: []
272
272
  rubyforge_project:
273
273
  rubygems_version: 2.1.9