fluentd 1.16.0 → 1.16.7

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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/{bug_report.yaml → bug_report.yml} +1 -0
  3. data/.github/ISSUE_TEMPLATE/{feature_request.yaml → feature_request.yml} +1 -0
  4. data/.github/workflows/stale-actions.yml +24 -0
  5. data/.github/workflows/test.yml +39 -0
  6. data/CHANGELOG.md +161 -0
  7. data/CONTRIBUTING.md +1 -1
  8. data/MAINTAINERS.md +3 -3
  9. data/README.md +0 -2
  10. data/SECURITY.md +5 -9
  11. data/fluentd.gemspec +10 -2
  12. data/lib/fluent/command/ctl.rb +2 -2
  13. data/lib/fluent/command/fluentd.rb +1 -1
  14. data/lib/fluent/command/plugin_config_formatter.rb +1 -1
  15. data/lib/fluent/config/dsl.rb +1 -1
  16. data/lib/fluent/config/v1_parser.rb +2 -2
  17. data/lib/fluent/config/yaml_parser/parser.rb +4 -0
  18. data/lib/fluent/counter/server.rb +1 -1
  19. data/lib/fluent/counter/validator.rb +3 -3
  20. data/lib/fluent/engine.rb +1 -1
  21. data/lib/fluent/event.rb +8 -4
  22. data/lib/fluent/log.rb +9 -0
  23. data/lib/fluent/match.rb +1 -1
  24. data/lib/fluent/msgpack_factory.rb +6 -1
  25. data/lib/fluent/plugin/base.rb +1 -1
  26. data/lib/fluent/plugin/buffer.rb +96 -87
  27. data/lib/fluent/plugin/filter_record_transformer.rb +1 -1
  28. data/lib/fluent/plugin/in_forward.rb +1 -1
  29. data/lib/fluent/plugin/in_http.rb +8 -8
  30. data/lib/fluent/plugin/in_sample.rb +1 -1
  31. data/lib/fluent/plugin/in_tail/position_file.rb +32 -18
  32. data/lib/fluent/plugin/in_tail.rb +99 -31
  33. data/lib/fluent/plugin/in_tcp.rb +43 -0
  34. data/lib/fluent/plugin/out_exec_filter.rb +2 -2
  35. data/lib/fluent/plugin/out_file.rb +8 -0
  36. data/lib/fluent/plugin/output.rb +2 -2
  37. data/lib/fluent/plugin/parser_json.rb +4 -12
  38. data/lib/fluent/plugin_helper/event_loop.rb +2 -2
  39. data/lib/fluent/plugin_helper/record_accessor.rb +1 -1
  40. data/lib/fluent/plugin_helper/server.rb +8 -0
  41. data/lib/fluent/plugin_helper/thread.rb +3 -3
  42. data/lib/fluent/plugin_id.rb +1 -1
  43. data/lib/fluent/supervisor.rb +2 -2
  44. data/lib/fluent/system_config.rb +1 -1
  45. data/lib/fluent/version.rb +1 -1
  46. data/lib/fluent/winsvc.rb +28 -3
  47. data/templates/new_gem/test/helper.rb.erb +0 -1
  48. data/test/command/test_cat.rb +2 -2
  49. data/test/command/test_fluentd.rb +65 -12
  50. data/test/config/test_system_config.rb +2 -2
  51. data/test/helper.rb +27 -7
  52. data/test/plugin/in_tail/test_io_handler.rb +13 -14
  53. data/test/plugin/in_tail/test_position_file.rb +37 -8
  54. data/test/plugin/out_forward/test_ack_handler.rb +3 -3
  55. data/test/plugin/out_forward/test_socket_cache.rb +3 -3
  56. data/test/plugin/test_base.rb +1 -1
  57. data/test/plugin/test_buffer.rb +110 -0
  58. data/test/plugin/test_buffer_chunk.rb +11 -0
  59. data/test/plugin/test_in_forward.rb +11 -10
  60. data/test/plugin/test_in_http.rb +1 -1
  61. data/test/plugin/test_in_monitor_agent.rb +6 -6
  62. data/test/plugin/test_in_syslog.rb +25 -18
  63. data/test/plugin/test_in_tail.rb +639 -0
  64. data/test/plugin/test_in_tcp.rb +75 -5
  65. data/test/plugin/test_in_udp.rb +35 -1
  66. data/test/plugin/test_in_unix.rb +2 -2
  67. data/test/plugin/test_multi_output.rb +1 -1
  68. data/test/plugin/test_out_exec_filter.rb +14 -9
  69. data/test/plugin/test_out_file.rb +24 -4
  70. data/test/plugin/test_out_forward.rb +36 -42
  71. data/test/plugin/test_out_stream.rb +1 -1
  72. data/test/plugin/test_output.rb +12 -12
  73. data/test/plugin/test_output_as_buffered.rb +44 -44
  74. data/test/plugin/test_output_as_buffered_compress.rb +32 -18
  75. data/test/plugin/test_output_as_buffered_retries.rb +1 -1
  76. data/test/plugin/test_output_as_buffered_secondary.rb +2 -2
  77. data/test/plugin/test_parser_json.rb +31 -0
  78. data/test/plugin_helper/test_child_process.rb +15 -5
  79. data/test/plugin_helper/test_http_server_helper.rb +1 -1
  80. data/test/plugin_helper/test_server.rb +110 -38
  81. data/test/plugin_helper/test_socket.rb +1 -1
  82. data/test/test_config.rb +6 -0
  83. data/test/test_event_router.rb +2 -2
  84. data/test/test_log.rb +38 -1
  85. data/test/test_msgpack_factory.rb +32 -0
  86. data/test/test_supervisor.rb +13 -0
  87. metadata +44 -17
  88. data/.github/workflows/linux-test.yaml +0 -36
  89. data/.github/workflows/macos-test.yaml +0 -34
  90. data/.github/workflows/windows-test.yaml +0 -49
