fluent-plugin-kafka 0.19.6 → 0.19.8

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.
@@ -6,6 +6,7 @@ class KafkaPluginUtilTest < Test::Unit::TestCase
6
6
  def self.config_param(name, type, options)
7
7
  end
8
8
  include Fluent::KafkaPluginUtil::SSLSettings
9
+ include Fluent::KafkaPluginUtil::PartitionSettings
9
10
 
10
11
  def config_param
11
12
  end
@@ -13,6 +14,31 @@ class KafkaPluginUtilTest < Test::Unit::TestCase
13
14
  Fluent::Test.setup
14
15
  end
15
16
 
17
+ data("integer" => [3, 3],
18
+ "decimal string" => ["3", 3],
19
+ "zero padded string" => ["010", 10],
20
+ "unassigned partition" => [-1, -1],
21
+ "max int32" => [2**31 - 1, 2**31 - 1])
22
+ def test_coerce_partition(data)
23
+ given, expected = data
24
+ assert_equal(expected, coerce_partition(given))
25
+ end
26
+
27
+ data("non numeric string" => ["not-a-number", ArgumentError],
28
+ "empty string" => ["", ArgumentError],
29
+ "hexadecimal string" => ["0x10", ArgumentError],
30
+ "float string" => ["3.5", ArgumentError],
31
+ "float" => [3.9, TypeError],
32
+ "nil" => [nil, TypeError],
33
+ "negative partition" => [-2, RangeError],
34
+ "too big for int32" => [2**31, RangeError])
35
+ def test_coerce_partition_rejects_invalid_value(data)
36
+ given, expected = data
37
+ assert_raise(expected) do
38
+ coerce_partition(given)
39
+ end
40
+ end
41
+
16
42
  def test_read_ssl_file_when_nil
17
43
  stub(File).read(anything) do |path|
18
44
  path
@@ -27,6 +27,32 @@ class Kafka2OutputTest < Test::Unit::TestCase
27
27
  Fluent::Test::Driver::Output.new(Fluent::Kafka2Output).configure(conf)
28
28
  end
29
29
 
30
+ class DummyProducer
31
+ attr_reader :produced
32
+
33
+ # Coerces like ruby-kafka's Producer#produce, which is where an invalid
34
+ # partition raises today
35
+ def produce(value, key:, partition_key:, partition:, headers:, create_time:, topic:)
36
+ @produced ||= []
37
+ @produced << {value: value && value.to_s, key: key && key.to_s,
38
+ partition: partition && Integer(partition)}
39
+ end
40
+
41
+ def deliver_messages
42
+ end
43
+
44
+ def clear_buffer
45
+ end
46
+ end
47
+
48
+ def create_chunk(records, tag: 'test')
49
+ metadata = Fluent::Plugin::Buffer::Metadata.new(nil, tag, nil)
50
+ chunk = Fluent::Plugin::Buffer::MemoryChunk.new(metadata)
51
+ chunk.extend(Fluent::ChunkMessagePackEventStreamer)
52
+ chunk.append(records.map { |record| [event_time, record].to_msgpack })
53
+ chunk
54
+ end
55
+
30
56
  def test_configure
