jls-lumberjack 0.0.24 → 0.0.25.beta1

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: d58912ce6afda9045d016510618a04363349628d
4
- data.tar.gz: faeaba187853930185d78673f4b69c16c5b107bb
3
+ metadata.gz: e2b2959f02e7f417cf30eb4d840508950edf0808
4
+ data.tar.gz: a1f37209be258dbcc97617b8eed7e5c61ca66db5
5
5
  SHA512:
6
- metadata.gz: e40270de1f12efc47375be7a5a9cd9c83fae6ce2b615fe50885037746ecf074e1a26bf2dd4b2bf8a2ebdd1816c95f6d36b56e84de32e3fc5f73a225be7bc6aa7
7
- data.tar.gz: 6224d5a7b5475195b0afd5168eea3c37a8c704cef432cf90b01cb8fcd520ea3293f1503e89b6bd4e0eaf2e84f24afeab78b7681ab9be4999e2720b2987291dd8
6
+ metadata.gz: a87220c6b45a3c53f4bf685c2309c37cdd953c06d489f56cb442eb9ebc482357067b3fcad526b7e44c28455117be4c189226815184d65049bb6b429326ef5d42
7
+ data.tar.gz: 8d326476bf7fe3d76873fc998dae45fbaf94cbe776de1a9e276fe8d4fadcfe6243a3ac2db7c90568056998c8296dabe44424fc3b03dfb1a7f91f7d87a5df41ae
data/lib/lumberjack.rb CHANGED
@@ -1,3 +1,22 @@
1
+ require "json"
2
+
1
3
  module Lumberjack
2
4
  SEQUENCE_MAX = (2**32-1).freeze
5
+
6
+ @@json = Class.new do
7
+ def self.load(blob)
8
+ JSON.parse(blob)
9
+ end
10
+ def self.dump(v)
11
+ v.to_json
12
+ end
13
+ end
14
+
15
+ def self.json
16
+ @@json
17
+ end
18
+
19
+ def self.json=(j)
20
+ @@json = j
21
+ end
3
22
  end
@@ -12,15 +12,16 @@ module Lumberjack
12
12
  :port => 0,
13
13
  :addresses => [],
14
14
  :ssl_certificate => nil,
15
+ :ssl => true,
16
+ :json => false,
15
17
  }.merge(opts)
16
18
 
17
19
  @opts[:addresses] = [@opts[:addresses]] if @opts[:addresses].class == String
18
20
  raise "Must set a port." if @opts[:port] == 0
19
21
  raise "Must set atleast one address" if @opts[:addresses].empty? == 0
20
- raise "Must set a ssl certificate or path" if @opts[:ssl_certificate].nil?
22
+ raise "Must set a ssl certificate or path" if @opts[:ssl_certificate].nil? && @opts[:ssl]
21
23
 
22
24
  @socket = connect
23
-
24
25
  end
25
26
 
26
27
  private
@@ -37,8 +38,8 @@ module Lumberjack
37
38
  end
38
39
 
39
40
  public
40
- def write(elements)
41
- @socket.write_sync(elements)
41
+ def write(elements, opts={})
42
+ @socket.write_sync(elements, opts)
42
43
  end
43
44
 
44
45
  public
@@ -54,7 +55,10 @@ module Lumberjack
54
55
  #
55
56
  # * :port - the port to listen on
56
57
  # * :address - the host/address to bind to
57
- # * :ssl_certificate - the path to the ssl cert to use
58
+ # * :ssl - enable/disable ssl support
59
+ # * :ssl_certificate - the path to the ssl cert to use.
60
+ # If ssl_certificate is not set, a plain tcp connection
61
+ # will be used.
58
62
  attr_reader :sequence
59
63
  attr_reader :host
60
64
  def initialize(opts={})
@@ -64,6 +68,8 @@ module Lumberjack
64
68
  :port => 0,
65
69
  :address => "127.0.0.1",
66
70
  :ssl_certificate => nil,
71
+ :ssl => true,
72
+ :json => false,
67
73
  }.merge(opts)
68
74
  @host = @opts[:address]
69
75
 
@@ -73,18 +79,21 @@ module Lumberjack
73
79
  private
74
80
  def connection_start(opts)
75
81
  tcp_socket = TCPSocket.new(opts[:address], opts[:port])
82
+ if !opts[:ssl]
83
+ @socket = tcp_socket
84
+ else
85
+ certificate = OpenSSL::X509::Certificate.new(File.read(opts[:ssl_certificate]))
76
86
 
77
- certificate = OpenSSL::X509::Certificate.new(File.read(opts[:ssl_certificate]))
78
-
79
- certificate_store = OpenSSL::X509::Store.new
80
- certificate_store.add_cert(certificate)
87
+ certificate_store = OpenSSL::X509::Store.new
88
+ certificate_store.add_cert(certificate)
81
89
 
