log-courier 1.10.0 → 2.7.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,217 @@
1
+ # Copyright 2014-2021 Jason Woods.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'cabin'
16
+ require 'log-courier/client'
17
+ require 'log-courier/server'
18
+
19
+ TEMP_PATH = File.join(File.dirname(__FILE__), 'tmp')
20
+ EVENT_WAIT_COUNT = 50
21
+ EVENT_WAIT_TIME = 0.5
22
+
23
+ # Common helpers for testing both ruby client and the courier
24
+ shared_context 'LogCourier' do
25
+ before :all do
26
+ Thread.abort_on_exception = true
27
+
28
+ FileUtils.rm_r(TEMP_PATH) if File.directory?(TEMP_PATH)
29
+ Dir.mkdir(TEMP_PATH)
30
+
31
+ @ssl_cert = File.open(File.join(TEMP_PATH, 'ssl_cert'), 'w')
32
+ @ssl_key = File.open(File.join(TEMP_PATH, 'ssl_key'), 'w')
33
+ @ssl_csr = File.open(File.join(TEMP_PATH, 'ssl_csr'), 'w')
34
+
35
+ # Generate the ssl key
36
+ cnf_path = "#{File.dirname(__FILE__)}/openssl.cnf"
37
+ system("openssl req -config #{cnf_path} -new -batch -keyout #{@ssl_key.path} -out #{@ssl_csr.path}")
38
+ system(
39
+ "openssl x509 -extfile #{cnf_path} -extensions extensions_section -req -days 365 -in #{@ssl_csr.path}" \
40
+ " -signkey #{@ssl_key.path} -out #{@ssl_cert.path}",
41
+ )
42
+ end
43
+
44
+ after :all do
45
+ FileUtils.rm_r(TEMP_PATH) if File.directory?(TEMP_PATH)
46
+ end
47
+
48
+ before :each do
49
+ @event_queue = SizedQueue.new 10_000
50
+
51
+ @clients = {}
52
+ @servers = {}
53
+ @server_counts = {}
54
+ @server_threads = {}
55
+ end
56
+
57
+ after :each do
58
+ unless @servers.length.zero?
59
+ id, = @servers.first
60
+ raise "Server was not shutdown: #{id}"
61
+ end
62
+ unless @clients.length.zero?
63
+ id, = @clients.first
64
+ raise "Client was not shutdown: #{id}"
65
+ end
66
+ end
67
+
68
+ def start_client(**args)
69
+ args = {
70
+ id: '__default__',
71
+ transport: 'tls',
72
+ addresses: ['127.0.0.1'],
73
+ }.merge!(**args)
74
+
75
+ args[:ssl_ca] = @ssl_cert.path if args[:transport] == 'tls'
76
+
77
+ id = args[:id]
78
+ args[:port] = server_port(id) unless args.key?(:port)
79
+
80
+ logger = Cabin::Channel.new
81
+ logger.subscribe $stdout
82
+ logger['instance'] = "Client #{id}"
83
+ logger.level = :debug
84
+
85
+ # Reset server for each test
86
+ @clients[id] = LogCourier::Client.new(
87
+ logger: logger,
88
+ **args,
89
+ )
90
+ end
91
+
92
+ def shutdown_client(which = nil)
93
+ which = if which.nil?
94
+ @clients.keys
95
+ else
96
+ [which]
97
+ end
98
+ which.each do |id|
99
+ @clients[id].shutdown
100
+ @clients.delete id
101
+ end
102
+ nil
103
+ end
104
+
105
+ def start_server(**args)
106
+ args = {
107
+ id: '__default__',
108
+ transport: 'tls',
109
+ }.merge!(**args)
110
+
111
+ if args[:transport] == 'tls'
112
+ args[:ssl_certificate] = @ssl_cert.path
113
+ args[:ssl_key] = @ssl_key.path
114
+ end
115
+
116
+ id = args[:id]
117
+
118
+ logger = Cabin::Channel.new
119
+ logger.subscribe $stdout
120
+ logger['instance'] = "Server #{id}"
121
+ logger.level = :debug
122
+
123
+ raise 'Server already initialised' if @servers.key?(id)
124
+
125
+ # Reset server for each test
126
+ @servers[id] = LogCourier::Server.new(
127
+ logger: logger,
128
+ **args,
129
+ )
130
+
131
+ @server_counts[id] = 0
132
+ @server_threads[id] = Thread.new do
133
+ @servers[id].run do |event|
134
+ @server_counts[id] += 1
135
+ @event_queue << event
136
+ end
137
+ rescue LogCourier::ShutdownSignal
138
+ 0
139
+ end
140
+ @servers[id]
141
+ end
142
+
143
+ # A helper to shutdown a Log Courier server
144
+ def shutdown_server(which = nil)
145
+ which = if which.nil?
146
+ @servers.keys
147
+ else
148
+ [which]
149
+ end
150
+ which.each do |id|
151
+ @server_threads[id].raise LogCourier::ShutdownSignal
152
+ @server_threads[id].join
153
+ @server_threads.delete id
154
+ @server_counts.delete id
155
+ @servers.delete id
156
+ end
157
+ nil
158
+ end
159
+
160
+ # A helper to get the port a server is bound to
161
+ def server_port(id = '__default__')
162
+ @servers[id].port
163
+ end
164
+
165
+ # A helper to get number of events received on the server
166
+ def server_count(id = '__default__')
167
+ @server_counts[id]
168
+ end
169
+
170
+ def receive_and_check(args = {})
171
+ args = {
172
+ total: nil,
173
+ check: true,
174
+ check_file: true,
175
+ check_order: true,
176
+ host: nil,
177
+ }.merge!(args)
178
+
179
+ # Quick check of the total events we are expecting - but allow time to receive them
180
+ total = if args[:total].nil?
181
+ @files.reduce(0) do |sum, f|
182
+ sum + f.count
183
+ end
184
+ else
185
+ args[:total]
186
+ end
187
+
188
+ args.delete_if do |_, v|
189
+ v.nil?
190
+ end
191
+
192
+ orig_total = total
193
+ check = args[:check]
194
+
195
+ waited = 0
196
+ while total.positive? && waited <= EVENT_WAIT_COUNT
197
+ if @event_queue.length.zero?
198
+ sleep(EVENT_WAIT_TIME)
199
+ waited += 1
200
+ next
201
+ end
202
+
203
+ waited = 0
204
+ until @event_queue.length.zero?
205
+ e = @event_queue.pop
206
+ total -= 1
207
+ next unless check
208
+
209
+ yield e
210
+ end
211
+ end
212
+
213
+ # Fancy calculation to give a nice "expected" output of expected num of events
214
+ expect(orig_total - total).to eq orig_total
215
+ nil
216
+ end
217
+ end
@@ -1,6 +1,4 @@
1
- # encoding: utf-8
2
-
3
- # Copyright 2014-2019 Jason Woods and Contributors.
1
+ # Copyright 2014-2021 Jason Woods and Contributors.
4
2
  #