@@ -128,11 +128,14 @@ class TestFluentdCommand < ::Test::Unit::TestCase
128
128
 
129
129
  # ATTENTION: This stops taking logs when all `pattern_list` match or timeout,
130
130
  # so `patterns_not_match` can test only logs up to that point.
131
+ # You can pass a block to assert something after log matching.
131
132
  def assert_log_matches(cmdline, *pattern_list, patterns_not_match: [], timeout: 20, env: {})
132
133
  matched = false
133
134
  matched_wrongly = false
134
- assert_error_msg = ""
135
+ error_msg_match = ""
135
136
  stdio_buf = ""
137
+ succeeded_block = true
138
+ error_msg_block = ""
136
139
  begin
137
140
  execute_command(cmdline, @tmp_dir, env) do |pid, stdout|
138
141
  begin
@@ -163,6 +166,13 @@ class TestFluentdCommand < ::Test::Unit::TestCase
163
166
  end
164
167
  end
165
168
  end
169
+
170
+ begin
171
+ yield if block_given?
172
+ rescue => e
173
+ succeeded_block = false
174
+ error_msg_block = "failed block execution after matching: #{e}"
175
+ end
166
176
  ensure
167
177
  if SUPERVISOR_PID_PATTERN =~ stdio_buf
168
178
  @supervisor_pid = $1.to_i
@@ -173,19 +183,19 @@ class TestFluentdCommand < ::Test::Unit::TestCase
173
183
  end
174
184
  end
175
185
  rescue Timeout::Error
176
- assert_error_msg = "execution timeout"
186
+ error_msg_match = "execution timeout"
177
187
  # https://github.com/fluent/fluentd/issues/4095
178
188
  # On Windows, timeout without `@supervisor_pid` means that the test is invalid,
179
189
  # since the supervisor process will survive without being killed correctly.
180
190
  flunk("Invalid test: The pid of supervisor could not be taken, which is necessary on Windows.") if Fluent.windows? && @supervisor_pid.nil?
181
191
  rescue => e
182
- assert_error_msg = "unexpected error in launching fluentd: #{e.inspect}"
192
+ error_msg_match = "unexpected error in launching fluentd: #{e.inspect}"
183
193
  else
184
- assert_error_msg = "log doesn't match" unless matched
194
+ error_msg_match = "log doesn't match" unless matched
185
195
  end
186
196
 
187
197
  if patterns_not_match.empty?