82
- ssl_context = OpenSSL::SSL::SSLContext.new
83
- ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
84
- ssl_context.cert_store = certificate_store
90
+ ssl_context = OpenSSL::SSL::SSLContext.new
91
+ ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
92
+ ssl_context.cert_store = certificate_store
85
93
 
86
- @socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, ssl_context)
87
- @socket.connect
94
+ @socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, ssl_context)
95
+ @socket.connect
96
+ end
88
97
  end
89
98
 
90
99
  private
@@ -109,11 +118,16 @@ module Lumberjack
109
118
  end
110
119
 
111
120
  public
112
- def write_sync(elements)
121
+ def write_sync(elements, opts={})
122
+ options = {
123
+ :json => @opts[:json],
124
+ }.merge(opts)
125
+
113
126
  elements = [elements] if elements.is_a?(Hash)
114
127
  send_window_size(elements.size)
115
128
 
116
- payload = elements.map { |element| Encoder.to_frame(element, inc) }.join
129
+ encoder = options[:json] ? JsonEncoder : FrameEncoder
130
+ payload = elements.map { |element| encoder.to_frame(element, inc) }.join
117
131
  compress = compress_payload(payload)
118
132
  send_payload(compress)
119
133
 
@@ -151,7 +165,17 @@ module Lumberjack
151
165
  end
152
166
  end
153
167
 
154
- module Encoder
168
+ module JsonEncoder
169
+ def self.to_frame(hash, sequence)
170
+ json = Lumberjack::json.dump(hash)
171
+ json_length = json.bytesize
172
+ pack = "AANNA#{json_length}"
173
+ frame = ["1", "J", sequence, json_length, json]
174
+ frame.pack(pack)
175
+ end
176
+ end # JsonEncoder
177
+
178
+ module FrameEncoder
155
179
  def self.to_frame(hash, sequence)
156
180
  frame = ["1", "D", sequence]
157
181
  pack = "AAN"
@@ -4,9 +4,13 @@ require "socket"
4
4
  require "thread"
5
5
  require "openssl"
6
6
  require "zlib"
7
+ require "json"
8
+ require "concurrent"
7
9
 
8
10
  module Lumberjack
9
11
  class Server
12
+ SOCKET_TIMEOUT = 1 # seconds
13
+
10
14
  attr_reader :port
11
15
 
12
16
  # Create a new Lumberjack server.
@@ -22,55 +26,113 @@ module Lumberjack
22
26
  @options = {
23
27
  :port => 0,
24
28
  :address => "0.0.0.0",
29
+ :ssl => true,
25
30
  :ssl_certificate => nil,
26
31
  :ssl_key => nil,
27
32
  :ssl_key_passphrase => nil
28
33
  }.merge(options)
29
34
 
30
- [:ssl_certificate, :ssl_key].each do |k|
31
- if @options[k].nil?
32
- raise "You must specify #{k} in Lumberjack::Server.new(...)"
35
+ if @options[:ssl]
36
+ [:ssl_certificate, :ssl_key].each do |k|
37
+ if @options[k].nil?
38
+ raise "You must specify #{k} in Lumberjack::Server.new(...)"
39
+ end
33
40
  end
34
41
  end
35
42
 
36
- @tcp_server = TCPServer.new(@options[:address], @options[:port])
43
+ @server = TCPServer.new(@options[:address], @options[:port])
44
+
45
+ @close = Concurrent::AtomicBoolean.new
37
46
 
38
47
  # Query the port in case the port number is '0'
39
48
  # TCPServer#addr == [ address_family, port, address, address ]
40
- @port = @tcp_server.addr[1]
41
- @ssl = OpenSSL::SSL::SSLContext.new
42
- @ssl.cert = OpenSSL::X509::Certificate.new(File.read(@options[:ssl_certificate]))
43
- @ssl.key = OpenSSL::PKey::RSA.new(File.read(@options[:ssl_key]),
44
- @options[:ssl_key_passphrase])
45
- @ssl_server = OpenSSL::SSL::SSLServer.new(@tcp_server, @ssl)
49
+ @port = @server.addr[1]
50
+
51
+ if @options[:ssl]
52
+ # load SSL certificate
53
+ @ssl = OpenSSL::SSL::SSLContext.new
54
+ @ssl.cert = OpenSSL::X509::Certificate.new(File.read(@options[:ssl_certificate]))
55
+ @ssl.key = OpenSSL::PKey::RSA.new(File.read(@options[:ssl_key]),
56
+ @options[:ssl_key_passphrase])
57
+ end
46
58
  end # def initialize
47
59
 
48
60
  def run(&block)
49
- while true
61
+ while !closed?
50
62
  connection = accept
63
+
64
+ # Some exception may occur in the accept loop
65
+ # we will try again in the next iteration
66
+ # unless the server is closing
67
+ next unless connection
68
+
51
69
  Thread.new(connection) do |connection|
52
70
  connection.run(&block)
53
71
  end
54
72
  end
55
73
  end # def run
56
74
 
75
+ def ssl?
76
+ @ssl
77
+ end
78
+
57
79
  def accept(&block)
58
80
  begin
