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.
@@ -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