188
- assert_error_msg = build_message(assert_error_msg,
198
+ error_msg_match = build_message(error_msg_match,
189
199
  "<?>\nwas expected to include:\n<?>",
190
200
  stdio_buf, pattern_list)
191
201
  else
@@ -197,16 +207,17 @@ class TestFluentdCommand < ::Test::Unit::TestCase
197
207
  lines.any?{|line| line.include?(ptn) }
198
208
  end
199
209
  if matched_wrongly
200
- assert_error_msg << "\n" unless assert_error_msg.empty?
201
- assert_error_msg << "pattern exists in logs wrongly: #{ptn}"
210
+ error_msg_match << "\n" unless error_msg_match.empty?
211
+ error_msg_match << "pattern exists in logs wrongly: #{ptn}"
202
212
  end
203
213
  end
204
- assert_error_msg = build_message(assert_error_msg,
214
+ error_msg_match = build_message(error_msg_match,
205
215
  "<?>\nwas expected to include:\n<?>\nand not include:\n<?>",
206
216
  stdio_buf, pattern_list, patterns_not_match)
207
217
  end
208
218
 
209
- assert matched && !matched_wrongly, assert_error_msg
219
+ assert matched && !matched_wrongly, error_msg_match
220
+ assert succeeded_block, error_msg_block if block_given?
210
221
  end
211
222
 
212
223
  def assert_fluentd_fails_to_start(cmdline, *pattern_list, timeout: 20)
@@ -941,7 +952,7 @@ CONF
941
952
  '-external-encoding' => '--external-encoding=utf-8',
942
953
  '-internal-encoding' => '--internal-encoding=utf-8',
943
954
  )
944
- test "-E option is set to RUBYOPT" do |opt|
955
+ test "-E option is set to RUBYOPT" do |base_opt|
945
956
  conf = <<CONF
946
957
  <source>
947
958
  @type dummy
@@ -952,6 +963,7 @@ CONF
952
963
  </match>
953
964
  CONF
954
965
  conf_path = create_conf_file('rubyopt_test.conf', conf)
966
+ opt = base_opt.dup
955
967
  opt << " #{ENV['RUBYOPT']}" if ENV['RUBYOPT']
956
968
  assert_log_matches(
957
969
  create_cmdline(conf_path),
@@ -991,9 +1003,14 @@ CONF
991
1003
  </match>
992
1004
  CONF
993
1005
  conf_path = create_conf_file('rubyopt_invalid_test.conf', conf)
1006
+ if Gem::Version.create(RUBY_VERSION) >= Gem::Version.create('3.3.0')
1007
+ expected_phrase = 'ruby: invalid switch in RUBYOPT'
1008
+ else
1009
+ expected_phrase = 'Invalid option is passed to RUBYOPT'
1010
+ end
994
1011
  assert_log_matches(
995
1012
  create_cmdline(conf_path),
996
- 'Invalid option is passed to RUBYOPT',
1013
+ expected_phrase,
997
1014
  env: { 'RUBYOPT' => 'a' },
998
1015
  )
999
1016
  end
@@ -1158,7 +1175,7 @@ CONF
1158
1175
  end
1159
1176
  end
1160
1177
 
1161
- sub_test_case 'sahred socket options' do
1178
+ sub_test_case 'shared socket options' do
1162
1179
  test 'enable shared socket by default' do
1163
1180
  conf = ""
1164
1181
  conf_path = create_conf_file('empty.conf', conf)
@@ -1282,4 +1299,40 @@ CONF
1282
1299
  "[debug]")
1283
1300
  end
1284
1301
  end
1302
+
1303
+ sub_test_case "plugin option" do
1304
+ test "should be the default value when not specifying" do
1305
+ conf_path = create_conf_file('test.conf', <<~CONF)
1306
+ <source>
1307
+ @type monitor_agent
1308
+ </source>
1309
+ CONF
1310
+ assert File.exist?(conf_path)
1311
+ cmdline = create_cmdline(conf_path)
1312
+
1313
+ assert_log_matches(cmdline, "fluentd worker is now running") do
1314
+ response = Net::HTTP.get(URI.parse("http://localhost:24220/api/config.json"))
1315
+ actual_conf = JSON.parse(response)
1316
+ assert_equal Fluent::Supervisor.default_options[:plugin_dirs], actual_conf["plugin_dirs"]
1317
+ end
1318
+ end
1319
+
1320
+ data(short: "-p")
1321
+ data(long: "--plugin")
1322
+ test "can be added by specifying the option" do |option_name|
1323
+ conf_path = create_conf_file('test.conf', <<~CONF)
1324
+ <source>
1325
+ @type monitor_agent
1326
+ </source>
1327
+ CONF
1328
+ assert File.exist?(conf_path)
1329
+ cmdline = create_cmdline(conf_path, option_name, @tmp_dir, option_name, @tmp_dir)
1330
+
1331
+ assert_log_matches(cmdline, "fluentd worker is now running") do
1332
+ response = Net::HTTP.get(URI.parse("http://localhost:24220/api/config.json"))
1333
+ actual_conf = JSON.parse(response)
1334
+ assert_equal Fluent::Supervisor.default_options[:plugin_dirs] + [@tmp_dir, @tmp_dir], actual_conf["plugin_dirs"]
1335
+ end
1336
+ end
1337
+ end
1285
1338
  end
@@ -151,7 +151,7 @@ module Fluent::Config
151
151
  data('daily' => "daily",
152
152
  'weekly' => 'weekly',
153
153
  'monthly' => 'monthly')
154
- test "symbols for rotate_age" do |age|
154
+ test "strings for rotate_age" do |age|
155
155
  conf = parse_text(<<-EOS)
156
156
  <system>
157
157
  <log>
@@ -160,7 +160,7 @@ module Fluent::Config
160
160
  </system>
161
161
  EOS
162
162
  sc = Fluent::SystemConfig.new(conf)
163
- assert_equal(age.to_sym, sc.log.rotate_age)
163
+ assert_equal(age, sc.log.rotate_age)
164
164
  end
165
165
 
166
166
  test "numeric number for rotate age" do
data/test/helper.rb CHANGED
@@ -71,17 +71,31 @@ end
71
71
 
72
72
  include Fluent::Test::Helpers
73
73
 
74
- def unused_port(num = 1, protocol: :tcp, bind: "0.0.0.0")
74
+ def unused_port(num = 1, protocol:, bind: "0.0.0.0")
75
75
  case protocol
76
- when :tcp
76
+ when :tcp, :tls
77
77
  unused_port_tcp(num)
78
78
  when :udp
79
79
  unused_port_udp(num, bind: bind)
80
+ when :all
81
+ unused_port_tcp_udp(num)
80
82
  else
81
83
  raise ArgumentError, "unknown protocol: #{protocol}"
82
84
  end
83
85
  end
84
86
 
87
+ def unused_port_tcp_udp(num = 1)
88
+ raise "not support num > 1" if num > 1
89
+
90
+ # The default maximum number of file descriptors in macOS is 256.
91
+ # It might need to set num to a smaller value than that.
92
+ tcp_ports = unused_port_tcp(200)
93
+ port = unused_port_udp(1, port_list: tcp_ports)
94
+ raise "can't find unused port" unless port
95
+
96
+ port
97
+ end
98
+
85
99
  def unused_port_tcp(num = 1)
86
100
  ports = []
87
101
  sockets = []
@@ -90,7 +104,7 @@ def unused_port_tcp(num = 1)
90
104
  sockets << s
91
105
  ports << s.addr[1]
92
106
  end
93
- sockets.each{|s| s.close }
107
+ sockets.each(&:close)
94
108
  if num == 1
95
109
  return ports.first
96
110
  else
@@ -100,12 +114,15 @@ end
100
114
 
101
115
  PORT_RANGE_AVAILABLE = (1024...65535)
102
116
 
103
- def unused_port_udp(num = 1, bind: "0.0.0.0")
117
+ def unused_port_udp(num = 1, port_list: [], bind: "0.0.0.0")
104
118
  family = IPAddr.new(IPSocket.getaddress(bind)).ipv4? ? ::Socket::AF_INET : ::Socket::AF_INET6
105
119
  ports = []
106
120
  sockets = []
107
- while ports.size < num
108
- port = rand(PORT_RANGE_AVAILABLE)
121
+
122
+ use_random_port = port_list.empty?
123
+ i = 0
124
+ loop do
125
+ port = use_random_port ? rand(PORT_RANGE_AVAILABLE) : port_list[i]
109
126
  u = UDPSocket.new(family)
110
127
  if (u.bind(bind, port) rescue nil)
111
128
  ports << port
@@ -113,8 +130,11 @@ def unused_port_udp(num = 1, bind: "0.0.0.0")
113
130
  else
114
131
  u.close
115
132
  end
133
+ i += 1
134
+ break if ports.size >= num
135
+ break if !use_random_port && i >= port_list.size
116
136
  end
117
- sockets.each{|s| s.close }
137
+ sockets.each(&:close)
118
138
  if num == 1
119
139
  return ports.first
120
140
  else
@@ -5,20 +5,19 @@ require 'fluent/plugin/metrics_local'
5
5
  require 'tempfile'
6
6
 
7
7
  class IntailIOHandlerTest < Test::Unit::TestCase
8
- setup do
9
- @file = Tempfile.new('intail_io_handler').binmode
10
- opened_file_metrics = Fluent::Plugin::LocalMetrics.new
11
- opened_file_metrics.configure(config_element('metrics', '', {}))
12
- closed_file_metrics = Fluent::Plugin::LocalMetrics.new
13
- closed_file_metrics.configure(config_element('metrics', '', {}))
14
- rotated_file_metrics = Fluent::Plugin::LocalMetrics.new
15
- rotated_file_metrics.configure(config_element('metrics', '', {}))
16
- @metrics = Fluent::Plugin::TailInput::MetricsInfo.new(opened_file_metrics, closed_file_metrics, rotated_file_metrics)
17
- end
18
-
19
- teardown do
20
- @file.close rescue nil
21
- @file.unlink rescue nil
8
+ def setup
9
+ Tempfile.create('intail_io_handler') do |file|
10
+ file.binmode
11
+ @file = file
12
+ opened_file_metrics = Fluent::Plugin::LocalMetrics.new
13
+ opened_file_metrics.configure(config_element('metrics', '', {}))
14
+ closed_file_metrics = Fluent::Plugin::LocalMetrics.new
15
+ closed_file_metrics.configure(config_element('metrics', '', {}))
16
+ rotated_file_metrics = Fluent::Plugin::LocalMetrics.new
17
+ rotated_file_metrics.configure(config_element('metrics', '', {}))
18
+ @metrics = Fluent::Plugin::TailInput::MetricsInfo.new(opened_file_metrics, closed_file_metrics, rotated_file_metrics)
19
+ yield
20
+ end
22
21
  end
23
22
 
24
23
  def create_target_info
@@ -6,13 +6,12 @@ require 'fileutils'
6
6
  require 'tempfile'
7
7
 
8
8
  class IntailPositionFileTest < Test::Unit::TestCase
9
- setup do
10
- @file = Tempfile.new('intail_position_file_test').binmode
11
- end
12
-
13
- teardown do
14
- @file.close rescue nil
15
- @file.unlink rescue nil
9
+ def setup
10
+ Tempfile.create('intail_position_file_test') do |file|
11
+ file.binmode
12
+ @file = file
13
+ yield
14
+ end
16
15
  end
17
16
 
18
17
  UNWATCHED_STR = '%016x' % Fluent::Plugin::TailInput::PositionFile::UNWATCHED_POSITION
@@ -26,6 +25,10 @@ class IntailPositionFileTest < Test::Unit::TestCase
26
25
  "valid_path" => Fluent::Plugin::TailInput::TargetInfo.new("valid_path", 1),
27
26
  "inode23bit" => Fluent::Plugin::TailInput::TargetInfo.new("inode23bit", 0),
28
27
  }