59
- fd = @ssl_server.accept
60
- rescue EOFError, OpenSSL::SSL::SSLError, IOError
61
- # ssl handshake or other accept-related failure.
62
- # TODO(sissel): Make it possible to log this.
63
- retry
81
+ socket = @server.accept_nonblock
82
+ # update the socket with a SSL layer
83
+ socket = accept_ssl(socket) if ssl?
84
+
85
+ if block_given?
86
+ block.call(socket, self)
87
+ else
88
+ return Connection.new(socket, self)
89
+ end
90
+ rescue OpenSSL::SSL::SSLError, IOError, EOFError, Errno::EBADF
91
+ socket.close rescue nil
92
+ retry unless closed?
93
+ rescue IO::WaitReadable, Errno::EAGAIN # Resource not ready yet, so lets try again
94
+ begin
95
+ IO.select([@server], nil, nil, SOCKET_TIMEOUT)
96
+ retry unless closed?
97
+ rescue IOError, Errno::EBADF => e # we currently closing
98
+ raise e unless closed?
99
+ end
64
100
  end
65
- if block_given?
66
- block.call(fd)
67
- else
68
- Connection.new(fd)
101
+ end
102
+
103
+ def accept_ssl(tcp_socket)
104
+ ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, @ssl)
105
+ ssl_socket.sync_close
106
+
107
+ begin
108
+ ssl_socket.accept_nonblock
109
+
110
+ return ssl_socket
111
+ rescue IO::WaitReadable # handshake
112
+ IO.select([ssl_socket], nil, nil, SOCKET_TIMEOUT)
113
+ retry unless closed?
114
+ rescue IO::WaitWritable # handshake
115
+ IO.select(nil, [ssl_socket], nil, SOCKET_TIMEOUT)
116
+ retry unless closed?
69
117
  end
70
118
  end
119
+
120
+ def closed?
121
+ @close.value
122
+ end
123
+
124
+ def close
125
+ @close.make_true
126
+ @server.close unless @server.closed?
127
+ end
71
128
  end # class Server
72
129
 
73
130
  class Parser
131
+ PROTOCOL_VERSION_1 = "1".ord
132
+ PROTOCOL_VERSION_2 = "2".ord
133
+
134
+ SUPPORTED_PROTOCOLS = [PROTOCOL_VERSION_1, PROTOCOL_VERSION_2]
135
+
74
136
  def initialize
75
137
  @buffer_offset = 0
76
138
  @buffer = ""
@@ -139,24 +201,52 @@ module Lumberjack
139
201
 
140
202
  FRAME_WINDOW = "W".ord
141
203
  FRAME_DATA = "D".ord
204
+ FRAME_JSON_DATA = "J".ord
142
205
  FRAME_COMPRESSED = "C".ord
143
206
  def header(&block)
144
207
  version, frame_type = get.bytes.to_a[0..1]
208
+ version ||= PROTOCOL_VERSION_1
209
+
210
+ handle_version(version, &block)
145
211
 
146
212
  case frame_type
147
213
  when FRAME_WINDOW; transition(:window_size, 4)
148
214
  when FRAME_DATA; transition(:data_lead, 8)
215
+ when FRAME_JSON_DATA; transition(:json_data_lead, 8)
149
216
  when FRAME_COMPRESSED; transition(:compressed_lead, 4)
150
- else; raise "Unknown frame type: #{frame_type}"
217
+ else; raise "Unknown frame type: `#{frame_type}`"
151
218
  end
152
219
  end
153
220
 
221
+ def handle_version(version, &block)
222
+ if supported_protocol?(version)
223
+ yield :version, version
224
+ else
225
+ raise "unsupported protocol #{version}"
226
+ end
227
+ end
228
+
229
+ def supported_protocol?(version)
230
+ SUPPORTED_PROTOCOLS.include?(version)
231
+ end
232
+
154
233
  def window_size(&block)
155
234
  @window_size = get.unpack("N").first
156
235
  transition(:header, 2)
157
236
  yield :window_size, @window_size
158
237
  end # def window_size
159
238
 
239
+ def json_data_lead(&block)
240
+ @sequence, payload_size = get.unpack("NN")
241
+ transition(:json_data_payload, payload_size)
242
+ end
243
+
244
+ def json_data_payload(&block)
245
+ payload = get
246
+ yield :json, @sequence, Lumberjack::json.load(payload)
247
+ transition(:header, 2)
248
+ end
249
+
160
250
  def data_lead(&block)
161
251
  @sequence, @data_count = get.unpack("NN")
162
252
  @data = {}
@@ -210,17 +300,18 @@ module Lumberjack
210
300
  class Connection
211
301
  READ_SIZE = 16384
212
302
 
213
- def initialize(fd)
214
- super()
303
+ attr_accessor :server
304
+
305
+ def initialize(fd, server)
215
306
  @parser = Parser.new
216
307
  @fd = fd
217
308
 
218
- # a safe default until we are told by the client what window size to use
219
- @window_size = 1
309
+ @server = server
310
+ @ack_handler = nil
220
311
  end