5
3
  # This file is a modification of code from Logstash Forwarder.
6
4
  # Copyright 2012-2013 Jordan Sissel and contributors.
@@ -18,44 +16,27 @@
18
16
  # limitations under the License.
19
17
 
20
18
  require 'log-courier/event_queue'
19
+ require 'log-courier/protocol'
20
+ require 'log-courier/version'
21
21
  require 'multi_json'
22
- require 'thread'
23
22
  require 'zlib'
24
23
 
25
- class NativeException; end
26
-
27
24
  module LogCourier
28
25
  class TimeoutError < StandardError; end
26
+
29
27
  class ShutdownSignal < StandardError; end
28
+
30
29
  class ProtocolError < StandardError; end
31
30
 
32
31
  # Implementation of the server
33
32
  class Server
34
33
  attr_reader :port
35
34
 
36
- # TODO(driskell): Consolidate singleton into another file
37
- class << self
38
- @json_adapter
39
- @json_parseerror
40
-
41
- def get_json_adapter
42
- @json_adapter = MultiJson.adapter.instance if @json_adapter.nil?
43
- @json_adapter
44
- end
45
-
46
- def get_json_parseerror
47
- if @json_parseerror.nil?
48
- @json_parseerror = get_json_adapter.class::ParseError
49
- end
50
- @json_parseerror
51
- end
52
- end
53
-
54
35
  def initialize(options = {})
55
36
  @options = {
56
- logger: nil,
57
- transport: 'tls',
58
- raw_events: true,
37
+ logger: nil,
38
+ transport: 'tls',
39
+ disable_handshake: false,
59
40
  }.merge!(options)
60
41
 
61
42
  @logger = @options[:logger]
@@ -64,11 +45,8 @@ module LogCourier
64
45
  when 'tcp', 'tls'
65
46
  require 'log-courier/server_tcp'
66
47
  @server = ServerTcp.new(@options)
67
- when 'plainzmq', 'zmq'
68
- require 'log-courier/server_zmq'
69
- @server = ServerZmq.new(@options)
70
48
  else
71
- fail 'input/courier: \'transport\' must be tcp, tls, plainzmq or zmq'
49
+ raise 'input/courier: \'transport\' must be tcp or tls'
72
50
  end
73
51
 
74
52
  # Grab the port back and update the logger context
@@ -91,9 +69,9 @@ module LogCourier
91
69
  process_jdat message, comm, @event_queue
92
70
  else
93
71
  if comm.peer.nil?
94
- @logger.warn 'Unknown message received', :from => 'unknown' unless @logger.nil?
72
+ @logger&.warn 'Unknown message received', from: 'unknown'
95
73
  else
96
- @logger.warn 'Unknown message received', :from => comm.peer unless @logger.nil?
74
+ @logger&.warn 'Unknown message received', from: comm.peer
97
75
  end
98
76
  # Don't kill a client that sends a bad message
99
77
  # Just reject it and let it send it again, potentially to another server
@@ -105,6 +83,7 @@ module LogCourier
105
83
  loop do
106
84
  event = @event_queue.pop
107
85
  break if event.nil?
86
+
108
87
  block.call event
109
88
  end
110
89
  ensure
@@ -114,25 +93,23 @@ module LogCourier
114
93
  server_thread.join