28
+ TEST_CONTENT_INODES = {
29
+ 1 => Fluent::Plugin::TailInput::TargetInfo.new("valid_path", 1),
30
+ 0 => Fluent::Plugin::TailInput::TargetInfo.new("inode23bit", 0),
31
+ }
29
32
 
30
33
  def write_data(f, content)
31
34
  f.write(content)
@@ -221,7 +224,7 @@ class IntailPositionFileTest < Test::Unit::TestCase
221
224
  end
222
225
 
223
226
  sub_test_case '#unwatch' do
224
- test 'deletes entry by path' do
227
+ test 'unwatch entry by path' do
225
228
  write_data(@file, TEST_CONTENT)
226
229
  pf = Fluent::Plugin::TailInput::PositionFile.load(@file, false, {}, logger: $log)
227
230
  inode1 = File.stat(@file).ino
@@ -239,6 +242,32 @@ class IntailPositionFileTest < Test::Unit::TestCase
239
242
 
240
243
  assert_not_equal p1, p2
241
244
  end
245
+
246
+ test 'unwatch entries by inode' do
247
+ write_data(@file, TEST_CONTENT)
248
+ pf = Fluent::Plugin::TailInput::PositionFile.load(@file, true, TEST_CONTENT_INODES, logger: $log)
249
+
250
+ existing_targets = TEST_CONTENT_INODES.select do |inode, target_info|
251
+ inode == 1
252
+ end
253
+ pe_to_unwatch = pf[TEST_CONTENT_INODES[0]]
254
+
255
+ pf.unwatch_removed_targets(existing_targets)
256
+
257
+ assert_equal(
258
+ {
259
+ map_keys: [TEST_CONTENT_INODES[1].ino],
260
+ unwatched_pe_pos: Fluent::Plugin::TailInput::PositionFile::UNWATCHED_POSITION,
261
+ },
262
+ {
263
+ map_keys: pf.instance_variable_get(:@map).keys,
264
+ unwatched_pe_pos: pe_to_unwatch.read_pos,
265
+ }
266
+ )
267
+
268
+ unwatched_pe_retaken = pf[TEST_CONTENT_INODES[0]]
269
+ assert_not_equal pe_to_unwatch, unwatched_pe_retaken
270
+ end
242
271
  end