221
312
 
222
313
  def run(&block)
223
- while true
314
+ while !server.closed?
224
315
  read_socket(&block)
225
316
  end
226
317
  rescue EOFError, OpenSSL::SSL::SSLError, IOError, Errno::ECONNRESET
@@ -238,42 +329,97 @@ module Lumberjack
238
329
  # X: too many events after errors.
239
330
  @parser.feed(@fd.sysread(READ_SIZE)) do |event, *args|
240
331
  case event
332
+ when :version
333
+ version(*args)
241
334
  when :window_size
242
- # We receive a new payload
243
- window_size(*args)
244
- reset_next_ack
335
+ reset_next_ack(*args)
245
336
  when :data
246
- data(*args, &block)
337
+ sequence, map = args
338
+ ack_if_needed(sequence) { data(map, &block) }
339
+ when :json
340
+ # If the payload is an array of items we will emit multiple events
341
+ # this behavior was moved from the plugin to the library.
342
+ # see this commit: https://github.com/logstash-plugins/logstash-input-lumberjack/pull/57/files#diff-1b9590423b15f04f215635164e7376ecR158
343
+ sequence, map = args
344
+
345
+ ack_if_needed(sequence) do
346
+ if map.is_a?(Array)
347
+ map.each { |e| data(e, &block) }
348
+ else
349
+ data(map, &block)
350
+ end
351
+ end
247
352
  end
248
353
  end
249
354
  end
250
355
 
251
- def close
252
- @fd.close
356
+ def version(version)
357
+ @version = version
253
358
  end
254
359
 
255
- def window_size(size)
256
- @window_size = size
360
+ def ack_if_needed(sequence, &block)
361
+ block.call
362
+ send_ack(sequence) if @ack_handler.ack?(sequence)
257
363
  end
258
364
 
259
- def reset_next_ack
260
- @next_ack = nil
365
+ def close
366
+ @fd.close unless @fd.closed?
261
367
  end
262
368
 
263
- def data(sequence, map, &block)
369
+ def data(map, &block)
264
370
  block.call(map) if block_given?
265
- ack_if_needed(sequence)
266
371
  end
267
-
268
- def compute_next_ack(sequence)
269
- (sequence + @window_size - 1) % SEQUENCE_MAX
372
+
373
+ def reset_next_ack(window_size)
374
+ klass = (@version == Parser::PROTOCOL_VERSION_1) ? AckingProtocolV1 : AckingProtocolV2
375
+ @ack_handler = klass.new(window_size)
376
+ end
377
+
378
+ def send_ack(sequence)
379
+ @fd.syswrite(["1A", sequence].pack("A*N"))
380
+ end
381
+ end # class Connection
382
+
383
+ class AckingProtocolV1
384
+ def initialize(window_size)
385
+ @next_ack = nil
386
+ @window_size = window_size
270
387
  end
271
388
 
272
- def ack_if_needed(sequence)
389
+ def ack?(sequence)
273
390
  # The first encoded event will contain the sequence number
274
391
  # this is needed to know when we should ack.
275
392
  @next_ack = compute_next_ack(sequence) if @next_ack.nil?
276
- @fd.syswrite(["1A", sequence].pack("A*N")) if sequence == @next_ack
393
+ sequence == @next_ack
277
394
  end
278
- end # class Connection
395
+
396
+ private
397
+ def compute_next_ack(sequence)
398
+ (sequence + @window_size - 1) % SEQUENCE_MAX
399
+ end
400
+ end
401
+
402
+ # Allow lumberjack to send partial ack back to the producer
403
+ # only V2 client support partial Acks
404
+ #
405
+ # Send Ack on every 20% of the data, so with default settings every 200 events
406
+ # This should reduce the congestion on retransmit.
407
+ class AckingProtocolV2
408
+ ACK_RATIO = 5
409
+
410
+ def initialize(window_size)
411
+ @window_size = window_size
412
+ @every = (window_size / ACK_RATIO).round
413
+ end
414
+
415
+ def ack?(sequence)
416
+ if @window_size == sequence
417
+ true
418
+ elsif sequence % @every == 0
419
+ true
420
+ else
421
+ false
422
+ end
423
+ end
424
+ end
279
425
  end # module Lumberjack
@@ -8,60 +8,46 @@ require "thread"
8
8
  require "spec_helper"
9
9
 
10
10
  Thread.abort_on_exception = true
11
-
12
11
  describe "A client" do
13
12
  let(:certificate) { Flores::PKI.generate }
14
13
  let(:certificate_file_crt) { "certificate.crt" }
15
14
  let(:certificate_file_key) { "certificate.key" }
16
15
  let(:port) { Flores::Random.integer(1024..65335) }
16
+ let(:tcp_port) { port + 1 }
17
17
  let(:host) { "127.0.0.1" }
18
18
  let(:queue) { [] }
19
19
 
20
20
  before do