115
94
  end
116
95
  end
117
- return
96
+ nil
118
97
  end
119
98
 
120
99
  def stop
121
100
  @event_queue << nil
101
+ nil
122
102
  end
123
103
 
124
104
  private
125
105
 
126
106
  def process_ping(message, comm)
127
107
  # Size of message should be 0
128
- if message.bytesize != 0
129
- fail ProtocolError, "unexpected data attached to ping message (#{message.bytesize})"
130
- end
108
+ raise ProtocolError, "unexpected data attached to ping message (#{message.bytesize})" unless message.bytesize.zero?
131
109
 
132
110
  # PONG!
133
111
  # NOTE: comm.send can raise a Timeout::Error of its own
134
112
  comm.send 'PONG', ''
135
- return
136
113
  end
137
114
 
138
115
  def process_jdat(message, comm, event_queue)
@@ -142,13 +119,11 @@ module LogCourier
142
119
  # OK - first is a nonce - we send this back with sequence acks
143
120
  # This allows the client to know what is being acknowledged
144
121
  # Nonce is 16 so check we have enough
145
- if message.bytesize < 17
146
- fail ProtocolError, "JDAT message too small (#{message.bytesize})"
147
- end
122
+ raise ProtocolError, "JDAT message too small (#{message.bytesize})" if message.bytesize < 17
148
123
 
149
124
  nonce = message[0...16]
150
125
 
151
- if !@logger.nil? && @logger.debug?
126
+ if @logger&.debug?
152
127
  nonce_str = nonce.each_byte.map do |b|
153
128
  b.to_s(16).rjust(2, '0')
154
129
  end
@@ -162,25 +137,21 @@ module LogCourier
162
137
  # We acknowledge them by their 1-index position in the stream
163
138
  # A 0 sequence acknowledgement means we haven't processed any yet
164
139
  sequence = 0
165
- events = []
166
140
  length_buf = ''
167
141
  data_buf = ''
168
142
  loop do
169
143
  ret = message.read 4, length_buf
170
- if ret.nil?
171
- # Finished!
172
- break
173
- elsif length_buf.bytesize < 4
174
- fail ProtocolError, "JDAT length extraction failed (#{ret} #{length_buf.bytesize})"
175
- end
144
+ # Finished?
145
+ break if ret.nil?
146
+ raise ProtocolError, "JDAT length extraction failed (#{ret} #{length_buf.bytesize})" if length_buf.bytesize < 4
176
147
 
177
- length = length_buf.unpack('N').first
148
+ length = length_buf.unpack1('N')
178
149
 
179
150
  # Extract message
180
151
  ret = message.read length, data_buf
181
- if ret.nil? or data_buf.bytesize < length
182
- @logger.warn()
183
- fail ProtocolError, "JDAT message extraction failed #{ret} #{data_buf.bytesize}"
152
+ if ret.nil? || data_buf.bytesize < length
153
+ @logger&.warn()
154
+ raise ProtocolError, "JDAT message extraction failed #{ret} #{data_buf.bytesize}"
184
155
  end
185
156
 
186
157
  data_buf.force_encoding('utf-8')
@@ -200,9 +171,9 @@ module LogCourier
200
171
 
201
172
  # Decode the JSON
202
173
  begin
203
- event = self.class.get_json_adapter.load(data_buf, :raw => @options[:raw_events])
204
- rescue self.class.get_json_parseerror => e
205
- @logger.warn e, :invalid_encodings => invalid_encodings, :hint => 'JSON parse failure, falling back to plain-text' unless @logger.nil?
174
+ event = MultiJson.load(data_buf)
175
+ rescue MultiJson::ParseError => e
176
+ @logger&.warn e, invalid_encodings: invalid_encodings, hint: 'JSON parse failure, falling back to plain-text'
206
177
  event = { 'message' => data_buf }
207
178
  end
208
179
 
@@ -215,7 +186,7 @@ module LogCourier
215
186
  rescue TimeoutError
216
187
  # Full pipeline, partial ack
217
188
  # NOTE: comm.send can raise a Timeout::Error of its own
218
- @logger.debug 'Partially acknowledging message', :nonce => nonce_str.join, :sequence => sequence if !@logger.nil? && @logger.debug?
189
+ @logger&.debug 'Partially acknowledging message', nonce: nonce_str.join, sequence: sequence if @logger&.debug?
219
190
  comm.send 'ACKN', [nonce, sequence].pack('a*N')
220
191
  ack_timeout = Time.now.to_i + 5
221
192
  retry
@@ -226,9 +197,8 @@ module LogCourier
226
197
 
227
198
  # Acknowledge the full message
228
199
  # NOTE: comm.send can raise a Timeout::Error
229
- @logger.debug 'Acknowledging message', :nonce => nonce_str.join, :sequence => sequence if !@logger.nil? && @logger.debug?
200
+ @logger&.debug 'Acknowledging message', nonce: nonce_str.join, sequence: sequence if @logger&.debug?
230
201
  comm.send 'ACKN', [nonce, sequence].pack('A*N')
231
- return
232
202
  end
233
203
  end
234
204
  end