31
57
  assert_nothing_raised(Fluent::ConfigError) {
32
58
  create_driver(base_config)
@@ -45,6 +71,79 @@ class Kafka2OutputTest < Test::Unit::TestCase
45
71
  assert_equal ['localhost:9092'], d.instance.brokers
46
72
  end
47
73
 
74
+ data("cert without key" => {"ssl_client_cert" => "/path/to/cert.pem"},
75
+ "key without cert" => {"ssl_client_cert_key" => "/path/to/key.pem"},
76
+ "chain without cert" => {"ssl_client_cert_chain" => "/path/to/chain.pem"},
77
+ "key password without key" => {"ssl_client_cert_key_password" => "secret"})
78
+ def test_configure_incomplete_ssl_client_cert(params)
79
+ conf = config + config_element('ROOT', '', params, [])
80
+
81
+ assert_raise(Fluent::ConfigError) {
82
+ create_driver(conf)
83
+ }
84
+ end
85
+
86
+ def test_configure_ssl_client_cert_with_key
87
+ conf = config + config_element('ROOT', '', {"ssl_client_cert" => "/path/to/cert.pem",
88
+ "ssl_client_cert_key" => "/path/to/key.pem"}, [])
89
+
90
+ assert_nothing_raised(Fluent::ConfigError) {
91
+ create_driver(conf)
92
+ }
93
+ end
94
+
95
+ data("sha256" => "sha256",
96
+ "sha512" => "sha512")
97
+ def test_configure_scram_mechanism(mechanism)
98
+ conf = config + config_element('ROOT', '', {"username" => "testuser",
99
+ "password" => "testpass",
100
+ "scram_mechanism" => mechanism,
101
+ "ssl_ca_certs_from_system" => "true"}, [])
102
+ d = create_driver(conf)
103
+
104
+ assert_equal mechanism, d.instance.scram_mechanism
105
+
106
+ assert_nothing_raised {
107
+ d.instance.refresh_client
108
+ }
109
+ end
110
+
111
+ def test_configure_unsupported_scram_mechanism
112
+ conf = config + config_element('ROOT', '', {"username" => "testuser",
113
+ "password" => "testpass",
114
+ "scram_mechanism" => "sha1"}, [])
115
+
116
+ assert_raise(Fluent::ConfigError) {
117
+ create_driver(conf)
118
+ }
119
+ end
120
+
121
+ data("non numeric partition" => "not-a-number",
122
+ "hash partition" => {"x" => 1},
123
+ "negative partition" => -2,
124
+ "out of int32 range" => 2**31)
125
+ def test_write_skips_event_with_invalid_partition(partition)
126
+ d = create_driver
127
+ producer = DummyProducer.new
128
+ stub(d.instance).get_producer { producer }
129
+
130
+ assert_nothing_raised {
131
+ d.instance.write(create_chunk([{"a" => "b"}, {"a" => "c", "partition" => partition}, {"a" => "d"}]))
132
+ }
133
+
134
+ assert_equal ['{"a":"b"}', '{"a":"d"}'], producer.produced.collect { |message| message[:value] }
135
+ end
136
+
137
+ def test_write_keeps_event_with_zero_padded_partition
138
+ d = create_driver
139
+ producer = DummyProducer.new
140
+ stub(d.instance).get_producer { producer }
141
+
142
+ d.instance.write(create_chunk([{"a" => "b", "partition" => "010"}]))
143
+
144
+ assert_equal [10], producer.produced.collect { |message| message[:partition] }
145
+ end
146
+
48
147
  data("crc32" => "crc32",
49
148
  "murmur2" => "murmur2")
50
149
  def test_partitioner_hash_function(data)
@@ -0,0 +1,105 @@
1
+ require 'helper'
2
+ require 'fluent/test/helpers'
3
+ require 'fluent/test/driver/output'
4
+
5
+ class RdkafkaOutputTest < Test::Unit::TestCase
6
+ include Fluent::Test::Helpers
7
+
8
+ def have_rdkafka
9
+ begin
10
+ require 'fluent/plugin/out_rdkafka'
11
+ true
12
+ rescue LoadError
13
+ false
14
+ end
15
+ end
16
+
17
+ def setup
18
+ omit_unless(have_rdkafka, "rdkafka isn't installed")
19
+ Fluent::Test.setup
20
+ end
21
+
22
+ def base_config(params = {})
23
+ config_element('ROOT', '', {"@type" => "rdkafka",
24
+ "brokers" => "localhost:9092"}.merge(params), [])
25
+ end
26
+
27
+ def create_driver(conf = base_config)
28
+ Fluent::Test::Driver::Output.new(Fluent::KafkaOutputBuffered2).configure(conf)
29
+ end
30
+
31
+ def test_configure
32
+ d = create_driver
33
+
34
+ assert_equal 'localhost:9092', d.instance.brokers
35
+ end
36
+
37
+ def test_configure_ssl_ca_cert
38
+ d = create_driver(base_config("ssl_ca_cert" => "/path/to/ca_cert.pem"))
39
+
40
+ config = d.instance.build_config
41
+
42
+ assert_equal 'SSL', config[:"security.protocol"]
43
+ assert_equal '/path/to/ca_cert.pem', config[:"ssl.ca.location"]
44
+ end
45
+
46
+ def test_configure_ssl_ca_certs_from_system
47
+ d = create_driver(base_config("ssl_ca_certs_from_system" => "true"))
48
+
49
+ config = d.instance.build_config
50
+
51
+ assert_equal 'SSL', config[:"security.protocol"]
52
+ assert_nil config[:"ssl.ca.location"]
53
+ end
54
+
55
+ def test_configure_ssl_client_cert_without_ca_cert
56
+ d = create_driver(base_config("ssl_client_cert" => "/path/to/cert.pem",
57
+ "ssl_client_cert_key" => "/path/to/key.pem"))
58
+
59
+ config = d.instance.build_config
60
+
61
+ assert_equal 'SSL', config[:"security.protocol"]
62
+ assert_equal '/path/to/cert.pem', config[:"ssl.certificate.location"]
63
+ assert_equal '/path/to/key.pem', config[:"ssl.key.location"]
64
+ assert_nil config[:"ssl.ca.location"]
65
+ end
66
+
67
+ def test_configure_ssl_verify_hostname_default
68
+ d = create_driver(base_config("ssl_ca_cert" => "/path/to/ca_cert.pem"))
69
+
70
+ config = d.instance.build_config
71
+
72
+ assert_equal 'SSL', config[:"security.protocol"]
73
+ assert_equal 'https', config[:"ssl.endpoint.identification.algorithm"]
74
+ assert_equal true, config[:"enable.ssl.certificate.verification"]
75
+ end
76
+
77
+ def test_configure_ssl_verify_hostname_false
78
+ d = create_driver(base_config("ssl_ca_cert" => "/path/to/ca_cert.pem",
79
+ "ssl_verify_hostname" => "false"))
80
+
81
+ config = d.instance.build_config
82
+
83
+ assert_equal 'none', config[:"ssl.endpoint.identification.algorithm"]
84
+ assert_equal true, config[:"enable.ssl.certificate.verification"]
85
+ end
86
+
87
+ def test_configure_without_ssl_has_no_endpoint_identification
88
+ config = create_driver.instance.build_config
89
+
90
+ assert_equal 'PLAINTEXT', config[:"security.protocol"]
91
+ assert_nil config[:"ssl.endpoint.identification.algorithm"]
92
+ assert_nil config[:"enable.ssl.certificate.verification"]
93
+ end
94
+
95
+ def test_configure_sasl_gssapi_over_ssl
96
+ d = create_driver(base_config("principal" => "testuser@EXAMPLE.COM",
97
+ "ssl_client_cert" => "/path/to/cert.pem",
98
+ "ssl_client_cert_key" => "/path/to/key.pem"))
99
+
100
+ config = d.instance.build_config
101
+
102
+ assert_equal 'SASL_SSL', config[:"security.protocol"]
103
+ assert_equal 'GSSAPI', config[:"sasl.mechanisms"]
104
+ end
105
+ end
@@ -37,6 +37,80 @@ class Rdkafka2OutputTest < Test::Unit::TestCase
37
37
  Fluent::Test::Driver::Output.new(Fluent::Rdkafka2Output).configure(conf)
38
38
  end
39
39
 
40
+ class DummyDeliveryHandle
41
+ def initialize(error = nil)
42
+ @error = error
43
+ end
44
+
45
+ def wait(**)
46
+ raise @error if @error
47
+ end
48
+ end
49
+
50
+ class DummyProducer
51
+ attr_reader :produced
52
+
53
+ def initialize(produce_error: nil, delivery_error: nil)
54
+ @produced = []
55
+ @produce_error = produce_error
56
+ @delivery_error = delivery_error
57
+ end
58
+
59
+ # Raises what the rdkafka FFI binding raises: the partition must be an
60
+ # int32 and the key must respond to #bytesize
61
+ def produce(topic:, payload:, key:, partition:, headers:, timestamp:)
62
+ unless partition.nil?
63
+ raise TypeError, "no implicit conversion of #{partition.class} into Integer" unless partition.is_a?(Integer)
64
+ raise RangeError, "integer #{partition} too big to convert to 'int'" unless (-2**31..2**31 - 1).cover?(partition)
65
+ end
66
+ key.bytesize unless key.nil?
67
+ raise @produce_error if @produce_error
68
+
69
+ @produced << {payload: payload, key: key, partition: partition}
70
+ DummyDeliveryHandle.new(@delivery_error)
71
+ end
72
+ end
73
+
74
+ RD_KAFKA_RESP_ERR__UNKNOWN_PARTITION = -190
75
+ RD_KAFKA_RESP_ERR_MSG_SIZE_TOO_LARGE = 10
76
+
77
+ class DummyErrorWithCode < StandardError
78
+ def code
79
+ :msg_size_too_large
80
+ end
81
+ end
82
+
83
+ def create_chunk(records, tag: 'test')
84
+ metadata = Fluent::Plugin::Buffer::Metadata.new(nil, tag, nil)
85
+ chunk = Fluent::Plugin::Buffer::MemoryChunk.new(metadata)
86
+ chunk.extend(Fluent::ChunkMessagePackEventStreamer)
87
+ chunk.append(records.map { |record| [event_time, record].to_msgpack })
88
+ chunk
89
+ end
90
+
91
+ def test_rdkafka_patch_is_applied
92
+ version = Gem::Version.create(Rdkafka::VERSION)
93
+ expected = if version >= Gem::Version.create('0.16.0')
94
+ '0_16_0.rb'
95
+ elsif version >= Gem::Version.create('0.14.0')
96
+ '0_14_0.rb'
97
+ elsif version >= Gem::Version.create('0.13.0')
98
+ '0_13_0.rb'
99
+ elsif version >= Gem::Version.create('0.12.0')
100
+ '0_12_0.rb'
101
+ else
102
+ '0_11_0.rb'
103
+ end
104
+
105
+ assert_equal expected, File.basename(Rdkafka::Producer.instance_method(:close).source_location.first)
106
+ end
107
+
108
+ def test_producer_close_accepts_timeout
109
+ producer = Rdkafka::Config.new("bootstrap.servers" => "localhost:9092").producer
110
+
111
+ assert_true producer.close(10)
112
+ end
113
+
40
114
  def test_configure
41
115
  assert_nothing_raised(Fluent::ConfigError) {
42
116
  create_driver(base_config)
@@ -55,8 +129,30 @@ class Rdkafka2OutputTest < Test::Unit::TestCase
55
129
  assert_equal 'localhost:9092', d.instance.brokers
56
130
  end
57
131
 
58
- def test_configure_sasl_plain
132
+ def test_configure_sasl_plain_over_ssl
133
+ conf = base_config + config_element('ROOT', '', {"username" => "testuser", "password" => "testpass",
134
+ "ssl_ca_cert" => "/path/to/ca_cert.pem"}, [])
135
+ d = create_driver(conf)
136
+
137
+ config = d.instance.build_config
138
+
139
+ assert_equal 'PLAIN', config[:"sasl.mechanisms"]
140
+ assert_equal 'SASL_SSL', config[:"security.protocol"]
141
+ assert_equal 'testuser', config[:"sasl.username"]
142
+ assert_equal 'testpass', config[:"sasl.password"]
143
+ end
144
+
145
+ def test_configure_sasl_plain_without_ssl
59
146
  conf = base_config + config_element('ROOT', '', {"username" => "testuser", "password" => "testpass"}, [])
147
+
148
+ assert_raise(Fluent::ConfigError) {
149
+ create_driver(conf)
150
+ }
151
+ end
152
+
153
+ def test_configure_sasl_plain_without_ssl_allowed_by_sasl_over_ssl
154
+ conf = base_config + config_element('ROOT', '', {"username" => "testuser", "password" => "testpass",
155
+ "sasl_over_ssl" => "false"}, [])
60
156
  d = create_driver(conf)
61
157
 
62
158
  config = d.instance.build_config
@@ -67,6 +163,250 @@ class Rdkafka2OutputTest < Test::Unit::TestCase
67
163
  assert_equal 'testpass', config[:"sasl.password"]
68
164
  end
69
165
 
166
+ def test_configure_sasl_plain_with_security_protocol_from_rdkafka_options
167
+ conf = base_config + config_element('ROOT', '', {"username" => "testuser", "password" => "testpass",
168
+ "rdkafka_options" => '{"security.protocol": "SASL_SSL"}'}, [])
169
+ d = create_driver(conf)
170
+
171
+ config = d.instance.build_config
172
+
173
+ assert_equal 'SASL_SSL', config[:"security.protocol"]
174
+ end
175
+
176
+ def test_configure_sasl_plain_with_lowercase_security_protocol_from_rdkafka_options
177
+ conf = base_config + config_element('ROOT', '', {"username" => "testuser", "password" => "testpass",
178
+ "rdkafka_options" => '{"security.protocol": "sasl_plaintext"}'}, [])
179
+
180
+ assert_raise(Fluent::ConfigError) {
181
+ create_driver(conf)
182
+ }
183
+ end
184
+
185
+ data("sha256" => ["sha256", "SCRAM-SHA-256"],
186
+ "sha512" => ["sha512", "SCRAM-SHA-512"])
187
+ def test_configure_sasl_scram(data)
188
+ mechanism, expected = data
189
+ conf = base_config + config_element('ROOT', '', {"username" => "testuser", "password" => "testpass",
190
+ "scram_mechanism" => mechanism,
191
+ "ssl_ca_cert" => "/path/to/ca_cert.pem"}, [])
192
+ d = create_driver(conf)
193
+
194
+ config = d.instance.build_config
195
+
196
+ assert_equal expected, config[:"sasl.mechanisms"]
197
+ assert_equal 'SASL_SSL', config[:"security.protocol"]
198
+ assert_equal 'testuser', config[:"sasl.username"]
199
+ assert_equal 'testpass', config[:"sasl.password"]
200
+ end
201
+
202
+ def test_configure_ssl_ca_certs_from_system
203
+ conf = base_config + config_element('ROOT', '', {"ssl_ca_certs_from_system" => "true"}, [])
204
+ d = create_driver(conf)
205
+
206
+ config = d.instance.build_config
207
+
208
+ assert_equal 'SSL', config[:"security.protocol"]
209
+ assert_nil config[:"ssl.ca.location"]
210
+ end
211
+
212
+ def test_configure_ssl_client_cert_without_ca_cert
213
+ conf = base_config + config_element('ROOT', '', {"ssl_client_cert" => "/path/to/cert.pem",
214
+ "ssl_client_cert_key" => "/path/to/key.pem"}, [])
215
+ d = create_driver(conf)
216
+
217
+ config = d.instance.build_config
218
+
219
+ assert_equal 'SSL', config[:"security.protocol"]
220
+ assert_equal '/path/to/cert.pem', config[:"ssl.certificate.location"]
221
+ assert_equal '/path/to/key.pem', config[:"ssl.key.location"]
222
+ assert_nil config[:"ssl.ca.location"]
223
+ end
224
+
225
+ def test_configure_sasl_plain_over_ssl_ca_certs_from_system
226
+ conf = base_config + config_element('ROOT', '', {"username" => "testuser", "password" => "testpass",
227
+ "ssl_ca_certs_from_system" => "true"}, [])
228
+ d = create_driver(conf)
229
+
230
+ config = d.instance.build_config
231
+
232
+ assert_equal 'SASL_SSL', config[:"security.protocol"]
233
+ assert_equal 'testpass', config[:"sasl.password"]
234
+ end
235
+
236
+ def test_configure_ssl_verify_hostname_default
237
+ conf = base_config + config_element('ROOT', '', {"ssl_ca_cert" => "/path/to/ca_cert.pem"}, [])
238
+ d = create_driver(conf)
239
+
240
+ config = d.instance.build_config
241
+
242
+ assert_equal 'SSL', config[:"security.protocol"]
243
+ assert_equal 'https', config[:"ssl.endpoint.identification.algorithm"]
244
+ assert_equal true, config[:"enable.ssl.certificate.verification"]
245
+ end
246
+
247
+ def test_configure_ssl_verify_hostname_false
248
+ conf = base_config + config_element('ROOT', '', {"ssl_ca_cert" => "/path/to/ca_cert.pem",
249
+ "ssl_verify_hostname" => "false"}, [])
250
+ d = create_driver(conf)
251
+
252
+ config = d.instance.build_config
253
+
254
+ assert_equal 'none', config[:"ssl.endpoint.identification.algorithm"]
255
+ assert_equal true, config[:"enable.ssl.certificate.verification"]
256
+ end
257
+
258
+ def test_configure_without_ssl_has_no_endpoint_identification
259
+ d = create_driver
260
+
261
+ config = d.instance.build_config
262
+
263
+ assert_equal 'PLAINTEXT', config[:"security.protocol"]
264
+ assert_nil config[:"ssl.endpoint.identification.algorithm"]
265
+ assert_nil config[:"enable.ssl.certificate.verification"]
266
+ end
267
+
268
+ data("non numeric partition" => "not-a-number",
269
+ "hexadecimal partition" => "0x10",
270
+ "float partition" => 3.9,
271
+ "negative partition" => -2,
272
+ "out of int32 range" => 2**31)
273
+ def test_write_skips_event_with_invalid_partition(partition)
274
+ d = create_driver
275
+ producer = DummyProducer.new
276
+ stub(d.instance).get_producer { producer }
277
+
278
+ assert_nothing_raised {
279
+ d.instance.write(create_chunk([{"a" => "b"}, {"a" => "c", "partition" => partition}, {"a" => "d"}]))
280
+ }
281
+
282
+ assert_equal ['{"a":"b"}', '{"a":"d"}'], producer.produced.collect { |message| message[:payload] }
283
+ end
284
+
285
+ def test_write_keeps_event_with_zero_padded_partition
286
+ d = create_driver
287
+ producer = DummyProducer.new
288
+ stub(d.instance).get_producer { producer }
289
+
290
+ d.instance.write(create_chunk([{"a" => "b", "partition" => "010"}]))
291
+
292
+ assert_equal [10], producer.produced.collect { |message| message[:partition] }
293
+ end
294
+
295
+ def test_write_converts_non_string_message_key
296
+ d = create_driver
297
+ producer = DummyProducer.new
298
+ stub(d.instance).get_producer { producer }
299
+
300
+ d.instance.write(create_chunk([{"a" => "b", "message_key" => 12345}]))
301
+
302
+ assert_equal ["12345"], producer.produced.collect { |message| message[:key] }
303
+ end
304
+
305
+ def test_rdkafka_error_codes_under_test
306
+ assert_equal [:unknown_partition, :msg_size_too_large],
307
+ [Rdkafka::RdkafkaError.new(RD_KAFKA_RESP_ERR__UNKNOWN_PARTITION).code,
308
+ Rdkafka::RdkafkaError.new(RD_KAFKA_RESP_ERR_MSG_SIZE_TOO_LARGE).code]
309
+ end
310
+
311
+ data("produce" => :produce_error,
312
+ "delivery report" => :delivery_error)
313
+ def test_write_raises_unrecoverable_error_for_listed_error_code(path)
314
+ d = create_driver
315
+ producer = DummyProducer.new(path => Rdkafka::RdkafkaError.new(RD_KAFKA_RESP_ERR_MSG_SIZE_TOO_LARGE))
316
+ stub(d.instance).get_producer { producer }
317
+
318
+ assert_raise(Fluent::UnrecoverableError) {
319
+ d.instance.write(create_chunk([{"a" => "b"}]))
320
+ }
321
+ end
322
+
323
+ data("produce" => :produce_error,
324
+ "delivery report" => :delivery_error)
325
+ def test_write_reraises_unlisted_error_code(path)
326
+ d = create_driver
327
+ producer = DummyProducer.new(path => Rdkafka::RdkafkaError.new(RD_KAFKA_RESP_ERR__UNKNOWN_PARTITION))
328
+ stub(d.instance).get_producer { producer }
329
+
330
+ assert_raise(Rdkafka::RdkafkaError) {
331
+ d.instance.write(create_chunk([{"a" => "b"}]))
332
+ }
333
+ end
334
+
335
+ data("produce" => :produce_error,
336
+ "delivery report" => :delivery_error)
337
+ def test_write_raises_unrecoverable_error_for_unknown_partition_when_listed(path)
338
+ d = create_driver(config + config_element('ROOT', '', {"unrecoverable_error_codes" => "unknown_partition"}))
339
+ producer = DummyProducer.new(path => Rdkafka::RdkafkaError.new(RD_KAFKA_RESP_ERR__UNKNOWN_PARTITION))
340
+ stub(d.instance).get_producer { producer }
341
+
342
+ assert_raise(Fluent::UnrecoverableError) {
343
+ d.instance.write(create_chunk([{"a" => "b"}]))
344
+ }
345
+ end
346
+
347
+ data("produce" => :produce_error,
348
+ "delivery report" => :delivery_error)
349
+ def test_write_discards_listed_error_code_when_discard_kafka_delivery_failed(path)
350
+ d = create_driver(config + config_element('ROOT', '', {"discard_kafka_delivery_failed" => "true"}))
351
+ producer = DummyProducer.new(path => Rdkafka::RdkafkaError.new(RD_KAFKA_RESP_ERR_MSG_SIZE_TOO_LARGE))
352
+ stub(d.instance).get_producer { producer }
353
+
354
+ assert_nothing_raised {
355
+ d.instance.write(create_chunk([{"a" => "b"}]))
356
+ }
357
+ assert_true d.logs.any? { |log| log.include?("Delivery failed. Discard events") }
358
+ end
359
+
360
+ data("produce" => :produce_error,
361
+ "delivery report" => :delivery_error)
362
+ def test_write_reraises_error_that_is_not_a_kafka_error(path)
363
+ d = create_driver
364
+ producer = DummyProducer.new(path => DummyErrorWithCode.new)
365
+ stub(d.instance).get_producer { producer }
366
+
367
+ assert_raise(DummyErrorWithCode) {
368
+ d.instance.write(create_chunk([{"a" => "b"}]))
369
+ }
370
+ end
371
+
372
+ data("produce" => :produce_error,
373
+ "delivery report" => :delivery_error)
374
+ def test_write_discards_listed_error_code_matching_discard_kafka_delivery_failed_regex(path)
375
+ d = create_driver(config + config_element('ROOT', '', {"discard_kafka_delivery_failed_regex" => "/msg_size_too_large/"}))
376
+ producer = DummyProducer.new(path => Rdkafka::RdkafkaError.new(RD_KAFKA_RESP_ERR_MSG_SIZE_TOO_LARGE))
377
+ stub(d.instance).get_producer { producer }
378
+
379
+ assert_nothing_raised {
380
+ d.instance.write(create_chunk([{"a" => "b"}]))
381
+ }
382
+ assert_true d.logs.any? { |log| log.include?("Delivery failed and matched regexp pattern") }
383
+ end
384
+
385
+ def test_write_produces_every_record_before_raising_unrecoverable_error
386
+ d = create_driver
387
+ producer = DummyProducer.new(delivery_error: Rdkafka::RdkafkaError.new(RD_KAFKA_RESP_ERR_MSG_SIZE_TOO_LARGE))
388
+ stub(d.instance).get_producer { producer }
389
+
390
+ assert_raise(Fluent::UnrecoverableError) {
391
+ d.instance.write(create_chunk([{"a" => "b"}, {"a" => "c"}, {"a" => "d"}]))
392
+ }
393
+
394
+ assert_equal ['{"a":"b"}', '{"a":"c"}', '{"a":"d"}'], producer.produced.collect { |message| message[:payload] }
395
+ end
396
+
397
+ data("produce" => :produce_error,
398
+ "delivery report" => :delivery_error)
399
+ def test_write_reraises_listed_error_code_when_unrecoverable_error_codes_is_empty(path)
400
+ d = create_driver(config + config_element('ROOT', '', {"unrecoverable_error_codes" => ""}))
401
+ producer = DummyProducer.new(path => Rdkafka::RdkafkaError.new(RD_KAFKA_RESP_ERR_MSG_SIZE_TOO_LARGE))
402
+ stub(d.instance).get_producer { producer }
403
+
404
+ assert_equal [], d.instance.unrecoverable_error_codes
405
+ assert_raise(Rdkafka::RdkafkaError) {
406
+ d.instance.write(create_chunk([{"a" => "b"}]))
407
+ }
408
+ end
409
+
70
410
  def test_mutli_worker_support
71
411
  d = create_driver
72
412
  assert_equal true, d.instance.multi_workers_ready?
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-kafka
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.19.6
4
+ version: 0.19.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hidemasa Togashi
@@ -187,15 +187,18 @@ files:
187
187
  - lib/fluent/plugin/out_rdkafka2.rb
188
188
  - lib/fluent/plugin/rdkafka_patch/0_11_0.rb
189
189
  - lib/fluent/plugin/rdkafka_patch/0_12_0.rb
190
+ - lib/fluent/plugin/rdkafka_patch/0_13_0.rb
190
191
  - lib/fluent/plugin/rdkafka_patch/0_14_0.rb
191
192
  - lib/fluent/plugin/rdkafka_patch/0_16_0.rb
192
193
  - test/helper.rb
193
194
  - test/plugin/test_in_kafka.rb
194
195
  - test/plugin/test_in_kafka_group.rb
196
+ - test/plugin/test_in_rdkafka_group.rb
195
197
  - test/plugin/test_kafka_plugin_util.rb
196
198
  - test/plugin/test_out_kafka.rb
197
199
  - test/plugin/test_out_kafka2.rb
198
200
  - test/plugin/test_out_kafka_buffered.rb
201
+ - test/plugin/test_out_rdkafka.rb
199
202
  - test/plugin/test_out_rdkafka2.rb
200
203
  homepage: https://github.com/fluent/fluent-plugin-kafka
201
204
  licenses:
@@ -215,15 +218,17 @@ required_rubygems_version: !ruby/object:Gem::Requirement
215
218
  - !ruby/object:Gem::Version
216
219
  version: '0'
217
220
  requirements: []
218
- rubygems_version: 4.0.6
221
+ rubygems_version: 4.0.18
219
222
  specification_version: 4
220
223
  summary: Fluentd plugin for Apache Kafka > 0.8
221
224
  test_files:
222
225
  - test/helper.rb
223
226
  - test/plugin/test_in_kafka.rb
224
227
  - test/plugin/test_in_kafka_group.rb
228
+ - test/plugin/test_in_rdkafka_group.rb
225
229
  - test/plugin/test_kafka_plugin_util.rb
226
230
  - test/plugin/test_out_kafka.rb
227
231
  - test/plugin/test_out_kafka2.rb
228
232
  - test/plugin/test_out_kafka_buffered.rb
233
+ - test/plugin/test_out_rdkafka.rb
229
234
  - test/plugin/test_out_rdkafka2.rb