243
272
 
244
273
  sub_test_case 'FilePositionEntry' do
@@ -111,11 +111,11 @@ class AckHandlerTest < Test::Unit::TestCase
111
111
  r, w = IO.pipe
112
112
  begin
113
113
  w.write(chunk_id)
114
- stub(r).recv { |_|
114
+ def r.recv(arg)
115
115
  sleep(1) # To ensure that multiple threads select the socket before closing.
116
- raise IOError, 'stream closed in another thread' if r.closed?
116
+ raise IOError, 'stream closed in another thread' if self.closed?
117
117
  MessagePack.pack({ 'ack' => Base64.encode64('chunk_id 111') })
118
- }
118
+ end
119
119
  ack.enqueue(r)
120
120
 
121
121
  threads = []
@@ -17,7 +17,7 @@ class SocketCacheTest < Test::Unit::TestCase
17
17
  assert_equal(socket, c.checkout_or('key') { socket })
18
18
  c.checkin(socket)
19
19
 
20
- sock = dont_allow(mock!).open
20
+ sock = mock!.open.never.subject
21
21
  assert_equal(socket, c.checkout_or('key') { sock.open })
22
22
  end
23
23
 
@@ -130,7 +130,7 @@ class SocketCacheTest < Test::Unit::TestCase
130
130
 
131
131
  c = Fluent::Plugin::ForwardOutput::SocketCache.new(10, $log)
132
132
  sock = mock!.close { 'closed' }.subject
133
- sock2 = dont_allow(mock!).close
133
+ sock2 = mock!.close.never.subject
134
134
  stub(sock).inspect
135
135
  stub(sock2).inspect
136
136
 
@@ -154,7 +154,7 @@ class SocketCacheTest < Test::Unit::TestCase
154
154
  Timecop.freeze(Time.parse('2016-04-13 14:00:00 +0900'))
