sensu 0.13.1-java → 0.14.0.beta-java

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 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