21
21
  expect(File).to receive(:read).at_least(1).with(certificate_file_crt) { certificate.first.to_s }
22
22
  expect(File).to receive(:read).at_least(1).with(certificate_file_key) { certificate.last.to_s }
23
-
24
- server = Lumberjack::Server.new(:port => port,
25
- :address => host,
26
- :ssl_certificate => certificate_file_crt,
27
- :ssl_key => certificate_file_key)
28
-
29
- @server = Thread.new do
30
- server.run { |data| queue << data }
31
- end
32
- end
33
-
34
- context "with a valid certificate" do
35
- it "successfully connect to the server" do
36
- expect {
37
- Lumberjack::Client.new(:port => port,
38
- :host => host,
39
- :addresses => host,
40
- :ssl_certificate => certificate_file_crt)
41
23
 
42
- }.not_to raise_error
43
- end
44
- end
45
-
46
- context "with an invalid certificate" do
47
- let(:invalid_certificate) { Flores::PKI.generate }
48
- let(:invalid_certificate_file) { "invalid.crt" }
49
-
50
- before do
51
- expect(File).to receive(:read).with(invalid_certificate_file) { invalid_certificate.first.to_s }
24
+ tcp_server = Lumberjack::Server.new(:port => tcp_port, :address => host, :ssl => false)
25
+
26
+ ssl_server = Lumberjack::Server.new(:port => port,
27
+ :address => host,
28
+ :ssl_certificate => certificate_file_crt,
29
+ :ssl_key => certificate_file_key)
30
+
31
+ @tcp_server = Thread.new do
32
+ while true
33
+ tcp_server.accept do |socket|
34
+ con = Lumberjack::Connection.new(socket, tcp_server)
35
+ begin
36
+ con.run { |data| queue << data }
37
+ rescue
38
+ # Close connection on failure. For example SSL client will make
39
+ # parser for TCP based server trip.
40
+ # Connection is closed by Server connection object
41
+ end
42
+ end
43
+ end
52
44
  end
53
45
 
54
- it "should refuse to connect" do
55
- expect {
56
- Lumberjack::Client.new(:port => port,
57
- :host => host,
58
- :addresses => host,
59
- :ssl_certificate => invalid_certificate_file)
60
-
61
- }.to raise_error(OpenSSL::SSL::SSLError, /certificate verify failed/)
46
+ @ssl_server = Thread.new do
47
+ ssl_server.run { |data| queue << data }
62
48
  end
63
49
  end
64
-
50
+
65
51
  shared_examples "send payload" do
66
52
  it "supports single element" do
67
53
  (1..random_number_of_events).each do |n|
@@ -80,15 +66,9 @@ describe "A client" do
80
66
  end
81
67
  end
82
68
 
83
- context "When transmitting a payload" do
69
+ shared_examples "transmit payloads" do
84
70
  let(:random_number_of_events) { Flores::Random.integer(2..10) }
85
71
  let(:payload) { { "line" => "foobar" } }
86
- let(:client) do
87
- Lumberjack::Client.new(:port => port,
88
- :host => host,
89
- :addresses => host,
90
- :ssl_certificate => certificate_file_crt)
91
- end
92
72
  let(:batch_size) { Flores::Random.integer(1..1024) }
93
73
  let(:batch_payload) do
94
74
  batch = []
@@ -130,4 +110,101 @@ describe "A client" do
130
110
  end
131
111
  end
132
112
  end