155
155
 
156
156
  c = Fluent::Plugin::ForwardOutput::SocketCache.new(10, $log)
157
- sock = dont_allow(mock!).close
157
+ sock = mock!.close.never.subject
158
158
  stub(sock).inspect
159
159
  c.checkout_or('key') { sock }
160
160
 
@@ -108,7 +108,7 @@ class BaseTest < Test::Unit::TestCase
108
108
  @p.extend m
109
109
  assert_equal [], logger.logs
110
110
 
111
- ret = @p.string_safe_encoding("abc\xff.\x01f"){|s| s.split(/\./) }
111
+ ret = @p.string_safe_encoding("abc\xff.\x01f"){|s| s.split(".") }
112
112
  assert_equal ['abc?', "\u0001f"], ret
113
113
  assert_equal 1, logger.logs.size
114
114
  assert{ logger.logs.first.include?("invalid byte sequence is replaced in ") }
@@ -850,6 +850,116 @@ class BufferTest < Test::Unit::TestCase
850
850
  test '#compress returns :text' do
851
851
  assert_equal :text, @p.compress
852
852
  end
853
+
854
+ # https://github.com/fluent/fluentd/issues/3089
855
+ test "closed chunk should not be committed" do
856
+ assert_equal 8 * 1024 * 1024, @p.chunk_limit_size
857
+ assert_equal 0.95, @p.chunk_full_threshold
858
+
859
+ purge_count = 0
860
+
861
+ stub.proxy(@p).generate_chunk(anything) do |chunk|
862
+ stub.proxy(chunk).purge do |result|
863
+ purge_count += 1
864
+ result
865
+ end
866
+ stub.proxy(chunk).commit do |result|
867
+ assert_false(chunk.closed?)
868
+ result
869
+ end
870
+ stub.proxy(chunk).rollback do |result|
871
+ assert_false(chunk.closed?)
872
+ result
873
+ end
874
+ chunk
875
+ end
876
+
877
+ m = @p.metadata(timekey: Time.parse('2016-04-11 16:40:00 +0000').to_i)
878
+ small_row = "x" * 1024 * 400
879
+ big_row = "x" * 1024 * 1024 * 8 # just `chunk_size_limit`, it does't cause BufferOverFlowError.
880
+
881
+ # Write 42 events in 1 event stream, last one is for triggering `ShouldRetry`
882
+ @p.write({m => [small_row] * 40 + [big_row] + ["x"]})
883
+
884
+ # Above event strem will be splitted twice by `Buffer#write_step_by_step`
885
+ #
886
+ # 1. `write_once`: 42 [events] * 1 [stream]
887
+ # 2. `write_step_by_step`: 4 [events]* 10 [streams] + 2 [events] * 1 [stream]
888
+ # 3. `write_step_by_step` (by `ShouldRetry`): 1 [event] * 42 [streams]
889
+ #
890
+ # The problematic data is built in the 2nd stage.
891
+ # In the 2nd stage, 5 streams are packed in a chunk.
892
+ # ((1024 * 400) [bytes] * 4 [events] * 5 [streams] = 8192000 [bytes] < `chunk_limit_size` (8MB)).
893
+ # So 3 chunks are used to store all data.
894
+ # The 1st chunk is already staged by `write_once`.
895
+ # The 2nd & 3rd chunks are newly created as unstaged.
896
+ # The 3rd chunk is purged before `ShouldRetry`, it's no problem:
897
+ # https://github.com/fluent/fluentd/blob/7e9eba736ff40ad985341be800ddc46558be75f2/lib/fluent/plugin/buffer.rb#L850
898
+ # The 2nd chunk is purged in `rescue ShouldRetry`:
899
+ # https://github.com/fluent/fluentd/blob/7e9eba736ff40ad985341be800ddc46558be75f2/lib/fluent/plugin/buffer.rb#L862
900
+ # It causes the issue described in https://github.com/fluent/fluentd/issues/3089#issuecomment-1811839198
901
+
902
+ assert_equal 2, purge_count
903
+ end
904
+
905
+ # https://github.com/fluent/fluentd/issues/4446
906
+ test "#write_step_by_step keeps chunks kept in locked in entire #write process" do
907
+ assert_equal 8 * 1024 * 1024, @p.chunk_limit_size
908
+ assert_equal 0.95, @p.chunk_full_threshold
909
+
910
+ mon_enter_counts_by_chunk = {}
911
+ mon_exit_counts_by_chunk = {}
912
+
913
+ stub.proxy(@p).generate_chunk(anything) do |chunk|
914
+ stub(chunk).mon_enter do
915
+ enter_count = 1 + mon_enter_counts_by_chunk.fetch(chunk, 0)
916
+ exit_count = mon_exit_counts_by_chunk.fetch(chunk, 0)
917
+ mon_enter_counts_by_chunk[chunk] = enter_count
918
+
919
+ # Assert that chunk is passed to &block of write_step_by_step before exiting the lock.
920
+ # (i.e. The lock count must be 2 greater than the exit count).
921
+ # Since ShouldRetry occurs once, the staged chunk takes the lock 3 times when calling the block.
922
+ if chunk.staged?
923
+ lock_in_block = enter_count == 3
924
+ assert_equal(enter_count - 2, exit_count) if lock_in_block
925
+ else
926
+ lock_in_block = enter_count == 2
927
+ assert_equal(enter_count - 2, exit_count) if lock_in_block
928
+ end
929
+ end
930
+ stub(chunk).mon_exit do
931
+ exit_count = 1 + mon_exit_counts_by_chunk.fetch(chunk, 0)
932
+ mon_exit_counts_by_chunk[chunk] = exit_count
933
+ end
934
+ chunk
935
+ end
936
+
937
+ m = @p.metadata(timekey: Time.parse('2016-04-11 16:40:00 +0000').to_i)
938
+ small_row = "x" * 1024 * 400
939
+ big_row = "x" * 1024 * 1024 * 8 # just `chunk_size_limit`, it does't cause BufferOverFlowError.
940
+
941
+ # Write 42 events in 1 event stream, last one is for triggering `ShouldRetry`
942
+ @p.write({m => [small_row] * 40 + [big_row] + ["x"]})
943
+
944
+ # Above event strem will be splitted twice by `Buffer#write_step_by_step`
945
+ #
946
+ # 1. `write_once`: 42 [events] * 1 [stream]
947
+ # 2. `write_step_by_step`: 4 [events]* 10 [streams] + 2 [events] * 1 [stream]
948
+ # 3. `write_step_by_step` (by `ShouldRetry`): 1 [event] * 42 [streams]
949
+ #
950
+ # Example of staged chunk lock behavior:
951
+ #
952
+ # 1. mon_enter in write_step_by_step
953
+ # 2. ShouldRetry occurs
954
+ # 3. mon_exit in write_step_by_step
955
+ # 4. mon_enter again in write_step_by_step (retry)
956
+ # 5. passed to &block of write_step_by_step
957
+ # 6. mon_enter in the block (write)
958
+ # 7. mon_exit in write_step_by_step
959
+ # 8. mon_exit in write
960
+
961
+ assert_equal(mon_enter_counts_by_chunk.values, mon_exit_counts_by_chunk.values)
962
+ end
853
963
  end
