logstash-integration-kafka 10.0.1-java → 10.5.0-java
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.
- 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
|