logstash-integration-kafka 10.0.1-java → 10.5.0-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +24 -0
- data/CONTRIBUTORS +2 -0
- data/LICENSE +199 -10
- data/docs/index.asciidoc +1 -1
- data/docs/input-kafka.asciidoc +118 -70
- data/docs/output-kafka.asciidoc +68 -23
- data/lib/logstash-integration-kafka_jars.rb +3 -3
- data/lib/logstash/inputs/kafka.rb +90 -54
- data/lib/logstash/outputs/kafka.rb +83 -45
- data/logstash-integration-kafka.gemspec +2 -2
- data/spec/integration/inputs/kafka_spec.rb +81 -112
- data/spec/integration/outputs/kafka_spec.rb +89 -72
- data/spec/unit/inputs/kafka_spec.rb +63 -1
- data/spec/unit/outputs/kafka_spec.rb +62 -9
- data/vendor/jar-dependencies/com/github/luben/zstd-jni/1.4.3-1/zstd-jni-1.4.3-1.jar +0 -0
- data/vendor/jar-dependencies/org/apache/kafka/kafka-clients/2.4.1/kafka-clients-2.4.1.jar +0 -0
- data/vendor/jar-dependencies/org/slf4j/slf4j-api/1.7.28/slf4j-api-1.7.28.jar +0 -0
- metadata +6 -6
- data/vendor/jar-dependencies/com/github/luben/zstd-jni/1.4.2-1/zstd-jni-1.4.2-1.jar +0 -0
- data/vendor/jar-dependencies/org/apache/kafka/kafka-clients/2.3.0/kafka-clients-2.3.0.jar +0 -0
- data/vendor/jar-dependencies/org/slf4j/slf4j-api/1.7.26/slf4j-api-1.7.26.jar +0 -0
@@ -67,7 +67,7 @@ class LogStash::Outputs::Kafka < LogStash::Outputs::Base
|
|
67
67
|
# The producer will attempt to batch records together into fewer requests whenever multiple
|
68
68
|
# records are being sent to the same partition. This helps performance on both the client
|
69
69
|
# and the server. This configuration controls the default batch size in bytes.
|
70
|
-
config :batch_size, :validate => :number, :default =>
|
70
|
+
config :batch_size, :validate => :number, :default => 16_384 # Kafka default
|
71
71
|
# This is for bootstrapping and the producer will only use it for getting metadata (topics,
|
72
72
|
# partitions and replicas). The socket connections for sending the actual data will be
|
73
73
|
# established based on the broker information returned in the metadata. The format is
|
@@ -75,10 +75,15 @@ class LogStash::Outputs::Kafka < LogStash::Outputs::Base
|
|
75
75
|
# subset of brokers.
|
76
76
|
config :bootstrap_servers, :validate => :string, :default => 'localhost:9092'
|
77
77
|
# The total bytes of memory the producer can use to buffer records waiting to be sent to the server.
|
78
|
-
config :buffer_memory, :validate => :number, :default =>
|
78
|
+
config :buffer_memory, :validate => :number, :default => 33_554_432 # (32M) Kafka default
|
79
79
|
# The compression type for all data generated by the producer.
|
80
80
|
# The default is none (i.e. no compression). Valid values are none, gzip, or snappy.
|
81
81
|
config :compression_type, :validate => ["none", "gzip", "snappy", "lz4"], :default => "none"
|
82
|
+
# How DNS lookups should be done. If set to `use_all_dns_ips`, when the lookup returns multiple
|
83
|
+
# IP addresses for a hostname, they will all be attempted to connect to before failing the
|
84
|
+
# connection. If the value is `resolve_canonical_bootstrap_servers_only` each entry will be
|
85
|
+
# resolved and expanded into a list of canonical names.
|
86
|
+
config :client_dns_lookup, :validate => ["default", "use_all_dns_ips", "resolve_canonical_bootstrap_servers_only"], :default => "default"
|
82
87
|
# The id string to pass to the server when making requests.
|
83
88
|
# The purpose of this is to be able to track the source of requests beyond just
|
84
89
|
# ip/port by allowing a logical application name to be included with the request
|
@@ -92,24 +97,26 @@ class LogStash::Outputs::Kafka < LogStash::Outputs::Base
|
|
92
97
|
# This setting accomplishes this by adding a small amount of artificial delay—that is,
|
93
98
|
# rather than immediately sending out a record the producer will wait for up to the given delay
|
94
99
|
# to allow other records to be sent so that the sends can be batched together.
|
95
|
-
config :linger_ms, :validate => :number, :default => 0
|
100
|
+
config :linger_ms, :validate => :number, :default => 0 # Kafka default
|
96
101
|
# The maximum size of a request
|
97
|
-
config :max_request_size, :validate => :number, :default =>
|
102
|
+
config :max_request_size, :validate => :number, :default => 1_048_576 # (1MB) Kafka default
|
98
103
|
# The key for the message
|
99
104
|
config :message_key, :validate => :string
|
100
105
|
# the timeout setting for initial metadata request to fetch topic metadata.
|
101
|
-
config :metadata_fetch_timeout_ms, :validate => :number, :default =>
|
106
|
+
config :metadata_fetch_timeout_ms, :validate => :number, :default => 60_000
|
102
107
|
# the max time in milliseconds before a metadata refresh is forced.
|
103
|
-
config :metadata_max_age_ms, :validate => :number, :default =>
|
108
|
+
config :metadata_max_age_ms, :validate => :number, :default => 300_000 # (5m) Kafka default
|
109
|
+
# Partitioner to use - can be `default`, `uniform_sticky`, `round_robin` or a fully qualified class name of a custom partitioner.
|
110
|
+
config :partitioner, :validate => :string
|
104
111
|
# The size of the TCP receive buffer to use when reading data
|
105
|
-
config :receive_buffer_bytes, :validate => :number, :default =>
|
112
|
+
config :receive_buffer_bytes, :validate => :number, :default => 32_768 # (32KB) Kafka default
|
106
113
|
# The amount of time to wait before attempting to reconnect to a given host when a connection fails.
|
107
|
-
config :reconnect_backoff_ms, :validate => :number, :default =>
|
114
|
+
config :reconnect_backoff_ms, :validate => :number, :default => 50 # Kafka default
|
108
115
|
# The configuration controls the maximum amount of time the client will wait
|
109
116
|
# for the response of a request. If the response is not received before the timeout
|
110
117
|
# elapses the client will resend the request if necessary or fail the request if
|
111
118
|
# retries are exhausted.
|
112
|
-
config :request_timeout_ms, :validate => :
|
119
|
+
config :request_timeout_ms, :validate => :number, :default => 40_000 # (40s) Kafka default
|
113
120
|
# The default retry behavior is to retry until successful. To prevent data loss,
|
114
121
|
# the use of this setting is discouraged.
|
115
122
|
#
|
@@ -120,9 +127,9 @@ class LogStash::Outputs::Kafka < LogStash::Outputs::Base
|
|
120
127
|
# A value less than zero is a configuration error.
|
121
128
|
config :retries, :validate => :number
|
122
129
|
# The amount of time to wait before attempting to retry a failed produce request to a given topic partition.
|
123
|
-
config :retry_backoff_ms, :validate => :number, :default => 100
|
130
|
+
config :retry_backoff_ms, :validate => :number, :default => 100 # Kafka default
|
124
131
|
# The size of the TCP send buffer to use when sending data.
|
125
|
-
config :send_buffer_bytes, :validate => :number, :default =>
|
132
|
+
config :send_buffer_bytes, :validate => :number, :default => 131_072 # (128KB) Kafka default
|
126
133
|
# The truststore type.
|
127
134
|
config :ssl_truststore_type, :validate => :string
|
128
135
|
# The JKS truststore path to validate the Kafka broker's certificate.
|
@@ -184,7 +191,7 @@ class LogStash::Outputs::Kafka < LogStash::Outputs::Base
|
|
184
191
|
raise ConfigurationError, "A negative retry count (#{@retries}) is not valid. Must be a value >= 0"
|
185
192
|
end
|
186
193
|
|
187
|
-
|
194
|
+
logger.warn("Kafka output is configured with finite retry. This instructs Logstash to LOSE DATA after a set number of send attempts fails. If you do not want to lose data if Kafka is down, then you must remove the retry setting.", :retries => @retries)
|
188
195
|
end
|
189
196
|
|
190
197
|
|
@@ -202,8 +209,6 @@ class LogStash::Outputs::Kafka < LogStash::Outputs::Base
|
|
202
209
|
end
|
203
210
|
end
|
204
211
|
|
205
|
-
# def register
|
206
|
-
|
207
212
|
def prepare(record)
|
208
213
|
# This output is threadsafe, so we need to keep a batch per thread.
|
209
214
|
@thread_batch_map[Thread.current].add(record)
|
@@ -231,7 +236,7 @@ class LogStash::Outputs::Kafka < LogStash::Outputs::Base
|
|
231
236
|
remaining = @retries
|
232
237
|
|
233
238
|
while batch.any?
|
234
|
-
|
239
|
+
unless remaining.nil?
|
235
240
|
if remaining < 0
|
236
241
|
# TODO(sissel): Offer to DLQ? Then again, if it's a transient fault,
|
237
242
|
# DLQing would make things worse (you dlq data that would be successful
|
@@ -250,27 +255,39 @@ class LogStash::Outputs::Kafka < LogStash::Outputs::Base
|
|
250
255
|
begin
|
251
256
|
# send() can throw an exception even before the future is created.
|
252
257
|
@producer.send(record)
|
253
|
-
rescue org.apache.kafka.common.errors.
|
254
|
-
|
255
|
-
|
256
|
-
rescue org.apache.kafka.common.errors.InterruptException => e
|
258
|
+
rescue org.apache.kafka.common.errors.InterruptException,
|
259
|
+
org.apache.kafka.common.errors.RetriableException => e
|
260
|
+
logger.info("producer send failed, will retry sending", :exception => e.class, :message => e.message)
|
257
261
|
failures << record
|
258
262
|
nil
|
259
|
-
rescue org.apache.kafka.common.
|
260
|
-
#
|
261
|
-
# TODO
|
262
|
-
|
263
|
+
rescue org.apache.kafka.common.KafkaException => e
|
264
|
+
# This error is not retriable, drop event
|
265
|
+
# TODO: add DLQ support
|
266
|
+
logger.warn("producer send failed, dropping record",:exception => e.class, :message => e.message,
|
267
|
+
:record_value => record.value)
|
263
268
|
nil
|
264
269
|
end
|
265
|
-
end
|
270
|
+
end
|
266
271
|
|
267
272
|
futures.each_with_index do |future, i|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
273
|
+
# We cannot skip nils using `futures.compact` because then our index `i` will not align with `batch`
|
274
|
+
unless future.nil?
|
275
|
+
begin
|
276
|
+
future.get
|
277
|
+
rescue java.util.concurrent.ExecutionException => e
|
278
|
+
# TODO(sissel): Add metric to count failures, possibly by exception type.
|
279
|
+
if e.get_cause.is_a? org.apache.kafka.common.errors.RetriableException or
|
280
|
+
e.get_cause.is_a? org.apache.kafka.common.errors.InterruptException
|
281
|
+
logger.info("producer send failed, will retry sending", :exception => e.cause.class,
|
282
|
+
:message => e.cause.message)
|
283
|
+
failures << batch[i]
|
284
|
+
elsif e.get_cause.is_a? org.apache.kafka.common.KafkaException
|
285
|
+
# This error is not retriable, drop event
|
286
|
+
# TODO: add DLQ support
|
287
|
+
logger.warn("producer send failed, dropping record", :exception => e.cause.class,
|
288
|
+
:message => e.cause.message, :record_value => batch[i].value)
|
289
|
+
end
|
290
|
+
end
|
274
291
|
end
|
275
292
|
end
|
276
293
|
|
@@ -303,10 +320,9 @@ class LogStash::Outputs::Kafka < LogStash::Outputs::Base
|
|
303
320
|
end
|
304
321
|
prepare(record)
|
305
322
|
rescue LogStash::ShutdownSignal
|
306
|
-
|
323
|
+
logger.debug('producer received shutdown signal')
|
307
324
|
rescue => e
|
308
|
-
|
309
|
-
:exception => e)
|
325
|
+
logger.warn('producer threw exception, restarting', :exception => e.class, :message => e.message)
|
310
326
|
end
|
311
327
|
|
312
328
|
def create_producer
|
@@ -319,14 +335,19 @@ class LogStash::Outputs::Kafka < LogStash::Outputs::Base
|
|
319
335
|
props.put(kafka::BOOTSTRAP_SERVERS_CONFIG, bootstrap_servers)
|
320
336
|
props.put(kafka::BUFFER_MEMORY_CONFIG, buffer_memory.to_s)
|
321
337
|
props.put(kafka::COMPRESSION_TYPE_CONFIG, compression_type)
|
338
|
+
props.put(kafka::CLIENT_DNS_LOOKUP_CONFIG, client_dns_lookup)
|
322
339
|
props.put(kafka::CLIENT_ID_CONFIG, client_id) unless client_id.nil?
|
323
340
|
props.put(kafka::KEY_SERIALIZER_CLASS_CONFIG, key_serializer)
|
324
341
|
props.put(kafka::LINGER_MS_CONFIG, linger_ms.to_s)
|
325
342
|
props.put(kafka::MAX_REQUEST_SIZE_CONFIG, max_request_size.to_s)
|
326
|
-
props.put(kafka::METADATA_MAX_AGE_CONFIG, metadata_max_age_ms) unless metadata_max_age_ms.nil?
|
343
|
+
props.put(kafka::METADATA_MAX_AGE_CONFIG, metadata_max_age_ms.to_s) unless metadata_max_age_ms.nil?
|
344
|
+
unless partitioner.nil?
|
345
|
+
props.put(kafka::PARTITIONER_CLASS_CONFIG, partitioner = partitioner_class)
|
346
|
+
logger.debug('producer configured using partitioner', :partitioner_class => partitioner)
|
347
|
+
end
|
327
348
|
props.put(kafka::RECEIVE_BUFFER_CONFIG, receive_buffer_bytes.to_s) unless receive_buffer_bytes.nil?
|
328
|
-
props.put(kafka::RECONNECT_BACKOFF_MS_CONFIG, reconnect_backoff_ms) unless reconnect_backoff_ms.nil?
|
329
|
-
props.put(kafka::REQUEST_TIMEOUT_MS_CONFIG, request_timeout_ms) unless request_timeout_ms.nil?
|
349
|
+
props.put(kafka::RECONNECT_BACKOFF_MS_CONFIG, reconnect_backoff_ms.to_s) unless reconnect_backoff_ms.nil?
|
350
|
+
props.put(kafka::REQUEST_TIMEOUT_MS_CONFIG, request_timeout_ms.to_s) unless request_timeout_ms.nil?
|
330
351
|
props.put(kafka::RETRIES_CONFIG, retries.to_s) unless retries.nil?
|
331
352
|
props.put(kafka::RETRY_BACKOFF_MS_CONFIG, retry_backoff_ms.to_s)
|
332
353
|
props.put(kafka::SEND_BUFFER_CONFIG, send_buffer_bytes.to_s)
|
@@ -343,7 +364,6 @@ class LogStash::Outputs::Kafka < LogStash::Outputs::Base
|
|
343
364
|
set_sasl_config(props)
|
344
365
|
end
|
345
366
|
|
346
|
-
|
347
367
|
org.apache.kafka.clients.producer.KafkaProducer.new(props)
|
348
368
|
rescue => e
|
349
369
|
logger.error("Unable to create Kafka producer from given configuration",
|
@@ -353,13 +373,31 @@ class LogStash::Outputs::Kafka < LogStash::Outputs::Base
|
|
353
373
|
end
|
354
374
|
end
|
355
375
|
|
376
|
+
def partitioner_class
|
377
|
+
case partitioner
|
378
|
+
when 'round_robin'
|
379
|
+
'org.apache.kafka.clients.producer.RoundRobinPartitioner'
|
380
|
+
when 'uniform_sticky'
|
381
|
+
'org.apache.kafka.clients.producer.UniformStickyPartitioner'
|
382
|
+
when 'default'
|
383
|
+
'org.apache.kafka.clients.producer.internals.DefaultPartitioner'
|
384
|
+
else
|
385
|
+
unless partitioner.index('.')
|
386
|
+
raise LogStash::ConfigurationError, "unsupported partitioner: #{partitioner.inspect}"
|
387
|
+
end
|
388
|
+
partitioner # assume a fully qualified class-name
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
356
392
|
def set_trustore_keystore_config(props)
|
357
|
-
|
358
|
-
|
393
|
+
unless ssl_endpoint_identification_algorithm.to_s.strip.empty?
|
394
|
+
if ssl_truststore_location.nil?
|
395
|
+
raise LogStash::ConfigurationError, "ssl_truststore_location must be set when SSL is enabled"
|
396
|
+
end
|
397
|
+
props.put("ssl.truststore.type", ssl_truststore_type) unless ssl_truststore_type.nil?
|
398
|
+
props.put("ssl.truststore.location", ssl_truststore_location)
|
399
|
+
props.put("ssl.truststore.password", ssl_truststore_password.value) unless ssl_truststore_password.nil?
|
359
400
|
end
|
360
|
-
props.put("ssl.truststore.type", ssl_truststore_type) unless ssl_truststore_type.nil?
|
361
|
-
props.put("ssl.truststore.location", ssl_truststore_location)
|
362
|
-
props.put("ssl.truststore.password", ssl_truststore_password.value) unless ssl_truststore_password.nil?
|
363
401
|
|
364
402
|
# Client auth stuff
|
365
403
|
props.put("ssl.keystore.type", ssl_keystore_type) unless ssl_keystore_type.nil?
|
@@ -370,15 +408,15 @@ class LogStash::Outputs::Kafka < LogStash::Outputs::Base
|
|
370
408
|
end
|
371
409
|
|
372
410
|
def set_sasl_config(props)
|
373
|
-
java.lang.System.setProperty("java.security.auth.login.config",jaas_path) unless jaas_path.nil?
|
374
|
-
java.lang.System.setProperty("java.security.krb5.conf",kerberos_config) unless kerberos_config.nil?
|
411
|
+
java.lang.System.setProperty("java.security.auth.login.config", jaas_path) unless jaas_path.nil?
|
412
|
+
java.lang.System.setProperty("java.security.krb5.conf", kerberos_config) unless kerberos_config.nil?
|
375
413
|
|
376
414
|
props.put("sasl.mechanism",sasl_mechanism)
|
377
415
|
if sasl_mechanism == "GSSAPI" && sasl_kerberos_service_name.nil?
|
378
416
|
raise LogStash::ConfigurationError, "sasl_kerberos_service_name must be specified when SASL mechanism is GSSAPI"
|
379
417
|
end
|
380
418
|
|
381
|
-
props.put("sasl.kerberos.service.name",sasl_kerberos_service_name) unless sasl_kerberos_service_name.nil?
|
419
|
+
props.put("sasl.kerberos.service.name", sasl_kerberos_service_name) unless sasl_kerberos_service_name.nil?
|
382
420
|
props.put("sasl.jaas.config", sasl_jaas_config) unless sasl_jaas_config.nil?
|
383
421
|
end
|
384
422
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'logstash-integration-kafka'
|
3
|
-
s.version = '10.0
|
3
|
+
s.version = '10.5.0'
|
4
4
|
s.licenses = ['Apache-2.0']
|
5
5
|
s.summary = "Integration with Kafka - input and output plugins"
|
6
6
|
s.description = "This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline "+
|
@@ -49,6 +49,6 @@ Gem::Specification.new do |s|
|
|
49
49
|
|
50
50
|
s.add_development_dependency 'logstash-devutils'
|
51
51
|
s.add_development_dependency 'rspec-wait'
|
52
|
-
s.add_development_dependency '
|
52
|
+
s.add_development_dependency 'ruby-kafka'
|
53
53
|
s.add_development_dependency 'snappy'
|
54
54
|
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require "logstash/devutils/rspec/spec_helper"
|
3
3
|
require "logstash/inputs/kafka"
|
4
|
-
require "digest"
|
5
4
|
require "rspec/wait"
|
6
5
|
|
7
6
|
# Please run kafka_test_setup.sh prior to executing this integration test.
|
@@ -12,159 +11,129 @@ describe "inputs/kafka", :integration => true do
|
|
12
11
|
let(:group_id_3) {rand(36**8).to_s(36)}
|
13
12
|
let(:group_id_4) {rand(36**8).to_s(36)}
|
14
13
|
let(:group_id_5) {rand(36**8).to_s(36)}
|
15
|
-
let(:
|
16
|
-
let(:
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
let(:
|
21
|
-
|
14
|
+
let(:group_id_6) {rand(36**8).to_s(36)}
|
15
|
+
let(:plain_config) do
|
16
|
+
{ 'topics' => ['logstash_integration_topic_plain'], 'codec' => 'plain', 'group_id' => group_id_1,
|
17
|
+
'auto_offset_reset' => 'earliest' }
|
18
|
+
end
|
19
|
+
let(:multi_consumer_config) do
|
20
|
+
plain_config.merge({"group_id" => group_id_4, "client_id" => "spec", "consumer_threads" => 3})
|
21
|
+
end
|
22
|
+
let(:snappy_config) do
|
23
|
+
{ 'topics' => ['logstash_integration_topic_snappy'], 'codec' => 'plain', 'group_id' => group_id_1,
|
24
|
+
'auto_offset_reset' => 'earliest' }
|
25
|
+
end
|
26
|
+
let(:lz4_config) do
|
27
|
+
{ 'topics' => ['logstash_integration_topic_lz4'], 'codec' => 'plain', 'group_id' => group_id_1,
|
28
|
+
'auto_offset_reset' => 'earliest' }
|
29
|
+
end
|
30
|
+
let(:pattern_config) do
|
31
|
+
{ 'topics_pattern' => 'logstash_integration_topic_.*', 'group_id' => group_id_2, 'codec' => 'plain',
|
32
|
+
'auto_offset_reset' => 'earliest' }
|
33
|
+
end
|
34
|
+
let(:decorate_config) do
|
35
|
+
{ 'topics' => ['logstash_integration_topic_plain'], 'codec' => 'plain', 'group_id' => group_id_3,
|
36
|
+
'auto_offset_reset' => 'earliest', 'decorate_events' => true }
|
37
|
+
end
|
38
|
+
let(:manual_commit_config) do
|
39
|
+
{ 'topics' => ['logstash_integration_topic_plain'], 'codec' => 'plain', 'group_id' => group_id_5,
|
40
|
+
'auto_offset_reset' => 'earliest', 'enable_auto_commit' => 'false' }
|
41
|
+
end
|
22
42
|
let(:timeout_seconds) { 30 }
|
23
43
|
let(:num_events) { 103 }
|
24
44
|
|
25
45
|
describe "#kafka-topics" do
|
26
|
-
def thread_it(kafka_input, queue)
|
27
|
-
Thread.new do
|
28
|
-
begin
|
29
|
-
kafka_input.run(queue)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
46
|
|
34
47
|
it "should consume all messages from plain 3-partition topic" do
|
35
|
-
|
36
|
-
queue
|
37
|
-
t = thread_it(kafka_input, queue)
|
38
|
-
begin
|
39
|
-
t.run
|
40
|
-
wait(timeout_seconds).for {queue.length}.to eq(num_events)
|
41
|
-
expect(queue.length).to eq(num_events)
|
42
|
-
ensure
|
43
|
-
t.kill
|
44
|
-
t.join(30_000)
|
45
|
-
end
|
48
|
+
queue = consume_messages(plain_config, timeout: timeout_seconds, event_count: num_events)
|
49
|
+
expect(queue.length).to eq(num_events)
|
46
50
|
end
|
47
51
|
|
48
52
|
it "should consume all messages from snappy 3-partition topic" do
|
49
|
-
|
50
|
-
queue
|
51
|
-
t = thread_it(kafka_input, queue)
|
52
|
-
begin
|
53
|
-
t.run
|
54
|
-
wait(timeout_seconds).for {queue.length}.to eq(num_events)
|
55
|
-
expect(queue.length).to eq(num_events)
|
56
|
-
ensure
|
57
|
-
t.kill
|
58
|
-
t.join(30_000)
|
59
|
-
end
|
53
|
+
queue = consume_messages(snappy_config, timeout: timeout_seconds, event_count: num_events)
|
54
|
+
expect(queue.length).to eq(num_events)
|
60
55
|
end
|
61
56
|
|
62
57
|
it "should consume all messages from lz4 3-partition topic" do
|
63
|
-
|
64
|
-
queue
|
65
|
-
t = thread_it(kafka_input, queue)
|
66
|
-
begin
|
67
|
-
t.run
|
68
|
-
wait(timeout_seconds).for {queue.length}.to eq(num_events)
|
69
|
-
expect(queue.length).to eq(num_events)
|
70
|
-
ensure
|
71
|
-
t.kill
|
72
|
-
t.join(30_000)
|
73
|
-
end
|
58
|
+
queue = consume_messages(lz4_config, timeout: timeout_seconds, event_count: num_events)
|
59
|
+
expect(queue.length).to eq(num_events)
|
74
60
|
end
|
75
61
|
|
76
62
|
it "should consumer all messages with multiple consumers" do
|
77
|
-
|
78
|
-
queue = Queue.new
|
79
|
-
t = thread_it(kafka_input, queue)
|
80
|
-
begin
|
81
|
-
t.run
|
82
|
-
wait(timeout_seconds).for {queue.length}.to eq(num_events)
|
63
|
+
consume_messages(multi_consumer_config, timeout: timeout_seconds, event_count: num_events) do |queue, kafka_input|
|
83
64
|
expect(queue.length).to eq(num_events)
|
84
65
|
kafka_input.kafka_consumers.each_with_index do |consumer, i|
|
85
66
|
expect(consumer.metrics.keys.first.tags["client-id"]).to eq("spec-#{i}")
|
86
67
|
end
|
87
|
-
ensure
|
88
|
-
t.kill
|
89
|
-
t.join(30_000)
|
90
68
|
end
|
91
69
|
end
|
92
70
|
end
|
93
71
|
|
94
|
-
|
95
|
-
def thread_it(kafka_input, queue)
|
96
|
-
Thread.new do
|
97
|
-
begin
|
98
|
-
kafka_input.run(queue)
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
72
|
+
context "#kafka-topics-pattern" do
|
103
73
|
it "should consume all messages from all 3 topics" do
|
104
|
-
|
105
|
-
queue =
|
106
|
-
|
107
|
-
begin
|
108
|
-
t.run
|
109
|
-
wait(timeout_seconds).for {queue.length}.to eq(3*num_events)
|
110
|
-
expect(queue.length).to eq(3*num_events)
|
111
|
-
ensure
|
112
|
-
t.kill
|
113
|
-
t.join(30_000)
|
114
|
-
end
|
74
|
+
total_events = num_events * 3
|
75
|
+
queue = consume_messages(pattern_config, timeout: timeout_seconds, event_count: total_events)
|
76
|
+
expect(queue.length).to eq(total_events)
|
115
77
|
end
|
116
78
|
end
|
117
79
|
|
118
|
-
|
119
|
-
def thread_it(kafka_input, queue)
|
120
|
-
Thread.new do
|
121
|
-
begin
|
122
|
-
kafka_input.run(queue)
|
123
|
-
end
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
80
|
+
context "#kafka-decorate" do
|
127
81
|
it "should show the right topic and group name in decorated kafka section" do
|
128
82
|
start = LogStash::Timestamp.now.time.to_i
|
129
|
-
|
130
|
-
queue = Queue.new
|
131
|
-
t = thread_it(kafka_input, queue)
|
132
|
-
begin
|
133
|
-
t.run
|
134
|
-
wait(timeout_seconds).for {queue.length}.to eq(num_events)
|
83
|
+
consume_messages(decorate_config, timeout: timeout_seconds, event_count: num_events) do |queue, _|
|
135
84
|
expect(queue.length).to eq(num_events)
|
136
85
|
event = queue.shift
|
137
86
|
expect(event.get("[@metadata][kafka][topic]")).to eq("logstash_integration_topic_plain")
|
138
87
|
expect(event.get("[@metadata][kafka][consumer_group]")).to eq(group_id_3)
|
139
88
|
expect(event.get("[@metadata][kafka][timestamp]")).to be >= start
|
140
|
-
ensure
|
141
|
-
t.kill
|
142
|
-
t.join(30_000)
|
143
89
|
end
|
144
90
|
end
|
145
91
|
end
|
146
92
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
93
|
+
context "#kafka-offset-commit" do
|
94
|
+
it "should manually commit offsets" do
|
95
|
+
queue = consume_messages(manual_commit_config, timeout: timeout_seconds, event_count: num_events)
|
96
|
+
expect(queue.length).to eq(num_events)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'setting partition_assignment_strategy' do
|
101
|
+
let(:test_topic) { 'logstash_integration_partitioner_topic' }
|
102
|
+
let(:consumer_config) do
|
103
|
+
plain_config.merge(
|
104
|
+
"topics" => [test_topic],
|
105
|
+
'group_id' => group_id_6,
|
106
|
+
"client_id" => "partition_assignment_strategy-spec",
|
107
|
+
"consumer_threads" => 2,
|
108
|
+
"partition_assignment_strategy" => partition_assignment_strategy
|
109
|
+
)
|
110
|
+
end
|
111
|
+
let(:partition_assignment_strategy) { nil }
|
112
|
+
|
113
|
+
# NOTE: just verify setting works, as its a bit cumbersome to do in a unit spec
|
114
|
+
[ 'range', 'round_robin', 'sticky', 'org.apache.kafka.clients.consumer.CooperativeStickyAssignor' ].each do |partition_assignment_strategy|
|
115
|
+
describe partition_assignment_strategy do
|
116
|
+
let(:partition_assignment_strategy) { partition_assignment_strategy }
|
117
|
+
it 'consumes data' do
|
118
|
+
consume_messages(consumer_config, timeout: false, event_count: 0)
|
152
119
|
end
|
153
120
|
end
|
154
121
|
end
|
122
|
+
end
|
155
123
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
124
|
+
private
|
125
|
+
|
126
|
+
def consume_messages(config, queue: Queue.new, timeout:, event_count:)
|
127
|
+
kafka_input = LogStash::Inputs::Kafka.new(config)
|
128
|
+
t = Thread.new { kafka_input.run(queue) }
|
129
|
+
begin
|
130
|
+
t.run
|
131
|
+
wait(timeout).for { queue.length }.to eq(event_count) unless timeout.eql?(false)
|
132
|
+
block_given? ? yield(queue, kafka_input) : queue
|
133
|
+
ensure
|
134
|
+
t.kill
|
135
|
+
t.join(30_000)
|
168
136
|
end
|
169
137
|
end
|
138
|
+
|
170
139
|
end
|