854
964
 
855
965
  sub_test_case 'standard format with configuration for test with lower chunk limit size' do
@@ -57,6 +57,17 @@ class BufferChunkTest < Test::Unit::TestCase
57
57
  assert chunk.respond_to?(:msgpack_each)
58
58
  end
59
59
 
60
+ test 'unpacker arg is not implemented for ChunkMessagePackEventStreamer' do
61
+ meta = Object.new
62
+ chunk = Fluent::Plugin::Buffer::Chunk.new(meta)
63
+ chunk.extend Fluent::ChunkMessagePackEventStreamer
64
+
65
+ unpacker = Fluent::MessagePackFactory.thread_local_msgpack_unpacker
66
+
67
+ assert_raise(NotImplementedError){ chunk.each(unpacker: unpacker) }
68
+ assert_raise(NotImplementedError){ chunk.msgpack_each(unpacker: unpacker) }
69
+ end
70
+
60
71
  test 'some methods raise ArgumentError with an option of `compressed: :gzip` and without extending Compressble`' do
61
72
  meta = Object.new
62
73
  chunk = Fluent::Plugin::Buffer::Chunk.new(meta)
@@ -18,7 +18,8 @@ class ForwardInputTest < Test::Unit::TestCase
18
18
  Fluent::Test.setup
19
19
  @responses = [] # for testing responses after sending data
20
20
  @d = nil
21
- @port = unused_port
21
+ # forward plugin uses TCP and UDP sockets on the same port number
22
+ @port = unused_port(protocol: :all)
22
23
  end
23
24
 
24
25
  def teardown
@@ -367,7 +368,7 @@ class ForwardInputTest < Test::Unit::TestCase
367
368
  end
368
369
 
369
370
  logs = d.instance.log.out.logs
370
- assert{ logs.select{|line| line =~ /skip invalid event/ }.size == 2 }
371
+ assert{ logs.count{|line| line =~ /skip invalid event/ } == 2 }
371
372
 
372
373
  d.instance_shutdown
373
374
  end
@@ -593,10 +594,10 @@ class ForwardInputTest < Test::Unit::TestCase
593
594
 
594
595
  # check log
595
596
  logs = d.instance.log.logs
596
- assert_equal 1, logs.select{|line|
597
+ assert_equal 1, logs.count{|line|
597
598
  line =~ / \[warn\]: Input chunk size is larger than 'chunk_size_warn_limit':/ &&
598
599
  line =~ / tag="test.tag" host="#{LOCALHOST_HOSTNAME}" limit=16777216 size=16777501/
599
- }.size, "large chunk warning is not logged"
600
+ }, "large chunk warning is not logged"
600
601
 