113
+
114
+ context "using plain tcp connection" do
115
+ it "should successfully connect to tcp server if ssl explicitely disabled" do
116
+ expect {
117
+ Lumberjack::Client.new(:port => tcp_port, :host => host, :addresses => host, :ssl => false)
118
+ }.not_to raise_error
119
+ end
120
+
121
+ it "should fail to connect to tcp server if ssl not explicitely disabled" do
122
+ expect {
123
+ Lumberjack::Client.new(:port => tcp_port, :host => host, :addresses => host)
124
+ }.to raise_error(RuntimeError, /Must set a ssl certificate/)
125
+ end
126
+
127
+ it "should fail to communicate to ssl based server" do
128
+ expect {
129
+ client = Lumberjack::Client.new(:port => port,
130
+ :host => host,
131
+ :addresses => host,
132
+ :ssl => false)
133
+ client.write({ "line" => "foobar" })
134
+ }.to raise_error(RuntimeError)
135
+ end
136
+
137
+ context "When transmitting a payload" do
138
+ let(:options) { {:port => tcp_port, :host => host, :addresses => host, :ssl => false } }
139
+ let(:client) { Lumberjack::Client.new(options) }
140
+
141
+ context "json" do
142
+ let(:options) { super.merge({ :json => true }) }
143
+ include_examples "transmit payloads"
144
+ end
145
+
146
+ context "v1 frame" do
147
+ include_examples "transmit payloads"
148
+ end
149
+ end
150
+ end
151
+
152
+ context "using ssl encrypted connection" do
153
+ context "with a valid certificate" do
154
+ it "successfully connect to the server" do
155
+ expect {
156
+ Lumberjack::Client.new(:port => port,
157
+ :host => host,
158
+ :addresses => host,
159
+ :ssl_certificate => certificate_file_crt)
160
+ }.not_to raise_error
161
+ end
162
+
163
+ it "should fail connecting to plain tcp server" do
164
+ expect {
165
+ Lumberjack::Client.new(:port => tcp_port,
166
+ :host => host,
167
+ :addresses => host,
168
+ :ssl_certificate => certificate_file_crt)
169
+ }.to raise_error(OpenSSL::SSL::SSLError)
170
+ end
171
+ end
172
+
173
+ context "with an invalid certificate" do
174
+ let(:invalid_certificate) { Flores::PKI.generate }
175
+ let(:invalid_certificate_file) { "invalid.crt" }
176
+
177
+ before do
178
+ expect(File).to receive(:read).with(invalid_certificate_file) { invalid_certificate.first.to_s }
179
+ end
180
+
181
+ it "should refuse to connect" do
182
+ expect {
183
+ Lumberjack::Client.new(:port => port,
184
+ :host => host,
185
+ :addresses => host,
186
+ :ssl_certificate => invalid_certificate_file)
187
+
188
+ }.to raise_error(OpenSSL::SSL::SSLError, /certificate verify failed/)
189
+ end
190
+ end
191
+
192
+ context "When transmitting a payload" do
193
+ let(:client) do
194
+ Lumberjack::Client.new(:port => port,
195
+ :host => host,
196
+ :addresses => host,
197
+ :ssl_certificate => certificate_file_crt)
198
+ end
199
+
200
+ context "json" do
201
+ let(:options) { super.merge({ :json => true }) }
202
+ include_examples "transmit payloads"
203
+ end
204
+
205
+ context "v1 frame" do
206
+ include_examples "transmit payloads"
207
+ end
208
+ end
209
+ end
133
210
  end
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+ require "lumberjack/server"
3
+ require "flores/random"
4
+
5
+ describe Lumberjack::AckingProtocolV1 do
6
+ let(:number_of_events) { Flores::Random.integer(100..1024) }
7
+
8
+ subject { Lumberjack::AckingProtocolV1.new(number_of_events) }
9
+
10
+ it "should return true only once" do
11
+ results = []
12
+ number_of_events.times { |n| results << subject.ack?(n) }
13
+ expect(results.size).to eq(number_of_events)
14
+ expect(results.count(true)).to eq(1)
15
+ end
16
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+ require "lumberjack/server"
3
+ require "flores/random"
4
+
5
+ describe Lumberjack::AckingProtocolV2 do
6
+ let(:results) { [] }
7
+ subject { Lumberjack::AckingProtocolV2.new(number_of_events) }
8
+ before { 1.upto(number_of_events) { |n| results << subject.ack?(n) } }
9
+
10
+ context "with multiples events" do
11
+ let(:number_of_events) { Flores::Random.integer(100..1024) }
12
+
13
+ it "should return multiples partial acks" do
14
+ expect(results.size).to eq(number_of_events)
15
+ expect(results.count(true)).to eq((number_of_events / number_of_events * Lumberjack::AckingProtocolV2::ACK_RATIO).ceil + 1)
16
+ end
17
+
18
+ it "last ack should be true" do
19
+ expect(results.last).to be_truthy
20
+ end
21
+ end
22
+
23
+ context "with only one event" do
24
+ let(:number_of_events) { 1 }
25
+
26
+ it "should return true only once" do
27
+ expect(results.size).to eq(number_of_events)
28
+ expect(results.count(true)).to eq(1)
29
+ end
30
+ end
31
+ end
@@ -8,9 +8,7 @@ require "openssl"
8
8
  require "zlib"
9
9
 
10
10
  describe "Lumberjack::Client" do
11
-
12
11
  describe "Lumberjack::Socket" do
13
-
14
12
  let(:port) { 5000 }
15
13
 
16
14
  subject(:socket) { Lumberjack::Socket.new(:port => port, :ssl_certificate => "" ) }
@@ -38,16 +36,60 @@ describe "Lumberjack::Client" do
38
36
  end
39
37
  end
40
38
 
