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.
@@ -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 => 16384
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 => 33554432
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 => 1048576
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 => 60000
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 => 300000
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 => 32768
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 => 10
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 => :string
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 => 131072
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
- @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)
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
- if !remaining.nil?
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.TimeoutException => e
254
- failures << record
255
- nil
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.errors.SerializationException => e
260
- # TODO(sissel): Retrying will fail because the data itself has a problem serializing.
261
- # TODO(sissel): Let's add DLQ here.
262
- failures << record
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.compact
270
+ end
266
271
 
267
272
  futures.each_with_index do |future, i|
268
- begin
269
- result = future.get()
270
- rescue => e
271
- # TODO(sissel): Add metric to count failures, possibly by exception type.
272
- logger.warn("KafkaProducer.send() failed: #{e}", :exception => e)
273
- failures << batch[i]
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
- @logger.debug('Kafka producer got shutdown signal')
323
+ logger.debug('producer received shutdown signal')
307
324
  rescue => e
308
- @logger.warn('kafka producer threw exception, restarting',
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
- if ssl_truststore_location.nil?
358
- raise LogStash::ConfigurationError, "ssl_truststore_location must be set when SSL is enabled"
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.1'
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 'poseidon'
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(:plain_config) { { 'topics' => ['logstash_integration_topic_plain'], 'codec' => 'plain', 'group_id' => group_id_1, 'auto_offset_reset' => 'earliest'} }
16
- let(:multi_consumer_config) { plain_config.merge({"group_id" => group_id_4, "client_id" => "spec", "consumer_threads" => 3}) }
17
- let(:snappy_config) { { 'topics' => ['logstash_integration_topic_snappy'], 'codec' => 'plain', 'group_id' => group_id_1, 'auto_offset_reset' => 'earliest'} }
18
- let(:lz4_config) { { 'topics' => ['logstash_integration_topic_lz4'], 'codec' => 'plain', 'group_id' => group_id_1, 'auto_offset_reset' => 'earliest'} }
19
- let(:pattern_config) { { 'topics_pattern' => 'logstash_integration_topic_.*', 'group_id' => group_id_2, 'codec' => 'plain', 'auto_offset_reset' => 'earliest'} }
20
- let(:decorate_config) { { 'topics' => ['logstash_integration_topic_plain'], 'codec' => 'plain', 'group_id' => group_id_3, 'auto_offset_reset' => 'earliest', 'decorate_events' => true} }
21
- let(:manual_commit_config) { { 'topics' => ['logstash_integration_topic_plain'], 'codec' => 'plain', 'group_id' => group_id_5, 'auto_offset_reset' => 'earliest', 'enable_auto_commit' => 'false'} }
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
- kafka_input = LogStash::Inputs::Kafka.new(plain_config)
36
- queue = Queue.new
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
- kafka_input = LogStash::Inputs::Kafka.new(snappy_config)
50
- queue = Queue.new
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
- kafka_input = LogStash::Inputs::Kafka.new(lz4_config)
64
- queue = Queue.new
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
- kafka_input = LogStash::Inputs::Kafka.new(multi_consumer_config)
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
- describe "#kafka-topics-pattern" do
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
- kafka_input = LogStash::Inputs::Kafka.new(pattern_config)
105
- queue = Queue.new
106
- t = thread_it(kafka_input, queue)
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
- describe "#kafka-decorate" do
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
- kafka_input = LogStash::Inputs::Kafka.new(decorate_config)
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
- describe "#kafka-offset-commit" do
148
- def thread_it(kafka_input, queue)
149
- Thread.new do
150
- begin
151
- kafka_input.run(queue)
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
- it "should manually commit offsets" do
157
- kafka_input = LogStash::Inputs::Kafka.new(manual_commit_config)
158
- queue = Queue.new
159
- t = thread_it(kafka_input, queue)
160
- begin
161
- t.run
162
- wait(timeout_seconds).for {queue.length}.to eq(num_events)
163
- expect(queue.length).to eq(num_events)
164
- ensure
165
- t.kill
166
- t.join(30_000)
167
- end
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