601
602
  d.instance_shutdown
602
603
  end
@@ -619,10 +620,10 @@ class ForwardInputTest < Test::Unit::TestCase
619
620
 
620
621
  # check log
621
622
  logs = d.instance.log.logs
622
- assert_equal 1, logs.select{ |line|
623
+ assert_equal 1, logs.count{ |line|
623
624
  line =~ / \[warn\]: Input chunk size is larger than 'chunk_size_warn_limit':/ &&
624
625
  line =~ / tag="test.tag" host="#{LOCALHOST_HOSTNAME}" limit=16777216 size=16777501/
625
- }.size, "large chunk warning is not logged"
626
+ }, "large chunk warning is not logged"
626
627
 
627
628
  d.instance_shutdown
628
629
  end
@@ -653,10 +654,10 @@ class ForwardInputTest < Test::Unit::TestCase
653
654
 
654
655
  # check log
655
656
  logs = d.instance.log.logs
656
- assert_equal 1, logs.select{|line|
657
+ assert_equal 1, logs.count{|line|
657
658
  line =~ / \[warn\]: Input chunk size is larger than 'chunk_size_limit', dropped:/ &&
658
659
  line =~ / tag="test.tag" host="#{LOCALHOST_HOSTNAME}" limit=33554432 size=33554989/
659
- }.size, "large chunk warning is not logged"
660
+ }, "large chunk warning is not logged"
660
661
 
661
662
  d.instance_shutdown
662
663
  end
@@ -676,9 +677,9 @@ class ForwardInputTest < Test::Unit::TestCase
676
677
 
677
678
  # check log
678
679
  logs = d.instance.log.logs
679
- assert_equal 1, logs.select{|line|
680
+ assert_equal 1, logs.count{|line|
680
681
  line =~ / \[warn\]: incoming chunk is broken: host="#{LOCALHOST_HOSTNAME}" msg=#{data.inspect}/
681
- }.size, "should not accept broken chunk"
682
+ }, "should not accept broken chunk"
682
683
 
683
684
  d.instance_shutdown
684
685
  end
@@ -18,7 +18,7 @@ class HttpInputTest < Test::Unit::TestCase
18
18
 
19
19
  def setup
20
20
  Fluent::Test.setup
21
- @port = unused_port
21
+ @port = unused_port(protocol: :tcp)
22
22
  end
23
23
 
24
24
  def teardown
@@ -392,7 +392,7 @@ EOC
392
392
  end
393
393
 
394
394
  test "emit" do
395
- port = unused_port
395
+ port = unused_port(protocol: :tcp)
396
396
  d = create_driver("
397
397
  @type monitor_agent
398
398
  bind '127.0.0.1'
@@ -451,7 +451,7 @@ EOC
451
451
 
452
452
  sub_test_case "servlets" do
453
453
  setup do
454
- @port = unused_port
454
+ @port = unused_port(protocol: :tcp)
455
455
  # check @type and type in one configuration
456
456
  conf = <<-EOC
457
457
  <source>
@@ -759,7 +759,7 @@ plugin_id:test_filter\tplugin_category:filter\ttype:test_filter\toutput_plugin:f
759
759
  end
760
760
 
761
761
  setup do
762
- @port = unused_port
762
+ @port = unused_port(protocol: :tcp)
763
763
  # check @type and type in one configuration
764
764
  conf = <<-EOC
765
765
  <source>
@@ -840,7 +840,7 @@ plugin_id:test_filter\tplugin_category:filter\ttype:test_filter\toutput_plugin:f
840
840
 
841
841
  sub_test_case "check the port number of http server" do
842
842
  test "on single worker environment" do
843
- port = unused_port
843
+ port = unused_port(protocol: :tcp)
844
844
  d = create_driver("
845
845
  @type monitor_agent
846
846
  bind '127.0.0.1'
@@ -851,7 +851,7 @@ plugin_id:test_filter\tplugin_category:filter\ttype:test_filter\toutput_plugin:f
851
851
  end
852
852
 
853
853
  test "worker_id = 2 on multi worker environment" do
854
- port = unused_port
854
+ port = unused_port(protocol: :tcp)
855
855
  Fluent::SystemConfig.overwrite_system_config('workers' => 4) do
856
856
  d = Fluent::Test::Driver::Input.new(Fluent::Plugin::MonitorAgentInput)
857
857
  d.instance.instance_eval{ @_fluentd_worker_id = 2 }
@@ -905,7 +905,7 @@ EOC
905
905
  end
906
906
 
907
907
  test "plugins have a variable named buffer does not throws NoMethodError" do
908
- port = unused_port
908
+ port = unused_port(protocol: :tcp)
909
909
  d = create_driver("
910
910
  @type monitor_agent
911
911
  bind '127.0.0.1'