41
- describe Lumberjack::Encoder do
39
+ describe Lumberjack::FrameEncoder do
40
+ it 'should creates frames without truncating accentued characters' do
41
+ content = {
42
+ "message" => "Le Canadien de Montréal est la meilleure équipe au monde!",
43
+ "other" => "éléphant"
44
+ }
45
+ parser = Lumberjack::Parser.new
46
+ parser.feed(Lumberjack::FrameEncoder.to_frame(content, 0)) do |code, sequence, data|
47
+ if code == :data
48
+ expect(data["message"].force_encoding('UTF-8')).to eq(content["message"])
49
+ expect(data["other"].force_encoding('UTF-8')).to eq(content["other"])
50
+ end
51
+ end
52
+ end
53
+
54
+ it 'should creates frames without dropping multibytes characters' do
55
+ content = {
56
+ "message" => "国際ホッケー連盟" # International Hockey Federation
57
+ }
58
+ parser = Lumberjack::Parser.new
59
+ parser.feed(Lumberjack::FrameEncoder.to_frame(content, 0)) do |code, sequence, data|
60
+ expect(data["message"].force_encoding('UTF-8')).to eq(content["message"]) if code == :data
61
+ end
62
+ end
63
+ end
64
+
65
+ describe Lumberjack::JsonEncoder do
66
+ it 'should create frames from nested hash' do
67
+ content = {
68
+ "number" => 1,
69
+ "string" => "hello world",
70
+ "array" => [1,2,3],
71
+ "sub" => {
72
+ "a" => 1
73
+ }
74
+ }
75
+ parser = Lumberjack::Parser.new
76
+ frame = Lumberjack::JsonEncoder.to_frame(content, 0)
77
+ parser.feed(frame) do |code, sequence, data|
78
+ expect(data).to eq(content) if code == :json
79
+ end
80
+ end
81
+
42
82
  it 'should creates frames without truncating accentued characters' do
43
83
  content = {
44
84
  "message" => "Le Canadien de Montréal est la meilleure équipe au monde!",
45
85
  "other" => "éléphant"
46
86
  }
47
87
  parser = Lumberjack::Parser.new
48
- parser.feed(Lumberjack::Encoder.to_frame(content, 0)) do |code, sequence, data|
49
- expect(data["message"].force_encoding('UTF-8')).to eq(content["message"])
50
- expect(data["other"].force_encoding('UTF-8')).to eq(content["other"])
88
+ parser.feed(Lumberjack::JsonEncoder.to_frame(content, 0)) do |code, sequence, data|
89
+ if code == :json
90
+ expect(data["message"]).to eq(content["message"])
91
+ expect(data["other"]).to eq(content["other"])
92
+ end
51
93
  end
52
94
  end
53
95
 
@@ -56,8 +98,8 @@ describe "Lumberjack::Client" do
56
98
  "message" => "国際ホッケー連盟" # International Hockey Federation
57
99
  }
58
100
  parser = Lumberjack::Parser.new
59
- parser.feed(Lumberjack::Encoder.to_frame(content, 0)) do |code, sequence, data|
60
- expect(data["message"].force_encoding('UTF-8')).to eq(content["message"])
101
+ parser.feed(Lumberjack::JsonEncoder.to_frame(content, 0)) do |code, sequence, data|
102
+ expect(data["message"]).to eq(content["message"]) if code == :json
61
103
  end
62
104
  end
63
105
  end
@@ -0,0 +1,47 @@
1
+ # encoding: utf-8
2
+ require "lumberjack/server"
3
+ require "spec_helper"
4
+ require "flores/random"
5
+
6
+ describe "Connnection" do
7
+ let(:server) { double("server", :closed? => false) }
8
+ let(:socket) { double("socket", :closed? => false) }
9
+ let(:connection) { Lumberjack::Connection.new(socket, server) }
10
+ let(:payload) { {"line" => "foobar" } }
11
+ let(:start_sequence) { Flores::Random.integer(0..2000) }
12
+ let(:random_number_of_events) { Flores::Random.integer(2..200) }
13
+
14
+ context "when the server is running" do
15
+ before do
16
+ expect(socket).to receive(:sysread).at_least(:once).with(Lumberjack::Connection::READ_SIZE).and_return("")
17
+ allow(socket).to receive(:syswrite).with(anything).and_return(true)
18
+ allow(socket).to receive(:close)
19
+
20
+
21
+ expectation = receive(:feed)
22
+ .with("")
23
+ .and_yield(:version, Lumberjack::Parser::PROTOCOL_VERSION_1)
24
+ .and_yield(:window_size, random_number_of_events)
25
+
26
+ random_number_of_events.times { |n| expectation.and_yield(:data, start_sequence + n + 1, payload) }
27
+
28
+ expect_any_instance_of(Lumberjack::Parser).to expectation
29
+ end
30
+
31
+ it "should ack the end of a sequence" do
32
+ expect(socket).to receive(:syswrite).with(["1A", random_number_of_events + start_sequence].pack("A*N"))
33
+ connection.read_socket
34
+ end
35
+ end
36
+
37
+ context "when the server stop" do
38
+ let(:server) { double("server", :closed? => true) }
39
+ before do
40
+ expect(socket).to receive(:close).and_return(true)
41
+ end
42
+
43
+ it "stop reading from the socket" do
44
+ expect { |b| connection.run(&b) }.not_to yield_control
45
+ end
46
+ end
47
+ end
@@ -1,33 +1,42 @@
1
1
  # encoding: utf-8
2
+ require "lumberjack/client"
2
3
  require "lumberjack/server"
3
- require "spec_helper"
4
4
  require "flores/random"
5
+ require "flores/pki"
6
+ require "spec_helper"
7
+
8
+ Thread.abort_on_exception = true
5
9
 
6
10
  describe "Server" do
7
- let(:socket) { double("socket") }
8
- let(:connection) { Lumberjack::Connection.new(socket) }
9
- let(:payload) { {"line" => "foobar" } }
10
- let(:start_sequence) { Flores::Random.integer(0..2000) }
11
- let(:random_number_of_events) { Flores::Random.integer(2..200) }
11
+ let(:certificate) { Flores::PKI.generate }
12
+ let(:certificate_file_crt) { "certificate.crt" }
13
+ let(:certificate_file_key) { "certificate.key" }
14
+ let(:port) { Flores::Random.integer(1024..65335) }
15
+ let(:tcp_port) { port + 1 }
16
+ let(:host) { "127.0.0.1" }
17
+ let(:queue) { [] }
12
18
 
13
19
  before do
14
- expect(socket).to receive(:sysread).at_least(:once).with(Lumberjack::Connection::READ_SIZE).and_return("")
15
- allow(socket).to receive(:syswrite).with(anything).and_return(true)
16
- allow(socket).to receive(:close)
17
-
18
- expectation = receive(:feed)
19
- .with("")
20
- .and_yield(:window_size, random_number_of_events)
21
-
22
- random_number_of_events.times { |n| expectation.and_yield(:data, start_sequence + n + 1, payload) }
20
+ expect(File).to receive(:read).at_least(1).with(certificate_file_crt) { certificate.first.to_s }
21
+ expect(File).to receive(:read).at_least(1).with(certificate_file_key) { certificate.last.to_s }
22
+ end
23
23
 
24
- expect_any_instance_of(Lumberjack::Parser).to expectation
24
+ subject do
25
+ Lumberjack::Server.new(:port => port,
26
+ :address => host,
27
+ :ssl_certificate => certificate_file_crt,
28
+ :ssl_key => certificate_file_key)
25
29
  end
26
30
 
27
- describe "Connnection" do
28
- it "should ack the end of a sequence" do
29
- expect(socket).to receive(:syswrite).with(["1A", random_number_of_events + start_sequence].pack("A*N"))
30
- connection.read_socket
31
+ it "should not block when closing the server" do
32
+ thread = Thread.new do
33
+ subject.run do |event|
34
+ queue << event
35
+ end
31
36
  end
37
+
38
+ sleep(1) while thread.status != "run"
39
+ subject.close
40
+ wait_for { thread.status }.to be_falsey
32
41
  end
33
42
  end
data/spec/spec_helper.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  # encoding: utf-8
2
2
  require 'rspec'
3
3
  require 'rspec/mocks'
4
+ require 'rspec/wait'
5
+
4
6
  $: << File.realpath(File.join(File.dirname(__FILE__), "..", "lib"))
5
7
 
6
8
  RSpec.configure do |config|
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jls-lumberjack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.24
4
+ version: 0.0.25.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jordan Sissel
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-08-17 00:00:00.000000000 Z
11
+ date: 2015-10-17 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - '>='
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ name: concurrent-ruby
20
+ prerelease: false
21
+ type: :runtime
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  requirement: !ruby/object:Gem::Requirement
15
29
  requirements:
@@ -66,6 +80,20 @@ dependencies:
66
80
  - - '>='
67
81
  - !ruby/object:Gem::Version
68
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ name: rspec-wait
90
+ prerelease: false
91
+ type: :development
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
69
97
  description: lumberjack log transport library
70
98
  email:
71
99
  - jls@semicomplete.com
@@ -77,7 +105,10 @@ files:
77
105
  - lib/lumberjack/client.rb
78
106
  - lib/lumberjack/server.rb
79
107
  - spec/integration_spec.rb
108
+ - spec/lumberjack/acking_protocol_v1_spec.rb
109
+ - spec/lumberjack/acking_protocol_v2_spec.rb
80
110
  - spec/lumberjack/client_spec.rb
111
+ - spec/lumberjack/connection_spec.rb
81
112
  - spec/lumberjack/server_spec.rb
82
113
  - spec/spec_helper.rb
83
114
  homepage: https://github.com/jordansissel/lumberjack
@@ -94,9 +125,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
94
125
  version: '0'
95
126
  required_rubygems_version: !ruby/object:Gem::Requirement
96
127
  requirements:
97
- - - '>='
128
+ - - '>'
98
129
  - !ruby/object:Gem::Version
99
- version: '0'
130
+ version: 1.3.1
100
131
  requirements: []
101
132
  rubyforge_project:
102
133
  rubygems_version: 2.4.8
@@ -106,5 +137,8 @@ summary: lumberjack log transport library
106
137
  test_files:
107
138
  - spec/integration_spec.rb
108
139
  - spec/spec_helper.rb
140
+ - spec/lumberjack/acking_protocol_v1_spec.rb
141
+ - spec/lumberjack/acking_protocol_v2_spec.rb
109
142
  - spec/lumberjack/client_spec.rb
143
+ - spec/lumberjack/connection_spec.rb
110
144
  - spec/lumberjack/server_spec.rb